0001: /*
0002:
0003: This software is OSI Certified Open Source Software.
0004: OSI Certified is a certification mark of the Open Source Initiative.
0005:
0006: The license (Mozilla version 1.0) can be read at the MMBase site.
0007: See http://www.MMBase.org/license
0008:
0009: */
0010: package org.mmbase.module.core;
0011:
0012: import java.io.File;
0013: import java.net.URLEncoder;
0014: import java.text.DateFormat;
0015: import java.text.NumberFormat;
0016: import java.util.*;
0017:
0018: import org.mmbase.bridge.*;
0019:
0020: import org.mmbase.cache.*;
0021:
0022: import org.mmbase.datatypes.DataTypeCollector;
0023:
0024: import org.mmbase.module.corebuilders.*;
0025:
0026: import org.mmbase.core.*;
0027: import org.mmbase.core.event.*;
0028: import org.mmbase.core.util.Fields;
0029: import org.mmbase.core.util.StorageConnector;
0030:
0031: import org.mmbase.datatypes.DataType;
0032:
0033: import org.mmbase.storage.StorageException;
0034: import org.mmbase.storage.search.*;
0035: import org.mmbase.storage.search.implementation.*;
0036:
0037: import org.mmbase.util.*;
0038: import org.mmbase.util.functions.*;
0039: import org.mmbase.util.logging.Logger;
0040: import org.mmbase.util.logging.Logging;
0041:
0042: /**
0043: * This class is the base class for all builders.
0044: * It offers a list of routines which are useful in maintaining the nodes in the MMBase
0045: * object cloud.
0046: * <br />
0047: * Builders are the core of the MMBase system. They create, delete and search the MMObjectNodes.
0048: * Most manipulations concern nodes of that builders type. However, a number of retrieval routines extend
0049: * beyond a builders scope and work on the cloud in general, allowing some ease in retrieval of nodes.
0050: * The basic routines in this class can be extended to handle more specific demands for nodes.
0051: * Most of these 'extended builders' will be stored in mmbase.org.builders or mmbase.org.corebuilders.
0052: * Examples include relation builders or builders for handling binary data such as images.
0053: * The various builders are registered by the 'TypeDef' builder class (one of the core builders, itself
0054: * an extension of this class).
0055: *
0056: * @author Daniel Ockeloen
0057: * @author Rob Vermeulen
0058: * @author Pierre van Rooden
0059: * @author Eduard Witteveen
0060: * @author Johannes Verelst
0061: * @author Rob van Maris
0062: * @author Michiel Meeuwissen
0063: * @author Ernst Bunders
0064: * @version $Id: MMObjectBuilder.java,v 1.421 2007/11/16 09:47:34 michiel Exp $
0065: */
0066: public class MMObjectBuilder extends MMTable implements
0067: NodeEventListener, RelationEventListener {
0068:
0069: /**
0070: * Name of the field containing the object number, which uniquely identifies the node.
0071: * @since MMBase-1.8
0072: */
0073: public static final String FIELD_NUMBER = "number";
0074:
0075: /**
0076: * Name of the field containing the owner. The owner field is used for security implementations.
0077: * @since MMBase-1.8
0078: */
0079: public static final String FIELD_OWNER = "owner";
0080:
0081: /**
0082: * Name of the field containing the object type number. This refers to an entry in the 'typedef' builder table.
0083: * @since MMBase-1.8
0084: */
0085: public static final String FIELD_OBJECT_TYPE = "otype";
0086:
0087: /**
0088: * @since MMBase-1.8
0089: */
0090: public static final String TMP_FIELD_NUMBER = "_number";
0091: public static final String TMP_FIELD_EXISTS = "_exists";
0092:
0093: /**
0094: * Default (system) owner name for the owner field.
0095: * @since MMBase-1.8
0096: */
0097: public static final String SYSTEM_OWNER = "system";
0098:
0099: /** Default size of the temporary node cache */
0100: public final static int TEMPNODE_DEFAULT_SIZE = 1024;
0101:
0102: /** Default replacements for method getHTML() */
0103: public final static String DEFAULT_ALINEA = "<br /> <br />";
0104: public final static String DEFAULT_EOL = "<br />";
0105:
0106: public final static int EVENT_TYPE_LOCAL = 0;
0107: public final static int EVENT_TYPE_REMOTE = 1;
0108:
0109: /**
0110: * Parameters for the age function
0111: * @since MMBase-1.7
0112: */
0113: public final static Parameter<?>[] AGE_PARAMETERS = {};
0114:
0115: /**
0116: * Collection for temporary nodes,
0117: * Used by the Temporarynodemanager when working with transactions
0118: * The default size is 1024.
0119: * @duplicate use Cache object instead
0120: * @scope protected
0121: */
0122: public static Map<String, MMObjectNode> temporaryNodes = new Hashtable<String, MMObjectNode>(
0123: TEMPNODE_DEFAULT_SIZE);
0124:
0125: /**
0126: * Default output when no data is available to determine a node's GUI description
0127: */
0128: public static final String GUI_INDICATOR = "no info";
0129:
0130: /**
0131: * The cache for all blobs.
0132: * @since 1.8.0
0133: */
0134: protected static BlobCache genericBlobCache = new BlobCache(200) {
0135: public String getName() {
0136: return "GenericBlobCache";
0137: }
0138: };
0139:
0140: static {
0141: genericBlobCache.putCache();
0142: }
0143:
0144: /**
0145: * The cache that contains the X last requested nodes
0146: */
0147: protected static org.mmbase.cache.NodeCache nodeCache = org.mmbase.cache.NodeCache
0148: .getCache();
0149:
0150: /**
0151: * Determines whether the cache is locked.
0152: * A locked cache can be read, and nodes can be removed from it (allowing it to
0153: * clean invalid nodes), but nodes cannot be added.
0154: * Needed for committing nodes from transactions.
0155: */
0156: private static int cacheLocked = 0;
0157:
0158: private static final Logger log = Logging
0159: .getLoggerInstance(MMObjectBuilder.class);
0160:
0161: private List<MMObjectBuilder> descendants;
0162:
0163: /**
0164: * The string that can be used inside the builder.xml as property,
0165: * to define the maximum number of nodes to return.
0166: */
0167: private static String MAX_NODES_FROM_QUERY_PROPERTY = "max-nodes-from-query";
0168:
0169: /**
0170: * The string that can be used inside the builder.xml as property,
0171: * to set whether the builder broadcasts changes to nodes to eventlisteners.
0172: */
0173: private static String BROADCAST_CHANGES_PROPERTY = "broadcast-changes";
0174:
0175: /**
0176: * Description of the builder in the currently selected language
0177: * Not that the first time the builder is created, this value is what is stored in the TypeDef table.
0178: * @scope protected
0179: */
0180: public String description = "Base Object";
0181:
0182: /**
0183: * Descriptions of the builder per language
0184: * Can be set with the <descriptions> tag in the xml builder file.
0185: * @scope protected
0186: */
0187: public Hashtable<String, String> descriptions;
0188:
0189: /**
0190: * The default search age for this builder.
0191: * Used for intializing editor search forms (see HtmlBase)
0192: * Default value is 31. Can be changed with the <searchage> tag in the xml builder file.
0193: * @scope protected
0194: */
0195: public String searchAge = "31";
0196:
0197: /**
0198: * Determines whether changes to this builder need be broadcast to other known mmbase servers.
0199: */
0200: protected boolean broadCastChanges = true;
0201:
0202: /**
0203: * Internal (instance) version number of this builder.
0204: */
0205: protected long internalVersion = -1;
0206:
0207: /**
0208: * The current builder's object type
0209: * Retrieved from the TypeDef builder.
0210: */
0211: protected int oType = -1;
0212:
0213: /**
0214: * Maintainer information for builder registration
0215: * Set with <builder maintainer="mmbase.org" version="0"> in the xml builder file
0216: * @scope protected
0217: */
0218: String maintainer = "mmbase.org";
0219:
0220: /** Collections of (GUI) names (singular) for the builder's objects, divided by language
0221: * @scope protected
0222: */
0223: Hashtable<String, String> singularNames;
0224:
0225: /** Collections of (GUI) names (plural) for the builder's objects, divided by language
0226: * @scope protected
0227: */
0228: Hashtable<String, String> pluralNames;
0229:
0230: /**
0231: * Full filename (path + buildername + ".xml") where we loaded the builder from
0232: * It is relative from the '/builders/' subdir
0233: * @scope protected
0234: */
0235: String xmlPath = "";
0236:
0237: /**
0238: * Parameters for the GUI function
0239: * @since MMBase-1.7
0240: */
0241: public final static Parameter<?>[] GUI_PARAMETERS = org.mmbase.util.functions.GuiFunction.PARAMETERS;
0242:
0243: /**
0244: * The famous GUI function as a function object.
0245: * @since MMBase-1.8
0246: */
0247: protected Function<String> guiFunction = new GuiFunction();
0248: {
0249: addFunction(guiFunction);
0250: }
0251: /**
0252: * Parameters constants for the NodeFunction {@link #wrapFunction}.
0253: * @since MMBase-1.8
0254: */
0255: protected final static Parameter<?>[] WRAP_PARAMETERS = {
0256: new Parameter<String>(Parameter.FIELD, true),
0257: new Parameter<Number>("length", Number.class, Integer
0258: .valueOf(20)) };
0259:
0260: /**
0261: * This function wraps the text of a node's field and returns the result as a String.
0262: * It takes as parameters a fieldname, the line length to wrap, and the Node containing the data.
0263: * This function can be called through the function framework.
0264: * @since MMBase-1.8
0265: */
0266: protected Function<String> wrapFunction = new NodeFunction<String>(
0267: "wrap", WRAP_PARAMETERS, ReturnType.STRING) {
0268: {
0269: setDescription("This function wraps a field, word-by-word. You can use this, e.g. in <pre>-tags. This functionality should be available as an 'escaper', and this version should now be considered an example.");
0270: }
0271:
0272: public String getFunctionValue(Node node, Parameters parameters) {
0273: String val = node.getStringValue(parameters
0274: .getString(Parameter.FIELD));
0275: Number wrappos = (Number) parameters.get("length");
0276: return MMObjectBuilder.this .wrap(val, wrappos.intValue());
0277: }
0278: };
0279: {
0280: addFunction(wrapFunction);
0281: }
0282:
0283: /**
0284: * Every Function Provider provides least the 'getFunctions' function, which returns a Set of all functions which it provides.
0285: * This is overridden from FunctionProvider, because this one needs to be (also) a NodeFunction
0286: * @since MMBase-1.8
0287: */
0288: protected Function<Collection<? extends Function>> getFunctions = new NodeFunction<Collection<? extends Function>>(
0289: "getFunctions", Parameter.emptyArray(),
0290: ReturnType.COLLECTION) {
0291: {
0292: setDescription("The 'getFunctions' returns a Map of al Function object which are available on this FunctionProvider");
0293: }
0294:
0295: public Collection<? extends Function> getFunctionValue(
0296: Node node, Parameters parameters) {
0297: return MMObjectBuilder.this .getFunctions(getCoreNode(
0298: MMObjectBuilder.this , node));
0299: }
0300:
0301: public Collection<? extends Function> getFunctionValue(
0302: Parameters parameters) {
0303: Node node = parameters.get(Parameter.NODE);
0304: if (node == null) {
0305: return MMObjectBuilder.this .getFunctions();
0306: } else {
0307: return MMObjectBuilder.this .getFunctions(getCoreNode(
0308: MMObjectBuilder.this , node));
0309: }
0310: }
0311: };
0312: {
0313: addFunction(getFunctions);
0314: }
0315:
0316: /**
0317: * The info-function is a node-function and a builder-function. Therefore it is defined as a node-function, but also overidesd getFunctionValue(Parameters).
0318: * @since MMBase-1.8
0319: */
0320: protected Function<Object> infoFunction = new NodeFunction<Object>(
0321: "info", new Parameter[] { new Parameter<Object>("function",
0322: String.class) }, ReturnType.UNKNOWN) {
0323: {
0324: setDescription("Returns information about available functions");
0325: }
0326:
0327: protected Object getFunctionValue(
0328: Collection<Function<?>> functions, Parameters parameters) {
0329: String function = (String) parameters.get("function");
0330: if (function == null || function.equals("")) {
0331: Map<String, String> info = new HashMap<String, String>();
0332: for (Function<?> f : functions) {
0333: info.put(f.getName(), f.getDescription());
0334: }
0335: return info;
0336: } else {
0337: Function<?> func = getFunction(function);
0338: if (func == null)
0339: return "No such function " + function;
0340: return func.getDescription();
0341: }
0342: }
0343:
0344: public Object getFunctionValue(Node node, Parameters parameters) {
0345: return getFunctionValue(MMObjectBuilder.this
0346: .getFunctions(getCoreNode(MMObjectBuilder.this ,
0347: node)), parameters);
0348: }
0349:
0350: public Object getFunctionValue(Parameters parameters) {
0351: MMObjectNode node = (MMObjectNode) parameters
0352: .get(Parameter.CORENODE);
0353: if (node == null) {
0354: return getFunctionValue(MMObjectBuilder.this
0355: .getFunctions(), parameters);
0356: } else {
0357: return getFunctionValue(MMObjectBuilder.this
0358: .getFunctions(node), parameters);
0359: }
0360: }
0361: };
0362: {
0363: addFunction(infoFunction);
0364: }
0365:
0366: // contains the builder's field definitions
0367: protected final Map<String, CoreField> fields = new HashMap<String, CoreField>();
0368:
0369: /**
0370: * Determines whether a builder is virtual (data is not stored in the storage layer).
0371: */
0372: protected boolean virtual = false;
0373:
0374: /**
0375: * Set of remote observers, which are notified when a node of this type changes
0376: */
0377: private final Set<MMBaseObserver> remoteObservers = Collections
0378: .synchronizedSet(new HashSet<MMBaseObserver>());
0379:
0380: /**
0381: * Set of local observers, which are notified when a node of this type changes
0382: */
0383: private final Set<MMBaseObserver> localObservers = Collections
0384: .synchronizedSet(new HashSet<MMBaseObserver>());
0385:
0386: /**
0387: * Reference to the builders that this builder extends.
0388: * @since MMBase-1.6.2 (parentBuilder in 1.6.0)
0389: */
0390: private Stack<MMObjectBuilder> ancestors = new Stack<MMObjectBuilder>();
0391:
0392: /**
0393: * Version information for builder registration
0394: * Set with <builder maintainer="mmbase.org" version="0"> in the xml
0395: * builder file
0396: */
0397: private int version = 0;
0398:
0399: /**
0400: * Contains lists of builder fields in specified order
0401: * (ORDER_CREATE, ORDER_EDIT, ORDER_LIST, ORDER_SEARCH)
0402: */
0403: private Map<Integer, List<CoreField>> sortedFieldLists = new HashMap<Integer, List<CoreField>>();
0404:
0405: /** Properties of a specific Builder.
0406: * Specified in the xml builder file with the <properties> tag.
0407: * The use of properties is determined by builder
0408: */
0409: private final Map<String, String> properties = new HashMap<String, String>();
0410:
0411: /**
0412: * The datatype collector for this builder
0413: */
0414: private DataTypeCollector dataTypeCollector = null;
0415:
0416: /**
0417: * Constructor.
0418: */
0419: public MMObjectBuilder() {
0420: storageConnector = new StorageConnector(this );
0421: }
0422:
0423: private void initAncestors() {
0424: if (!ancestors.empty()) {
0425: ancestors.peek().init();
0426: }
0427: }
0428:
0429: /**
0430: * Initializes this builder
0431: * The property 'mmb' needs to be set for the builder before this method can be called.
0432: * The method retrieves data from the TypeDef builder, or adds data to that builder if the
0433: * current builder is not yet registered.
0434: * @return true if init was completed, false if uncompleted.
0435: * @see #create
0436: */
0437: public boolean init() {
0438: synchronized (mmb) { // synchronized on mmb because can only init builder if mmb is inited completely
0439:
0440: // skip initialisation if oType has been set (happend at end of init)
0441: // note that init can be called twice
0442: if (oType != -1)
0443: return true;
0444:
0445: log.debug("Init of builder " + getTableName());
0446:
0447: loadInitParameters();
0448:
0449: // first make sure parent builder is initalized
0450: initAncestors();
0451:
0452: String broadCastChangesProperty = getInitParameter(BROADCAST_CHANGES_PROPERTY);
0453: if (broadCastChangesProperty != null) {
0454: broadCastChanges = broadCastChangesProperty
0455: .equals("true");
0456: }
0457:
0458: if (!created()) {
0459: log.info("Creating table for builder " + tableName);
0460: if (!create()) {
0461: // can't create buildertable.
0462: // Throw an exception
0463: throw new BuilderConfigurationException(
0464: "Cannot create table for " + getTableName()
0465: + ".");
0466: }
0467: ;
0468: }
0469: TypeDef typeDef = mmb.getTypeDef();
0470: // only deteremine otype if typedef is available,
0471: // or this is typedef itself (have to start somewhere)
0472: if (((typeDef != null) && (typeDef.getObjectType() != -1))
0473: || (this == typeDef)) {
0474: oType = typeDef.getIntValue(tableName);
0475: if (oType == -1) { // no object type number defined yet
0476: if (log.isDebugEnabled())
0477: log.debug("Creating typedef entry for "
0478: + tableName);
0479: MMObjectNode node = typeDef
0480: .getNewNode(SYSTEM_OWNER);
0481: node.storeValue("name", tableName);
0482:
0483: // This sucks:
0484: if (description == null)
0485: description = "not defined in this language";
0486:
0487: node.storeValue("description", description);
0488:
0489: try {
0490: oType = mmb.getStorageManager().createKey();
0491: } catch (StorageException se) {
0492: log.error(se.getMessage()
0493: + Logging.stackTrace(se));
0494: return false;
0495: }
0496:
0497: log.debug("Got key " + oType);
0498: node.storeValue(FIELD_NUMBER, oType);
0499: // for typedef, set otype explictly, as it wasn't set in getNewNode()
0500: if (this == typeDef) {
0501: node.storeValue(FIELD_OBJECT_TYPE, oType);
0502: }
0503: typeDef.insert(SYSTEM_OWNER, node, false);
0504: // for typedef, call it's parents init again, as otype is only now set
0505: if (this == typeDef) {
0506: initAncestors();
0507: }
0508: }
0509: } else {
0510: // warn if typedef was not created
0511: // except for the 'object' and 'typedef' basic builders
0512: if (!tableName.equals("typedef")
0513: && !tableName.equals("object")) {
0514: log.warn("init(): for tablename(" + tableName
0515: + ") -> can't get to typeDef");
0516: return false;
0517: }
0518: }
0519: // XXX: wtf
0520: // add temporary fields
0521: checkAddTmpField(TMP_FIELD_NUMBER);
0522: checkAddTmpField(TMP_FIELD_EXISTS);
0523:
0524: // get property of maximum number of queries..
0525: String property = getInitParameter(MAX_NODES_FROM_QUERY_PROPERTY);
0526: if (property != null) {
0527: try {
0528: maxNodesFromQuery = Integer.parseInt(property);
0529: log.debug(getTableName() + " returns no more than "
0530: + maxNodesFromQuery
0531: + " records from a query.");
0532: } catch (NumberFormatException nfe) {
0533: log.warn("property:"
0534: + MAX_NODES_FROM_QUERY_PROPERTY
0535: + " contained an invalid integer value:'"
0536: + property + "'(" + nfe + ")");
0537: }
0538: }
0539: }
0540: update();
0541:
0542: //now register it as a listener for events of it's own type
0543: //this is only for backwards compatibility, to notify the MMBaseObserver's
0544: MMBase.getMMBase().addNodeRelatedEventsListener(getTableName(),
0545: this );
0546:
0547: return true;
0548: }
0549:
0550: /**
0551: * Returns the builder object number, which also functions as the objecttype.
0552: * This is the same value as the value of the 'otype' field of objects created by this builder
0553: * (rather than created by its descendants).
0554: * @return the builder number
0555: * @since MMBase-1.8
0556: */
0557: public int getNumber() {
0558: return oType;
0559: }
0560:
0561: /**
0562: * Returns the objecttype (otype).
0563: * By preference, use {@link #getNumber()} for future compatibility with the bridge NodeManager methods.
0564: * @return the objecttype
0565: */
0566: public int getObjectType() {
0567: return getNumber();
0568: }
0569:
0570: /**
0571: * Updates the internal version number of this buidler;
0572: */
0573: protected void update() {
0574: internalVersion = System.currentTimeMillis();
0575: }
0576:
0577: /**
0578: * Returns the builder's internal version number.
0579: * This number can be used to sync wrapper classes. I.e. to make sure that a
0580: * nodemanager's fieldlist is the same as that of the wrapped builder.
0581: */
0582: public long getInternalVersion() {
0583: return internalVersion;
0584: }
0585:
0586: /**
0587: * Creates a new builder table in the storage layer.
0588: */
0589: public boolean create() {
0590: log.debug(tableName);
0591: try {
0592: mmb.getStorageManager().create(this );
0593: return true;
0594: } catch (StorageException se) {
0595: log.error(se.getMessage() + Logging.stackTrace(se));
0596: return false;
0597: }
0598: }
0599:
0600: /**
0601: * Removes the builder from the storage.
0602: * @since MMBase-1.7
0603: */
0604: public void delete() {
0605: log.service("trying to drop table of builder: '" + tableName
0606: + "'");
0607: mmb.getStorageManager().delete(this );
0608: }
0609:
0610: /**
0611: * Tests whether the data in a node is valid (throws an exception if this is not the case).
0612: * @param node The node whose data to check
0613: * @throws org.mmbase.module.core.InvalidDataException
0614: * If the data was unrecoverably invalid (the references did not point to existing objects)
0615: */
0616: public void testValidData(MMObjectNode node)
0617: throws InvalidDataException {
0618: return;
0619: };
0620:
0621: /**
0622: * Insert a new, empty, object of a certain type.
0623: * @param oType The type of object to create
0624: * @param owner The administrator creating the node
0625: * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
0626: * The basic routine does not create any nodes this way and always fails.
0627: */
0628: public int insert(int oType, String owner) {
0629: return -1;
0630: }
0631:
0632: /**
0633: * Insert a new object (content provided) in the cloud, including an entry for the object alias (if provided).
0634: * This method indirectly calls {@link #preCommit}.
0635: * @param owner The administrator creating the node
0636: * @param node The object to insert. The object need be of the same type as the current builder.
0637: * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
0638: */
0639: public int insert(String owner, MMObjectNode node) {
0640: int n = mmb.getStorageManager().create(node);
0641: if (n >= 0) {
0642: node.isNew = false;
0643: }
0644:
0645: node.useAliases();
0646:
0647: // it is in the storage now, all caches can allready be invalidated, this makes sure
0648: // that imediate 'select' after 'insert' will be correct'.
0649: //xxx: this is bad.let's kill it!
0650: //QueryResultCache.invalidateAll(node, NodeEvent.TYPE_NEW);
0651: if (n <= 0) {
0652: log.warn("Did not get valid nodeNumber of storage " + n);
0653: }
0654: Integer nodeNumber = Integer.valueOf(n);
0655: if (isNodeCached(nodeNumber)) {
0656: // it seems that something put the node in the cache already.
0657: // This is usually because the ChangeManager indirectly called 'getNode'
0658: // This should in the new event-mechanism not be needed, because the NodeEvent
0659: // contains the node.
0660: //log.warn("New node '" + n + "' of type " + node.parent.getTableName() + " is already in node-cache!" + Logging.stackTrace());
0661: } else {
0662: safeCache(nodeNumber, node);
0663: }
0664: return n;
0665: }
0666:
0667: /**
0668: * This method is called before an actual write to the storage layer is performed.
0669: * @param node The node to be committed.
0670: * @return the node to be committed (possibly after changes have been made).
0671: */
0672: public MMObjectNode preCommit(MMObjectNode node) {
0673: return node;
0674: }
0675:
0676: /**
0677: * Commit changes to this node to the storage layer. This method indirectly calls {@link #preCommit}.
0678: * Use only to commit changes - for adding node, use {@link #insert}.
0679: * @param node The node to be committed
0680: * @return true if commit successful
0681: */
0682: public boolean commit(MMObjectNode node) {
0683: mmb.getStorageManager().change(node);
0684: return true;
0685: }
0686:
0687: /**
0688: * Determines whether changes to this builder need be broadcast to other known mmbase servers.
0689: * This setting also governs whether the cache for relation builders is emptied when a relation changes.
0690: * Actual broadcasting (and cache emptying) is initiated in the storage layer, when
0691: * changes are commited.
0692: * By default, all builders broadcast their changes, with the exception of the TypeDef builder.
0693: *
0694: * MM: Can somebody please explain _why_ typedef node changes, like e.g. creating a new node type are _not_ broadcast.
0695: * @since MMBase-1.8
0696: */
0697: public boolean broadcastChanges() {
0698: return broadCastChanges;
0699: }
0700:
0701: /**
0702: * Creates an alias for a node, provided the OAlias builder is loaded.
0703: * @param number the to-be-aliased node's unique number
0704: * @param alias the aliasname to associate with the object
0705: * @param owner the owner of the alias
0706: * @since MMBase-1.8
0707: * @return if the alias could be created
0708: */
0709: public boolean createAlias(int number, String alias, String owner) {
0710: if (mmb.getOAlias() != null) {
0711: if (getNode(alias) != null) { // this alias already exists! Don't add a new one!
0712: return false;
0713: }
0714: mmb.getOAlias().createAlias(alias, number, owner);
0715: return true;
0716: } else {
0717: return false;
0718: }
0719: }
0720:
0721: /**
0722: * Creates an alias for a node, provided the OAlias builder is loaded.
0723: * @param number the to-be-aliased node's unique number
0724: * @param alias the aliasname to associate with the object
0725: * @return if the alias could be created
0726: */
0727: public boolean createAlias(int number, String alias) {
0728: return createAlias(number, alias, "system");
0729: }
0730:
0731: /**
0732: * Returns the builder that this builder extends.
0733: *
0734: * @since MMBase-1.6
0735: * @return the extended (parent) builder, or null if not available
0736: */
0737: public MMObjectBuilder getParentBuilder() {
0738: if (ancestors.empty())
0739: return null;
0740: return ancestors.peek();
0741: }
0742:
0743: /**
0744: * Gives the list of parent-builders.
0745: *
0746: * @since MMBase-1.6.2
0747:
0748: */
0749: public List<MMObjectBuilder> getAncestors() {
0750: return Collections.unmodifiableList(ancestors);
0751: }
0752:
0753: /**
0754: * Creates list of descendant-builders.
0755: *
0756: * @since MMBase-1.6.2
0757: */
0758: public List<MMObjectBuilder> getDescendants() {
0759: if (descendants == null) {
0760: List<MMObjectBuilder> result = new ArrayList<MMObjectBuilder>();
0761: for (MMObjectBuilder builder : mmb.getBuilders()) {
0762: if (builder.isExtensionOf(this )) {
0763: result.add(builder);
0764: }
0765: }
0766: if (mmb.getState()) {
0767: // for some reason it gets a bit confused if this is done earlier
0768: // I don't quite know why
0769: descendants = result;
0770: }
0771: return result;
0772: }
0773: return descendants;
0774: }
0775:
0776: /**
0777: * Sets the builder that this builder extends, and registers it in the storage layer.
0778: * @param parent the extended (parent) builder, or null if not available
0779: *
0780: * @since MMBase-1.6
0781: */
0782: public void setParentBuilder(MMObjectBuilder parent) {
0783: ancestors.addAll(parent.getAncestors());
0784: ancestors.push(parent);
0785: getDataTypeCollector().addCollector(
0786: parent.getDataTypeCollector());
0787: }
0788:
0789: /**
0790: * Returns the datatype collector belonging to this buidler.
0791: * A datatype collector contains the datatypes that are local to this builder.
0792: * @since MMBase-1.8
0793: */
0794: public DataTypeCollector getDataTypeCollector() {
0795: if (dataTypeCollector == null) {
0796: Object signature = new String(getTableName() + "_"
0797: + System.currentTimeMillis());
0798: dataTypeCollector = new DataTypeCollector(signature);
0799: }
0800: return dataTypeCollector;
0801: }
0802:
0803: /**
0804: * Checks wether this builder is an extension of the argument builder
0805: *
0806: * @since MMBase-1.6.2
0807: */
0808: public boolean isExtensionOf(MMObjectBuilder o) {
0809: return ancestors.contains(o);
0810: }
0811:
0812: /**
0813: * Get a new node, using this builder as its parent. The new node is not a part of the cloud
0814: * yet, and thus has the value -1 as a number. (Call {@link #insert} to add the node to the
0815: * cloud).
0816: * @param owner The administrator creating the new node.
0817: * @return A newly initialized <code>MMObjectNode</code>.
0818: */
0819: public MMObjectNode getNewNode(String owner) {
0820: MMObjectNode node = getEmptyNode(owner);
0821: setDefaults(node);
0822: node.isNew = true;
0823: return node;
0824: }
0825:
0826: /**
0827: * Returns a new empty node object. This is used by Storage to create a non-new node object (isNew is false), which is then
0828: * be filled with actual values from storage.
0829: * @since MMBase-1.8.
0830: */
0831: public MMObjectNode getEmptyNode(String owner) {
0832: MMObjectNode node = new MMObjectNode(this , false);
0833: node.setValue(FIELD_NUMBER, -1);
0834: node.setValue(FIELD_OWNER, owner);
0835: node.setValue(FIELD_OBJECT_TYPE, oType);
0836: return node;
0837: }
0838:
0839: /**
0840: * Sets defaults for a node. Fields "number", "owner" and "otype" are not set by this method.
0841: * @param node The node to set the defaults of.
0842: */
0843: public void setDefaults(MMObjectNode node) {
0844: for (CoreField field : getFields()) {
0845: if (field.isVirtual())
0846: continue;
0847: if (field.getName().equals(FIELD_NUMBER))
0848: continue;
0849: if (field.getName().equals(FIELD_OWNER))
0850: continue;
0851: if (field.getName().equals(FIELD_OBJECT_TYPE))
0852: continue;
0853: if (field.getType() == Field.TYPE_NODE)
0854: continue;
0855:
0856: Object defaultValue = field.getDataType().getDefaultValue();
0857: if ((defaultValue == null) && field.isNotNull()) {
0858: Class clazz = Fields.typeToClass(field.getType());
0859: if (clazz != null) {
0860: defaultValue = Casting.toType(clazz, null, "");
0861: } else {
0862: log.warn("No class found for type of " + field);
0863: }
0864: }
0865: node.setValue(field.getName(), defaultValue);
0866: }
0867: }
0868:
0869: /**
0870: * In setDefault you could want to generate unique values for fields (if the field is 'unique').
0871: * @since MMBase-1.7
0872: */
0873: protected String setUniqueValue(MMObjectNode node, String field,
0874: String baseValue) {
0875: int seq = 0;
0876: boolean found = false;
0877: String value = baseValue;
0878: try {
0879: while (!found) {
0880: NodeSearchQuery query = new NodeSearchQuery(this );
0881: value = baseValue + seq;
0882: BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(
0883: query.getField(getField(field)), value);
0884: query.setConstraint(constraint);
0885: if (getNodes(query).size() == 0) {
0886: found = true;
0887: break;
0888: }
0889: seq++;
0890: }
0891: } catch (SearchQueryException e) {
0892: value = baseValue + System.currentTimeMillis();
0893: }
0894: node.setValue(field, value);
0895: return value;
0896: }
0897:
0898: /**
0899: * In setDefault you could want to generate unique values for fields (if the field is 'unique').
0900: * @since MMBase-1.7
0901: */
0902: protected int setUniqueValue(MMObjectNode node, String field,
0903: int offset) {
0904: int seq = offset;
0905: boolean found = false;
0906: try {
0907: while (!found) {
0908: NodeSearchQuery query = new NodeSearchQuery(this );
0909: BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(
0910: query.getField(getField(field)), Integer
0911: .valueOf(seq));
0912: query.setConstraint(constraint);
0913: if (getNodes(query).size() == 0) {
0914: found = true;
0915: break;
0916: }
0917: seq++;
0918: }
0919: } catch (SearchQueryException e) {
0920: seq = (int) System.currentTimeMillis() / 1000;
0921: }
0922: node.setValue(field, seq);
0923: return seq;
0924: }
0925:
0926: /**
0927: * Remove a node from the cloud.
0928: * @param node The node to remove.
0929: */
0930: public void removeNode(MMObjectNode node) {
0931: if (oType != node.getOType()) {
0932: // fixed comment's below..??
0933: // prevent from making storage inconsistent(say remove nodes from inactive builder)
0934: // the builder we are in is not the actual builder!!
0935: // ? why not an node.remove()
0936: throw new RuntimeException(
0937: "Builder with name: "
0938: + getTableName()
0939: + "(otype "
0940: + oType
0941: + ") is not the actual builder of the node that is to be deleted: "
0942: + node.getNumber() + " (otype: "
0943: + node.getOType() + ")");
0944: }
0945:
0946: removeSyncNodes(node);
0947:
0948: clearBlobCache(node.getNumber());
0949:
0950: // removes the node FROM THIS BUILDER
0951: // seems not a very logical call, as node.parent is the node's actual builder,
0952: // which may - possibly - be very different from the current builder
0953: mmb.getStorageManager().delete(node);
0954:
0955: // change is in storage, caches can be invalidated immediately
0956: //really bad!!!
0957: //QueryResultCache.invalidateAll(node, NodeEvent.EVENT_TYPE_DELETE);
0958: }
0959:
0960: /**
0961: * Removes the syncnodes to this node. This is logical, but also needed to maintain storage
0962: * integrety.
0963: *
0964: * @since MMBase-1.7
0965: */
0966: protected void removeSyncNodes(MMObjectNode node) {
0967: try {
0968: MMObjectBuilder syncnodes = mmb.getBuilder("syncnodes");
0969: NodeSearchQuery query = new NodeSearchQuery(syncnodes);
0970: Integer numericalValue = node.getNumber();
0971: BasicStepField field = query.getField(syncnodes
0972: .getField("localnumber"));
0973: BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(
0974: field, numericalValue);
0975: query.setConstraint(constraint);
0976: for (MMObjectNode syncnode : syncnodes.getNodes(query)) {
0977: syncnode.parent.removeNode(syncnode);
0978: if (log.isDebugEnabled()) {
0979: log.debug("Removed syncnode " + syncnode);
0980: }
0981: }
0982: } catch (SearchQueryException e) {
0983: throw new RuntimeException(e);
0984: }
0985: }
0986:
0987: /**
0988: * Remove the relations of a node.
0989: * @param node The node whose relations to remove.
0990: */
0991: public void removeRelations(MMObjectNode node) {
0992: List<MMObjectNode> relsv = getRelations_main(node.getNumber());
0993: if (relsv != null) {
0994: for (MMObjectNode relnode : relsv) {
0995: // determine the true builder for this node
0996: // (node.parent is always InsRel, but otype
0997: // indicates any derived builders, such as AuthRel)
0998: MMObjectBuilder bul = mmb.getMMObject(mmb.getTypeDef()
0999: .getValue(relnode.getOType()));
1000: // remove the node using this builder
1001: // circumvent problem in storage layer (?)
1002: bul.removeNode(relnode);
1003: }
1004: }
1005: }
1006:
1007: /**
1008: * Is this node cached at this moment?
1009: * @param number The number of the node to check.
1010: * @return <code>true</code> if the node is in the cache, <code>false</code> otherwise
1011: */
1012: public boolean isNodeCached(Integer number) {
1013: return nodeCache.containsKey(number);
1014: }
1015:
1016: /**
1017: * Retrieves a node from the cache, or <code>null</code> if it doesn't exist.
1018: * @param number The number of the node to retrieve.
1019: * @return an MMObjectNode or <code>null</code> if the node is not in the cache
1020: * @todo This is a simple wrapper around node cache, why not expose node cache in stead?
1021: * @since MMBase-1.8
1022: */
1023: public MMObjectNode getNodeFromCache(Integer number) {
1024: return nodeCache.get(number);
1025: }
1026:
1027: /**
1028: * Stores a node in the cache provided the cache is not write locked.
1029: * @return a valid node. If the node already was in the cache, the cached node is returned.
1030: * In that case the node given as parameter should become invalid
1031: */
1032: public MMObjectNode safeCache(Integer n, MMObjectNode node) {
1033: MMObjectNode retval = getNodeFromCache(n);
1034: if (retval != null) {
1035: return retval;
1036: } else {
1037: synchronized (nodeCache) {
1038: if (cacheLocked == 0) {
1039: nodeCache.put(n, node);
1040: }
1041: }
1042: return node;
1043: }
1044: }
1045:
1046: /**
1047: * Locks the node cache during the commit of a node. This prevents the cache from gaining an
1048: * invalid state during the commit.
1049: *
1050: * Basicly the goals is to ensure that nothing is put into the cache during a commit of a node,
1051: * because that may be the wrong node then.
1052: */
1053: boolean safeCommit(MMObjectNode node) {
1054: boolean res = false;
1055: try {
1056: synchronized (nodeCache) {
1057: cacheLocked++;
1058: }
1059: if (node.getNumber() > 0) {
1060: nodeCache.remove(node.getNumber());
1061: }
1062:
1063: res = node.commit();
1064: } finally {
1065: synchronized (nodeCache) {
1066: cacheLocked--;
1067: }
1068: }
1069: return res;
1070: }
1071:
1072: /**
1073: * Locks the node cache during the insert of a node.
1074: * This prevents the cache from adding the node, which
1075: * means that the next time the node is read it is 'refreshed'
1076: * from the storage
1077: */
1078: int safeInsert(MMObjectNode node, String userName) {
1079: int res = -1;
1080: try {
1081: synchronized (nodeCache) {
1082: cacheLocked++;
1083: }
1084: // determine valid username
1085: if ((userName == null) || (userName.length() <= 1)) { // may not have owner of 1 char??
1086: userName = node.getStringValue(FIELD_OWNER);
1087: if (log.isDebugEnabled()) {
1088: log.debug("Found username "
1089: + (userName == null ? "NULL" : userName));
1090: }
1091: }
1092: res = node.insert(userName);
1093: if (res > -1) {
1094: nodeCache.put(Integer.valueOf(res), node);
1095: }
1096: } finally {
1097: synchronized (nodeCache) {
1098: cacheLocked--;
1099: }
1100: }
1101: return res;
1102: }
1103:
1104: /**
1105: * Determine whether this builder is virtual.
1106: * A virtual builder represents nodes that are not stored or retrieved directly
1107: * from storage, but are created as needed.
1108: * @return <code>true</code> if the builder is virtual.
1109: */
1110: public boolean isVirtual() {
1111: return virtual;
1112: }
1113:
1114: /**
1115: * Retrieves a node based on a unique key. The key is either an entry from the OAlias table
1116: * or the string-form of an integer value (the number field of an object node).
1117: * Note that the OAlias builder needs to be active for the alias to be used
1118: * (otherwise using an alias is concidered invalid).
1119: * @param key The value to search for
1120: * @param useCache If true, the node is retrieved from the node cache if possible.
1121: * @return <code>null</code> if the node does not exist or the key is invalid, or a
1122: * <code>MMObjectNode</code> containing the contents of the requested node.
1123: */
1124: public MMObjectNode getNode(String key, boolean useCache) {
1125: if (key == null) {
1126: log.error("getNode(null) for builder '" + tableName
1127: + "': key is null!");
1128: // who is doing that?
1129: log.info(Logging.stackTrace(6));
1130: return null;
1131: }
1132: int nr = -1;
1133: // first look if we have a number...
1134: try {
1135: nr = Integer.parseInt(key);
1136: } catch (Exception e) {
1137: }
1138: if (nr != -1) {
1139: // key passed was a number.
1140: // return node with this number
1141: return getNode(nr, useCache);
1142: } else {
1143: // key passed was an alias
1144: // return node with this alias
1145: log.debug("Getting node by alias");
1146: if (mmb.getOAlias() != null) {
1147: return mmb.getOAlias().getAliasedNode(key);
1148: } else {
1149: return null;
1150: }
1151: }
1152: }
1153:
1154: /**
1155: * Retrieves a node based on a unique key. The key is either an entry from the OAlias table
1156: * or the string-form of an integer value (the number field of an object node).
1157: * Retrieves a node from the node cache if possible.
1158: * @param key The value to search for
1159: * @return <code>null</code> if the node does not exist or the key is invalid, or a
1160: * <code>MMObjectNode</code> containing the contents of the requested node.
1161: */
1162: public MMObjectNode getNode(String key) {
1163: return getNode(key, true);
1164: }
1165:
1166: /**
1167: * Retrieves a node based on it's number (a unique key), retrieving the node
1168: * from the node cache if possible.
1169: * @param number The number of the node to search for
1170: * @return <code>null</code> if the node does not exist or the key is invalid, or a
1171: * <code>MMObjectNode</code> containign the contents of the requested node.
1172: */
1173: public MMObjectNode getNode(int number) {
1174: return getNode(number, true);
1175: }
1176:
1177: /**
1178: * Create a new temporary node and put it in the temporary _exist
1179: * node space
1180: */
1181: protected MMObjectNode getNewTmpNode(String owner, String key) {
1182: MMObjectNode node = getNewNode(owner);
1183: putTmpNode(key, node);
1184: return node;
1185: }
1186:
1187: /**
1188: * Put a Node in the temporary node list
1189: * @param key The (temporary) key under which to store the node
1190: * @param node The node to store
1191: */
1192: static void putTmpNode(String key, MMObjectNode node) {
1193: node.storeValue(TMP_FIELD_NUMBER, key);
1194: temporaryNodes.put(key, node);
1195: }
1196:
1197: /**
1198: * Defines a virtual field to use for temporary nodes. If the given field-name does not start
1199: * with underscore ('_'), wich it usually does, then the field does also get a 'dbpos' (1000) as if it
1200: * was actually present in the builder's XML as a virtual field (this is accompanied with a log
1201: * message).
1202: *
1203: * Normally this is used to add 'tmp' fields like _number, _exists and _snumber which are system
1204: * fields which are normally invisible.
1205: *
1206: * @param field the name of the temporary field
1207: * @return true if the field was added, false if it already existed.
1208: */
1209: public boolean checkAddTmpField(String field) {
1210: if (getDBState(field) == Field.STATE_UNKNOWN) { // means that field is not yet defined.
1211: CoreField fd = Fields.createField(field, Field.TYPE_STRING,
1212: Field.TYPE_UNKNOWN, Field.STATE_VIRTUAL, null);
1213: if (!fd.isTemporary()) {
1214: fd.setStoragePosition(1000);
1215: log
1216: .service("Added a virtual field '"
1217: + field
1218: + "' to builder '"
1219: + getTableName()
1220: + "' because it was not defined in the builder's XML, but the implementation requires it to exist.");
1221: } else {
1222: log.debug("Adding tmp (virtual) field '" + field
1223: + "' to builder '" + getTableName() + "'");
1224: }
1225:
1226: fd.setParent(this );
1227: fd.finish();
1228:
1229: addField(fd);
1230: // added field, so update version
1231: update();
1232: return true;
1233: } else {
1234: return false;
1235: }
1236: }
1237:
1238: /**
1239: * Get nodes from the temporary node space
1240: * @param key The (temporary) key to use under which the node is stored
1241: */
1242: static protected MMObjectNode getTmpNode(String key) {
1243: MMObjectNode node = temporaryNodes.get(key);
1244: if (node == null && log.isTraceEnabled()) {
1245: log.trace("getTmpNode(): node not found " + key);
1246: }
1247: return node;
1248: }
1249:
1250: /**
1251: * Remove a node from the temporary node space
1252: * @param key The (temporary) key under which the node is stored
1253: */
1254: static void removeTmpNode(String key) {
1255: MMObjectNode node = temporaryNodes.remove(key);
1256: if (node == null) {
1257: log.debug("removeTmpNode: node with " + key
1258: + " didn't exists");
1259: }
1260: }
1261:
1262: /**
1263: * Return a copy of the list of field definitions of this table.
1264: * @return An unmodifiable <code>Collection</code> with the tables fields
1265: */
1266: public Collection<CoreField> getFields() {
1267: return Collections.unmodifiableCollection(fields.values());
1268: }
1269:
1270: /**
1271: * Return a list of field names of this table.
1272: * @return a unmodifiable <code>Set</code> with the tables field names
1273: * @todo return an unmodifiable Set.
1274: */
1275: public Set<String> getFieldNames() {
1276: return Collections.unmodifiableSet(fields.keySet());
1277: }
1278:
1279: /**
1280: * Return a field's definition
1281: * @param fieldName the requested field's name
1282: * @return a <code>FieldDefs</code> belonging with the indicated field
1283: * @todo Should return CoreField
1284: */
1285: public FieldDefs getField(String fieldName) {
1286: return (FieldDefs) fields.get(fieldName.toLowerCase());
1287: }
1288:
1289: /**
1290: * @since MMBase-1.8
1291: */
1292: public boolean hasField(String fieldName) {
1293: return fields.containsKey(fieldName.toLowerCase());
1294: }
1295:
1296: /**
1297: * Clears all field list caches, and recalculates the field list.
1298: */
1299: protected void updateFields() {
1300: sortedFieldLists.clear();
1301: update();
1302: }
1303:
1304: /**
1305: * Add a field to this builder.
1306: * This does not affect the builder config file, nor the table used.
1307: * @param def the field definiton to add
1308: */
1309: public void addField(CoreField def) {
1310: Object oldField = fields.put(def.getName().toLowerCase(), def);
1311: if (oldField != null) {
1312: log.warn("Replaced " + oldField + " !!");
1313: }
1314: updateFields();
1315: }
1316:
1317: /**
1318: * Remove a field from this builder.
1319: * This does not affect the builder config file, nor the table used.
1320: * @param fieldName the name of the field to remove
1321: */
1322: public void removeField(String fieldName) {
1323: CoreField def = getField(fieldName);
1324: int dbpos = def.getStoragePosition();
1325: fields.remove(fieldName);
1326: for (Object element : fields.values()) {
1327: def = (CoreField) element;
1328: int curpos = def.getStoragePosition();
1329: if (curpos >= dbpos)
1330: def.setStoragePosition(curpos - 1);
1331: }
1332: updateFields();
1333: }
1334:
1335: /**
1336: * Return a field's storage type. The returned value is one of the following values
1337: * declared in Field:
1338: * TYPE_STRING,
1339: * TYPE_INTEGER,
1340: * TYPE_BINARY,
1341: * TYPE_FLOAT,
1342: * TYPE_DOUBLE,
1343: * TYPE_LONG,
1344: * TYPE_NODE,
1345: * TYPE_UNKNOWN
1346: * @param fieldName the requested field's name
1347: * @return the field's type.
1348: */
1349: public int getDBType(String fieldName) {
1350: if (fields == null) {
1351: log.error("getDBType(): fields are null on object : "
1352: + tableName);
1353: return Field.TYPE_UNKNOWN;
1354: }
1355: Field field = getField(fieldName);
1356: if (field == null) {
1357: //perhaps prefixed with own tableName[0-9]? (allowed since MMBase-1.7)
1358: int dot = fieldName.indexOf('.');
1359: if (dot > 0) {
1360: if (fieldName.startsWith(tableName)) {
1361: if (tableName.length() <= dot
1362: || Character.isDigit(fieldName
1363: .charAt(dot - 1))) {
1364: fieldName = fieldName.substring(dot + 1);
1365: field = getField(fieldName);
1366: }
1367: }
1368: }
1369: }
1370:
1371: if (field == null) {
1372:
1373: // log warning, except for virtual builders
1374: if (!virtual) { // should getDBType not be overridden in Virtual Builder then?
1375: log
1376: .warn("getDBType(): Can't find definition on field '"
1377: + fieldName
1378: + "' of builder "
1379: + tableName);
1380: log.debug(Logging.stackTrace());
1381: }
1382: return Field.TYPE_UNKNOWN;
1383: }
1384: return field.getType();
1385: }
1386:
1387: /**
1388: * Return a field's storage state. The returned value is one of the following values
1389: * declared in Field:
1390: * STATE_VIRTUAL,
1391: * STATE_PERSISTENT,
1392: * STATE_SYSTEM,
1393: * STATE_UNKNOWN
1394: * @param fieldName the requested field's name
1395: * @return the field's state.
1396: */
1397: public int getDBState(String fieldName) {
1398: if (fields == null)
1399: return Field.STATE_UNKNOWN;
1400: Field field = getField(fieldName);
1401: if (field == null)
1402: return Field.STATE_UNKNOWN;
1403: return field.getState();
1404: }
1405:
1406: /**
1407: * A complicated default implementation for GUI.
1408: * @since MMBase-1.8
1409: */
1410: public String getGUIIndicator(MMObjectNode node, Parameters pars) {
1411: Locale locale = pars.get(Parameter.LOCALE);
1412: String language = pars.get(Parameter.LANGUAGE);
1413: if (locale == null) {
1414: if (language != null) {
1415: locale = new Locale(language, "");
1416: }
1417: } else {
1418: if (language != null
1419: && (!locale.getLanguage().equals(language))) { // odd, but well,
1420: locale = new Locale(language, locale.getCountry());
1421: }
1422: }
1423: if (locale == null)
1424: locale = mmb.getLocale();
1425:
1426: if (log.isDebugEnabled()) {
1427: log.debug("language " + locale.getLanguage() + " country "
1428: + locale.getCountry());
1429: }
1430:
1431: String rtn;
1432: String field = pars.getString("field");
1433:
1434: if (locale == null) {
1435: if (field == null || "".equals(field)) {
1436: rtn = getGUIIndicator(node);
1437: if (rtn == GUI_INDICATOR) { // not overridden
1438: rtn = getNodeGUIIndicator(node, pars);
1439: }
1440: } else {
1441: rtn = getGUIIndicator(field, node);
1442: }
1443: } else {
1444: if (field == null || "".equals(field)) {
1445: rtn = getLocaleGUIIndicator(locale, node);
1446: if (rtn == GUI_INDICATOR) { // not overridden
1447: rtn = getNodeGUIIndicator(node, pars);
1448: }
1449: } else {
1450: rtn = getLocaleGUIIndicator(locale, field, node);
1451: }
1452: }
1453:
1454: if (rtn == null) {
1455: CoreField fdef = getField(field);
1456:
1457: Object returnValue;
1458: if (fdef != null) {
1459: // test if the value can be derived from the enumerationlist of a datatype
1460: DataType dataType = fdef.getDataType();
1461: if (dataType instanceof org.mmbase.datatypes.BinaryDataType) {
1462: returnValue = node.isNull(field) ? "" : ""
1463: + node.getSize(field) + " byte";
1464: } else {
1465: returnValue = dataType.getEnumerationValue(locale,
1466: pars.get(Parameter.CLOUD), pars
1467: .get(Parameter.NODE), fdef, node
1468: .getStringValue(field));
1469: }
1470: } else {
1471: returnValue = null;
1472: }
1473: if (returnValue != null) {
1474: rtn = returnValue.toString();
1475: } else {
1476: if (fdef != null
1477: && ("eventtime".equals(fdef.getGUIType()) || fdef
1478: .getDataType() instanceof org.mmbase.datatypes.DateTimeDataType)) { // do something reasonable for this
1479: Date date;
1480: if (fdef.getType() == Field.TYPE_DATETIME) {
1481: date = node.getDateValue(field);
1482: } else {
1483: date = new Date(node.getLongValue(field) * 1000);
1484: }
1485: rtn = DateFormat.getDateTimeInstance(
1486: DateFormat.LONG, DateFormat.MEDIUM, locale)
1487: .format(date);
1488: Calendar calendar = new GregorianCalendar(locale);
1489: calendar.setTime(date);
1490: if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) {
1491: java.text.DateFormat df = new java.text.SimpleDateFormat(
1492: " G", locale);
1493: rtn += df.format(date);
1494: }
1495: } else {
1496: rtn = (String) pars.get("stringvalue");
1497: if (rtn == null) {
1498: rtn = node.getStringValue(field);
1499: }
1500: }
1501: }
1502: rtn = org.mmbase.util.transformers.Xml.XMLEscape(rtn);
1503: }
1504: return rtn;
1505: }
1506:
1507: /**
1508: * Returns a GUI-indicator for the node itself.
1509: * @since MMBase-1.8.2
1510: */
1511: protected String getNodeGUIIndicator(MMObjectNode node,
1512: Parameters params) {
1513: // do the best we can because this method was not implemented
1514: // we get the first field in the object and try to make it
1515: // to a string we can return
1516: List<CoreField> list = getFields(NodeManager.ORDER_LIST);
1517: if (list.size() > 0) {
1518: String fname = list.get(0).getName();
1519: String str = node.getStringValue(fname);
1520: if (str.length() > 128) {
1521: str = str.substring(0, 128) + "...";
1522: }
1523: if (params == null) {
1524: // Needed for getGuiIndicator calls for NODE fields
1525: // Temporary fix, should perhaps be solved in getGuiIndicator(node,params)
1526: String result = getGUIIndicator(fname, node);
1527: if (result == null) {
1528: result = str;
1529: }
1530: return result;
1531: } else {
1532: params.set("field", fname);
1533: params.set("stringvalue", str);
1534: return getGUIIndicator(node, params);
1535: }
1536: } else {
1537: return GUI_INDICATOR;
1538: }
1539: }
1540:
1541: /**
1542: * What should a GUI display for this node.
1543: * Default the value returned is GUI_INDICATOR ('no info').
1544: * Override this to display your own choice (see Images.java).
1545: * You may want to override {@link #getNodeGUIIndicator} for more flexibility.
1546: * @param node The node to display
1547: * @return the display of the node as a <code>String</code>
1548: */
1549: public String getGUIIndicator(MMObjectNode node) {
1550: return GUI_INDICATOR;
1551: }
1552:
1553: /**
1554: * What should a GUI display for this node/field combo.
1555: * Default is null (indicating to display the field as is)
1556: * Override this to display your own choice.
1557: * @param node The node to display
1558: * @param fieldName the name field of the field to display
1559: * @return the display of the node's field as a <code>String</code>, null if not specified
1560: */
1561: public String getGUIIndicator(String fieldName, MMObjectNode node) {
1562: CoreField field = getField(fieldName);
1563:
1564: if (field != null && field.getType() == Field.TYPE_NODE
1565: && !fieldName.equals(FIELD_NUMBER)) {
1566: try {
1567: MMObjectNode otherNode = node.getNodeValue(fieldName);
1568: if (otherNode == null) {
1569: return "";
1570: } else {
1571: // may return GUI_INDICATOR
1572: String rtn = otherNode.parent
1573: .getGUIIndicator(otherNode);
1574: if (rtn == GUI_INDICATOR) {
1575: rtn = otherNode.parent.getNodeGUIIndicator(
1576: otherNode, null);
1577: }
1578: return rtn;
1579: }
1580: } catch (RuntimeException rte) {
1581: log.warn("Cannot load node from field " + fieldName
1582: + " in node " + node.getNumber() + ":" + rte);
1583: return "invalid";
1584: }
1585: } else {
1586: return null;
1587: }
1588: }
1589:
1590: /**
1591: * The GUIIndicator can depend on the locale. Override this function
1592: * @since MMBase-1.6
1593: */
1594: protected String getLocaleGUIIndicator(Locale locale, String field,
1595: MMObjectNode node) {
1596: return getGUIIndicator(field, node);
1597: }
1598:
1599: /**
1600: * The GUIIndicator can depend on the locale. Override this function
1601: * You may want to override {@link #getNodeGUIIndicator} for more flexibility.
1602: * @since MMBase-1.6
1603: */
1604: protected String getLocaleGUIIndicator(Locale locale,
1605: MMObjectNode node) {
1606: return getGUIIndicator(node);
1607: }
1608:
1609: /**
1610: * Gets the field definitions for the editor, sorted according
1611: * to the specified order, and excluding the fields that have
1612: * not been assigned a valid position (valid is >= 0).
1613: * This method makes an explicit sort (it does not use a cached list).
1614: *
1615: * @param sortOrder One of the sortorders defined in
1616: * {@link org.mmbase.core.CoreField CoreField}
1617: * @return The ordered list of field definitions.
1618: */
1619: public List<CoreField> getFields(int sortOrder) {
1620: List<CoreField> orderedFields = sortedFieldLists.get(sortOrder);
1621: if (orderedFields == null) {
1622: orderedFields = new ArrayList<CoreField>();
1623: for (CoreField field : fields.values()) {
1624: if (field.isTemporary()) {
1625: continue;
1626: }
1627:
1628: // include only fields which have been assigned a valid position, and are
1629: if ((sortOrder == NodeManager.ORDER_NONE)
1630: || ((sortOrder == NodeManager.ORDER_CREATE) && (field
1631: .getStoragePosition() > -1))
1632: || ((sortOrder == NodeManager.ORDER_EDIT) && (field
1633: .getEditPosition() > -1))
1634: || ((sortOrder == NodeManager.ORDER_SEARCH) && (field
1635: .getSearchPosition() > -1))
1636: || ((sortOrder == NodeManager.ORDER_LIST) && (field
1637: .getListPosition() > -1))) {
1638: orderedFields.add(field);
1639: }
1640: }
1641: Fields.sort(orderedFields, sortOrder);
1642: sortedFieldLists.put(sortOrder, Collections
1643: .unmodifiableList(orderedFields));
1644: } else {
1645: //log.info("From cache!");
1646: }
1647: return orderedFields;
1648: }
1649:
1650: /**
1651: * Returns the next field as defined by its sortorder, according to the specified order.
1652: */
1653: public FieldDefs getNextField(String currentfield, int sortorder) {
1654: CoreField cdef = getField(currentfield);
1655: List<CoreField> sortedFields = getFields(sortorder);
1656: int pos = sortedFields.indexOf(cdef);
1657: if (pos != -1 && (pos + 1) < sortedFields.size()) {
1658: return (FieldDefs) sortedFields.get(pos + 1);
1659: }
1660: return null;
1661: }
1662:
1663: /**
1664: * Returns the next field as defined by its sortorder, according to it's GUIPos property (as set in the builder xml file).
1665: * Used for moving between fields in an edit-form.
1666: * @deprecated use getNextField() with sortorder ORDER_EDIT
1667: */
1668: public FieldDefs getNextField(String currentfield) {
1669: return getNextField(currentfield, NodeManager.ORDER_EDIT);
1670: }
1671:
1672: /**
1673: * Returns
1674: * @since MMBase-1.7.4
1675: */
1676: protected BlobCache getBlobCache(String fieldName) {
1677: return genericBlobCache;
1678: }
1679:
1680: /**
1681: * @since MMBase-1.8
1682: */
1683: public int clearBlobCache(int nodeNumber) {
1684: int result = 0;
1685: for (CoreField field : getFields()) {
1686: String fieldName = field.getName();
1687: BlobCache cache = getBlobCache(fieldName);
1688: String key = cache.getKey(nodeNumber, fieldName);
1689: if (cache.remove(key) != null)
1690: result++;
1691: }
1692: return result;
1693: }
1694:
1695: /**
1696: * Provides additional functionality when obtaining field values.
1697: * This method is called whenever a Node of the builder's type fails at evaluating a getValue() request
1698: * (generally when a fieldname is supplied that doesn't exist).
1699: * It allows the system to add 'functions' to be included with a field name, such as 'html(body)' or 'time(lastmodified)'.
1700: * This method will parse the fieldname, determining functions and calling the {@link #executeFunction} method to handle it.
1701: * Functions in fieldnames can be given in the format 'functionname(fieldname)'. An old format allows 'functionname_fieldname' instead,
1702: * though this only applies to the text functions 'short', 'html', and 'wap'.
1703: * Functions can be nested, i.e. 'html(shorted(body))'.
1704: * Derived builders should override this method only if they want to provide virtual fieldnames. To provide additonal functions,
1705: * call {@link #addFunction} instead. See also the source code for {@link org.mmbase.util.functions.ExampleBuilder}.
1706: * @param node the node whos efields are queries
1707: * @param field the fieldname that is requested
1708: * @return the result of the 'function', or null if no valid functions could be determined.
1709: */
1710: public Object getValue(MMObjectNode node, String field) {
1711: Object rtn = getObjectValue(node, field);
1712:
1713: // Old code
1714: if (field.indexOf("short_") == 0) {
1715: String val = node.getStringValue(field.substring(6));
1716: val = getShort(val, 34);
1717: rtn = val;
1718: } else if (field.indexOf("html_") == 0) {
1719: String val = node.getStringValue(field.substring(5));
1720: val = getHTML(val);
1721: rtn = val;
1722: } else if (field.indexOf("wap_") == 0) {
1723: String val = node.getStringValue(field.substring(4));
1724: val = getWAP(val);
1725: rtn = val;
1726: }
1727: // end old
1728: return rtn;
1729: }
1730:
1731: /**
1732: * Like getValue, but without the 'old' code (short_ html_ etc). This is for
1733: * protected use, when you are sure this is not used, and you can
1734: * avoid the overhead.
1735: *
1736: * @since MMBase-1.6
1737: * @see #getValue
1738: */
1739:
1740: protected Object getObjectValue(MMObjectNode node, String field) {
1741: Object rtn = null;
1742: int pos1 = field.indexOf('(');
1743: if (pos1 != -1) {
1744: int pos2 = field.lastIndexOf(')');
1745: if (pos2 != -1) {
1746: String name = field.substring(pos1 + 1, pos2);
1747: String function = field.substring(0, pos1);
1748: if (log.isDebugEnabled()) {
1749: log.debug("function = '" + function
1750: + "', fieldname = '" + name + "'");
1751: }
1752: List<String> a = new ArrayList<String>();
1753: a.add(name);
1754: rtn = getFunctionValue(node, function, a);
1755:
1756: }
1757: }
1758: return rtn;
1759: }
1760:
1761: /**
1762: * Parses string containing function parameters.
1763: * The parameters must be separated by ',' or ';' and may be functions
1764: * themselves (i.e. a functionname, followed by a parameter list between
1765: * parenthesis).
1766: *
1767: * @param fields The string, containing function parameters.
1768: * @return List of function parameters (may be functions themselves).
1769: * @deprecated use executeFunction(node, function, list)
1770: */
1771: protected Vector<String> getFunctionParameters(String fields) {
1772: int commapos = 0;
1773: int nested = 0;
1774: Vector<String> v = new Vector<String>();
1775: int i;
1776: if (log.isDebugEnabled())
1777: log.debug("Fields=" + fields);
1778: for (i = 0; i < fields.length(); i++) {
1779: if ((fields.charAt(i) == ',') || (fields.charAt(i) == ';')) {
1780: if (nested == 0) {
1781: v.add(fields.substring(commapos, i).trim());
1782: commapos = i + 1;
1783: }
1784: }
1785: if (fields.charAt(i) == '(') {
1786: nested++;
1787: }
1788: if (fields.charAt(i) == ')') {
1789: nested--;
1790: }
1791: }
1792: if (i > 0) {
1793: v.add(fields.substring(commapos).trim());
1794: }
1795: return v;
1796: }
1797:
1798: /**
1799: * Executes a 'function' on a MMObjectNode. The function is
1800: * identified by a string, and its arguments are passed by a List.
1801: *
1802: * The function 'info' should exist, and this will return a Map
1803: * with descriptions of the possible functions.
1804: *
1805: * Call {@link #addFunction} in your extension if you want to add functions.
1806: *
1807: * @param node The node on which the function must be executed
1808: * @param functionName The string identifying the funcion
1809: * @param parameters The list with function argument or null (which means 'no arguments')
1810: *
1811: * @see #executeFunction
1812: * @since MMBase-1.6
1813: */
1814: // package because called from MMObjectNode
1815: final Object getFunctionValue(MMObjectNode node,
1816: String functionName, List<?> parameters) {
1817: if (parameters == null)
1818: parameters = new ArrayList<String>();
1819: // for backwards compatibility (calling with string function with more than one argument)
1820: if (parameters.size() == 1
1821: && parameters.get(0) instanceof String) {
1822: String arg = (String) parameters.get(0);
1823: Object result = executeFunction(node, functionName, arg);
1824: if (result != null) {
1825: return result;
1826: }
1827: parameters = StringSplitter.splitFunctions(arg);
1828: }
1829: Function<?> function = getFunction(node, functionName);
1830: if (function != null) {
1831: return function.getFunctionValueWithList(parameters);
1832: } else {
1833: // fallback
1834: return executeFunction(node, functionName, parameters);
1835: }
1836: }
1837:
1838: /**
1839: * Instantiates a Function object for a certain function on a certain node of this type.
1840: * @param node The Node for on which the function must work
1841: * @param functionName Name of the request function.
1842: * @return a Function object or <code>null</code> if no such function.
1843: * @since MMBase-1.8
1844: */
1845: protected Function<?> getFunction(MMObjectNode node,
1846: String functionName) {
1847: Function<?> function = getFunction(functionName);
1848: if (function instanceof NodeFunction) {
1849: return ((NodeFunction<?>) function).newInstance(node);
1850: } else {
1851: return null;
1852: }
1853: }
1854:
1855: /**
1856: * Returns all Functions which are available (or at least known to be available) on a Node.
1857: * @since MMBase-1.8
1858: */
1859: protected Collection<Function<?>> getFunctions(MMObjectNode node) {
1860: Collection<Function<?>> nodeFunctions = new HashSet<Function<?>>();
1861: for (Function<?> function : getFunctions()) {
1862: if (function instanceof NodeFunction) {
1863: nodeFunctions.add(((NodeFunction<?>) function)
1864: .newInstance(node));
1865: }
1866: }
1867: return nodeFunctions;
1868: }
1869:
1870: /**
1871: *
1872: * @inheritDoc
1873: * @since MMBase-1.8
1874: */
1875: protected Function newFunctionInstance(String name,
1876: Parameter[] parameters, ReturnType returnType) {
1877: return new NodeFunction<Object>(name, parameters, returnType) {
1878: public Object getFunctionValue(Node node,
1879: Parameters parameters) {
1880: return MMObjectBuilder.this .executeFunction(
1881: getCoreNode(MMObjectBuilder.this , node), name,
1882: parameters.subList(0, parameters.size() - 1) // removes the node-argument, some legacy impl. get confused
1883: );
1884: }
1885: };
1886: }
1887:
1888: /**
1889: * Executes a function on the field of a node, and returns the result.
1890: * This method is called by the builder's {@link #getValue} method.
1891: * Derived builders should override this method to provide additional functions.
1892: *
1893: * @since MMBase-1.6
1894: * @throws IllegalArgumentException if the argument List does not
1895: * fit the function
1896: * @see #executeFunction
1897: */
1898: protected Object executeFunction(MMObjectNode node,
1899: String function, List<?> arguments) {
1900: if (log.isDebugEnabled()) {
1901: log.debug("Executing function " + function + " on node "
1902: + node.getNumber() + " with argument " + arguments);
1903: }
1904:
1905: if (function.equals("info")) {
1906: Map<String, String> info = new HashMap<String, String>();
1907: for (Function<?> f : getFunctions(node)) {
1908: info.put(f.getName(), f.getDescription());
1909: }
1910: info
1911: .put(
1912: "info",
1913: "(functionname) Returns information about a certain 'function'. Or a map of all function if no arguments.");
1914: if (arguments == null || arguments.size() == 0
1915: || arguments.get(0) == null) {
1916: log.info("returing " + info);
1917: return info;
1918: } else {
1919: return info.get(arguments.get(0));
1920: }
1921: } else if (function.equals("wrap")) {
1922: if (arguments.size() < 2)
1923: throw new IllegalArgumentException(
1924: "wrap function needs 2 arguments (currently:"
1925: + arguments.size() + " : " + arguments
1926: + ")");
1927: try {
1928: String val = node.getStringValue((String) arguments
1929: .get(0));
1930: int wrappos = Integer.parseInt((String) arguments
1931: .get(1));
1932: return wrap(val, wrappos);
1933: } catch (Exception e) {
1934: }
1935:
1936: } else if (function.equals("substring")) {
1937: if (arguments.size() < 2)
1938: throw new IllegalArgumentException(
1939: "substring function needs 2 or 3 arguments (currently:"
1940: + arguments.size() + " : " + arguments
1941: + ")");
1942: try {
1943: String val = node.getStringValue((String) arguments
1944: .get(0));
1945: int len = Integer.parseInt((String) arguments.get(1));
1946: if (arguments.size() > 2) {
1947: String filler = (String) arguments.get(2);
1948: return substring(val, len, filler);
1949: } else {
1950: return substring(val, len, null);
1951: }
1952: } catch (Exception e) {
1953: log.debug(Logging.stackTrace(e));
1954: return e.toString();
1955: }
1956: }
1957:
1958: String field = "";
1959: if (arguments != null && arguments.size() > 0) {
1960: Object o = arguments.get(0);
1961: if (o instanceof String) {
1962: field = (String) o;
1963: }
1964: }
1965:
1966: // time functions
1967: if (function.equals("date")) { // date
1968: int v = node.getIntValue(field);
1969: return DateSupport.date2string(v);
1970: } else if (function.equals("time")) { // time hh:mm
1971: int v = node.getIntValue(field);
1972: return DateSupport.getTime(v);
1973: } else if (function.equals("timesec")) { // timesec hh:mm:ss
1974: int v = node.getIntValue(field);
1975: return DateSupport.getTimeSec(v);
1976: } else if (function.equals("longmonth")) { // longmonth September
1977: int v = node.getIntValue(field);
1978: return DateStrings.ENGLISH_DATESTRINGS.getMonth(DateSupport
1979: .getMonthInt(v));
1980: } else if (function.equals("monthnumber")) {
1981: int v = node.getIntValue(field);
1982: return "" + (DateSupport.getMonthInt(v) + 1);
1983: } else if (function.equals("month")) { // month Sep
1984: int v = node.getIntValue(field);
1985: return DateStrings.DUTCH_DATESTRINGS
1986: .getShortMonth(DateSupport.getMonthInt(v));
1987: } else if (function.equals("weekday")) { // weekday Sunday
1988: int v = node.getIntValue(field);
1989: return DateStrings.DUTCH_DATESTRINGS.getDay(DateSupport
1990: .getWeekDayInt(v));
1991: } else if (function.equals("shortday")) { // shortday Sun
1992: int v = node.getIntValue(field);
1993: return DateStrings.DUTCH_DATESTRINGS
1994: .getShortDay(DateSupport.getWeekDayInt(v));
1995: } else if (function.equals("day")) { // day 4
1996: int v = node.getIntValue(field);
1997: return "" + DateSupport.getDayInt(v);
1998: } else if (function.equals("shortyear")) { // year 01
1999: int v = node.getIntValue(field);
2000: return (DateSupport.getYear(v)).substring(2);
2001: } else if (function.equals("year")) { // year 2001
2002: int v = node.getIntValue(field);
2003: return DateSupport.getYear(v);
2004: } else if (function.equals("thisdaycurtime")) { //
2005: int curtime = node.getIntValue(field);
2006: // gives us the next full day based on time (00:00)
2007: int days = curtime / (3600 * 24);
2008: return "" + ((days * (3600 * 24)) - 3600);
2009: } else if (function.equals("age")) {
2010: Integer val = Integer.valueOf(node.getAge());
2011: return val.toString();
2012: } else if (function.equals("wap")) {
2013: String val = node.getStringValue(field);
2014: return getWAP(val);
2015: } else if (function.equals("html")) {
2016: String val = node.getStringValue(field);
2017: return getHTML(val);
2018: } else if (function.equals("shorted")) {
2019: String val = node.getStringValue(field);
2020: return getShort(val, 32);
2021: } else if (function.equals("uppercase")) {
2022: String val = node.getStringValue(field);
2023: return val.toUpperCase();
2024: } else if (function.equals("lowercase")) {
2025: String val = node.getStringValue(field);
2026: return val.toLowerCase();
2027: } else if (function.equals("hostname")) {
2028: String val = node.getStringValue(field);
2029: return hostname_function(val);
2030: } else if (function.equals("urlencode")) {
2031: String val = node.getStringValue(field);
2032: return getURLEncode(val);
2033: } else if (function.startsWith("wrap_")) {
2034: String val = node.getStringValue(field);
2035: try {
2036: int wrappos = Integer.parseInt(function.substring(5));
2037: return wrap(val, wrappos);
2038: } catch (Exception e) {
2039: }
2040: } else if (function.equals("currency_euro")) {
2041: double val = node.getDoubleValue(field);
2042: NumberFormat nf = NumberFormat
2043: .getNumberInstance(Locale.GERMANY);
2044: return "" + nf.format(val);
2045: } else {
2046: StringBuilder arg = new StringBuilder(field);
2047: if (arguments != null) {
2048: for (int i = 1; i < arguments.size(); i++) {
2049: if (arg.length() > 0)
2050: arg.append(',');
2051: arg.append(arguments.get(i));
2052: }
2053: }
2054: return executeFunction(node, function, arg.toString());
2055: }
2056: return null;
2057: }
2058:
2059: /**
2060: * Executes a function on the field of a node, and returns the result.
2061: * This method is called by the builder's {@link #getValue} method.
2062: *
2063: * current functions are:<br />
2064: * on dates: date, time, timesec, longmonth, month, monthnumber, weekday, shortday, day, yearhort year<br />
2065: * on text: wap, html, shorted, uppercase, lowercase <br />
2066: * on node: age() <br />
2067: * on numbers: wrap_<int>, currency_euro <br />
2068: *
2069: * @param node the node whose fields are queries
2070: * @param field the fieldname that is requested
2071: * @return the result of the 'function', or null if no valid functions could be determined.
2072: * @deprecated use {@link #getFunction(MMObjectNode, String)}
2073: */
2074: protected Object executeFunction(MMObjectNode node,
2075: String function, String field) {
2076: if (log.isDebugEnabled()) {
2077: log.debug("Executing function " + function + " on node "
2078: + node.getNumber() + " with argument " + field);
2079: }
2080: return null;
2081: }
2082:
2083: /**
2084: * Returns all relations of a node.
2085: * This returns the relation objects, not the objects related to.
2086: * Note that the relations returned are always of builder type 'InsRel', even if they are really from a derived builser such as AuthRel.
2087: * @param src the number of the node to obtain the relations from
2088: * @return a <code>Vector</code> with InsRel nodes
2089: * @todo Return-type and name of this function are not sound.
2090: */
2091: public Vector<MMObjectNode> getRelations_main(int src) {
2092: InsRel bul = mmb.getInsRel();
2093: if (bul == null) {
2094: log.error("getMMObject(): InsRel not yet loaded");
2095: return null;
2096: }
2097: return bul.getRelationsVector(src);
2098: }
2099:
2100: /**
2101: * Return the default url of this object.
2102: * The basic value returned is <code>null</code>.
2103: * @param src the number of the node to obtain the url from
2104: * @return the basic url as a <code>String</code>, or <code>null</code> if unknown.
2105: */
2106: public String getDefaultUrl(int src) {
2107: return null;
2108: }
2109:
2110: /**
2111: * @deprecated This method will be finalized in MMBase 1.9 and removed afterwards.
2112: *
2113: * You can implement a new smart-path for your builders, with a class like {@link
2114: * org.mmbase.module.core.SmartPathFunction} in stead, and configure it in your builder xml as
2115: * the implementation for the 'smartpath' function. This makes extensions less dependent on
2116: * precise arguments (e.g. 'documentRoot' is not relevant for 'resourceloader' implementation),
2117: * and makes this function pluggable on all builders. See also MMB-1449.
2118: *
2119: */
2120: public String getSmartPath(String documentRoot, String path,
2121: String nodeNumber, String version) {
2122: if (log.isDebugEnabled()) {
2123: log.debug("Getting smartpath for " + documentRoot + " /"
2124: + path + "/" + nodeNumber + "/" + version);
2125: }
2126: File dir = new File(documentRoot + path);
2127: if (version != null)
2128: nodeNumber += "." + version;
2129: String[] matches = dir.list(new SPartFileFilter(nodeNumber));
2130: if ((matches == null) || (matches.length == 0)) {
2131: return null;
2132: }
2133: return path + matches[0] + File.separator;
2134: }
2135:
2136: /**
2137: * Get the name of this mmserver from the MMBase Root
2138: * @return a <code>String</code> which is the server's name
2139: */
2140: public String getMachineName() {
2141: return mmb.getMachineName();
2142: }
2143:
2144: /**
2145: * Called when a remote node is changed.
2146: * Should be called by subclasses if they override it.
2147: * @param machine Name of the machine that changed the node.
2148: * @param number Number of the changed node as a <code>String</code>
2149: * @param builder type of the changed node
2150: * @param ctype command type, 'c'=changed, 'd'=deleted', 'r'=relations changed, 'n'=new
2151: * @return always <code>true</code>
2152: * @deprecated use notify(NodeEvent) in stead
2153: */
2154: public boolean nodeRemoteChanged(String machine, String number,
2155: String builder, String ctype) {
2156: // signal all the other objects that have shown interest in changes of nodes of this builder type.
2157: for (MMBaseObserver o : remoteObservers) {
2158: if (o != this ) {
2159: o.nodeRemoteChanged(machine, number, builder, ctype);
2160: } else {
2161: log.warn(getClass().getName() + " " + toString()
2162: + " observes itself");
2163: }
2164: }
2165: return true;
2166: }
2167:
2168: /**
2169: * Called when a local node is changed.
2170: * Should be called by subclasses if they override it.
2171: * @param machine Name of the machine that changed the node.
2172: * @param number Number of the changed node as a <code>String</code>
2173: * @param builder type of the changed node
2174: * @param ctype command type, 'c'=changed, 'd'=deleted', 'r'=relations changed, 'n'=new
2175: * @return always <code>true</code>
2176: * @deprecated use notify(NodeEvent) in stead
2177: */
2178:
2179: public boolean nodeLocalChanged(String machine, String number,
2180: String builder, String ctype) {
2181: // signal all the other objects that have shown interest in changes of nodes of this builder type.
2182: synchronized (localObservers) {
2183: for (MMBaseObserver o : localObservers) {
2184: if (o != this ) {
2185: o.nodeLocalChanged(machine, number, builder, ctype);
2186: } else {
2187: log.warn(getClass().getName() + " " + toString()
2188: + " observes itself");
2189: }
2190: }
2191: }
2192:
2193: return true;
2194: }
2195:
2196: /**
2197: * Called when a local field is changed.
2198: * @param number Number of the changed node as a <code>String</code>
2199: * @param builder type of the changed node
2200: * @param field name of the changed field
2201: * @param value value it changed to
2202: * @return always <code>true</code>
2203: */
2204: public boolean fieldLocalChanged(String number, String builder,
2205: String field, String value) {
2206: if (log.isDebugEnabled()) {
2207: log.debug("FLC=" + number + " BUL=" + builder + " FIELD="
2208: + field + " value=" + value);
2209: }
2210: return true;
2211: }
2212:
2213: /**
2214: * Adds a remote observer to this builder.
2215: * The observer is notified whenever an object of this builder is changed, added, or removed.
2216: * @return always <code>true</code>
2217: * @deprecated use the new event system as well. check out addEventListener(Object listener) or MMBase.addEventListener(EventListener listener)
2218: */
2219: public boolean addRemoteObserver(MMBaseObserver obs) {
2220: if (!remoteObservers.contains(obs)) {
2221: remoteObservers.add(obs);
2222: }
2223: return true;
2224: }
2225:
2226: /**
2227: * Adds a local observer to this builder.
2228: * The observer is notified whenever an object of this builder is changed, added, or removed.
2229: * @return always <code>true</code>
2230: * @deprecated use the new event system as well. check out addEventListener(Object listener) or MMBase.addEventListener(EventListener listener)
2231: */
2232: public boolean addLocalObserver(MMBaseObserver obs) {
2233: if (!localObservers.contains(obs)) {
2234: localObservers.add(obs);
2235: }
2236: return true;
2237: }
2238:
2239: /**
2240: * @since MMBase-1.8
2241: */
2242: public boolean removeLocalObserver(MMBaseObserver obs) {
2243: return localObservers.remove(obs);
2244: }
2245:
2246: /**
2247: * @since MMBase-1.8
2248: */
2249: public boolean removeRemoteObserver(MMBaseObserver obs) {
2250: return remoteObservers.remove(obs);
2251: }
2252:
2253: /**
2254: * Used to create a default teaser by any builder
2255: * @deprecated Will be removed?
2256: */
2257: public MMObjectNode getDefaultTeaser(MMObjectNode node,
2258: MMObjectNode tnode) {
2259: log
2260: .warn("getDefaultTeaser(): Generate Teaser,Should be overridden");
2261: return tnode;
2262: }
2263:
2264: /**
2265: * Waits until a node is changed (multicast).
2266: * @param node the node to wait for
2267: */
2268: /*
2269: public boolean waitUntilNodeChanged(MMObjectNode node) {
2270: return mmb.mmc.waitUntilNodeChanged(node);
2271: }
2272: */
2273:
2274: /**
2275: * Obtains a list of string values by performing the provided command and parameters.
2276: * This method is SCAN related and may fail if called outside the context of the SCAN servlet.
2277: * @param sp The PageInfo (containing http and user info) that calls the function
2278: * @param tagger a Hashtable of parameters (name-value pairs) for the command
2279: * @param tok a list of strings that describe the (sub)command to execute
2280: * @return a <code>Vector</code> containing the result values as a <code>String</code>
2281: */
2282: public Vector<String> getList(PageInfo sp, StringTagger tagger,
2283: StringTokenizer tok) {
2284: throw new UnsupportedOperationException(
2285: getClass().getName()
2286: + " should override the getList method (you've probably made a typo)");
2287: }
2288:
2289: /**
2290: * Obtains a string value by performing the provided command.
2291: * The command can be called:
2292: * <ul>
2293: * <li>by SCAN : $MOD-MMBASE-BUILDER-[buildername]-[command]</li>
2294: * <li>in jsp : cloud.getNodeManager(buildername).getInfo(command);</li>
2295: * </lu>
2296: * This method is SCAN related and some commands may fail if called outside the context of the SCAN servlet.
2297: * @param sp The PageInfo (containing http and user info) that calls the function
2298: * @param tok a list of strings that describe the (sub)command to execute
2299: * @return the result value as a <code>String</code>
2300: */
2301: public String replace(PageInfo sp, StringTokenizer tok) {
2302: log.warn("replace(): replace called should be overridden");
2303: return "";
2304: }
2305:
2306: /**
2307: * The hook that passes all form related pages to the correct handler.
2308: * This method is SCAN related and may fail if called outside the context of the SCAN servlet.
2309: * The methood is currentkly called by the MMEDIT module, whenever a 'PRC-CMD-BUILDER-...' command
2310: * is encountered in the list of commands to be processed.
2311: * @param sp The PageInfo (containing http and user info) that calls the function
2312: * @param command a list of strings that describe the (sub)command to execute (the portion after ' PRC-CMD-BUILDER')
2313: * @param cmds the commands (PRC-CMD) that are iurrently being processed, including the current command.
2314: * @param vars variables (PRC-VAR) thatw ere set to be used during processing.
2315: * @return the result value as a <code>String</code>
2316: */
2317: public boolean process(PageInfo sp, StringTokenizer command,
2318: Hashtable cmds, Hashtable vars) {
2319: return false;
2320: }
2321:
2322: /**
2323: * Set description of the builder
2324: * @param e the description text
2325: */
2326: public void setDescription(String e) {
2327: this .description = e;
2328: update();
2329: }
2330:
2331: /**
2332: * Set descriptions of the builder
2333: * @param e a <code>Hashtable</code> containing the descriptions
2334: */
2335: public void setDescriptions(Hashtable<String, String> e) {
2336: this .descriptions = e;
2337: update();
2338: }
2339:
2340: /**
2341: * Get description of the builder
2342: * @return the description text
2343: */
2344: public String getDescription() {
2345: return description;
2346: }
2347:
2348: /**
2349: * Gets description of the builder, using the specified language.
2350: * @param lang The language requested
2351: * @return the descriptions in that language, or <code>null</code> if it is not avaialble
2352: */
2353: public String getDescription(String lang) {
2354: if (descriptions == null)
2355: return null;
2356: String retval = descriptions.get(lang);
2357: if (retval == null) {
2358: return getDescription();
2359: }
2360: return retval;
2361: }
2362:
2363: /**
2364: * Get descriptions of the builder
2365: * @return a <code>Hashtable</code> containing the descriptions
2366: */
2367: public Hashtable<String, String> getDescriptions() {
2368: return descriptions;
2369: }
2370:
2371: /**
2372: * Sets search Age.
2373: * @param age the search age as a <code>String</code>
2374: */
2375: public void setSearchAge(String age) {
2376: this .searchAge = age;
2377: update();
2378: }
2379:
2380: /**
2381: * Gets search Age
2382: * @return the search age as a <code>String</code>
2383: */
2384: public String getSearchAge() {
2385: return searchAge;
2386: }
2387:
2388: /**
2389: * Gets short name of the builder, using the specified language.
2390: * @param lang The language requested
2391: * @return the short name in that language, or <code>null</code> if it is not available
2392: */
2393: public String getSingularName(String lang) {
2394: String tmp = null;
2395: if (singularNames != null) {
2396: tmp = singularNames.get(lang);
2397: if (tmp == null)
2398: tmp = singularNames.get(mmb.getLanguage());
2399: if (tmp == null)
2400: tmp = singularNames.get("en");
2401: }
2402: if (tmp == null)
2403: tmp = tableName;
2404: return tmp;
2405: }
2406:
2407: /**
2408: * Gets short name of the builder in the current default language.
2409: * If the current language is not available, the "en" version is returned instead.
2410: * If that name is not available, the internal builder name (table name) is returned.
2411: * @return the short name in either the default language or in "en"
2412: */
2413: public String getSingularName() {
2414: return getSingularName(mmb.getLanguage());
2415: }
2416:
2417: /**
2418: * Gets long name of the builder, using the specified language.
2419: * @param lang The language requested
2420: * @return the long name in that language, or <code>null</code> if it is not available
2421: */
2422: public String getPluralName(String lang) {
2423: String tmp = null;
2424: if (pluralNames != null) {
2425: tmp = pluralNames.get(lang);
2426: if (tmp == null)
2427: tmp = pluralNames.get(mmb.getLanguage());
2428: if (tmp == null)
2429: tmp = pluralNames.get("en");
2430: if (tmp == null)
2431: tmp = getSingularName(lang);
2432: }
2433: if (tmp == null)
2434: tmp = tableName;
2435: return tmp;
2436: }
2437:
2438: /**
2439: * Gets long name of the builder in the current default language.
2440: * If the current language is not available, the "en" version is returned instead.
2441: * If that name is not available, the singular name is returned.
2442: * @return the long name in either the default language or in "en"
2443: */
2444: public String getPluralName() {
2445: return getPluralName(mmb.getLanguage());
2446: }
2447:
2448: /**
2449: * Returns the classname of this builder
2450: * @deprecated don't use
2451: */
2452: public String getClassName() {
2453: return this .getClass().getName();
2454: }
2455:
2456: /**
2457: * Send a signal to other servers that a field was changed.
2458: * @param node the node the field was changed in
2459: * @param fieldName the name of the field that was changed
2460: * @return always <code>true</code>
2461: */
2462: public boolean sendFieldChangeSignal(MMObjectNode node,
2463: String fieldName) {
2464: // we need to find out what the DBState is of this field so we know
2465: // who to notify of this change
2466: int state = getDBState(fieldName);
2467: log.debug("Changed field=" + fieldName + " dbstate=" + state);
2468:
2469: // still a large hack need to figure out remote changes
2470: if (state == 0) {
2471: }
2472: // convert the field to a string
2473:
2474: int type = getDBType(fieldName);
2475: String value = "";
2476: if ((type == Field.TYPE_INTEGER) || (type == Field.TYPE_NODE)) {
2477: value = "" + node.getIntValue(fieldName);
2478: } else if (type == Field.TYPE_STRING) {
2479: value = node.getStringValue(fieldName);
2480: } else {
2481: // should be mapped to the builder
2482: }
2483:
2484: fieldLocalChanged("" + node.getNumber(), tableName, fieldName,
2485: value);
2486: //mmb.mmc.changedNode(node.getNumber(),tableName,"f");
2487: return true;
2488: }
2489:
2490: /**
2491: * Send a signal to other servers that a new node was created.
2492: * @param tableName the table in which a node was edited (?)
2493: * @param number the number of the new node
2494: * @return always <code>true</code>
2495: */
2496: /*
2497: public boolean signalNewObject(String tableName,int number) {
2498: if (mmb.mmc!=null) {
2499: mmb.mmc.changedNode(number,tableName,"n");
2500: }
2501: return true;
2502: }
2503: */
2504:
2505: /**
2506: * Sets a list of singular names (language - value pairs)
2507: */
2508: public void setSingularNames(Hashtable<String, String> names) {
2509: singularNames = names;
2510: update();
2511: }
2512:
2513: /**
2514: * Gets a list of singular names (language - value pairs)
2515: */
2516: public Hashtable<String, String> getSingularNames() {
2517: return singularNames;
2518: }
2519:
2520: /**
2521: * Sets a list of plural names (language - value pairs)
2522: */
2523: public void setPluralNames(Hashtable<String, String> names) {
2524: pluralNames = names;
2525: update();
2526: }
2527:
2528: /**
2529: * Gets a list of plural names (language - value pairs)
2530: */
2531: public Hashtable<String, String> getPluralNames() {
2532: return pluralNames;
2533: }
2534:
2535: /**
2536: * Get text from a blob field. This function is called to 'load' a field into the node, because
2537: * it was not loaded together with the node, because it is supposed to be too big.
2538: * @param fieldName name of the field
2539: * @param node
2540: * @return a <code>String</code> containing the complate contents of a field as text.
2541: * @since MMBase-1.8
2542: */
2543: protected String getShortedText(String fieldName, MMObjectNode node) {
2544: if (node.getNumber() < 0)
2545: return null; // capture calls from temporary nodes
2546: try {
2547: return mmb.getStorageManager().getStringValue(node,
2548: getField(fieldName));
2549: } catch (StorageException se) {
2550: log.error(se.getMessage());
2551: log.error(Logging.stackTrace(se));
2552: return null;
2553: }
2554: }
2555:
2556: /**
2557: * Get binary data of a blob field. This function is called to 'load' a field into the node, because
2558: * it was not loaded together with the node, because it is supposed to be too big.
2559: * @param fieldName name of the field
2560: * @param node
2561: * @return an array of <code>byte</code> containing the complete contents of the field.
2562: * @since MMBase-1.8
2563: */
2564: protected byte[] getShortedByte(String fieldName, MMObjectNode node) {
2565: if (node.getNumber() < 0)
2566: return null; // capture calls from temporary nodes
2567: try {
2568: return mmb.getStorageManager().getBinaryValue(node,
2569: getField(fieldName));
2570: } catch (StorageException se) {
2571: log.error(se.getMessage());
2572: log.error(Logging.stackTrace(se));
2573: return null;
2574: }
2575: }
2576:
2577: /**
2578: * Sets a key/value pair in the main values of this node.
2579: * Note that if this node is a node in cache, the changes are immediately visible to
2580: * everyone, even if the changes are not committed.
2581: * The fieldname is added to the (public) 'changed' vector to track changes.
2582: * @param fieldName the name of the field to change
2583: * @param node The node on which to change the field (the new value is in this node)
2584: * @param originalValue the value which was original in the field
2585: * @return <code>true</code> When an update is required(when changed),
2586: * <code>false</code> if original value was set back into the field.
2587: */
2588: public boolean setValue(MMObjectNode node, String fieldName,
2589: Object originalValue) {
2590: return setValue(node, fieldName);
2591: }
2592:
2593: /**
2594: * Provides additional functionality when setting field values.
2595: * This method is called whenever a Node of the builder's type tries to change a value.
2596: * It allows the system to add functionality such as checking valid data.
2597: * Derived builders should override this method if they want to add functionality.
2598: * @param node the node whose fields are changed
2599: * @param fieldName the fieldname that is changed
2600: * @return <code>true</code> if the call was handled.
2601: */
2602: public boolean setValue(MMObjectNode node, String fieldName) {
2603: return true;
2604: }
2605:
2606: /**
2607: * Returns a HTML-version of a string.
2608: * This replaces a number of tokens with HTML sequences.
2609: * The default output does not match well with the new xhtml standards (ugly html), nor does it replace all tokens.
2610: *
2611: * Default replacements can be overridden by setting the builder properties in your <builder>.xml:
2612: *
2613: * html.alinea
2614: * html.endofline
2615: *
2616: * Example:
2617: * <properties>
2618: * <property name="html.alinea"> <br /> <br /></property>
2619: * <property name="html.endofline"> <br /> </property>
2620: * </properties>
2621: *
2622: * @param body text to convert
2623: * @return the convert text
2624: * @deprecated
2625: */
2626:
2627: protected String getHTML(String body) {
2628: String rtn = "";
2629: if (body != null) {
2630: StringObject obj = new StringObject(body);
2631: // escape ampersand first
2632: obj.replace("&", "&");
2633:
2634: obj.replace("<", "<");
2635: obj.replace(">", ">");
2636: // escape dollar-sign (prevent SCAN code to be run)
2637: obj.replace("$", "$");
2638: // unquote ampersand and quotes (see escapeXML method)
2639: obj.replace("\"", """);
2640: obj.replace("'", "'");
2641:
2642: String alinea = getInitParameter("html.alinea");
2643: String endofline = getInitParameter("html.endofline");
2644:
2645: if (alinea != null) {
2646: obj.replace("\r\n\r\n", alinea);
2647: obj.replace("\n\n", alinea);
2648: } else {
2649: obj.replace("\r\n\r\n", DEFAULT_ALINEA);
2650: obj.replace("\n\n", DEFAULT_ALINEA);
2651: }
2652:
2653: if (endofline != null) {
2654: obj.replace("\r\n", endofline);
2655: obj.replace("\n", endofline);
2656: } else {
2657: obj.replace("\r\n", DEFAULT_EOL);
2658: obj.replace("\n", DEFAULT_EOL);
2659: }
2660:
2661: rtn = obj.toString();
2662: }
2663: return rtn;
2664: }
2665:
2666: /**
2667: * Returns a WAP-version of a string.
2668: * This replaces a number of tokens with WAP sequences.
2669: * @param body text to convert
2670: * @return the convert text
2671: */
2672: protected static String getWAP(String body) {
2673: String result = "";
2674: if (body != null) {
2675: StringObject obj = new StringObject(body);
2676: obj.replace("\"", """);
2677: obj.replace("&", "&#38;");
2678: obj.replace("'", "'");
2679: obj.replace("<", "&#60;");
2680: obj.replace(">", ">");
2681: result = obj.toString();
2682: }
2683: return result;
2684: }
2685:
2686: /**
2687: * Returns a URLEncoded-version (MIME x-www-form-urlencoded) of a string.
2688: * This version uses the java.net.URLEncoder class to encode it.
2689: * @param body text to convert
2690: * @return the URLEncoded text
2691: */
2692: protected static String getURLEncode(String body) {
2693: String rtn = "";
2694: if (body != null) {
2695: rtn = URLEncoder.encode(body); // UTF8?
2696: }
2697: return rtn;
2698: }
2699:
2700: /**
2701: * Support routine to return shorter strings.
2702: * Cuts a string to a amximum length if it exceeds the length specified.
2703: * @param str the string to shorten
2704: * @param len the maximum length
2705: * @return the (possibly shortened) string
2706: */
2707: public String getShort(String str, int len) {
2708: if (str.length() > len) {
2709: return str.substring(0, (len - 3)) + "...";
2710: } else {
2711: return str;
2712: }
2713: }
2714:
2715: /**
2716: * Stores fields information of this table.
2717: * Asside from the fields supplied by the caller, a field 'otype' is added (if missing).
2718: *
2719: * @param f A List with fields (as CoreField objects) as defined by MMBase. This may not be in sync with the actual database table, about which Storage will report then.
2720: */
2721: public void setFields(List<CoreField> f) {
2722: fields.clear();
2723:
2724: for (CoreField def : f) {
2725: String name = def.getName();
2726: def.setParent(this );
2727: fields.put(name.toLowerCase(), def);
2728: }
2729:
2730: // should be TYPE_NODE ???
2731: if (fields.get(FIELD_OBJECT_TYPE) == null) {
2732: log
2733: .warn("Object 'otype' field is not defined. Please update your object.xml, or update '"
2734: + getConfigResource()
2735: + "' to extend object");
2736: // if not defined in XML (legacy?)
2737: // It does currently not work if otype is actually defined in object.xml (as a NODE field)
2738: CoreField def = Fields.createSystemField(FIELD_OBJECT_TYPE,
2739: Field.TYPE_NODE);
2740: def.setGUIName("Type");
2741: // here, we should set the DBPos to 2 and adapt those of the others fields
2742: def.setStoragePosition(2);
2743: def.getDataType().setRequired(true);
2744: def.setNotNull(true);
2745: for (CoreField field : f) {
2746: int pos = field.getStoragePosition();
2747: if (pos > 1)
2748: field.setStoragePosition(pos + 1);
2749: }
2750: def.setParent(this );
2751: def.finish();
2752: fields.put(FIELD_OBJECT_TYPE, def);
2753: }
2754: updateFields();
2755: }
2756:
2757: /**
2758: * Sets the subpath of the builder's xml configuration file.
2759: */
2760: public void setXMLPath(String m) {
2761: xmlPath = m;
2762: update();
2763: }
2764:
2765: /**
2766: * Retrieves the subpath of the builder's xml configuration file.
2767: * Needed for builders that reside in subdirectories in the builder configuration file directory.
2768: */
2769: public String getXMLPath() {
2770: return xmlPath;
2771: }
2772:
2773: /**
2774: * @since MMBase-1.8
2775: */
2776:
2777: public String getConfigResource() {
2778: return "builders/" + getXMLPath() + "/" + getTableName()
2779: + ".xml";
2780: }
2781:
2782: /**
2783: * Gets the file that contains the configuration of this builder
2784: * @return the builders configuration File object
2785: * @deprecated Need something as getConfigResource in stead.
2786: */
2787: public File getConfigFile() {
2788: // what is the location of our builder?
2789: List<File> files = ResourceLoader.getConfigurationRoot()
2790: .getFiles(getConfigResource());
2791: if (files.size() == 0) {
2792: return null;
2793: } else {
2794: return files.get(0);
2795: }
2796: }
2797:
2798: /**
2799: * Set all builder properties
2800: * Changed properties will not be saved.
2801: * @param properties the properties to set
2802: */
2803: void setInitParameters(Hashtable<String, String> properties) {
2804: this .properties.putAll(properties);
2805: loadInitParameters();
2806: update();
2807: }
2808:
2809: /**
2810: * Get all builder properties
2811: * @return a <code>Map</code> containing the current properties
2812: */
2813: public Map<String, String> getInitParameters() {
2814: return properties;
2815: }
2816:
2817: /**
2818: * Override properties through application context
2819: * @param contextPath path in application context where properties are located
2820: * @since MMBase 1.8.5
2821: */
2822: public void loadInitParameters() {
2823: try {
2824: Map<String, String> contextMap = ApplicationContextReader
2825: .getProperties("mmbase-builders/" + getTableName());
2826: properties.putAll(contextMap);
2827: } catch (javax.naming.NamingException ne) {
2828: log
2829: .debug("Can't obtain properties from application context: "
2830: + ne.getMessage());
2831: }
2832: }
2833:
2834: /**
2835: * Get all builder properties and override properties through application context
2836: * @param contextPath path in application context where properties are located
2837: * @return a <code>Map</code> containing the current properties
2838: * @since MMBase 1.8.2
2839: * @deprecated
2840: */
2841: public Map getInitParameters(String contextPath) {
2842: Map map = new HashMap();
2843: map.putAll(getInitParameters());
2844:
2845: try {
2846: Map contextMap = ApplicationContextReader
2847: .getProperties(contextPath);
2848: if (!contextMap.isEmpty()) {
2849: map.putAll(contextMap);
2850: }
2851: } catch (javax.naming.NamingException ne) {
2852: log
2853: .debug("Can't obtain properties from application context: "
2854: + ne.getMessage());
2855: }
2856: return map;
2857: }
2858:
2859: /**
2860: * Set a single builder property
2861: * The propertie will not be saved.
2862: * @param name name of the property
2863: * @param value value of the property
2864: */
2865: public void setInitParameter(String name, String value) {
2866: properties.put(name, value);
2867: update();
2868: }
2869:
2870: /**
2871: * Retrieve a specific property.
2872: * @param name the name of the property to get
2873: * @return the value of the property as a <code>String</code>
2874: */
2875: public String getInitParameter(String name) {
2876: if (properties == null) {
2877: return null;
2878: } else {
2879: return properties.get(name);
2880: }
2881: }
2882:
2883: /**
2884: * Sets the version of this builder
2885: * @param i the version number
2886: */
2887: public void setVersion(int i) {
2888: version = i;
2889: update();
2890: }
2891:
2892: /**
2893: * Retrieves the version of this builder
2894: * @return the version number
2895: */
2896: public int getVersion() {
2897: return version;
2898: }
2899:
2900: /**
2901: * Retrieves the maintainer of this builder
2902: * @return the name of the maintainer
2903: */
2904: public String getMaintainer() {
2905: return maintainer;
2906: }
2907:
2908: /**
2909: * Sets the maintainer of this builder
2910: * @param m the name of the maintainer
2911: */
2912: public void setMaintainer(String m) {
2913: maintainer = m;
2914: update();
2915: }
2916:
2917: /**
2918: * hostname, parses the hostname from a url, so http://www.mmbase.org/bug
2919: * becomed www.mmbase.org
2920: * @deprecated Has nothing to do with mmbase nodes. Should be in org.mmbase.util
2921: */
2922: public static String hostname_function(String url) {
2923: if (url.startsWith("http://")) {
2924: url = url.substring(7);
2925: }
2926: if (url.startsWith("https://")) {
2927: url = url.substring(8);
2928: }
2929: int pos = url.indexOf("/");
2930: if (pos != -1) {
2931: url = url.substring(0, pos);
2932: }
2933: return url;
2934: }
2935:
2936: /**
2937: * Wraps a string.
2938: * Inserts newlines (\n) into a string at periodic intervals, to simulate wrapping.
2939: * This also removes whitespace to the start of a line.
2940: * @param text the text to wrap
2941: * @param width the maximum width to wrap at
2942: * @return the wrapped tekst
2943: */
2944: public static String wrap(String text, int width) {
2945: StringBuilder dst = new StringBuilder();
2946: StringTokenizer tok = new StringTokenizer(text, " \n\r", true);
2947: int pos = 0;
2948: while (tok.hasMoreTokens()) {
2949: String word = tok.nextToken();
2950: if (word.equals("\n")) {
2951: pos = 0;
2952: } else if (word.equals(" ")) {
2953: if (pos == 0) {
2954: word = "";
2955: } else {
2956: pos++;
2957: if (pos >= width) {
2958: word = "\n";
2959: pos = 0;
2960: }
2961: }
2962: } else {
2963: pos += word.length();
2964: if (pos >= width) {
2965: dst.append("\n");
2966: pos = word.length();
2967: }
2968: }
2969: dst.append(word);
2970: }
2971: return dst.toString();
2972: }
2973:
2974: /**
2975: * Gets a substring.
2976: * @param value the string to get a substring of
2977: * @param len the length of the substring
2978: * @param filler if not null, this field is used as a trailing tekst
2979: * of the created substring.
2980: * @return the substring
2981: */
2982: private static String substring(String value, int len, String filler) {
2983: if (filler == null) {
2984: if (value.length() > len) {
2985: return value.substring(0, len);
2986: } else {
2987: return value;
2988: }
2989: } else {
2990: int len2 = filler.length();
2991: if ((value.length() + len2) > len) {
2992: return value.substring(0, (len - len2)) + filler;
2993: } else {
2994: return value;
2995: }
2996: }
2997: }
2998:
2999: /**
3000: * Implmenting a sensible toString is usefull for debugging.
3001: *
3002: * @since MMBase-1.6.2
3003: */
3004:
3005: public String toString() {
3006: return getSingularName();
3007: }
3008:
3009: /**
3010: * Equals must be implemented because of the list of MMObjectBuilder which is used for ancestors
3011: *
3012: * Declared the method final, because the instanceof operator is used. This is the only
3013: * MMObjectBuilder is frequently extended and subclasses will always break
3014: * the equals contract.
3015: * When subclasses require to implement the equals method then we should use
3016: * getClass() == o.getClass(), but this has its own issues. For more info, search for equality in Java
3017: *
3018: * @since MMBase-1.6.2
3019: */
3020: public final boolean equals(Object o) {
3021: if (o == this )
3022: return true;
3023: if (o == null)
3024: return false;
3025: if (o instanceof MMObjectBuilder) {
3026: MMObjectBuilder b = (MMObjectBuilder) o;
3027: return tableName.equals(b.tableName);
3028: }
3029: return false;
3030: }
3031:
3032: /**
3033: * @see java.lang.Object#hashCode()
3034: */
3035: public int hashCode() {
3036: return tableName == null ? 0 : tableName.hashCode();
3037: }
3038:
3039: /**
3040: * Implements for MMObjectNode
3041: * @since MMBase-1.6.2
3042: */
3043:
3044: public String toString(MMObjectNode n) {
3045: return n.defaultToString();
3046: }
3047:
3048: /**
3049: * Implements equals for nodes (this is in MMObjectBuilder because you cannot override MMObjectNode)
3050: *
3051: * @since MMBase-1.6.2
3052: */
3053:
3054: public boolean equals(MMObjectNode o1, MMObjectNode o2) {
3055: return o1.defaultEquals(o2);
3056: }
3057:
3058: /**
3059: * Implements for MMObjectNode
3060: * @since MMBase-1.6.2
3061: */
3062:
3063: public int hashCode(MMObjectNode o) {
3064: return 127 * o.getNumber();
3065: }
3066:
3067: /**
3068: * simple way to register a NodeEvent listener and a RelationEventListener
3069: * at the same time.
3070: * @see MMBase#addNodeRelatedEventsListener
3071: * @param listener
3072: * @since MMBase-1.8
3073: */
3074: public void addEventListener(
3075: org.mmbase.core.event.EventListener listener) {
3076: mmb.addNodeRelatedEventsListener(getTableName(), listener);
3077: }
3078:
3079: /**
3080: * @param listener
3081: * @since MMBase-1.8
3082: */
3083: public void removeEventListener(
3084: org.mmbase.core.event.EventListener listener) {
3085: mmb.removeNodeRelatedEventsListener(getTableName(), listener);
3086: }
3087:
3088: /**
3089: * @see org.mmbase.core.event.NodeEventListener#notify(org.mmbase.core.event.NodeEvent)
3090: * here we handle all the backward compatibility stuff.
3091: * this method covers for both node and relation events.
3092: * @since MMBase-1.8
3093: */
3094: public void notify(NodeEvent event) {
3095: if (log.isDebugEnabled()) {
3096: log.debug("" + this + " received node event " + event);
3097: }
3098: int type = event.getType();
3099: eventBackwardsCompatible(event.getMachine(), event
3100: .getNodeNumber(), type);
3101: }
3102:
3103: /**
3104: * @since MMBase-1.8
3105: */
3106: public void notify(RelationEvent event) {
3107: if (log.isDebugEnabled()) {
3108: log.debug("" + this + " received relation event " + event);
3109: }
3110: //for backwards compatibilty: create relation changed calls
3111: if (event.getRelationSourceType().equals(getTableName())) {
3112: eventBackwardsCompatible(event.getMachine(), event
3113: .getRelationSourceNumber(),
3114: NodeEvent.TYPE_RELATION_CHANGE);
3115: }
3116: if (event.getRelationDestinationType().equals(getTableName())) {
3117: eventBackwardsCompatible(event.getMachine(), event
3118: .getRelationDestinationNumber(),
3119: NodeEvent.TYPE_RELATION_CHANGE);
3120: }
3121:
3122: //update the cache
3123: Integer changedNode = Integer
3124: .valueOf((event.getRelationDestinationType().equals(
3125: getTableName()) ? event
3126: .getRelationSourceNumber() : event
3127: .getRelationDestinationNumber()));
3128: MMObjectNode.delRelationsCache(changedNode);
3129: }
3130:
3131: /**
3132: * @see org.mmbase.core.event.NodeEventListener#notify(org.mmbase.core.event.NodeEvent)
3133: * here we handle all the backward compatibility stuff.
3134: * this method covers for both node and relation events.
3135: * @since MMBase-1.8
3136: * @param event
3137: */
3138: private void eventBackwardsCompatible(String machineName,
3139: int nodeNumber, int eventType) {
3140: String ctype = NodeEvent.newTypeToOldType(eventType);
3141: boolean localEvent = mmb.getMachineName().equals(machineName);
3142:
3143: if (localEvent) {
3144: nodeLocalChanged(machineName, "" + nodeNumber,
3145: getTableName(), ctype);
3146: } else {
3147: nodeRemoteChanged(machineName, "" + nodeNumber,
3148: getTableName(), ctype);
3149: }
3150: }
3151:
3152: }
|