KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > info > jtrac > domain > Metadata


1 /*
2  * Copyright 2002-2005 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package info.jtrac.domain;
18
19 import static info.jtrac.Constants.*;
20
21 import info.jtrac.util.XmlUtils;
22
23 import java.io.Serializable JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Collection JavaDoc;
26 import java.util.EnumMap JavaDoc;
27 import java.util.EnumSet JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.HashSet JavaDoc;
30 import java.util.LinkedHashMap JavaDoc;
31 import java.util.LinkedList JavaDoc;
32 import java.util.List JavaDoc;
33 import java.util.Map JavaDoc;
34 import java.util.Set JavaDoc;
35 import java.util.TreeMap JavaDoc;
36
37
38 import org.dom4j.Document;
39 import org.dom4j.Element;
40
41 /**
42  * XML metadata is one of the interesting design decisions of JTrac.
43  * Metadata is defined for each space and so Items that belong to a
44  * space are customized by the space metadata. This class can marshall
45  * and unmarshall itself to XML and this XML is stored in the database
46  * in a single column. Because of this approach, Metadata can be made more
47  * and more complicated in the future without impact to the database schema.
48  *
49  * Things that the Metadata configures for a Space:
50  *
51  * 1) custom Fields for an Item (within a Space)
52  * - Label
53  * - whether mandatory or not
54  * - the option values (drop down list options)
55  * - the option "key" values are stored in the database (WITHOUT any relationships)
56  * - the values corresponding to "key"s are resolved in memory from the Metadata
57  * and not through a database join.
58  *
59  * 2) the Roles available within a space
60  * - for each (from) State the (to) State transitions allowed for this role
61  * - and within each (from) State the fields that this Role can view / edit
62  *
63  * 3) the State labels corresponding to each state
64  * - internally States are integers, but for display we need a label
65  * - labels can be customized
66  * - special State values: 0 = New, 1 = Open, 99 = Closed
67  *
68  * 4) the order in which the fields are displayed
69  * on the data entry screens and the query result screens etc.
70  *
71  * Metadata can be inherited, and this allows for "reuse" TODO
72  */

73 public class Metadata implements Serializable JavaDoc {
74     
75     private long id;
76     private int version;
77     private Integer JavaDoc type;
78     private String JavaDoc name;
79     private String JavaDoc description;
80     private Metadata parent;
81
82     private Map JavaDoc<Field.Name, Field> fields;
83     private Map JavaDoc<String JavaDoc, Role> roles;
84     private Map JavaDoc<Integer JavaDoc, String JavaDoc> states;
85     private List JavaDoc<Field.Name> fieldOrder;
86     
87     public Metadata() {
88         init();
89     }
90     
91     private void init() {
92         fields = new EnumMap JavaDoc<Field.Name, Field>(Field.Name.class);
93         roles = new HashMap JavaDoc<String JavaDoc, Role>();
94         states = new TreeMap JavaDoc<Integer JavaDoc, String JavaDoc>();
95         fieldOrder = new LinkedList JavaDoc<Field.Name>();
96     }
97     
98     /* accessor, will be used by Hibernate */
99     public void setXmlString(String JavaDoc xmlString) {
100         init();
101         if (xmlString == null) {
102             return;
103         }
104         Document document = XmlUtils.parse(xmlString);
105         for (Element e : (List JavaDoc<Element>) document.selectNodes(FIELD_XPATH)) {
106             Field field = new Field(e);
107             fields.put(field.getName(), field);
108         }
109         for (Element e : (List JavaDoc<Element>) document.selectNodes(ROLE_XPATH)) {
110             Role role = new Role(e);
111             roles.put(role.getName(), role);
112         }
113         for (Element e : (List JavaDoc<Element>) document.selectNodes(STATE_XPATH)) {
114             String JavaDoc key = e.attributeValue(STATUS);
115             String JavaDoc value = e.attributeValue(LABEL);
116             states.put(Integer.parseInt(key), value);
117         }
118         for (Element e : (List JavaDoc<Element>) document.selectNodes(FIELD_ORDER_XPATH)) {
119             String JavaDoc fieldName = e.attributeValue(NAME);
120             fieldOrder.add(Field.convertToName(fieldName));
121         }
122     }
123     
124     /* accessor, will be used by Hibernate */
125     public String JavaDoc getXmlString() {
126         Document d = XmlUtils.getNewDocument(METADATA);
127         Element root = d.getRootElement();
128         Element fs = root.addElement(FIELDS);
129         for (Field field : fields.values()) {
130             field.addAsChildOf(fs);
131         }
132         Element rs = root.addElement(ROLES);
133         for (Role role : roles.values()) {
134             role.addAsChildOf(rs);
135         }
136         Element ss = root.addElement(STATES);
137         for (Map.Entry JavaDoc<Integer JavaDoc, String JavaDoc> entry : states.entrySet()) {
138             Element e = ss.addElement(STATE);
139             e.addAttribute(STATUS, entry.getKey() + "");
140             e.addAttribute(LABEL, entry.getValue());
141         }
142         Element fo = fs.addElement(FIELD_ORDER);
143         for (Field.Name f : fieldOrder) {
144             Element e = fo.addElement(FIELD);
145             e.addAttribute(NAME, f.toString());
146         }
147         return d.asXML();
148     }
149     
150     public String JavaDoc getPrettyXml() {
151         return XmlUtils.getAsPrettyXml(getXmlString());
152     }
153     
154     //====================================================================
155

156     public void initRoles() {
157         // set up default simple workflow
158
states.put(State.NEW, "New");
159         states.put(State.OPEN, "Open");
160         states.put(State.CLOSED, "Closed");
161         addRole("DEFAULT");
162         toggleTransition("DEFAULT", State.NEW, State.OPEN);
163         toggleTransition("DEFAULT",State.OPEN, State.OPEN);
164         toggleTransition("DEFAULT", State.OPEN, State.CLOSED);
165         toggleTransition("DEFAULT", State.CLOSED, State.OPEN);
166     }
167     
168     public Field getField(String JavaDoc name) {
169         return fields.get(Field.convertToName(name));
170     }
171     
172     public void add(Field field) {
173         fields.put(field.getName(), field); // will overwrite if exists
174
if (!fieldOrder.contains(field.getName())) { // but for List, need to check
175
fieldOrder.add(field.getName());
176         }
177         for (Role role : roles.values()) {
178             for (State state : role.getStates().values()) {
179                 state.add(field.getName());
180             }
181         }
182     }
183     
184     public void removeField(String JavaDoc fieldName) {
185         Field.Name name = Field.convertToName(fieldName);
186         fields.remove(name);
187         fieldOrder.remove(name);
188         for (Role role : roles.values()) {
189             for (State state : role.getStates().values()) {
190                 state.remove(name);
191             }
192         }
193     }
194     
195     public void addState(String JavaDoc name) {
196         // first get the max of existing state keys
197
int maxStatus = 0;
198         for (int status : states.keySet()) {
199             if (status > maxStatus && status != State.CLOSED) {
200                 maxStatus = status;
201             }
202         }
203         int newStatus = maxStatus + 1;
204         states.put(newStatus, name);
205         // by default each role will have permissions for this state, for all fields
206
for (Role role : roles.values()) {
207             State state = new State(newStatus);
208             state.add(fields.keySet());
209             role.add(state);
210         }
211     }
212     
213     public void removeState(int stateId) {
214         states.remove(stateId);
215         for (Role role : roles.values()) {
216             role.removeState(stateId);
217         }
218         
219     }
220     
221     public void addRole(String JavaDoc name) {
222         Role role = new Role(name);
223         for (Map.Entry JavaDoc<Integer JavaDoc, String JavaDoc> entry : states.entrySet()) {
224             State state = new State(entry.getKey());
225             state.add(fields.keySet());
226             role.add(state);
227         }
228         roles.put(role.getName(), role);
229     }
230     
231     public void renameRole(String JavaDoc oldRole, String JavaDoc newRole) {
232         // important! this has to be combined with a database update
233
Role role = roles.get(oldRole);
234         if (role == null) {
235             return; // TODO improve JtracTest and assert not null here
236
}
237         role.setName(newRole);
238         roles.remove(oldRole);
239         roles.put(newRole, role);
240     }
241     
242     public void removeRole(String JavaDoc name) {
243         // important! this has to be combined with a database update
244
roles.remove(name);
245     }
246     
247     public Set JavaDoc<Field.Name> getUnusedFieldNames() {
248         EnumSet JavaDoc<Field.Name> allFieldNames = EnumSet.allOf(Field.Name.class);
249         for (Field f : getFields().values()) {
250             allFieldNames.remove(f.getName());
251         }
252         return allFieldNames;
253     }
254     
255     public Map JavaDoc<String JavaDoc, String JavaDoc> getAvailableFieldTypes() {
256         Map JavaDoc<String JavaDoc, String JavaDoc> fieldTypes = new LinkedHashMap JavaDoc<String JavaDoc, String JavaDoc>();
257         for (Field.Name fieldName : getUnusedFieldNames()) {
258             fieldTypes.put(fieldName.getType() + "", fieldName.getDescription());
259         }
260         return fieldTypes;
261     }
262     
263     public Field getNextAvailableField(int type) {
264         for (Field.Name fieldName : getUnusedFieldNames()) {
265             if (fieldName.getType() == type) {
266                 return new Field(fieldName + "");
267             }
268         }
269         throw new RuntimeException JavaDoc("No field available of type " + type);
270     }
271     
272     // customized accessor
273
public Map JavaDoc<Field.Name, Field> getFields() {
274         Map JavaDoc<Field.Name, Field> map = fields;
275         if (parent != null) {
276             map.putAll(parent.getFields());
277         }
278         return map;
279     }
280     
281     // to make JSTL easier
282
public Collection JavaDoc<Role> getRoleList() {
283         return roles.values();
284     }
285     
286     public List JavaDoc<Field> getFieldList() {
287         Map JavaDoc<Field.Name, Field> map = getFields();
288         List JavaDoc<Field> list = new ArrayList JavaDoc<Field>(fields.size());
289         for (Field.Name fieldName : getFieldOrder()) {
290             list.add(fields.get(fieldName));
291         }
292         return list;
293     }
294     
295     public String JavaDoc getCustomValue(Field.Name fieldName, Integer JavaDoc key) {
296         return getCustomValue(fieldName, key + "");
297     }
298     
299     public String JavaDoc getCustomValue(Field.Name fieldName, String JavaDoc key) {
300         Field field = fields.get(fieldName);
301         if (field != null) {
302             return field.getCustomValue(key);
303         }
304         if (parent != null) {
305             return parent.getCustomValue(fieldName, key);
306         }
307         return "";
308     }
309     
310     public String JavaDoc getStatusValue(Integer JavaDoc key) {
311         if (key == null) {
312             return "";
313         }
314         String JavaDoc s = states.get(key);
315         if (s == null) {
316             return "";
317         }
318         return s;
319     }
320     
321     public int getRoleCount() {
322         return roles.size();
323     }
324     
325     public int getFieldCount() {
326         return getFields().size();
327     }
328     
329     public int getStateCount() {
330         return states.size();
331     }
332     
333     /**
334      * logic for resolving the next possible transitions for a given role and state
335      * - lookup Role by roleKey
336      * - for this Role, lookup state by key (integer)
337      * - for the State, iterate over transitions, get the label for each and add to map
338      * The map returned is used to render the drop down list on screen, [ key = value ]
339      */

340     public Map JavaDoc<Integer JavaDoc, String JavaDoc> getPermittedTransitions(List JavaDoc<String JavaDoc> roleKeys, int status) {
341         Map JavaDoc<Integer JavaDoc, String JavaDoc> map = new LinkedHashMap JavaDoc<Integer JavaDoc, String JavaDoc>();
342         for(String JavaDoc roleKey : roleKeys) {
343             Role role = roles.get(roleKey);
344             if (role != null) {
345                 State state = role.getStates().get(status);
346                 if (state != null) {
347                     for(int transition : state.getTransitions()) {
348                         map.put(transition, this.states.get(transition));
349                     }
350                 }
351             }
352         }
353         return map;
354     }
355     
356     // returning map ideal for JSTL
357
public Map JavaDoc<String JavaDoc, Boolean JavaDoc> getRolesAbleToTransition(int fromStatus, int toStatus) {
358         Map JavaDoc<String JavaDoc, Boolean JavaDoc> map = new HashMap JavaDoc<String JavaDoc, Boolean JavaDoc>(roles.size());
359         for(Role role : roles.values()) {
360             State s = role.getStates().get(fromStatus);
361             if(s.getTransitions().contains(toStatus)) {
362                 map.put(role.getName(), true);
363             }
364         }
365         return map;
366     }
367     
368     public Set JavaDoc<String JavaDoc> getRolesAbleToTransitionFrom(int state) {
369         Set JavaDoc<String JavaDoc> set = new HashSet JavaDoc<String JavaDoc>(roles.size());
370         for(Role role : roles.values()) {
371             State s = role.getStates().get(state);
372             if(s.getTransitions().size() > 0) {
373                 set.add(role.getName());
374             }
375         }
376         return set;
377     }
378     
379     private State getRoleState(String JavaDoc roleKey, int stateKey) {
380         Role role = roles.get(roleKey);
381         return role.getStates().get(stateKey);
382     }
383     
384     public void toggleTransition(String JavaDoc roleKey, int fromState, int toState) {
385         State state = getRoleState(roleKey, fromState);
386         if (state.getTransitions().contains(toState)) {
387             state.getTransitions().remove(toState);
388         } else {
389             state.getTransitions().add(toState);
390         }
391     }
392     
393     public void switchMask(int stateKey, String JavaDoc roleKey, String JavaDoc fieldName) {
394         State state = getRoleState(roleKey, stateKey);
395         Field.Name name = Field.convertToName(fieldName);
396         Integer JavaDoc mask = state.getFields().get(name);
397         switch(mask) {
398             // case State.MASK_HIDE: state.getFields().put(name, State.MASK_VIEW); return; HIDE SUPPORT IN FUTURE
399
case State.MASK_VIEW: state.getFields().put(name, State.MASK_EDIT); return;
400             case State.MASK_EDIT: state.getFields().put(name, State.MASK_VIEW); return;
401         }
402     }
403     
404     public List JavaDoc<Field> getEditableFields(Collection JavaDoc<String JavaDoc> roleKeys, Collection JavaDoc<Integer JavaDoc> ss) {
405         Set JavaDoc<Field> fs = new HashSet JavaDoc<Field>();
406         for(String JavaDoc roleKey : roleKeys) {
407             if (roleKey.startsWith("ROLE_")) {
408                 continue;
409             }
410             for(Integer JavaDoc status : ss) {
411                 if (status == State.NEW) {
412                     continue; // we are looking only for editable after the NEW
413
}
414                 State state = getRoleState(roleKey, status);
415                 for(Map.Entry JavaDoc<Field.Name, Integer JavaDoc> entry : state.getFields().entrySet()) {
416                     if (entry.getValue() == State.MASK_EDIT) {
417                         fs.add(fields.get(entry.getKey()));
418                     }
419                 }
420             }
421         }
422         List JavaDoc<Field> result = getFieldList();
423         result.retainAll(fs); // just to retain the order of the fields
424
return result;
425     }
426     
427     public List JavaDoc<Field> getEditableFields() {
428         return getEditableFields(roles.keySet(), states.keySet());
429     }
430     
431     //==================================================================
432

433     public int getVersion() {
434         return version;
435     }
436
437     public void setVersion(int version) {
438         this.version = version;
439     }
440     
441     public String JavaDoc getName() {
442         return name;
443     }
444
445     public void setName(String JavaDoc name) {
446         this.name = name;
447     }
448
449     public String JavaDoc getDescription() {
450         return description;
451     }
452
453     public void setDescription(String JavaDoc description) {
454         this.description = description;
455     }
456
457     public long getId() {
458         return id;
459     }
460
461     public void setId(long id) {
462         this.id = id;
463     }
464
465     public Integer JavaDoc getType() {
466         return type;
467     }
468
469     public void setType(Integer JavaDoc type) {
470         this.type = type;
471     }
472
473     public Metadata getParent() {
474         return parent;
475     }
476
477     public void setParent(Metadata parent) {
478         this.parent = parent;
479     }
480     
481     //=======================================
482
// no setters required
483

484     public Map JavaDoc<String JavaDoc, Role> getRoles() {
485         return roles;
486     }
487
488     public Map JavaDoc<Integer JavaDoc, String JavaDoc> getStates() {
489         return states;
490     }
491     
492     public List JavaDoc<Field.Name> getFieldOrder() {
493         return fieldOrder;
494     }
495     
496     @Override JavaDoc
497     public String JavaDoc toString() {
498         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
499         sb.append("id [").append(id);
500         sb.append("]; parent [").append(parent);
501         sb.append("]; fields [").append(fields);
502         sb.append("]; roles [").append(roles);
503         sb.append("]; states [").append(states);
504         sb.append("]; fieldOrder [").append(fieldOrder);
505         sb.append("]");
506         return sb.toString();
507     }
508     
509 }
510
Popular Tags