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.io.*;
013: import java.util.*;
014:
015: import org.xml.sax.InputSource;
016:
017: import org.mmbase.storage.search.implementation.*;
018: import org.mmbase.storage.search.*;
019: import org.mmbase.module.core.*;
020: import org.mmbase.util.*;
021: import org.mmbase.util.logging.*;
022: import org.mmbase.util.xml.BuilderReader;
023:
024: /**
025: * TypeDef is used to define the* object types (builders).
026: * Nodes of this builder have a virtual 'config' field.
027: * This field contains the xml-Document of the builder represented by the node.
028: * The filename used to reference the xml document is derived by extending the field 'name'.
029: * Creating a new typedef node automatically creates a new xml file and loads a new builder.
030: * Removing a node drops and unloads a builder (including the xml).
031: * Changes to the config will also be active on commit of the node.
032: *
033: * @author Daniel Ockeloen
034: * @author Pierre van Rooden
035: * @version $Id: TypeDef.java,v 1.76 2008/02/08 10:40:20 michiel Exp $
036: */
037: public class TypeDef extends MMObjectBuilder {
038:
039: /**
040: * The property in the builder file ('deploy-dir') that sets the directory
041: * where new builder configuration files are to be deployed.
042: */
043: public static final String PROPERTY_DEPLOY_DIR = "deploy-dir";
044:
045: // Logger routine
046: private static final Logger log = Logging
047: .getLoggerInstance(TypeDef.class);
048: // Directory where new builder configuration files are deployed by default
049: String defaultDeploy = null;
050:
051: /**
052: * Number-to-name cache.
053: */
054: private Map<Integer, String> numberToNameCache = null; // object number -> typedef name
055:
056: /**
057: * Name-to-number cache.
058: */
059: private Map<String, Integer> nameToNumberCache = null; // typedef name -> object number
060:
061: /**
062: * List of known builders.
063: */
064: private final Vector<String> typedefsLoaded = new Vector<String>(); // Contains the names of all active builders
065:
066: /**
067: * Sets the default deploy directory for the builders.
068: * @return true if init was completed, false if uncompleted.
069: */
070: public boolean init() {
071: broadCastChanges = false;
072: boolean result = super .init();
073: if (defaultDeploy == null) {
074: // determine default deploy directory
075: String builderDeployDir = getInitParameter(PROPERTY_DEPLOY_DIR);
076: if (builderDeployDir == null) {
077: builderDeployDir = "applications";
078: }
079: defaultDeploy = builderDeployDir;
080: if (!defaultDeploy.endsWith("/")
081: && !defaultDeploy.endsWith("\\")) {
082: defaultDeploy += "/";
083: }
084: log.service("Using '" + defaultDeploy
085: + "' as default deploy dir for our builders.");
086: }
087: return result;
088: }
089:
090: protected Map<Integer, String> getNumberToNameCache() {
091: if (numberToNameCache == null)
092: readCache();
093: return numberToNameCache;
094: }
095:
096: protected Map<String, Integer> getNameToNumberCache() {
097: if (nameToNumberCache == null)
098: readCache();
099: return nameToNumberCache;
100: }
101:
102: /**
103: * Insert a new object (content provided) in the cloud, including an entry for the object alias (if provided).
104: * This method indirectly calls {@link #preCommit}.
105: * Asside from that, this method loads the builder this node represents, and initalizes it. If you do
106: * not wish to load the builder (i.e. because it is already loaded), use {@link #insert(String, MMObjectNode, boolean)}
107: * @param owner The administrator creating the node
108: * @param node The object to insert. The object need be of the same type as the current builder.
109: * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
110: */
111: public int insert(String owner, MMObjectNode node) {
112: return insert(owner, node, true);
113: }
114:
115: /**
116: * Insert a new object (content provided) in the cloud, including an entry for the object alias (if provided).
117: * This method indirectly calls {@link #preCommit}.
118: * @param owner The administrator creating the node
119: * @param node The object to insert. The object need be of the same type as the current builder.
120: * @param loadBuilder if <code>true</code>, the builder should be loaded. This method is set to
121: * <code>false</code> when it is called from the init() method of MMObjectBuilder to prevent
122: * it from being loaded twice
123: * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
124: */
125: public int insert(String owner, MMObjectNode node,
126: boolean loadBuilder) {
127: if (log.isDebugEnabled()) {
128: // would be logical to log this in SERVICE but the same occurance is logged on INFO already in MMObjectBuilder.init()
129: log.debug("Insert of builder-node with name '"
130: + node.getStringValue("name") + "', loadBuilder = "
131: + loadBuilder);
132: }
133: // look if we can store to file, if it aint there yet...
134: String path = getBuilderConfiguration(node);
135: java.net.URL url = mmb.getBuilderLoader().getResource(path);
136: try {
137: if (!url.openConnection().getDoInput()) {
138: // first store our config....
139: storeBuilderConfiguration(node);
140: }
141: } catch (Exception e) {
142: throw new RuntimeException(e.getMessage(), e);
143: }
144: // Quick fix around MMB-1590. Perhaps it should be solved more genericly, closer to the
145: // storage layer.
146: String desc = node.getStringValue("description");
147: if (desc.length() > getField("description").getMaxLength()) {
148: node.setValue("description", desc.substring(0, getField(
149: "description").getMaxLength()));
150: }
151:
152: // try if the builder was already in TypeDef for some reason
153: // this can happen when another thread was here first
154: int result = getIntValue(node.getStringValue("name"));
155: if (result < 0) {
156: // otherwise save the node
157: result = super .insert(owner, node);
158: }
159: if (result != -1) {
160: // update the cache
161: Integer number = result;
162: String name = node.getStringValue("name");
163: getNameToNumberCache().put(name, number);
164: getNumberToNameCache().put(number, name);
165: // Load the builder if needed
166: if (loadBuilder) {
167: loadBuilder(node);
168: }
169: }
170: return result;
171: }
172:
173: /**
174: * Commit changes to this node to the database. This method indirectly calls {@link #preCommit}.
175: * Use only to commit changes - for adding node, use {@link #insert}.
176: * @param node The node to be committed
177: * @return true if commit successful
178: */
179:
180: public boolean commit(MMObjectNode node) {
181: log.service("Commit of builder-node with name '"
182: + node.getStringValue("name") + "' ( #"
183: + node.getNumber() + ")");
184: try {
185: MMObjectBuilder builder = getBuilder(node);
186: BuilderReader originalBuilderXml = new BuilderReader(mmb
187: .getBuilderLoader().getDocument(
188: getBuilderConfiguration(node)), getMMBase());
189: String config = node.getStringValue("config");
190: StringReader stringReader;
191: if (config
192: .indexOf("xmlns=\"http://www.mmbase.org/xmlns/builder\"") > 0) {
193: stringReader = new StringReader(
194: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
195: + config);
196: } else {
197: stringReader = new StringReader(
198: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
199: + "<!DOCTYPE builder PUBLIC \""
200: + BuilderReader.PUBLIC_ID_BUILDER
201: + "\" \":http://www.mmbase.org/dtd/"
202: + BuilderReader.DTD_BUILDER + "\" >\n"
203: + config);
204: }
205: BuilderReader newBuilderXml = new BuilderReader(
206: new InputSource(stringReader), getMMBase());
207: if (!originalBuilderXml.equals(newBuilderXml)) {
208: try {
209: // unload the builder...
210: builder = unloadBuilder(node);
211: // attempt to apply changes to the database
212: // by dropping the buildertable (ARGH!)
213: if (!originalBuilderXml
214: .storageEquals(newBuilderXml)) {
215: builder.delete();
216: }
217: // finally save our new config.
218: storeBuilderConfiguration(node);
219: } finally {
220: // clear config, so it will be refreshed later on
221: node.storeValue("config", null);
222: // load the builder again.. (will possibly create a new table)
223: loadBuilder(node);
224: }
225: }
226: } catch (Exception ioe) {
227: log.error(ioe.getMessage(), ioe);
228: }
229: return super .commit(node);
230: }
231:
232: /**
233: * Remove a node from the cloud, when the represented builder was active
234: * it will also be unloaded
235: * @param node The node to remove.
236: * @throws RuntimeException When the operation could not be performed
237: */
238: public void removeNode(MMObjectNode node) {
239: log.info("Remove of builder-node with name '"
240: + node.getStringValue("name") + "' ( #"
241: + node.getNumber() + ")");
242: // only delete when builder is completely empty...
243: MMObjectBuilder builder = getBuilder(node);
244: testBuilderRemovable(builder, node);
245: builder = unloadBuilder(node);
246: // now that the builder cannot be started again (since config is now really missing)
247: if (builder != null) {
248: builder.delete();
249: }
250: // try to delete the configuration file first!.....
251: if (!deleteBuilderConfiguration(node)) {
252: // delete-ing failed, reload the builder again...
253: loadBuilder(node);
254: throw new RuntimeException(
255: "Could not delete builder config");
256: }
257: Integer number = node.getIntegerValue("number");
258: String name = node.getStringValue("name");
259: super .removeNode(node);
260: getNameToNumberCache().remove(name);
261: getNumberToNameCache().remove(number);
262: }
263:
264: /**
265: * Fill the typedef caches with the initial values.
266: * Caches filled are a number-to-name and a name-to-number cache.
267: * @duplicate should be moved to org.mmbase.cache
268: * @return always true
269: */
270: private boolean readCache() {
271: // at least fill in typedef
272: log.service("Reading typedef caches");
273: numberToNameCache = Collections
274: .synchronizedMap(new HashMap<Integer, String>());
275: nameToNumberCache = Collections
276: .synchronizedMap(new HashMap<String, Integer>());
277: NodeSearchQuery query = new NodeSearchQuery(this );
278: try {
279: for (MMObjectNode n : getNodes(query)) {
280: Integer number = n.getIntegerValue("number");
281: String name = n.getStringValue("name");
282: if (number != null && name != null) {
283: nameToNumberCache.put(name, number);
284: numberToNameCache.put(number, name);
285: } else {
286: log
287: .error("Could not add typedef cache-entry number/name= "
288: + number + "/" + name);
289: }
290: }
291: } catch (SearchQueryException sqe) {
292: // should never happen.
293: log.error(sqe);
294: }
295: return true;
296: }
297:
298: /**
299: * Obtain the type value of the requested builder
300: * @todo smarter cache update
301: * @param builderName name of the builder
302: * @return the object type as an int, -1 if not defined.
303: */
304: public int getIntValue(String builderName) {
305: Integer result = getNameToNumberCache().get(builderName);
306: if (result != null) {
307: return result.intValue();
308: } else {
309: return -1;
310: }
311: }
312:
313: /**
314: * Obtain the buildername of the requested type
315: * @param type the object type
316: * @return the name of the builder as a string, null if not found
317: */
318: public String getValue(int type) {
319: String result = getNumberToNameCache().get(type);
320: if (result == null) {
321: log.warn("Could not find builder name for typedef number "
322: + type);
323: }
324: return result;
325: }
326:
327: /**
328: * Obtain the buildername of the requested type
329: * @param type the object type
330: * @return the name of the builder as a string, "unknown" if not found
331: * @deprecated use getValue(int)
332: */
333: public String getValue(String type) {
334: try {
335: return getNumberToNameCache().get(Integer.parseInt(type));
336: } catch (Exception e) {
337: return "unknown";
338: }
339: }
340:
341: /**
342: * @javadoc
343: */
344: public String getSingularName(String builderName, String language) {
345: if (builderName == null)
346: return "unknown";
347: MMObjectBuilder bul = mmb.getBuilder(builderName);
348: if (bul != null) {
349: if (language == null) {
350: return bul.getSingularName();
351: } else {
352: return bul.getSingularName(language);
353: }
354: } else {
355: return "inactive (" + builderName + ")";
356: }
357: }
358:
359: /**
360: * @javadoc
361: */
362: public boolean isRelationTable(String name) {
363: return mmb.getRelDef().isRelationTable(name);
364: }
365:
366: /**
367: * Provides additional functionality when obtaining field values.
368: * This method is called whenever a Node of the builder's type fails at evaluating a getValue() request
369: * (generally when a fieldname is supplied that doesn't exist).
370: * It allows the system to add 'functions' to be included with a field name, such as 'html(body)' or 'time(lastmodified)'.
371: * This method will parse the fieldname, determining functions and calling the {@link #executeFunction} method to handle it.
372: * Functions in fieldnames can be given in the format 'functionname(fieldname)'. An old format allows 'functionname_fieldname' instead,
373: * though this only applies to the text functions 'short', 'html', and 'wap'.
374: * Functions can be nested, i.e. 'html(shorted(body))'.
375: * Derived builders should override this method only if they want to provide virtual fieldnames. To provide addiitonal functions,
376: * override {@link #executeFunction} instead.
377: * @param node the node whos efields are queries
378: * @param field the fieldname that is requested
379: * @return the result of the 'function', or null if no valid functions could be determined.
380: */
381: public Object getValue(MMObjectNode node, String field) {
382: if (log.isDebugEnabled()) {
383: log.debug("node:" + node.getNumber() + " field: " + field);
384: }
385: // return the Document from the config file..
386: if (field.equals("config")) {
387: // first check if we already have a value in node fields...
388: Object o = super .getValue(node, field);
389: if (o != null) {
390: return o;
391: }
392: // otherwise, open the file to return it...
393: if (log.isDebugEnabled()) {
394: log.debug("retrieving the document for node #"
395: + node.getNumber());
396: }
397:
398: // method node.getStringValue("name") should work, since getStringValue("path") checked it already...
399: String path = getBuilderConfiguration(node);
400: org.w3c.dom.Document doc;
401: try {
402: doc = mmb.getBuilderLoader().getDocument(path);
403: } catch (Exception e) {
404: log.warn("Error reading builder with name: " + path
405: + " " + e.getMessage());
406: return null;
407: }
408: if (doc == null) {
409: log
410: .warn("Resource with name: "
411: + path
412: + " didnt exist, getValue will return null for builder config");
413: return null;
414: }
415: node.setValue(field, doc);
416: return doc;
417: } else if (field.equals("state")) {
418: int val = node.getIntValue("state");
419: // is it set allready ? if not set it, this code should be
420: // removed ones the autoreloader/state code is done.
421: if (val == -1) {
422: // state 1 is up and running
423: node.setValue("state", 1);
424: }
425: return "" + val;
426: } else if (field.equals("dutchs(name)")) {
427: // replace this function with gui(name) ?
428: // but have to change admin pages first
429: return getGUIIndicator("name", node);
430: }
431: return super .getValue(node, field);
432: }
433:
434: /**
435: * Sets a key/value pair in the main values of this node.
436: * Note that if this node is a node in cache, the changes are immediately visible to
437: * everyone, even if the changes are not committed.
438: * The fieldname is added to the (public) 'changed' vector to track changes.
439: * @param node
440: * @param fieldName the name of the field to change
441: * @param originalValue the value which was original in the field
442: * @return <code>true</code> When an update is required(when changed),
443: * <code>false</code> if original value was set back into the field.
444: */
445: public boolean setValue(MMObjectNode node, String fieldName,
446: Object originalValue) {
447: Object newValue = node.retrieveValue(fieldName);
448: if (fieldName.equals("name")) {
449: // the field with the name 'name' may not be changed.....
450: if (originalValue != null && // perhaps legacy, name is null becaue name field was nullable?
451: !originalValue.equals("") && // name field is
452: !originalValue.equals(newValue)) {
453: // restore the original value...
454: node.storeValue(fieldName, originalValue);
455: throw new RuntimeException(
456: "Cannot change a builder's name from '"
457: + originalValue + "' to '" + newValue
458: + "' typedef node " + node.getNumber());
459: /* } else if (fieldName.equals("config")) {
460: MMObjectBuilder builder = getBuilder(node);
461: // TODO: active / not active code.. IT CAN MESS UP BUILDERS THAT ARE SET INACTIVE, AND STILL HAVE DATA IN DATABASE!
462: if (builder == null) {
463: log.warn("No builder found for typedef node " + node);
464: } else if (builder.size() > 0) {
465: throw new RuntimeException("Cannot change builder config it has nodes (otherwise information could get lost..)");
466: } else {
467: log.info("Changing config for typedef " + node + " associated with builder '" + builder.getTableName() + "'");
468: }
469: */
470: }
471: }
472: return true;
473: }
474:
475: /**
476: * @javadoc
477: */
478: public boolean fieldLocalChanged(String number, String builder,
479: String field, String value) {
480: if (field.equals("state")) {
481: if (value.equals("4")) {
482: // reload request
483: log.service("Reload wanted on : " + builder);
484: // perform reload
485: MMObjectNode node = getNode(number);
486: String objectname = node.getStringValue("name");
487: reloadBuilder(objectname);
488: if (node != null) {
489: node.setValue("state", 1);
490: }
491: }
492: }
493: return true;
494: }
495:
496: /**
497: * Returns the path, where the builderfile can be found, for not exising builders, a path will be generated.
498: * @param node The node, from which we want to know it;s MMObjectBuilder
499: * @return The path where the builder should live or <code>null</code> in case of strange failures
500: * When the builder was not loaded.
501: * @since MMBase-1.8
502: */
503: protected String getBuilderConfiguration(MMObjectNode node) {
504: // call our code above, to get our path...
505: String path = getBuilderPath(node);
506: // do we have a path?
507: if (path == null) {
508: log.error("field 'path' was empty.");
509: return null;
510: }
511: return path + node.getStringValue("name") + ".xml";
512: }
513:
514: /**
515: * Returns the MMObjectBuilder which is represented by the node.
516: * @param node The node, from which we want to know its MMObjectBuilder
517: * @return The builder which is represented by the node, or <code>null</code>
518: * if the builder was not loaded.
519: */
520: public MMObjectBuilder getBuilder(MMObjectNode node) {
521: String builderName = node.getStringValue("name");
522: return mmb.getMMObject(builderName);
523: }
524:
525: /**
526: * @javadoc
527: */
528: public boolean reloadBuilder(String objectname) {
529: log.service("Trying to reload builder : " + objectname);
530: // first get all the info we need from the builder allready running
531: MMObjectBuilder oldbul = mmb.getBuilder(objectname);
532: String classname = oldbul.getClass().getName();
533: String description = oldbul.getDescription();
534:
535: try {
536: Class newclass = Class
537: .forName("org.mmbase.module.builders." + classname);
538: log.debug("Loaded load class : " + newclass);
539:
540: MMObjectBuilder bul = (MMObjectBuilder) newclass
541: .newInstance();
542: log.debug("Started : " + newclass);
543:
544: bul.setMMBase(mmb);
545: bul.setTableName(objectname);
546: bul.setDescription(description);
547: bul.init();
548: mmb.addBuilder(objectname, bul);
549: } catch (Exception e) {
550: log.error(Logging.stackTrace(e));
551: return false;
552: }
553: return true;
554: }
555:
556: /**
557: * What should a GUI display for this node.
558: * This method returns the gui name (singular name) of the builder that goes with this node.
559: * @param node The node to display
560: * @return the display of the node as a <code>String</code>
561: */
562: public String getGUIIndicator(MMObjectNode node) {
563: return getSingularName(node.getStringValue("name"), null);
564: }
565:
566: /**
567: * The GUIIndicator can depend on the locale. Override this function
568: * @since MMBase-1.6
569: */
570: protected String getLocaleGUIIndicator(Locale locale, String field,
571: MMObjectNode node) {
572: if (field == null || "".equals(field)) {
573: return getLocaleGUIIndicator(locale, node);
574: } else if ("description".equals(field)) {
575: MMObjectBuilder bul = mmb.getBuilder(node
576: .getStringValue("name"));
577: if (bul != null) {
578: return bul.getDescription(locale.getLanguage());
579: }
580: }
581: return null;
582: }
583:
584: protected String getLocaleGUIIndicator(Locale locale,
585: MMObjectNode node) {
586: String rtn = getSingularName(node.getStringValue("name"),
587: locale.getLanguage());
588: if (rtn == null)
589: return node.getStringValue("name");
590: return rtn;
591: }
592:
593: /**
594: * @javadoc
595: */
596: public void loadTypeDef(String name) {
597: if (!typedefsLoaded.contains(name)) {
598: typedefsLoaded.add(name);
599: } else {
600: if (log.isDebugEnabled())
601: log.debug("Builder " + name + " is already loaded!");
602: }
603: }
604:
605: /**
606: * @javadoc
607: */
608: public void unloadTypeDef(String name) {
609: if (typedefsLoaded.contains(name)) {
610: typedefsLoaded.remove(name);
611: } else {
612: log.debug("Builder " + name + " is not loaded!");
613: }
614: }
615:
616: /**
617: * @javadoc
618: */
619: public Vector<String> getList(PageInfo sp, StringTagger tagger,
620: StringTokenizer tok) {
621: if (tok.hasMoreTokens()) {
622: String cmd = tok.nextToken();
623: if (cmd.equals("builders")) {
624: return typedefsLoaded;
625: }
626: }
627: return null;
628: }
629:
630: protected Object executeFunction(MMObjectNode node,
631: String function, List<?> args) {
632: log.debug("executefunction of typedef");
633: if (function.equals("info")) {
634: List<Object> empty = new ArrayList<Object>();
635: java.util.Map<String, String> info = (java.util.Map<String, String>) super
636: .executeFunction(node, function, empty);
637: info.put("gui", info.get("info") + " (localized)");
638: if (args == null || args.size() == 0) {
639: return info;
640: } else {
641: return info.get(args.get(0));
642: }
643: } else if (function.equals("gui")) {
644: log.debug("GUI of servlet builder with " + args);
645: if (args == null || args.size() == 0) {
646: return getGUIIndicator(node);
647: } else {
648: String rtn;
649: if (args.size() <= 1) {
650: rtn = getGUIIndicator((String) args.get(0), node);
651: } else {
652: String language = (String) args.get(1);
653: if (language == null)
654: language = mmb.getLanguage();
655: Locale locale = new Locale(language, "");
656: rtn = getLocaleGUIIndicator(locale, (String) args
657: .get(0), node);
658: }
659: if (rtn == null)
660: return super .executeFunction(node, function, args);
661: return rtn;
662: }
663: } else if (function.equals("defaultsearchage")) {
664: return getBuilder(node).getSearchAge();
665: } else {
666: return super .executeFunction(node, function, args);
667: }
668: }
669:
670: private void testBuilderRemovable(MMObjectBuilder builder,
671: MMObjectNode typeDefNode) {
672: if (builder != null && builder.size() > 0) {
673: throw new RuntimeException(
674: "Cannot delete this builder, it still contains nodes");
675: } else if (builder == null) {
676: // inactive builder, does it have nodes?
677: MMObjectBuilder rootBuilder = mmb.getRootBuilder();
678: NodeSearchQuery q = new NodeSearchQuery(rootBuilder);
679: Integer value = typeDefNode.getNumber();
680: Constraint constraint = new BasicFieldValueConstraint(q
681: .getField(rootBuilder.getField("otype")), value);
682: q.setConstraint(constraint);
683: try {
684: if (rootBuilder.count(q) > 0) {
685: throw new RuntimeException(
686: "Cannot delete this (inactive) builder with otype="
687: + value
688: + ", it still contains nodes " + q);
689: }
690: } catch (SearchQueryException sqe) {
691: // should never happen
692: log.error(sqe);
693: }
694: }
695:
696: // check if there are relations which use this builder
697: {
698: if (builder instanceof InsRel) {
699: MMObjectNode reldef = mmb.getRelDef()
700: .getDefaultForBuilder((InsRel) builder);
701: if (reldef != null) {
702: throw new RuntimeException(
703: "Cannot delete this builder, it is referenced in reldef #"
704: + reldef.getNumber());
705: }
706: }
707: try {
708: MMObjectBuilder typeRel = mmb.getTypeRel();
709: NodeSearchQuery q = new NodeSearchQuery(typeRel);
710: Integer value = typeDefNode.getNumber();
711: BasicCompositeConstraint constraint = new BasicCompositeConstraint(
712: CompositeConstraint.LOGICAL_OR);
713: Constraint constraint1 = new BasicFieldValueConstraint(
714: q.getField(typeRel.getField("snumber")), value);
715: Constraint constraint2 = new BasicFieldValueConstraint(
716: q.getField(typeRel.getField("dnumber")), value);
717: constraint.addChild(constraint1);
718: constraint.addChild(constraint2);
719: q.setConstraint(constraint);
720: List<MMObjectNode> typerels = typeRel.getNodes(q);
721: if (typerels.size() > 0) {
722: throw new RuntimeException(
723: "Cannot delete this builder, it is referenced by typerels: "
724: + typerels);
725: }
726: } catch (SearchQueryException sqe) {
727: // should never happen
728: log.error(sqe);
729: }
730: }
731: }
732:
733: /**
734: * Returns the path, where the builder configuration file can be found, for not exising builders, a path will be generated.
735: * @param node The node, from which we want to know it;s MMObjectBuilder
736: * @return The path where the builder should live or <code>null</code> in case of strange failures
737: * When the builder was not loaded.
738: */
739: protected String getBuilderPath(MMObjectNode node) {
740: if (log.isDebugEnabled()) {
741: log.debug("retrieving the path for node #"
742: + node.getNumber());
743: }
744: // some basic checking
745: if (node == null) {
746: log.error("node was null");
747: return null;
748: }
749: if (node.getStringValue("name") == null) {
750: log.error("field 'name' was null");
751: return null;
752: }
753: if (node.getStringValue("name").trim().length() == 0) {
754: log.error("field 'name' was empty.");
755: return null;
756: }
757:
758: String pathInBuilderDir = mmb.getBuilderPath(node
759: .getStringValue("name"), "");
760: if (pathInBuilderDir != null) {
761: // return the file path,..
762: String file = pathInBuilderDir;
763: if (log.isDebugEnabled()) {
764: log.debug("builder file:" + file);
765: }
766: return file;
767: }
768: // still null, make up a nice url for our builder!
769: if (defaultDeploy != null) {
770: String file = defaultDeploy;
771: if (log.isDebugEnabled()) {
772: log.debug("builder file:" + file);
773: }
774: return file;
775: }
776: return null;
777: }
778:
779: /**
780: */
781: protected MMObjectBuilder loadBuilder(MMObjectNode node) {
782: if (log.isDebugEnabled()) {
783: log.debug("Load builder '" + node.getStringValue("name")
784: + "' ( #" + node.getNumber() + ")");
785: }
786: String path = getBuilderPath(node);
787: log.info("Loading builder from " + path);
788: MMObjectBuilder builder = mmb.loadBuilderFromXML(node
789: .getStringValue("name"), path);
790: if (builder == null) {
791: // inactive builder?
792: log
793: .info("could not load builder from xml, is in inactive?(name: '"
794: + node.getStringValue("name")
795: + "' path: '"
796: + path + "')");
797: return null;
798: }
799: mmb.initBuilder(builder);
800: return builder;
801: }
802:
803: /**
804: */
805: protected void storeBuilderConfiguration(MMObjectNode node)
806: throws java.io.IOException {
807: if (log.isDebugEnabled()) {
808: log.debug("Store builder '" + node.getStringValue("name")
809: + "' ( #" + node.getNumber() + ")");
810: }
811:
812: org.w3c.dom.Document doc = node.getXMLValue("config");
813: if (doc == null) {
814: log
815: .error("Field config was null! Could not save the file for "
816: + node.getStringValue("name")
817: + Logging.stackTrace(new Throwable()));
818: return;
819: }
820: String path = getBuilderConfiguration(node);
821: log.info("Store builder '" + node.getStringValue("name")
822: + "' ( #" + node.getNumber() + ") to " + path);
823: mmb.getBuilderLoader().storeDocument(path, doc);
824:
825: }
826:
827: /**
828: */
829: protected MMObjectBuilder unloadBuilder(MMObjectNode node) {
830: if (log.isDebugEnabled()) {
831: log.debug("Unload builder '" + node.getStringValue("name")
832: + "' ( #" + node.getNumber() + ")");
833: }
834: // unload the builder,...
835: MMObjectBuilder builder = getBuilder(node);
836: if (builder != null) {
837: mmb.unloadBuilder(builder);
838: }
839: return builder;
840: }
841:
842: /**
843: */
844: protected boolean deleteBuilderConfiguration(MMObjectNode node) {
845: if (log.isDebugEnabled()) {
846: log.debug("Delete file of builder '"
847: + node.getStringValue("name") + "' ( #"
848: + node.getNumber() + ")");
849: }
850: File file = new File(getBuilderConfiguration(node));
851: if (file.exists()) {
852: if (!file.canWrite()) {
853: log.error("file: " + file
854: + " had no write rights for me.");
855: return false;
856: }
857: // remove the file from the file system..
858: file.delete();
859: if (log.isDebugEnabled()) {
860: log.debug("file: " + file + " has been deleted");
861: }
862: }
863: return true;
864: }
865: }
|