KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > module > corebuilders > RelDef


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10 package org.mmbase.module.corebuilders;
11
12 import java.util.*;
13 import org.mmbase.module.core.*;
14
15 import org.mmbase.storage.search.implementation.*;
16 import org.mmbase.storage.search.*;
17 import org.mmbase.util.logging.Logger;
18 import org.mmbase.util.logging.Logging;
19
20 /**
21  * RelDef, one of the meta stucture nodes, is used to define the possible relation types.
22  * <p>
23  * A Relation Definition consists of a source and destination, and a descriptor
24  * (direction) for it's use (unidirectional or bidirectional).
25  * </p><p>
26  * Relations are mapped to a builder.<br />
27  * This is so that additional functionality can be added by means of a builder (i.e. AuthRel).<br />
28  * The old system mapped the relations to a builder by name.
29  * Unfortunately, this means that some care need be taken when naming relations, as unintentionally
30  * naming a relation to a builder can give bad (if not disastrous) results.<br />
31  * Relations that are not directly mapped to a builder are mapped (internally) to the {@link InsRel} builder instead.
32  * </p><p>
33  * The new system uses an additional field to map to a builder.
34  * This 'builder' field contains a reference (otype) to the builder to be used.
35  * If null or 0, the builder is assumed to refer to the {@link InsRel} builder.
36  * <code>sname</code> is now the name of the relation and serves no function.
37  * </p><p>
38  * This patched version of RelDef can make use of either direct builder references (through the builder field), or the old system of using names.
39  * The system used is determined by examining whether the builder field has been defined in the builder's configuration (xml) file.
40  * See the documentation of the relations project at http://www.mmbase.org for more info.
41  * </p>
42  *
43  * @todo Fix cache so it will be updated using multicast.
44  * @author Daniel Ockeloen
45  * @author Pierre van Rooden
46  * @version $Id: RelDef.java,v 1.40 2006/07/05 15:16:34 pierre Exp $
47  */

48 public class RelDef extends MMObjectBuilder {
49
50     private static final Logger log = Logging.getLoggerInstance(RelDef.class);
51
52     /** Value of "dir" field indicating unidirectional relations. */
53     public final static int DIR_UNIDIRECTIONAL = 1;
54
55     /** Value of "dir" field indicating bidirectional relatios. */
56     public final static int DIR_BIDIRECTIONAL = 2;
57
58     /**
59      * Indicates whether the relationdefinitions use the 'builder' field (that is, whether the
60      * field has been defined in the xml file). Used for backward compatibility.
61      */

62     public static boolean usesbuilder = false;
63
64     // cache of relation definitions
65
// sname or sname/dname -> rnumber
66
private Map relCache = new HashMap();
67
68     // cache of valid relationbuilders
69
// otype of relations builder -> MMObjectBuilder
70
private Map relBuilderCache = null;
71
72     // rnumber -> MMObjectBuilder Name
73
private Map rnumberCache = new HashMap();
74
75     /**
76      * Contruct the builder
77      */

78     public RelDef() {
79     }
80
81     /**
82      * Initializes the builder by reading the cache. Also determines whether the 'builder' field is used.
83      * @return A <code>boolean</code> value, always success (<code>true</code>), as any exceptions are
84      * caught and logged.
85      */

86     public boolean init() {
87        super.init();
88        usesbuilder = getField("builder") != null;
89        return readCache();
90     }
91
92     /**
93      * Puts a role in the reldef cache.
94      * The role is entered both with its sname (primary identifier) and
95      * it's sname/dname combination.
96      */

97     private void addToCache(MMObjectNode node) {
98         Integer JavaDoc rnumber = (Integer JavaDoc) node.getValue("number");
99         relCache.put(node.getStringValue("sname"), rnumber);
100         relCache.put(node.getStringValue("sname") + "/" + node.getStringValue("dname"), rnumber);
101
102         rnumberCache.put(rnumber, findBuilderName(node));
103     }
104
105     /**
106      * Removes a role from the reldef cache.
107      * The role is removed both with its sname (primary identifier) and
108      * it's sname/dname combination.
109      */

110     private void removeFromCache(MMObjectNode node) {
111         relCache.remove(node.getStringValue("sname"));
112         relCache.remove(node.getStringValue("sname") + "/" + node.getStringValue("dname"));
113
114         rnumberCache.remove(new Integer JavaDoc(node.getNumber()));
115     }
116
117     /**
118      * @since MMBase-1.7.1
119      */

120     private void removeFromCache(int rnumber) {
121         Integer JavaDoc r = new Integer JavaDoc(rnumber);
122         Iterator i = relCache.entrySet().iterator();
123         while (i.hasNext()) {
124             Map.Entry entry = (Map.Entry) i.next();
125             Object JavaDoc value = entry.getValue();
126             if (r.equals(value)) {
127                 i.remove();
128             }
129         }
130         rnumberCache.remove(r);
131     }
132
133
134     /**
135      * Reads all relation definition names in an internal cache.
136      * The cache is used by {@link #isRelationTable}
137      * @return A <code>boolean</code> value, always success (<code>true</code>), as any exceptions are
138      * caught and logged.
139      */

140     private boolean readCache() {
141         rnumberCache.clear();
142         relCache.clear(); // add insrel (default behavior)
143
relCache.put("insrel", new Integer JavaDoc(-1));
144         // add relation definiation names
145
try {
146             for (Iterator i = getNodes(new NodeSearchQuery(this)).iterator(); i.hasNext();) {
147                 MMObjectNode n = (MMObjectNode) i.next();
148                 addToCache(n);
149             }
150         } catch (org.mmbase.storage.search.SearchQueryException sqe) {
151             log.error("Error while reading reldef cache" + sqe.getMessage());
152         }
153         return true;
154     }
155
156     /**
157      * Returns a GUI description of a relation definition.
158      * The description is dependent on the direction (uni/bi) of the relation
159      * @param node Relation definition to describe
160      * @return A <code>String</code> of descriptive text
161      */

162     public String JavaDoc getGUIIndicator(MMObjectNode node) {
163         int dir = node.getIntValue("dir");
164         if (dir == DIR_UNIDIRECTIONAL) {
165             return node.getStringValue("sguiname");
166         } else {
167             String JavaDoc st1 = node.getStringValue("sguiname");
168             String JavaDoc st2 = node.getStringValue("dguiname");
169             return st1 + "/" + st2;
170         }
171     }
172
173
174     /**
175      * @param reldefNodeNumber rnumber
176      * @since MMBase-1.7
177      */

178
179     public String JavaDoc getBuilderName(Integer JavaDoc reldefNodeNumber) {
180         return (String JavaDoc) rnumberCache.get(reldefNodeNumber);
181     }
182
183
184     /**
185      * @since MMBase-1.7
186      */

187     protected String JavaDoc findBuilderName(MMObjectNode node) {
188         String JavaDoc bulname = null;
189         if (usesbuilder) {
190             int builder = node.getIntValue("builder");
191             if (builder <= 0) {
192                 bulname = node.getStringValue("sname");
193             } else {
194                 bulname = mmb.getTypeDef().getValue(builder);
195               }
196         } else {
197             // fix for old mmbases that have no builder field
198
bulname = node.getStringValue("sname");
199             if (mmb.getMMObject(bulname) == null) bulname=null;
200         }
201         if (bulname == null) {
202             return "insrel";
203         } else {
204             return bulname;
205         }
206     }
207
208     /**
209      * Returns the builder name of a relation definition.
210      * If the buildername cannot be accurately determined, the <code>sname</code> field will be returned instead.
211      * @param node The reldef Node
212      * @return the builder name
213      */

214     public String JavaDoc getBuilderName(MMObjectNode node) {
215         if (node == null) return "NULL";
216         return (String JavaDoc) rnumberCache.get(new Integer JavaDoc(node.getNumber()));
217     }
218
219
220     /**
221      * Returns the builder of a relation definition.
222      * @return the builder
223      */

224     public InsRel getBuilder(int rnumber) {
225         return getBuilder(getNode(rnumber));
226     }
227
228     /**
229      * Returns the builder of a relation definition.
230      * @return the builder
231      */

232     public InsRel getBuilder(MMObjectNode node) {
233         String JavaDoc builderName = getBuilderName(node);
234         if (builderName == null) {
235             throw new RuntimeException JavaDoc("Node " + node + " has no builder?");
236         }
237         MMObjectBuilder builder = mmb.getBuilder(builderName);
238         if (builder == null) {
239             return mmb.getInsRel();
240         } else {
241             if (builder instanceof InsRel) {
242                 return (InsRel) builder;
243             } else {
244                 log.warn("The builder " + builderName + " of node " + node.getNumber() + " is no InsRel (but " + builder.getClass() + "). Perhaps it is inactive? Impossible here. Returing InsRel any way.");
245                 return mmb.getInsRel();
246
247             }
248         }
249     }
250
251     /**
252      * Returns the first occurrence of a reldef node of a relation definition.
253      * used to set the default reldef for a specific builder.
254      * @return the default reldef node, or <code>null</code> if not found.
255      */

256     public MMObjectNode getDefaultForBuilder(InsRel relBuilder) {
257         MMObjectNode node = null;
258         NodeSearchQuery query = new NodeSearchQuery(this);
259         if (usesbuilder) {
260             Integer JavaDoc value = new Integer JavaDoc(relBuilder.getNumber());
261             Constraint constraint = new BasicFieldValueConstraint(query.getField(getField("builder")), value);
262             query.setConstraint(constraint);
263         } else {
264             // backward compatibility with older reldefs builders.
265
// this should become obsolete at some point.
266
Constraint constraint1 = new BasicFieldValueConstraint(query.getField(getField("sname")), relBuilder.getTableName());
267             Constraint constraint2 = new BasicFieldValueConstraint(query.getField(getField("dname")), relBuilder.getTableName());
268             BasicCompositeConstraint constraint = new BasicCompositeConstraint(CompositeConstraint.LOGICAL_OR);
269             constraint.addChild(constraint1);
270             constraint.addChild(constraint2);
271             query.setConstraint(constraint);
272         }
273         query.setMaxNumber(1);
274         try {
275             List reldefs = getNodes(query);
276             if (reldefs.size() != 0) {
277                 node =(MMObjectNode)reldefs.get(0);
278             }
279         } catch (SearchQueryException sqe) {
280             // should never happen
281
log.error(sqe);
282         }
283         return node;
284     }
285
286     /**
287      * Tests whether the data in a node is valid (throws an exception if this is not the case).
288      * @param node The node whose data to check
289      */

290     public void testValidData(MMObjectNode node) throws InvalidDataException{
291         int dir=node.getIntValue("dir");
292         if ((dir!=DIR_UNIDIRECTIONAL) && (dir!=DIR_BIDIRECTIONAL)) {
293             throw new InvalidDataException("Invalid directionality ("+dir+") specified","dir");
294         }
295         if (usesbuilder) {
296             int builder=node.getIntValue("builder");
297             if (builder<=0) {
298                 builder=mmb.getInsRel().getNumber();
299             }
300             if (!isRelationBuilder(builder)) {
301                 throw new InvalidDataException("Builder ("+builder+") is not a relationbuilder","builder");
302             }
303         }
304     };
305
306     /**
307      * Insert a new object, and updated the cache after an insert.
308      * This method indirectly calls {@link #preCommit}.
309      * @param owner The administrator creating the node
310      * @param node The object to insert. The object need be of the same type as the current builder.
311      * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
312      */

313     public int insert(String JavaDoc owner, MMObjectNode node) {
314         // check RelDef for duplicates
315
String JavaDoc sname = node.getStringValue("sname");
316         String JavaDoc dname = node.getStringValue("dname");
317         if (getNumberByName(sname + '/' + dname) != -1) {
318             // log.error("The reldef with sname=" + sname + " and dname=" + dname + " already exists");
319
throw new RuntimeException JavaDoc("The reldef with sname=" + sname + " and dname=" + dname + " already exists");
320         }
321         int number = super.insert(owner,node);
322         log.service("Created new reldef " + sname + "/" + dname);
323         if (number != -1) {
324             addToCache(node);
325         }
326         return number;
327     };
328
329
330     /**
331      * Commit changes to this node and updated the cache. This method indirectly calls {@link #preCommit}.
332      * This method does not remove names from the cache, as currently, unique names are not enforced.
333      * @param node The node to be committed
334      * @return a <code>boolean</code> indicating success
335      */

336     public boolean commit(MMObjectNode node) {
337         boolean success = super.commit(node);
338         if (success) {
339             addToCache(node);
340         }
341         return success;
342    }
343
344     /**
345      * Remove a node from the cloud.
346      * @param node The node to remove.
347      */

348     public void removeNode(MMObjectNode node) {
349         // check occurrences in TypeRel
350
// perhaps this can also be done using getAllowedRelations() ?
351
try {
352             MMObjectBuilder typeRel = mmb.getTypeRel();
353             NodeSearchQuery query = new NodeSearchQuery(typeRel);
354             Integer JavaDoc value = new Integer JavaDoc(node.getNumber());
355             Constraint constraint = new BasicFieldValueConstraint(query.getField(typeRel.getField("rnumber")), value);
356             query.setConstraint(constraint);
357             List typerels = typeRel.getNodes(query);
358             if (typerels.size() > 0) {
359                 throw new RuntimeException JavaDoc("Cannot delete reldef, it is referenced by typerels: " + typerels);
360             }
361         } catch (SearchQueryException sqe) {
362             // should never happen
363
log.error(sqe);
364         }
365
366         // check occurrences in the relation builders
367
try {
368             MMObjectBuilder insRel = mmb.getInsRel();
369             NodeSearchQuery query = new NodeSearchQuery(insRel);
370             Integer JavaDoc value = new Integer JavaDoc(node.getNumber());
371             Constraint constraint = new BasicFieldValueConstraint(query.getField(insRel.getField("rnumber")), value);
372             query.setConstraint(constraint);
373             int i = insRel.count(query);
374             if (i > 0) {
375                 throw new RuntimeException JavaDoc("Cannot delete reldef node, it is still used in " + i + " relations");
376             }
377         } catch (SearchQueryException sqe) {
378             // should never happen
379
log.error(sqe);
380         }
381
382         super.removeNode(node);
383         removeFromCache(node);
384     }
385
386     /**
387      * Sets defaults for a new relation definition.
388      * Initializes a relation to be bidirectional, and, if applicable, to use the 'insrel' builder.
389      * @param node Node to be initialized
390      */

391     public void setDefaults(MMObjectNode node) {
392         node.setValue("dir", DIR_BIDIRECTIONAL);
393         if (usesbuilder) {
394             node.setValue("builder", mmb.getInsRel().getNumber());
395         }
396     }
397
398     /**
399      * Retrieve descriptors for a relation definition's fields,
400      * specifically a descriptive text for the relation's direction (dir)
401      * @param field Name of the field whose description should be returned.
402      * valid values : 'dir'
403      * @param node Relation definition containing the field's information
404      * @return A descriptive text for the field's contents, or null if no description could be generated
405      */

406
407     public String JavaDoc getGUIIndicator(String JavaDoc field,MMObjectNode node) {
408         try {
409             if (field.equals("dir")) {
410                 switch (node.getIntValue("dir")) {
411                 case DIR_BIDIRECTIONAL:
412                     return "bidirectional";
413
414                 case DIR_UNIDIRECTIONAL:
415                     return "unidirectional";
416
417                 default:
418                     return "unknown";
419                 }
420             } else if (field.equals("builder")) {
421                 int builder=node.getIntValue("builder");
422                 if (builder<=0) {
423                     return "insrel";
424                 } else {
425                     return mmb.getTypeDef().getValue(builder);
426                 }
427             }
428         } catch (Exception JavaDoc e) {}
429         return null;
430     }
431
432     /**
433      * Checks to see if a given relation definition is stored in the cache.
434      * @param name A <code>String</code> of the relation definitions' name
435      * @return a <code>boolean</code> indicating success if the relationname exists
436      */

437
438     public boolean isRelationTable(String JavaDoc name) {
439         Object JavaDoc ob;
440         ob=relCache.get(name);
441         return ob!=null;
442     }
443
444     // Retrieves the relationbuildercache (initializes a new cache if the old one is empty)
445
private Map getRelBuilderCache() {
446         // first make sure the buildercache is loaded
447
if (relBuilderCache == null) {
448             relBuilderCache = new HashMap();
449             // add all builders that descend from InsRel
450
Iterator buls = mmb.getBuilders().iterator();
451             while (buls.hasNext()) {
452                 MMObjectBuilder fbul = (MMObjectBuilder) buls.next();
453                 if (fbul instanceof InsRel) {
454                     relBuilderCache.put(new Integer JavaDoc(fbul.getNumber()), fbul);
455                 }
456             }
457         }
458         return relBuilderCache;
459     }
460
461     /**
462      * Checks to see if a given builder (otype) is known to be a relation builder.
463      * @param number The otype of the builder
464      * @return a <code>boolean</code> indicating success if the builder exists in the cache
465      */

466
467     public boolean isRelationBuilder(int number) {
468         Object JavaDoc ob;
469         ob = getRelBuilderCache().get(new Integer JavaDoc(number));
470         return ob != null;
471     }
472
473     /**
474      * Returns a list of builders currently implementing a relation node.
475      * @return an <code>Enumeration</code> containing the builders (as otype)
476      */

477
478     public Enumeration getRelationBuilders() {
479         return Collections.enumeration(getRelBuilderCache().values());
480     }
481
482     /**
483      * Search the relation definition table for the identifying number of
484      * a relation, by name of the relation to use
485      * Similar to {@link #getGuessedByName} (but does not make use of dname)
486      * Not very suitable to use, as success is dependent on the uniqueness of the builder in the table (not enforced, so unpredictable).
487      * @param role The builder name on which to search for the relation
488      * @return A <code>int</code> value indicating the relation's object number, or -1 if not found. If multiple relations use the
489      * indicated buildername, the first one found is returned.
490      * @deprecated renamed to {@link #getNumberByName} which better explains its use
491      */

492     public int getGuessedNumber(String JavaDoc role) {
493         return getNumberByName(role, false);
494     }
495
496     /**
497      * Search the relation definition table for the identifying number of
498      * a relationdefinition, by name of the role to use.
499      * The name should be either the primary identifying role name (sname),
500      * or a combination of sname and dname separated by a slash ("/").
501      * @todo support for searching on dname
502      * @param role The role name on which to search
503      * @return A <code>int</code> value indicating the relation's object number, or -1 if not found.
504      */

505     public int getNumberByName(String JavaDoc role) {
506         return getNumberByName(role, false);
507      }
508
509     /**
510      * Search the relation definition table for the identifying number of
511      * a relationdefinition, by name of the role to use.
512      * Initially, this method seraches on either the primary identifying
513      * role name (sname), or a combination of sname and dname separated by a slash ("/").
514      * If this yields no result, and searchBidirectional is true, the method then searches
515      * on the secondary identifying role name.
516      * The latter is not cached (to avoid conflict and is thus slower).
517      *
518      * @todo support for searching on dname
519      * @param role The role name on which to search
520      * @param searchBidirectional determines whether to also search in sname
521      * @return A <code>int</code> value indicating the relation's object number, or -1 if not found.
522      */

523     public int getNumberByName(String JavaDoc role, boolean searchBidirectional) {
524         Integer JavaDoc number = (Integer JavaDoc) relCache.get(role);
525         if (number != null) {
526             return number.intValue();
527         }
528         if (searchBidirectional) {
529             NodeSearchQuery query = new NodeSearchQuery(this);
530             Constraint constraint = new BasicFieldValueConstraint(query.getField(getField("dname")), role);
531             query.setConstraint(constraint);
532             query.setMaxNumber(1);
533             try {
534                 List reldefs = getNodes(query);
535                 if (reldefs.size() != 0) {
536                     MMObjectNode node = (MMObjectNode)reldefs.get(0);
537                     return node.getNumber();
538                 }
539             } catch (SearchQueryException sqe) {
540                 // should never happen
541
log.error(sqe);
542             }
543         }
544         return -1;
545      }
546
547     /**
548      * Search the relation definition table for the identifying number of
549      * a relation, by name of the relation to use.
550      * This function is used by descendants of Insrel to determine a default reference to a 'relation definition' (reldef entry).
551      * The 'default' is the relation with the same name as the builder. If no such relation exists, there is no default.
552      * @param role The role name on which to search for the relation
553      * @return A <code>int</code> value indicating the relation's object number, or -1 if not found. If multiple relations use the
554      * indicated buildername, the first one found is returned.
555      * @deprecated use {@link #getNumberByName} instead
556      */

557
558     public int getGuessedByName(String JavaDoc role) {
559         return getNumberByName(role,true);
560     }
561
562     /**
563      * Searches for the relation number on the combination of sname and dname.
564      * When there's no match found in this order a search with a swapped sname and dname will be done.
565      * Note that there is no real assurance that an sname/dname combination must be unique.
566      * @param sname The first name on which to search for the relation (preferred as the source)
567      * @param dname The second name on which to search for the relation (preferred as the destination)
568      * @return A <code>int</code> value indicating the relation's object number, or -1 if not found. If multiple relations use the
569      * indicated names, the first one found is returned.
570      * @deprecated use {@link #getNumberByName} instead
571      */

572     public int getRelNrByName(String JavaDoc sname, String JavaDoc dname) {
573         int res=getNumberByName(sname+"/"+dname);
574         if (res<-1) {
575             res=getNumberByName(dname+"/"+sname);
576         }
577         return res;
578     }
579
580     /**
581      * {@inheritDoc}
582      * Called when a remote node is changed.
583      * If a node is changed or newly created, this adds the new or updated role (sname and dname) to the
584      * cache.
585      * @todo Old roles are cuerrently not cleared or removed - which means that they may remain
586      * useable for some time after the actual role is deleted or renamed.
587      * This because old role information is no longer available when this call is made.
588      * @since MMBase-1.7.1
589      */

590     public boolean nodeRemoteChanged(String JavaDoc machine, String JavaDoc number, String JavaDoc builder, String JavaDoc ctype) {
591         if (builder.equals(getTableName())) {
592             if (ctype.equals("c") || ctype.equals("n")) {
593                 // should remove roles referencing this number from relCache here
594
int rnumber = Integer.parseInt(number);
595                 removeFromCache(rnumber);
596                 addToCache(getNode(rnumber));
597             } else if (ctype.equals("d")) {
598                 removeFromCache(Integer.parseInt(number));
599             }
600         }
601         return super.nodeRemoteChanged(machine, number, builder, ctype);
602     }
603 }
604
605
606
607
608
Popular Tags