001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.module.corebuilders;
011:
012: import java.util.*;
013: import org.mmbase.module.core.*;
014:
015: import org.mmbase.storage.search.implementation.*;
016: import org.mmbase.storage.search.*;
017: import org.mmbase.util.logging.Logger;
018: import org.mmbase.util.logging.Logging;
019:
020: /**
021: * RelDef, one of the meta stucture nodes, is used to define the possible relation types.
022: * <p>
023: * A Relation Definition consists of a source and destination, and a descriptor
024: * (direction) for it's use (unidirectional or bidirectional).
025: * </p><p>
026: * Relations are mapped to a builder.<br />
027: * This is so that additional functionality can be added by means of a builder (i.e. AuthRel).<br />
028: * The old system mapped the relations to a builder by name.
029: * Unfortunately, this means that some care need be taken when naming relations, as unintentionally
030: * naming a relation to a builder can give bad (if not disastrous) results.<br />
031: * Relations that are not directly mapped to a builder are mapped (internally) to the {@link InsRel} builder instead.
032: * </p><p>
033: * The new system uses an additional field to map to a builder.
034: * This 'builder' field contains a reference (otype) to the builder to be used.
035: * If null or 0, the builder is assumed to refer to the {@link InsRel} builder.
036: * <code>sname</code> is now the name of the relation and serves no function.
037: * </p><p>
038: * This patched version of RelDef can make use of either direct builder references (through the builder field), or the old system of using names.
039: * The system used is determined by examining whether the builder field has been defined in the builder's configuration (xml) file.
040: * See the documentation of the relations project at http://www.mmbase.org for more info.
041: * </p>
042: *
043: * @todo Fix cache so it will be updated using multicast.
044: * @author Daniel Ockeloen
045: * @author Pierre van Rooden
046: * @version $Id: RelDef.java,v 1.44 2007/02/25 17:56:58 nklasens Exp $
047: */
048: public class RelDef extends MMObjectBuilder {
049:
050: private static final Logger log = Logging
051: .getLoggerInstance(RelDef.class);
052:
053: /** Value of "dir" field indicating unidirectional relations. */
054: public final static int DIR_UNIDIRECTIONAL = 1;
055:
056: /** Value of "dir" field indicating bidirectional relatios. */
057: public final static int DIR_BIDIRECTIONAL = 2;
058:
059: /**
060: * Indicates whether the relationdefinitions use the 'builder' field (that is, whether the
061: * field has been defined in the xml file). Used for backward compatibility.
062: */
063: public static boolean usesbuilder = false;
064:
065: // cache of relation definitions
066: // sname or sname/dname -> rnumber
067: private final Map<String, Integer> relCache = new HashMap<String, Integer>();
068:
069: // cache of valid relationbuilders
070: // otype of relations builder -> MMObjectBuilder
071: private Map<Integer, MMObjectBuilder> relBuilderCache = null;
072:
073: // rnumber -> MMObjectBuilder Name
074: private Map<Integer, String> rnumberCache = new HashMap<Integer, String>();
075:
076: /**
077: * Contruct the builder
078: */
079: public RelDef() {
080: }
081:
082: /**
083: * Initializes the builder by reading the cache. Also determines whether the 'builder' field is used.
084: * @return A <code>boolean</code> value, always success (<code>true</code>), as any exceptions are
085: * caught and logged.
086: */
087: public boolean init() {
088: super .init();
089: usesbuilder = getField("builder") != null;
090: return readCache();
091: }
092:
093: /**
094: * Puts a role in the reldef cache.
095: * The role is entered both with its sname (primary identifier) and
096: * it's sname/dname combination.
097: */
098: private void addToCache(MMObjectNode node) {
099: Integer rnumber = node.getNumber();
100: relCache.put(node.getStringValue("sname"), rnumber);
101: relCache.put(node.getStringValue("sname") + "/"
102: + node.getStringValue("dname"), rnumber);
103:
104: rnumberCache.put(rnumber, findBuilderName(node));
105: }
106:
107: /**
108: * Removes a role from the reldef cache.
109: * The role is removed both with its sname (primary identifier) and
110: * it's sname/dname combination.
111: */
112: private void removeFromCache(MMObjectNode node) {
113: relCache.remove(node.getStringValue("sname"));
114: relCache.remove(node.getStringValue("sname") + "/"
115: + node.getStringValue("dname"));
116:
117: rnumberCache.remove(Integer.valueOf(node.getNumber()));
118: }
119:
120: /**
121: * @since MMBase-1.7.1
122: */
123: private void removeFromCache(int rnumber) {
124: Integer r = Integer.valueOf(rnumber);
125: Iterator<Map.Entry<String, Integer>> i = relCache.entrySet()
126: .iterator();
127: while (i.hasNext()) {
128: Map.Entry<String, Integer> entry = i.next();
129: Integer value = entry.getValue();
130: if (r.equals(value)) {
131: i.remove();
132: }
133: }
134: rnumberCache.remove(r);
135: }
136:
137: /**
138: * Reads all relation definition names in an internal cache.
139: * The cache is used by {@link #isRelationTable}
140: * @return A <code>boolean</code> value, always success (<code>true</code>), as any exceptions are
141: * caught and logged.
142: */
143: private boolean readCache() {
144: rnumberCache.clear();
145: relCache.clear(); // add insrel (default behavior)
146: relCache.put("insrel", Integer.valueOf(-1));
147: // add relation definiation names
148: try {
149: for (MMObjectNode n : getNodes(new NodeSearchQuery(this ))) {
150: addToCache(n);
151: }
152: } catch (org.mmbase.storage.search.SearchQueryException sqe) {
153: log.error("Error while reading reldef cache"
154: + sqe.getMessage(), sqe);
155: }
156: return true;
157: }
158:
159: /**
160: * Returns a GUI description of a relation definition.
161: * The description is dependent on the direction (uni/bi) of the relation
162: * @param node Relation definition to describe
163: * @return A <code>String</code> of descriptive text
164: */
165: public String getGUIIndicator(MMObjectNode node) {
166: int dir = node.getIntValue("dir");
167: if (dir == DIR_UNIDIRECTIONAL) {
168: return node.getStringValue("sguiname");
169: } else {
170: String st1 = node.getStringValue("sguiname");
171: String st2 = node.getStringValue("dguiname");
172: return st1 + "/" + st2;
173: }
174: }
175:
176: /**
177: * @param reldefNodeNumber rnumber
178: * @since MMBase-1.7
179: */
180:
181: public String getBuilderName(Integer reldefNodeNumber) {
182: return rnumberCache.get(reldefNodeNumber);
183: }
184:
185: /**
186: * @since MMBase-1.7
187: */
188: protected String findBuilderName(MMObjectNode node) {
189: String bulname = null;
190: if (usesbuilder) {
191: int builder = node.getIntValue("builder");
192: if (builder <= 0) {
193: bulname = node.getStringValue("sname");
194: } else {
195: bulname = mmb.getTypeDef().getValue(builder);
196: }
197: } else {
198: // fix for old mmbases that have no builder field
199: bulname = node.getStringValue("sname");
200: if (mmb.getMMObject(bulname) == null)
201: bulname = null;
202: }
203: if (bulname == null) {
204: return "insrel";
205: } else {
206: return bulname;
207: }
208: }
209:
210: /**
211: * Returns the builder name of a relation definition.
212: * If the buildername cannot be accurately determined, the <code>sname</code> field will be returned instead.
213: * @param node The reldef Node
214: * @return the builder name
215: */
216: public String getBuilderName(MMObjectNode node) {
217: if (node == null)
218: return "NULL";
219: return rnumberCache.get(Integer.valueOf(node.getNumber()));
220: }
221:
222: /**
223: * Returns the builder of a relation definition.
224: * @return the builder
225: */
226: public InsRel getBuilder(int rnumber) {
227: return getBuilder(getNode(rnumber));
228: }
229:
230: /**
231: * Returns the builder of a relation definition.
232: * @return the builder
233: */
234: public InsRel getBuilder(MMObjectNode node) {
235: String builderName = getBuilderName(node);
236: if (builderName == null) {
237: throw new RuntimeException("Node " + node
238: + " has no builder?");
239: }
240: MMObjectBuilder builder = mmb.getBuilder(builderName);
241: if (builder == null) {
242: return mmb.getInsRel();
243: } else {
244: if (builder instanceof InsRel) {
245: return (InsRel) builder;
246: } else {
247: log
248: .warn("The builder "
249: + builderName
250: + " of node "
251: + node.getNumber()
252: + " is no InsRel (but "
253: + builder.getClass()
254: + "). Perhaps it is inactive? Impossible here. Returing InsRel any way.");
255: return mmb.getInsRel();
256:
257: }
258: }
259: }
260:
261: /**
262: * Returns the first occurrence of a reldef node of a relation definition.
263: * used to set the default reldef for a specific builder.
264: * @return the default reldef node, or <code>null</code> if not found.
265: */
266: public MMObjectNode getDefaultForBuilder(InsRel relBuilder) {
267: MMObjectNode node = null;
268: NodeSearchQuery query = new NodeSearchQuery(this );
269: if (usesbuilder) {
270: Integer value = relBuilder.getNumber();
271: Constraint constraint = new BasicFieldValueConstraint(query
272: .getField(getField("builder")), value);
273: query.setConstraint(constraint);
274: } else {
275: // backward compatibility with older reldefs builders.
276: // this should become obsolete at some point.
277: Constraint constraint1 = new BasicFieldValueConstraint(
278: query.getField(getField("sname")), relBuilder
279: .getTableName());
280: Constraint constraint2 = new BasicFieldValueConstraint(
281: query.getField(getField("dname")), relBuilder
282: .getTableName());
283: BasicCompositeConstraint constraint = new BasicCompositeConstraint(
284: CompositeConstraint.LOGICAL_OR);
285: constraint.addChild(constraint1);
286: constraint.addChild(constraint2);
287: query.setConstraint(constraint);
288: }
289: query.setMaxNumber(1);
290: try {
291: List<MMObjectNode> reldefs = getNodes(query);
292: if (reldefs.size() != 0) {
293: node = reldefs.get(0);
294: }
295: } catch (SearchQueryException sqe) {
296: // should never happen
297: log.error(sqe);
298: }
299: return node;
300: }
301:
302: /**
303: * Tests whether the data in a node is valid (throws an exception if this is not the case).
304: * @param node The node whose data to check
305: */
306: public void testValidData(MMObjectNode node)
307: throws InvalidDataException {
308: int dir = node.getIntValue("dir");
309: if ((dir != DIR_UNIDIRECTIONAL) && (dir != DIR_BIDIRECTIONAL)) {
310: throw new InvalidDataException("Invalid directionality ("
311: + dir + ") specified", "dir");
312: }
313: if (usesbuilder) {
314: int builder = node.getIntValue("builder");
315: if (builder <= 0) {
316: builder = mmb.getInsRel().getNumber();
317: }
318: if (!isRelationBuilder(builder)) {
319: throw new InvalidDataException("Builder (" + builder
320: + ") is not a relationbuilder", "builder");
321: }
322: }
323: };
324:
325: /**
326: * Insert a new object, and updated the cache after an insert.
327: * This method indirectly calls {@link #preCommit}.
328: * @param owner The administrator creating the node
329: * @param node The object to insert. The object need be of the same type as the current builder.
330: * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
331: */
332: public int insert(String owner, MMObjectNode node) {
333: // check RelDef for duplicates
334: String sname = node.getStringValue("sname");
335: String dname = node.getStringValue("dname");
336: if (getNumberByName(sname + '/' + dname) != -1) {
337: // log.error("The reldef with sname=" + sname + " and dname=" + dname + " already exists");
338: throw new RuntimeException("The reldef with sname=" + sname
339: + " and dname=" + dname + " already exists");
340: }
341: int number = super .insert(owner, node);
342: log.service("Created new reldef " + sname + "/" + dname);
343: if (number != -1) {
344: addToCache(node);
345: }
346: return number;
347: };
348:
349: /**
350: * Commit changes to this node and updated the cache. This method indirectly calls {@link #preCommit}.
351: * This method does not remove names from the cache, as currently, unique names are not enforced.
352: * @param node The node to be committed
353: * @return a <code>boolean</code> indicating success
354: */
355: public boolean commit(MMObjectNode node) {
356: boolean success = super .commit(node);
357: if (success) {
358: addToCache(node);
359: }
360: return success;
361: }
362:
363: /**
364: * Remove a node from the cloud.
365: * @param node The node to remove.
366: */
367: public void removeNode(MMObjectNode node) {
368: // check occurrences in TypeRel
369: // perhaps this can also be done using getAllowedRelations() ?
370: try {
371: MMObjectBuilder typeRel = mmb.getTypeRel();
372: NodeSearchQuery query = new NodeSearchQuery(typeRel);
373: Integer value = node.getNumber();
374: Constraint constraint = new BasicFieldValueConstraint(query
375: .getField(typeRel.getField("rnumber")), value);
376: query.setConstraint(constraint);
377: List<MMObjectNode> typerels = typeRel.getNodes(query);
378: if (typerels.size() > 0) {
379: throw new RuntimeException(
380: "Cannot delete reldef, it is referenced by typerels: "
381: + typerels);
382: }
383: } catch (SearchQueryException sqe) {
384: // should never happen
385: log.error(sqe);
386: }
387:
388: // check occurrences in the relation builders
389: try {
390: MMObjectBuilder insRel = mmb.getInsRel();
391: NodeSearchQuery query = new NodeSearchQuery(insRel);
392: Integer value = node.getNumber();
393: Constraint constraint = new BasicFieldValueConstraint(query
394: .getField(insRel.getField("rnumber")), value);
395: query.setConstraint(constraint);
396: int i = insRel.count(query);
397: if (i > 0) {
398: throw new RuntimeException(
399: "Cannot delete reldef node, it is still used in "
400: + i + " relations");
401: }
402: } catch (SearchQueryException sqe) {
403: // should never happen
404: log.error(sqe);
405: }
406:
407: super .removeNode(node);
408: removeFromCache(node);
409: }
410:
411: /**
412: * Sets defaults for a new relation definition.
413: * Initializes a relation to be bidirectional, and, if applicable, to use the 'insrel' builder.
414: * @param node Node to be initialized
415: */
416: public void setDefaults(MMObjectNode node) {
417: node.setValue("dir", DIR_BIDIRECTIONAL);
418: if (usesbuilder) {
419: node.setValue("builder", mmb.getInsRel().getNumber());
420: }
421: }
422:
423: /**
424: * Returns all possible rnumbers ('roles').
425: * @since MMBase-1.9
426: */
427: public Set<Integer> getRoles() {
428: return Collections.unmodifiableSet(rnumberCache.keySet());
429: }
430:
431: /**
432: * Retrieve descriptors for a relation definition's fields,
433: * specifically a descriptive text for the relation's direction (dir)
434: * @param field Name of the field whose description should be returned.
435: * valid values : 'dir'
436: * @param node Relation definition containing the field's information
437: * @return A descriptive text for the field's contents, or null if no description could be generated
438: */
439:
440: public String getGUIIndicator(String field, MMObjectNode node) {
441: try {
442: if (field.equals("dir")) {
443: switch (node.getIntValue("dir")) {
444: case DIR_BIDIRECTIONAL:
445: return "bidirectional";
446:
447: case DIR_UNIDIRECTIONAL:
448: return "unidirectional";
449:
450: default:
451: return "unknown";
452: }
453: } else if (field.equals("builder")) {
454: int builder = node.getIntValue("builder");
455: if (builder <= 0) {
456: return "insrel";
457: } else {
458: return mmb.getTypeDef().getValue(builder);
459: }
460: }
461: } catch (Exception e) {
462: }
463: return null;
464: }
465:
466: /**
467: * Checks to see if a given relation definition is stored in the cache.
468: * @param name A <code>String</code> of the relation definitions' name
469: * @return a <code>boolean</code> indicating success if the relationname exists
470: */
471:
472: public boolean isRelationTable(String name) {
473: return relCache.containsKey(name);
474: }
475:
476: // Retrieves the relationbuildercache (initializes a new cache if the old one is empty)
477: private Map<Integer, MMObjectBuilder> getRelBuilderCache() {
478: // first make sure the buildercache is loaded
479: if (relBuilderCache == null) {
480: relBuilderCache = new HashMap<Integer, MMObjectBuilder>();
481: // add all builders that descend from InsRel
482: for (MMObjectBuilder fbul : mmb.getBuilders()) {
483: if (fbul instanceof InsRel) {
484: relBuilderCache.put(Integer.valueOf(fbul
485: .getNumber()), fbul);
486: }
487: }
488: }
489: return relBuilderCache;
490: }
491:
492: /**
493: * Checks to see if a given builder (otype) is known to be a relation builder.
494: * @param number The otype of the builder
495: * @return a <code>boolean</code> indicating success if the builder exists in the cache
496: */
497:
498: public boolean isRelationBuilder(int number) {
499: return getRelBuilderCache()
500: .containsKey(Integer.valueOf(number));
501: }
502:
503: /**
504: * Returns a list of builders currently implementing a relation node.
505: * @return an <code>Enumeration</code> containing the builders (as otype)
506: */
507:
508: public Enumeration<MMObjectBuilder> getRelationBuilders() {
509: return Collections.enumeration(getRelBuilderCache().values());
510: }
511:
512: /**
513: * Search the relation definition table for the identifying number of
514: * a relation, by name of the relation to use
515: * Similar to {@link #getGuessedByName} (but does not make use of dname)
516: * Not very suitable to use, as success is dependent on the uniqueness of the builder in the table (not enforced, so unpredictable).
517: * @param role The builder name on which to search for the relation
518: * @return A <code>int</code> value indicating the relation's object number, or -1 if not found. If multiple relations use the
519: * indicated buildername, the first one found is returned.
520: * @deprecated renamed to {@link #getNumberByName} which better explains its use
521: */
522: public int getGuessedNumber(String role) {
523: return getNumberByName(role, false);
524: }
525:
526: /**
527: * Search the relation definition table for the identifying number of
528: * a relationdefinition, by name of the role to use.
529: * The name should be either the primary identifying role name (sname),
530: * or a combination of sname and dname separated by a slash ("/").
531: * @todo support for searching on dname
532: * @param role The role name on which to search
533: * @return A <code>int</code> value indicating the relation's object number, or -1 if not found.
534: */
535: public int getNumberByName(String role) {
536: return getNumberByName(role, false);
537: }
538:
539: /**
540: * Search the relation definition table for the identifying number of
541: * a relationdefinition, by name of the role to use.
542: * Initially, this method seraches on either the primary identifying
543: * role name (sname), or a combination of sname and dname separated by a slash ("/").
544: * If this yields no result, and searchBidirectional is true, the method then searches
545: * on the secondary identifying role name.
546: * The latter is not cached (to avoid conflict and is thus slower).
547: *
548: * @todo support for searching on dname
549: * @param role The role name on which to search
550: * @param searchBidirectional determines whether to also search in sname
551: * @return A <code>int</code> value indicating the relation's object number, or -1 if not found.
552: */
553: public int getNumberByName(String role, boolean searchBidirectional) {
554: Integer number = relCache.get(role);
555: if (number != null) {
556: return number.intValue();
557: }
558: if (searchBidirectional) {
559: NodeSearchQuery query = new NodeSearchQuery(this );
560: Constraint constraint = new BasicFieldValueConstraint(query
561: .getField(getField("dname")), role);
562: query.setConstraint(constraint);
563: query.setMaxNumber(1);
564: try {
565: List<MMObjectNode> reldefs = getNodes(query);
566: if (reldefs.size() != 0) {
567: MMObjectNode node = reldefs.get(0);
568: return node.getNumber();
569: }
570: } catch (SearchQueryException sqe) {
571: // should never happen
572: log.error(sqe);
573: }
574: }
575: return -1;
576: }
577:
578: /**
579: * Search the relation definition table for the identifying number of
580: * a relation, by name of the relation to use.
581: * This function is used by descendants of Insrel to determine a default reference to a 'relation definition' (reldef entry).
582: * The 'default' is the relation with the same name as the builder. If no such relation exists, there is no default.
583: * @param role The role name on which to search for the relation
584: * @return A <code>int</code> value indicating the relation's object number, or -1 if not found. If multiple relations use the
585: * indicated buildername, the first one found is returned.
586: * @deprecated use {@link #getNumberByName} instead
587: */
588:
589: public int getGuessedByName(String role) {
590: return getNumberByName(role, true);
591: }
592:
593: /**
594: * Searches for the relation number on the combination of sname and dname.
595: * When there's no match found in this order a search with a swapped sname and dname will be done.
596: * Note that there is no real assurance that an sname/dname combination must be unique.
597: * @param sname The first name on which to search for the relation (preferred as the source)
598: * @param dname The second name on which to search for the relation (preferred as the destination)
599: * @return A <code>int</code> value indicating the relation's object number, or -1 if not found. If multiple relations use the
600: * indicated names, the first one found is returned.
601: * @deprecated use {@link #getNumberByName} instead
602: */
603: public int getRelsNrByName(String sname, String dname) {
604: int res = getNumberByName(sname + "/" + dname);
605: if (res < -1) {
606: res = getNumberByName(dname + "/" + sname);
607: }
608: return res;
609: }
610:
611: /**
612: * {@inheritDoc}
613: * Called when a remote node is changed.
614: * If a node is changed or newly created, this adds the new or updated role (sname and dname) to the
615: * cache.
616: * @todo Old roles are cuerrently not cleared or removed - which means that they may remain
617: * useable for some time after the actual role is deleted or renamed.
618: * @todo Use 1.8 event mechanism in stead.
619: *
620: * This because old role information is no longer available when this call is made.
621: * @since MMBase-1.7.1
622:
623: */
624: public boolean nodeRemoteChanged(String machine, String number,
625: String builder, String ctype) {
626: if (builder.equals(getTableName())) {
627: if (ctype.equals("c") || ctype.equals("n")) {
628: // should remove roles referencing this number from relCache here
629: int rnumber = Integer.parseInt(number);
630: removeFromCache(rnumber);
631: addToCache(getNode(rnumber));
632: } else if (ctype.equals("d")) {
633: removeFromCache(Integer.parseInt(number));
634: }
635: }
636: return super.nodeRemoteChanged(machine, number, builder, ctype);
637: }
638: }
|