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.util.*;
0013: import java.io.*;
0014:
0015: import org.mmbase.cache.*;
0016: import org.mmbase.bridge.Field;
0017: import org.mmbase.bridge.Node;
0018: import org.mmbase.module.corebuilders.InsRel;
0019: import org.mmbase.module.builders.DayMarkers;
0020: import org.mmbase.security.*;
0021: import org.mmbase.storage.search.*;
0022: import org.mmbase.util.Casting;
0023: import org.mmbase.util.SizeOf;
0024: import org.mmbase.util.DynamicDate;
0025: import org.mmbase.util.logging.*;
0026: import org.mmbase.util.functions.*;
0027: import org.w3c.dom.Document;
0028:
0029: /**
0030: * MMObjectNode is the core of the MMBase system.
0031: * This class is what its all about, because the instances of this class hold the content we are using.
0032: * All active Nodes with data and relations are MMObjectNodes and make up the
0033: * object world that is MMBase (Creating, searching, removing is done by the node's parent,
0034: * which is a class extended from MMObjectBuilder)
0035: *
0036: * @author Daniel Ockeloen
0037: * @author Pierre van Rooden
0038: * @author Eduard Witteveen
0039: * @author Michiel Meeuwissen
0040: * @author Ernst Bunders
0041: * @version $Id: MMObjectNode.java,v 1.213 2008/02/03 17:33:57 nklasens Exp $
0042: */
0043:
0044: public class MMObjectNode implements org.mmbase.util.SizeMeasurable,
0045: java.io.Serializable {
0046: private static final Logger log = Logging
0047: .getLoggerInstance(MMObjectNode.class);
0048:
0049: /**
0050: * Large fields (blobs) are loaded 'lazily', so only on explicit request. Until the first exlicit request this value is stored in such fields.
0051: * It can be set back into the field with {@link #storeValue}, to unload the field again.
0052: * @since MMBase-1.7.4
0053: */
0054: public final static String VALUE_SHORTED = "$SHORTED";
0055:
0056: /**
0057: * Map which stores the current database value for fields when
0058: * then change in the node.
0059: * it can be used to optimise cacheing
0060: * @since MMBase-1.8
0061: */
0062: private Map<String, Object> oldValues = new HashMap<String, Object>();
0063:
0064: /**
0065: * Holds the name - value pairs of this node (the node's fields).
0066: * Most nodes will have a 'number' and an 'otype' field, and fields which will differ by builder.
0067: * This collection should not be directly queried or changed -
0068: * use the SetValue and getXXXValue methods instead.
0069: * It should then be made private, and methods that change the map (storeValue) be made synchronized.
0070: * Note: To avoid synchronisation conflicts, we can't really change the type until the property is made private.
0071: */
0072: protected Map<String, Object> values = Collections
0073: .synchronizedMap(new HashMap<String, Object>());
0074: private Map<String, Long> sizes = Collections
0075: .synchronizedMap(new HashMap<String, Long>());
0076:
0077: /**
0078: * Determines whether the node is being initialized (typically when it is loaded from the database).
0079: * Use {@link #start} to start initializing, use {@link #finish} to end.
0080: * @since MMBase-1.7
0081: */
0082: protected boolean initializing = false;
0083:
0084: /**
0085: * Holds the 'extra' name-value pairs (the node's properties)
0086: * which are retrieved from the 'properties' table.
0087: * @scope private
0088: */
0089: public Hashtable<String, MMObjectNode> properties;
0090:
0091: /**
0092: * Set which stores the keys of the fields that were changed
0093: * since the last commit.
0094: */
0095: private Set<String> changed = Collections
0096: .synchronizedSet(new HashSet<String>());
0097:
0098: /**
0099: * Pointer to the parent builder that is responsible for this node.
0100: * Note: this may on occasion (due to optimization) duffer for the node's original builder.
0101: * Use {@link #getBuilder} instead.
0102: * @scope private
0103: */
0104: protected MMObjectBuilder parent;
0105:
0106: /**
0107: * Pointer to the actual builder to which this node belongs.
0108: * This value is initialised through the first call to {@link #getBuilder}
0109: */
0110: private MMObjectBuilder builder = null;
0111:
0112: /**
0113: * If <code>true</code>, the node is a new node, which is not (yet) stored in storage.
0114: */
0115: protected boolean isNew = false;
0116:
0117: /**
0118: * New aliases of the node
0119: */
0120: private Set<String> aliases = null;
0121:
0122: // object to sync access to properties
0123: private final Object properties_sync = new Object();
0124:
0125: /**
0126: * temporarily holds a new context for a node
0127: * @since MMBase-1.7
0128: */
0129:
0130: private String newContext = null;
0131:
0132: /**
0133: * Default Main constructor, creates a node that is new and not (yet) in storage.
0134: * @param parent the node's parent, an instance of the node's builder.
0135: * @throws IllegalArgumentException If parent is <code>null</code>
0136: */
0137: public MMObjectNode(MMObjectBuilder parent) {
0138: this (parent, true);
0139: }
0140:
0141: /**
0142: * Main constructor.
0143: * @param parent the node's parent, an instance of the node's builder.
0144: * @param isNew if the node is a newly created node
0145: * @throws IllegalArgumentException If parent is <code>null</code>
0146: */
0147: public MMObjectNode(MMObjectBuilder parent, boolean isNew) {
0148: this .isNew = isNew;
0149: if (parent != null) {
0150: this .parent = parent;
0151: } else {
0152: throw new IllegalArgumentException(
0153: "Constructor called with parent=null");
0154: }
0155: }
0156:
0157: /**
0158: * @since MMBase-1.8
0159: */
0160: public MMObjectNode(MMObjectNode node) {
0161: parent = node.parent;
0162: isNew = node.isNew();
0163: values.putAll(node.getValues());
0164: values.putAll(node.getOldValues());
0165: }
0166:
0167: /**
0168: * Creates an MMObject based on a given Map. This can e.g. be used to make an MMObjectNode of a bridge node (use {@link org.mmbase.bridge.util.NodeMap}).
0169: *
0170: * @since MMBase-1.8
0171: */
0172: public MMObjectNode(MMObjectBuilder parent, Map<String, Object> map) {
0173: isNew = false;
0174: this .parent = parent;
0175: values = map;
0176: }
0177:
0178: /**
0179: * Returns the actual builder of the node.
0180: * Note that it is possible that, due to optimization, a node is currently associated with
0181: * another (parent) builder, i.e. a posrel node may be associated with a insrel builder.
0182: * This method returns the actual builder.
0183: * The node may miss vital information (not retrieved from the database) to act as a node of such
0184: * a builder - if you need actual status you need to reload it.
0185: * @since MMBase-1.6
0186: * @return the builder of this node
0187: */
0188: public MMObjectBuilder getBuilder() {
0189: if (builder == null) {
0190: int oType = getOType();
0191: if (oType == -1 || parent.getNumber() == oType) {
0192: builder = parent;
0193: } else {
0194: String builderName = parent.mmb.getTypeDef().getValue(
0195: oType);
0196: if (builderName != null) { // avoid NPE from mmb.getBuilder.
0197: builder = parent.mmb.getBuilder(builderName);
0198: }
0199: }
0200: if (builder == null) {
0201: log.warn("Builder of node " + getNumber()
0202: + " not found, taking 'object'");
0203: builder = parent.mmb.getBuilder("object");
0204: }
0205: }
0206: return builder;
0207: }
0208:
0209: /**
0210: * Start the loading of a node
0211: * @since MMBase-1.7
0212: */
0213: public void start() {
0214: initializing = true;
0215: }
0216:
0217: /**
0218: * Finish the loading of a node
0219: * @since MMBase-1.7
0220: */
0221: public void finish() {
0222: initializing = false;
0223: }
0224:
0225: /**
0226: * Tests whether the data in a node is valid (throws an exception if this is not the case).
0227: * @throws org.mmbase.module.core.InvalidDataException
0228: * If the data was unrecoverably invalid (the references did not point to existing objects)
0229: */
0230: public void testValidData() throws InvalidDataException {
0231: parent.testValidData(this );
0232: };
0233:
0234: /**
0235: * Commit the node to the database or other storage system.
0236: * This can only be done on a existing (inserted) node. It will use the
0237: * changed Vector as its base of what to commit/change.
0238: * @return <code>true</code> if the commit was succesfull, <code>false</code> is it failed
0239: */
0240: public boolean commit() {
0241: boolean success = parent.commit(this );
0242: if (success) {
0243: isNew = false; // perhaps it is always already false (otherwise insert is called, I think), but no matter, now it certainly isn't new!
0244: } else {
0245: values.putAll(oldValues);
0246: }
0247: oldValues.clear();
0248: changed.clear();
0249: return success;
0250: }
0251:
0252: /**
0253: * Undo changes made to the node.
0254: *
0255: * @since MMBase-1.8
0256: */
0257: public void cancel() {
0258: values.putAll(oldValues);
0259: oldValues.clear();
0260: changed.clear();
0261: }
0262:
0263: /**
0264: * Insert this node into the storage
0265: * @param userName the name of the user who inserts the node. This value is ignored
0266: * @return the new node key (number field), or -1 if the insert failed
0267: */
0268: public int insert(String userName) {
0269: return parent.insert(userName, this );
0270: }
0271:
0272: /**
0273: * Insert this node into the database or other storage system.
0274: * @param user the user who inserts the node.
0275: * Used to set security-related information
0276: * @return the new node key (number field), or -1 if the insert failed
0277: * @since MMBase-1.7
0278: */
0279: public int insert(UserContext user) {
0280: int nodeID = parent.safeInsert(this , user.getIdentifier());
0281: if (nodeID != -1) {
0282: MMBaseCop mmbaseCop = parent.getMMBase().getMMBaseCop();
0283: mmbaseCop.getAuthorization().create(user, nodeID);
0284: if (newContext != null) {
0285: mmbaseCop.getAuthorization().setContext(user, nodeID,
0286: newContext);
0287: newContext = null;
0288: }
0289: }
0290: return nodeID;
0291: }
0292:
0293: /**
0294: * Commit this node to the storage
0295: * @param user the user who commits the node.
0296: * Used to set security-related information
0297: * @return <code>true</code> if succesful
0298: * @since MMBase-1.7
0299: */
0300: public boolean commit(UserContext user) {
0301: boolean success = parent.safeCommit(this );
0302: if (success) {
0303: MMBaseCop mmbaseCop = parent.getMMBase().getMMBaseCop();
0304: mmbaseCop.getAuthorization().update(user, getNumber());
0305: if (newContext != null) {
0306: mmbaseCop.getAuthorization().setContext(user,
0307: getNumber(), newContext);
0308: newContext = null;
0309: }
0310: }
0311: return success;
0312: }
0313:
0314: /**
0315: * Remove this node from the storage
0316: * @param user the user who removes the node.
0317: * Used to set security-related information
0318: * @since MMBase-1.7
0319: */
0320: public void remove(UserContext user) {
0321: if (log.isDebugEnabled()) {
0322: log.debug("Deleting node " + getNumber() + " because "
0323: + Logging.stackTrace(5));
0324: }
0325: parent.removeNode(this );
0326: parent.getMMBase().getMMBaseCop().getAuthorization().remove(
0327: user, getNumber());
0328: }
0329:
0330: /**
0331: * Sets the security context for this node
0332: * @param user the user who changes the context of the node.
0333: * @param context the new context
0334: * @param now if <code>true</code>, the context is changed instantly, otherwise it is changed
0335: * after the node is send to storage.
0336: * @since MMBase-1.7
0337: */
0338: public void setContext(UserContext user, String context, boolean now) {
0339: if (now) {
0340: parent.getMMBase().getMMBaseCop().getAuthorization()
0341: .setContext(user, getNumber(), context);
0342: } else {
0343: newContext = context;
0344: }
0345: }
0346:
0347: /**
0348: * Returns the security context for this node
0349: * @param user the user who requests the context of the node.
0350: * @since MMBase-1.7.1
0351: */
0352: public String getContext(UserContext user) {
0353: if (newContext != null)
0354: return newContext;
0355: if (getNumber() < 0)
0356: return user.getOwnerField();
0357: try {
0358: return parent.getMMBase().getMMBaseCop().getAuthorization()
0359: .getContext(user, getNumber());
0360: } catch (Exception e) {
0361: log.warn(e);
0362: return getStringValue("owner");
0363: }
0364: }
0365:
0366: /**
0367: * Returns the possible new security contexts for this node
0368: * @param user the user who requests the context of the node.
0369: * @since MMBase-1.7.1
0370: */
0371: public Set<String> getPossibleContexts(UserContext user) {
0372: if (getNumber() < 0) {
0373: // a new node has yet no context (except the default).
0374: // instead of searching the database for data, return a
0375: // standard set of values existing of the current context
0376: // and the contexts "system" and "admin".
0377: // A better way involves rewriting the security layer to accept
0378: // MMObjectNodes instead of node numbers
0379: Set<String> contexts = new HashSet<String>();
0380: contexts.add(getContext(user));
0381: contexts.add("admin");
0382: contexts.add("system");
0383: return contexts;
0384: /*
0385: NodeSearchQuery query = new NodeSearchQuery(parent);
0386: CoreField fieldDefs = parent.getField("owner");
0387: StepField field = query.getField(fieldDefs);
0388: BasicFieldValueConstraint cons = new BasicFieldValueConstraint(field, getContext(user));
0389: query.setMaxNumber(1);
0390: try {
0391: Iterator resultList = parent.getNodes(query).iterator();
0392: if (resultList.hasNext()) {
0393: return ((MMObjectNode) resultList.next()).getPossibleContexts(user);
0394: }
0395: } catch (SearchQueryException sqe) {
0396: log.error(sqe.toString());
0397: }
0398: return new HashSet();
0399: */
0400: }
0401: return parent.getMMBase().getMMBaseCop().getAuthorization()
0402: .getPossibleContexts(user, getNumber());
0403: }
0404:
0405: /**
0406: * Returns the core of this node in a string.
0407: * Used for debugging.
0408: * For data exchange use toXML() and getDTD().
0409: * @return the contents of the node as a string.
0410: */
0411: public String toString() {
0412: if (parent != null) {
0413: return parent.toString(this );
0414: } else {
0415: return defaultToString();
0416: }
0417: }
0418:
0419: /**
0420: * @since MMBase-1.6.2
0421: */
0422: String defaultToString() {
0423: StringBuilder result = new StringBuilder();
0424: try {
0425: Set<Map.Entry<String, Object>> entrySet = values.entrySet();
0426: synchronized (values) {
0427: Iterator<Map.Entry<String, Object>> i = entrySet
0428: .iterator();
0429: while (i.hasNext()) {
0430: Map.Entry<String, Object> entry = i.next();
0431: String key = entry.getKey();
0432: String value = "" + entry.getValue(); // XXX:should be retrieveValue ?
0433: if (result.length() == 0) {
0434: result.append(key).append("='").append(value)
0435: .append("'");
0436: } else {
0437: result.append(",").append(key).append("='")
0438: .append(value).append("'");
0439: }
0440: }
0441: }
0442: } catch (Throwable e) {
0443: result.append(values); // simpler version...
0444: }
0445: result.append(super .toString());
0446: return result.toString();
0447: }
0448:
0449: /**
0450: * @return <code>true</code> if field exists and may be used.
0451: * @since MMBase-1.8
0452: */
0453: protected boolean checkFieldExistance(String fieldName) {
0454: if (fieldName.charAt(0) == '_') {
0455: // don't complain then, a lot of hackery (apps1 import/export) is based on this.
0456: // This is just a hack to make app1 import/export working, withough exposing the values map.
0457: return true;
0458: }
0459: if (fieldName.indexOf('(') > 0) {
0460: return true;
0461: }
0462: if (!getBuilder().hasField(fieldName)) {
0463: if (MMBase.getMMBase().inDevelopment()) {
0464: throw new IllegalArgumentException(
0465: "You cannot use non-existing field '"
0466: + fieldName + "' of node '"
0467: + getNumber()
0468: + "' existing fields of '"
0469: + getBuilder().getTableName()
0470: + "' are "
0471: + getBuilder().getFieldNames());
0472: } else {
0473: log.warn("Tried to use non-existing field '"
0474: + fieldName + "' of node '" + getNumber()
0475: + "' from " + getBuilder().getTableName());
0476: log.warn(Logging.applicationStacktrace());
0477: return false;
0478: }
0479: }
0480: return true;
0481: }
0482:
0483: /**
0484: * Stores a value in the values hashtable.
0485: * This is a low-level method that circumvents typechecking and the triggers of extended classes.
0486: * You should normally call {@link #setValue} to change fields.
0487: * @todo This should become a synchronized method, once values becomes a private HashMap instead of a
0488: * public Hashtable.
0489: *
0490: *@param fieldName the name of the field to change
0491: *@param fieldValue the value to assign
0492: */
0493: public void storeValue(String fieldName, Object fieldValue) {
0494: if (fieldName.startsWith("_") && fieldValue == null) {
0495: // This is just a hack to make app1 import/export working, withough exposing the values map.
0496: values.remove(fieldName);
0497: }
0498: if (checkFieldExistance(fieldName)) {
0499: values.put(fieldName, fieldValue);
0500: }
0501: }
0502:
0503: /**
0504: * this method stores a fieldvalue only once. the purpose is to
0505: * store the value only the first time a field changes, so it reflects
0506: * the value in the database.
0507: * @param fieldName
0508: * @param object
0509: * @since MMBase-1.8
0510: */
0511: private void storeOldValue(String fieldName, Object object) {
0512: if (!oldValues.containsKey(fieldName)) {
0513: oldValues.put(fieldName, object);
0514: }
0515: }
0516:
0517: /**
0518: * Retrieves a value from the values hashtable.
0519: * This is a low-level method that circumvents typechecking and the triggers of extended classes.
0520: * You should normally call {@link #getValue} to load fields.
0521: *
0522: * @param fieldName the name of the field to change
0523: * @return the value of the field
0524: */
0525: public Object retrieveValue(String fieldName) {
0526: return values.get(fieldName);
0527: }
0528:
0529: /**
0530: * Determines whether the node is virtual.
0531: * A virtual node is not persistent (that is, stored in a database table).
0532: */
0533: public boolean isVirtual() {
0534: return false;
0535: }
0536:
0537: /**
0538: * If a node is still 'new' you must persistify it with {@link #insert}, and otherwise with {@link #commit}.
0539: * @since MMBase-1.8
0540: */
0541: public boolean isNew() {
0542: return isNew;
0543: }
0544:
0545: /*
0546: *
0547: * @since MMBase-1.6
0548: */
0549:
0550: protected Document toXML(Object value, String fieldName) {
0551: Document doc = Casting.toXML(value);
0552: if (doc == null && parent.getField(fieldName).isRequired()) {
0553: doc = Casting.toXML("<p/>");
0554: }
0555: return doc;
0556: }
0557:
0558: /**
0559: * Sets a key/value pair in the main values of this node.
0560: * Note that if this node is a node in cache, the changes are immediately visible to
0561: * everyone, even if the changes are not committed.
0562: * The fieldName is added to the (public) 'changed' vector to track changes.
0563: * @param fieldName the name of the field to change
0564: * @param fieldValue the value to assign
0565: * @return <code>true</code> When the field was changed, false otherwise.
0566: */
0567: public boolean setValue(String fieldName, Object fieldValue) {
0568: // check the value also when the parent thing is null
0569: Object originalValue = values.get(fieldName);
0570:
0571: if (fieldValue != VALUE_SHORTED) {
0572: // make sure this value remains not in the blob-cache.
0573: BlobCache blobs = parent.getBlobCache(fieldName);
0574: blobs.remove(blobs.getKey(getNumber(), fieldName));
0575: }
0576:
0577: if (fieldValue instanceof DynamicDate) {
0578: // 'dynamic' values can of course not be stored in database, and that is not the intentention too, so
0579: // store a static version
0580: fieldValue = new Date(((Date) fieldValue).getTime());
0581: }
0582:
0583: if (log.isDebugEnabled()) {
0584: String string;
0585: if (fieldValue instanceof byte[]) {
0586: string = "byte array of size "
0587: + ((byte[]) fieldValue).length;
0588: } else {
0589: string = Casting.toString(fieldValue);
0590: if (string.length() > 200)
0591: string = string.substring(0, 200);
0592: }
0593: log.debug("Setting " + fieldName + " to " + string);
0594: }
0595:
0596: boolean changed = (!values.containsKey(fieldName))
0597: || (originalValue == null ? fieldValue != null
0598: : !Casting.equals(originalValue, fieldValue));
0599: if (!changed)
0600: return false;
0601:
0602: if (log.isDebugEnabled()) {
0603: log.debug("" + fieldName + ":" + originalValue + " --> "
0604: + fieldValue);
0605: }
0606:
0607: //store the old value
0608: storeOldValue(fieldName, originalValue);
0609:
0610: // put the key/value in the value hashtable
0611: storeValue(fieldName, fieldValue);
0612: if (fieldValue instanceof byte[]) {
0613: setSize(fieldName, ((byte[]) fieldValue).length);
0614: log.debug("Setting length to "
0615: + ((byte[]) fieldValue).length);
0616: }
0617:
0618: // process the changed value (?)
0619: if (parent != null) {
0620: if (!parent.setValue(this , fieldName, originalValue)) {
0621: // setValue of parent returned false, no update needed...
0622: return false;
0623: }
0624: } else {
0625: log.error("parent was null for node with number"
0626: + getNumber());
0627: }
0628: setUpdate(fieldName);
0629: return true;
0630: }
0631:
0632: /**
0633: * Sets the size (in byte) of the given field. This is meant for byte-array fields, which you
0634: * fill using an InputStream.
0635: * @see #getSize(String)
0636: * @since MMBase-1.8
0637: */
0638: public void setSize(String fieldName, long size) {
0639: sizes.put(fieldName, size);
0640: }
0641:
0642: /**
0643: * Returns the size (in byte) of the given field. This is mainly targeted at fields of the type
0644: * byte array. For other fields this method will return something reasonable, but it is as yet
0645: * not well defined what...
0646: *
0647: * @since MMBase-1.8
0648: */
0649: public long getSize(String fieldName) {
0650: Long l = sizes.get(fieldName);
0651: if (l != null)
0652: return l;
0653: Object value = values.get(fieldName);
0654: // Value is null so it does not occupy any space.
0655: if (value == null) {
0656: checkFieldExistance(fieldName);
0657: return 0;
0658: }
0659: // Value is not yet loaded from the database?
0660: if (VALUE_SHORTED.equals(value))
0661: return -1;
0662: return SizeOf.getByteSize(value);
0663: }
0664:
0665: // Add the field to update to the changed Vector
0666: //
0667: private void setUpdate(String fieldName) {
0668: // obtain the type of field this is
0669: int state = getDBState(fieldName);
0670:
0671: // add it to the changed vector so we know that we have to update it
0672: // on the next commit
0673: if (!initializing && state != Field.STATE_VIRTUAL) {
0674: changed.add(fieldName);
0675: }
0676: // is it a memory only field ? then send a fieldchange
0677: if (state == 0)
0678: sendFieldChangeSignal(fieldName);
0679: }
0680:
0681: /**
0682: * Retrieve an object's number.
0683: * In case of a new node that is not committed, this will return -1.
0684: * @return the number of the node
0685: */
0686: public int getNumber() {
0687: return Casting.toInt(values.get(MMObjectBuilder.FIELD_NUMBER));
0688: }
0689:
0690: /**
0691: * Retrieve an object's object type.
0692: * This is a number (an index in the typedef builer), rather than a name.
0693: * @return the object type number of the node
0694: */
0695: public int getOType() {
0696: return Casting.toInt(values
0697: .get(MMObjectBuilder.FIELD_OBJECT_TYPE));
0698: }
0699:
0700: /**
0701: * @since MMBase-1.8
0702: */
0703: public boolean isNull(String fieldName) {
0704: if (checkFieldExistance(fieldName)) {
0705: Field field = getBuilder().getField(fieldName);
0706: if (field.isVirtual()) {
0707: return false;
0708: }
0709: if (field != null && field.getType() == Field.TYPE_NODE) {
0710: return getIntValue(fieldName) <= -1;
0711: }
0712: Object value = values.get(fieldName);
0713: if (VALUE_SHORTED.equals(value)) {
0714: // value is not loaded from the database. We have to check the database to be sure.
0715: value = getValue(fieldName);
0716: }
0717: return value == null;
0718: } else {
0719: return true;
0720: }
0721: }
0722:
0723: /**
0724: * Get a value of a certain field.
0725: * @performance do not store byte values directly in node (?)
0726: * @param fieldName the name of the field who's data to return
0727: * @return the field's value as an <code>Object</code>
0728: */
0729: public Object getValue(String fieldName) {
0730: // get the value from the values table
0731: Object value = values.get(fieldName);
0732:
0733: // explicitly load byte values if they are 'shortened'
0734: if (VALUE_SHORTED.equals(value)) { // could use == if we are sure that everybody uses the constant
0735:
0736: BlobCache blobs = parent.getBlobCache(fieldName);
0737: String key = blobs.getKey(getNumber(), fieldName);
0738: value = blobs.get(key);
0739: if (value == null) {
0740: int type = getDBType(fieldName);
0741: switch (type) {
0742: case Field.TYPE_BINARY:
0743: value = parent.getShortedByte(fieldName, this );
0744: break;
0745: case Field.TYPE_STRING:
0746: value = parent.getShortedText(fieldName, this );
0747: break;
0748: default:
0749: throw new UnsupportedOperationException(
0750: "Found shorted value for type " + type);
0751: }
0752: blobs.put(key, value);
0753: }
0754: }
0755:
0756: // if we have an XML-dbtype field, we always have to return a Document (or null).
0757: // note that if the value is null we store it as a null value
0758: if (parent != null && value != null
0759: && !(value instanceof Document)
0760: && getDBType(fieldName) == Field.TYPE_XML) {
0761: String string = Casting.toString(value).trim();
0762: Document doc = toXML(string, fieldName);
0763: if (doc != null) {
0764: // store the document inside the field.. much faster...
0765: value = doc;
0766: values.put(fieldName, value);
0767: } else {
0768: values.put(fieldName, null);
0769: }
0770: }
0771:
0772: // routine to check for indirect values
0773: // this are used for functions for example
0774: // its implemented per builder so lets give this
0775: // request to our builder
0776: if (value == null) {
0777: value = parent.getValue(this , fieldName);
0778: }
0779: // still null!
0780: if (value == null) {
0781: if (!checkFieldExistance(fieldName))
0782: return null;
0783: }
0784:
0785: if (value instanceof InputStream) {
0786: value = useInputStream(fieldName, (InputStream) value);
0787: }
0788:
0789: // return the found object
0790: return value;
0791: }
0792:
0793: /**
0794: * Get a value of a certain field. The value is returned as a
0795: * String. Non-string values are automatically converted to
0796: * String. 'null' is converted to an empty string.
0797: * @param fieldName the name of the field who's data to return
0798: * @return the field's value as a <code>String</code>
0799: */
0800: public String getStringValue(String fieldName) {
0801: Object value = getValue(fieldName);
0802: if (value instanceof MMObjectNode)
0803: return "" + ((MMObjectNode) value).getNumber();
0804: String s = Casting.toString(value);
0805: return s;
0806: }
0807:
0808: /**
0809: * XXX: return type of this method make it impossible to make MMObjectNode implements Node, perhaps it needs change
0810: * @javadoc
0811: * @since MMBase-1.6
0812: */
0813: public Object getFunctionValue(String functionName,
0814: List<?> parameters) {
0815: return parent.getFunctionValue(this , functionName, parameters);
0816: }
0817:
0818: /**
0819: * @javadoc
0820: * @since MMBase-1.8
0821: */
0822: public Parameters createParameters(String functionName) {
0823: return parent.createParameters(functionName);
0824: }
0825:
0826: /**
0827: * @javadoc
0828: * @since MMBase-1.8
0829: */
0830: public Function<?> getFunction(String functionName) {
0831: return parent.getFunction(this , functionName);
0832: }
0833:
0834: /**
0835: * @javadoc
0836: * @since MMBase-1.8
0837: */
0838: public Collection<Function<?>> getFunctions() {
0839: return parent.getFunctions(this );
0840: }
0841:
0842: /**
0843: * Returns the value of the specified field as a <code>dom.Document</code>
0844: * If the node value is not itself a Document, the method attempts to
0845: * attempts to convert the String value into an XML.
0846: * If the value cannot be converted, this method returns <code>null</code>
0847: *
0848: * @param fieldName the name of the field to be returned
0849: * @return the value of the specified field as a DOM Element or <code>null</code>
0850: * @throws IllegalArgumentException if the value cannot be converted.
0851: * @since MMBase-1.6
0852: */
0853: public Document getXMLValue(String fieldName) {
0854: Document o = toXML(getValue(fieldName), fieldName);
0855: if (o != null && getDBType(fieldName) == Field.TYPE_XML) {
0856: storeValue(fieldName, o);
0857: }
0858: return o;
0859: }
0860:
0861: /**
0862: * If the values map contains an InputStream, care must be taken because often an InputStream can be used only once.
0863: * @since MMBase-1.8
0864: */
0865: private byte[] useInputStream(String fieldName, InputStream stream) { // first, convert to byte-array
0866: ByteArrayOutputStream bos = new ByteArrayOutputStream();
0867: try {
0868: byte[] buf = new byte[1024];
0869: int n;
0870: while ((n = stream.read(buf)) > -1) {
0871: bos.write(buf, 0, n);
0872: }
0873: } catch (IOException ioe) {
0874: log.error(ioe);
0875: }
0876: byte[] b = bos.toByteArray();
0877: // check if we can cache it.
0878: BlobCache blobs = parent.getBlobCache(fieldName);
0879: String key = blobs.getKey(getNumber(), fieldName);
0880: if (b.length < blobs.getMaxEntrySize()) {
0881: blobs.put(key, b);
0882: }
0883: setSize(fieldName, b.length);
0884: values.put(fieldName, b);
0885: return b;
0886: }
0887:
0888: /**
0889: * Get a binary value of a certain field.
0890: * @performance do not store byte values directly in node (?)
0891: * @param fieldName the name of the field who's data to return
0892: * @return the field's value as an <code>byte []</code> (binary/blob field)
0893: */
0894: public byte[] getByteValue(String fieldName) {
0895: Object obj = getValue(fieldName);
0896: if (obj == null) {
0897: return new byte[0];
0898: } else if (obj instanceof byte[]) {
0899: // was already unmapped so return the value
0900: return (byte[]) obj;
0901: } else {
0902: byte[] b;
0903: if (getDBType(fieldName) == Field.TYPE_STRING) {
0904: String s = getStringValue(fieldName);
0905: try {
0906: b = s.getBytes(parent.getMMBase().getEncoding());
0907: } catch (UnsupportedEncodingException uee) {
0908: log.error(uee.getMessage());
0909: b = s.getBytes();
0910: }
0911: } else {
0912: b = new byte[0];
0913: }
0914: return b;
0915: }
0916: }
0917:
0918: public InputStream getInputStreamValue(String fieldName) {
0919: Object value = getValue(fieldName);
0920: if (value == null) {
0921: checkFieldExistance(fieldName);
0922: log.debug("NULL on " + fieldName + " " + this ,
0923: new Exception());
0924: return new ByteArrayInputStream(new byte[0]);
0925: } else {
0926: if (log.isTraceEnabled()) {
0927: log.trace("Found " + value);
0928: }
0929: }
0930:
0931: if (value instanceof InputStream) {
0932: // cannot return it directly, it would kill the inputstream, and perhaps it cannot be saved in db anymore then.
0933: // Sad, we have a buffer always now.
0934: // XXX think of something that the buffer is only needed if actually used a second time
0935: // help-file, i think
0936: return new ByteArrayInputStream(useInputStream(fieldName,
0937: (InputStream) value));
0938: }
0939:
0940: if (VALUE_SHORTED.equals(value)) {
0941: BlobCache blobs = parent.getBlobCache(fieldName);
0942: String key = blobs.getKey(getNumber(), fieldName);
0943: byte[] v = (byte[]) blobs.get(key);
0944: if (v == null) {
0945: if (getSize(fieldName) < blobs.getMaxEntrySize()) {
0946: v = parent.mmb.getStorageManager().getBinaryValue(
0947: this , parent.getField(fieldName));
0948: if (log.isDebugEnabled()) {
0949: log.debug("Putting in blob cache " + key);
0950: }
0951: blobs.put(key, v);
0952: } else {
0953: log
0954: .debug("Too big for cache, requesting InputStream directly from storage");
0955: return parent.mmb.getStorageManager()
0956: .getInputStreamValue(this ,
0957: parent.getField(fieldName));
0958: }
0959: } else {
0960: log.debug("Found in blob cache " + fieldName);
0961: }
0962: return new ByteArrayInputStream(v);
0963: } else {
0964: if (value instanceof byte[]) {
0965: return new ByteArrayInputStream((byte[]) value);
0966: } else {
0967: // probably not a byte-array field, do something.
0968: // this behavior is undefined!, don't depend on it.
0969: return new ByteArrayInputStream(("" + value).getBytes());
0970: }
0971: }
0972: }
0973:
0974: /**
0975: * Get a value of a certain field.
0976: * The value is returned as an MMObjectNode.
0977: * If the field contains an Numeric value, the method
0978: * tries to obtrain the object with that number.
0979: * If it is a String, the method tries to obtain the object with
0980: * that alias. The only other possible values are those created by
0981: * certain virtual fields.
0982: * All remaining situations return <code>null</code>.
0983: * @param fieldName the name of the field who's data to return
0984: * @return the field's value as an <code>int</code>
0985: */
0986: public MMObjectNode getNodeValue(String fieldName) {
0987: if (fieldName == null
0988: || fieldName.equals(MMObjectBuilder.FIELD_NUMBER))
0989: return this ;
0990: Object value = getValue(fieldName);
0991: MMObjectNode res = null;
0992: if (value instanceof MMObjectNode) {
0993: res = (MMObjectNode) value;
0994: } else if (value instanceof Node) {
0995: Node node = (Node) value;
0996: if (node.isNew()) {
0997: throw new UnsupportedOperationException(
0998: "dropped tmpnodemanager...");
0999: } else if (value instanceof org.mmbase.bridge.implementation.VirtualNode) {
1000: res = new VirtualNode(
1001: new org.mmbase.bridge.util.NodeMap(node));
1002: } else {
1003: res = parent.getNode(node.getNumber());
1004: }
1005: } else if (value instanceof Number) {
1006: int nodenumber = ((Number) value).intValue();
1007: if (nodenumber != -1) {
1008: res = parent.getNode(nodenumber);
1009: }
1010: } else if (value != null && !value.equals("")) {
1011: res = parent.getNode(value.toString());
1012: }
1013: return res;
1014: }
1015:
1016: /**
1017: * Get a value of a certain field.
1018: * The value is returned as an int value. Values of non-int, numeric fields are converted if possible.
1019: * Booelan fields return 0 for false, 1 for true.
1020: * String fields are parsed to a number, if possible.
1021: * If a value is an MMObjectNode, its numberfield is returned.
1022: * All remaining field values return -1.
1023: * @param fieldName the name of the field who's data to return
1024: * @return the field's value as an <code>int</code>
1025: */
1026: public int getIntValue(String fieldName) {
1027: Object value = getValue(fieldName);
1028: if (value instanceof MMObjectNode)
1029: return ((MMObjectNode) value).getNumber();
1030: return Casting.toInt(value);
1031: }
1032:
1033: /**
1034: * Get a value of a certain field.
1035: * The value is returned as an boolean value.
1036: * If the actual value is numeric, this call returns <code>true</code>
1037: * if the value is a positive, non-zero, value. In other words, values '0'
1038: * and '-1' are concidered <code>false</code>.
1039: * If the value is a string, this call returns <code>true</code> if
1040: * the value is "true" or "yes" (case-insensitive).
1041: * In all other cases (including calling byte fields), <code>false</code>
1042: * is returned.
1043: * Note that there is currently no basic MMBase boolean type, but some
1044: * <code>excecuteFunction</code> calls may return a Boolean result.
1045: *
1046: * @param fieldName the name of the field who's data to return
1047: * @return the field's value as an <code>int</code>
1048: */
1049: public boolean getBooleanValue(String fieldName) {
1050: return Casting.toBoolean(getValue(fieldName));
1051: }
1052:
1053: /**
1054: * Get a value of a certain field.
1055: * The value is returned as an Integer value. Values of non-Integer, numeric fields are converted if possible.
1056: * Boolean fields return 0 for false, 1 for true.
1057: * String fields are parsed to a number, if possible.
1058: * All remaining field values return -1.
1059: * @param fieldName the name of the field who's data to return
1060: * @return the field's value as an <code>Integer</code>
1061: */
1062: public Integer getIntegerValue(String fieldName) {
1063: Object value = getValue(fieldName);
1064: if (value instanceof MMObjectNode)
1065: return ((MMObjectNode) value).getNumber();
1066: return Casting.toInteger(value);
1067: }
1068:
1069: /**
1070: * Get a value of a certain field.
1071: * @see #getValue
1072: * @see Casting#toLong
1073: * @param fieldName the name of the field who's data to return
1074: * @return the field's value as a <code>long</code>
1075: */
1076: public long getLongValue(String fieldName) {
1077: Object value = getValue(fieldName);
1078: if (value instanceof MMObjectNode)
1079: return ((MMObjectNode) value).getNumber();
1080: return Casting.toLong(value);
1081: }
1082:
1083: /**
1084: * Get a value of a certain field.
1085: * The value is returned as a float value. Values of non-float, numeric fields are converted if possible.
1086: * Boolean fields return 0 for false, 1 for true.
1087: * String fields are parsed to a number, if possible.
1088: * All remaining field values return -1.
1089: * @param fieldName the name of the field who's data to return
1090: * @return the field's value as a <code>float</code>
1091: */
1092: public float getFloatValue(String fieldName) {
1093: Object value = getValue(fieldName);
1094: if (value instanceof MMObjectNode)
1095: return ((MMObjectNode) value).getNumber();
1096: return Casting.toFloat(value);
1097: }
1098:
1099: /**
1100: * Get a value of a certain field.
1101: * The value is returned as a double value. Values of non-double, numeric fields are converted if possible.
1102: * Boolean fields return 0 for false, 1 for true.
1103: * String fields are parsed to a number, if possible.
1104: * All remaining field values return -1.
1105: * @param fieldName the name of the field who's data to return
1106: * @return the field's value as a <code>double</code>
1107: */
1108: public double getDoubleValue(String fieldName) {
1109: Object value = getValue(fieldName);
1110: if (value instanceof MMObjectNode)
1111: return ((MMObjectNode) value).getNumber();
1112: return Casting.toDouble(value);
1113: }
1114:
1115: /**
1116: * Get a value of a certain field.
1117: * The value is returned as a Date value. Values of numeric fields are converted as if they were
1118: * time in seconds since 1/1/1970.
1119: * String fields are parsed to a date, if possible.
1120: * All remaining field values return -1.
1121: * @since MMBase-1.8
1122: * @param fieldName the name of the field who's data to return
1123: * @return the field's value as a <code>Date</code>
1124: */
1125: public Date getDateValue(String fieldName) {
1126: Object value = getValue(fieldName);
1127: org.mmbase.core.CoreField cf = getBuilder().getField(fieldName);
1128: if (cf != null && cf.getType() == Field.TYPE_NODE) {
1129: // cannot be handled by casting, because it would receive object-number and cannot make distinction with Nodes.
1130: return new Date(-1);
1131: }
1132: return Casting.toDate(value);
1133: }
1134:
1135: /**
1136: * Get a value of a certain field.
1137: * The value is returned as a List value.
1138: * Strings are treated as comma-seperated value lists, and split into their component parts.
1139: * Values of other fields are returned as Lists of one object.
1140: * @since MMBase-1.8
1141: * @param fieldName the name of the field who's data to return
1142: * @return the field's value as a <code>List</code>
1143: */
1144: public List getListValue(String fieldName) {
1145: return Casting.toList(getValue(fieldName));
1146: }
1147:
1148: /**
1149: * Returns the DBType of a field.
1150: * @param fieldName the name of the field which' type to return
1151: * @return the field's DBType
1152: */
1153: public int getDBType(String fieldName) {
1154: return parent.getDBType(fieldName);
1155: }
1156:
1157: /**
1158: * Returns the DBState of a field.
1159: * @param fieldName the name of the field who's state to return
1160: * @return the field's DBState
1161: */
1162: public int getDBState(String fieldName) {
1163: if (parent != null) {
1164: return parent.getDBState(fieldName);
1165: } else {
1166: return Field.STATE_UNKNOWN;
1167: }
1168: }
1169:
1170: /**
1171: * Return the names of all persistent fields that were changed.
1172: * Note that this is a direct reference. Changes (i.e. clearing the vector) will affect the node's status.
1173: * @return A Set containing Strings. The set is modifiable, and synchronized. Don't modify it though.
1174: */
1175: public Set<String> getChanged() {
1176: return changed;
1177: }
1178:
1179: /**
1180: * Tests whether one of the values of this node was changed since the last commit/insert.
1181: * @return <code>true</code> if changes have been made, <code>false</code> otherwise
1182: */
1183: public boolean isChanged() {
1184: return newContext != null || changed.size() > 0;
1185: }
1186:
1187: /**
1188: * Clear the 'signal' Vector with the changed keys since last commit/insert.
1189: * Marks the node as 'unchanged'.
1190: * Does not affect the values of the fields, nor does it commit the node.
1191: * @return always <code>true</code>
1192: */
1193: public boolean clearChanged() {
1194: changed.clear();
1195: oldValues.clear();
1196: return true;
1197: }
1198:
1199: /**
1200: * Deletes the propertie cache for this node.
1201: * Forces a reload of the properties on next use.
1202: */
1203: public void delPropertiesCache() {
1204: synchronized (properties_sync) {
1205: properties = null;
1206: }
1207: }
1208:
1209: public Map<String, Object> getValues() {
1210: return Collections.unmodifiableMap(values);
1211: }
1212:
1213: /**
1214: * @since MMBase-1.8
1215: */
1216: public Map<String, Object> getOldValues() {
1217: return Collections.unmodifiableMap(oldValues);
1218: }
1219:
1220: /**
1221: * Return a the properties for this node.
1222: * @return the properties as a <code>Hashtable</code>
1223: */
1224: public Hashtable<String, MMObjectNode> getProperties() {
1225: synchronized (properties_sync) {
1226: if (properties == null) {
1227: properties = new Hashtable<String, MMObjectNode>();
1228: MMObjectBuilder bul = parent.mmb
1229: .getMMObject("properties");
1230: Enumeration<MMObjectNode> e = bul.search("parent=="
1231: + getNumber());
1232: while (e.hasMoreElements()) {
1233: MMObjectNode pnode = e.nextElement();
1234: String key = pnode.getStringValue("key");
1235: properties.put(key, pnode);
1236: }
1237: }
1238: }
1239: return properties;
1240: }
1241:
1242: /**
1243: * Returns a specified property of this node.
1244: * @param key the name of the property to retrieve
1245: * @return the property object as a <code>MMObjectNode</code>
1246: */
1247: public MMObjectNode getProperty(String key) {
1248: MMObjectNode n;
1249: synchronized (properties_sync) {
1250: if (properties == null) {
1251: getProperties();
1252: }
1253: n = properties.get(key);
1254: }
1255: if (n != null) {
1256: return n;
1257: } else {
1258: return null;
1259: }
1260: }
1261:
1262: /**
1263: * Sets a specified property for this node.
1264: * This method does not commit anything - it merely updates the node's propertylist.
1265: * @param node the property object as a <code>MMObjectNode</code>
1266: */
1267: public void putProperty(MMObjectNode node) {
1268: synchronized (properties_sync) {
1269: if (properties == null) {
1270: getProperties();
1271: }
1272: properties.put(node.getStringValue("key"), node);
1273: }
1274: }
1275:
1276: /**
1277: * Return the GUI indicator for this node.
1278: * The GUI indicator is a string that represents the contents of this node.
1279: * By default it is the string-representation of the first non-system field of the node.
1280: * Individual builders can alter this behavior.
1281: * @return the GUI iddicator as a <code>String</code>
1282: */
1283: public String getGUIIndicator() {
1284: if (parent != null) {
1285: return parent.getGUIIndicator(this );
1286: } else {
1287: log.error("MMObjectNode -> can't get parent");
1288: return "problem";
1289: }
1290: }
1291:
1292: /**
1293: * Return the buildername of this node
1294: * @return the builder table name
1295: */
1296: public String getName() {
1297: return parent.getTableName();
1298: }
1299:
1300: /**
1301: * Delete the relation cache for this node.
1302: * This means it will be reloaded from the database/storage on next use.
1303: */
1304: public void delRelationsCache() {
1305: delRelationsCache(getNumber());
1306: }
1307:
1308: /**
1309: * Delete the relation cache for this node.
1310: * This means it will be reloaded from the database/storage on next use.
1311: * @param number nodenumber
1312: */
1313: public static void delRelationsCache(Integer number) {
1314: RelationsCache.getCache().remove(number);
1315: }
1316:
1317: /**
1318: * Returns whether this node has relations.
1319: * This includes unidirection relations which would otherwise not be counted.
1320: * @return <code>true</code> if any relations exist, <code>false</code> otherwise.
1321: */
1322: public boolean hasRelations() {
1323: // return getRelationCount()>0;
1324: return parent.mmb.getInsRel().hasRelations(getNumber());
1325: }
1326:
1327: /**
1328: * Return all the relations of this node.
1329: * Use only to delete the relations of a node.
1330: * Note that this returns the nodes describing the relation - not the nodes 'related to'.
1331: * @return An <code>Enumeration</code> containing the nodes
1332: */
1333: public Enumeration<MMObjectNode> getAllRelations() {
1334: Vector<MMObjectNode> allrelations = parent.mmb.getInsRel()
1335: .getAllRelationsVector(getNumber());
1336: if (allrelations != null) {
1337: return allrelations.elements();
1338: } else {
1339: return null;
1340: }
1341: }
1342:
1343: /**
1344: * Return the relations of this node.
1345: * Note that this returns the nodes describing the relation - not the nodes 'related to'.
1346: *
1347: *
1348: * XXX: return type of this method makes it impossible to make MMObjectNode implements Node, perhaps it needs change
1349: *
1350: * @return An <code>Enumeration</code> containing the nodes
1351: */
1352: public Enumeration<MMObjectNode> getRelations() {
1353: List<MMObjectNode> relations = getRelationNodes();
1354: if (relations != null) {
1355: return Collections.enumeration(relations);
1356: } else {
1357: return null;
1358: }
1359: }
1360:
1361: /**
1362: * @since MMBase-1.7
1363: * @scope public?
1364: */
1365: protected List<MMObjectNode> getRelationNodes() {
1366: Integer number = Integer.valueOf(getNumber());
1367: List<MMObjectNode> relations;
1368: RelationsCache relationsCache = RelationsCache.getCache();
1369: if (!relationsCache.contains(number)) {
1370: relations = parent.getRelations_main(getNumber());
1371: relationsCache.put(number, relations);
1372:
1373: } else {
1374: relations = relationsCache.get(number);
1375: }
1376: return relations;
1377: }
1378:
1379: /**
1380: * Remove the relations of the node.
1381: */
1382: public void removeRelations() {
1383: parent.removeRelations(this );
1384: }
1385:
1386: /**
1387: * Returns the number of relations of this node.
1388: * @return An <code>int</code> indicating the number of nodes found
1389: */
1390: public int getRelationCount() {
1391: List<MMObjectNode> relations = getRelationNodes();
1392: if (relations != null) {
1393: return relations.size();
1394: } else {
1395: return 0;
1396: }
1397: }
1398:
1399: /**
1400: * Return the relations of this node, filtered on a specified type.
1401: * Note that this returns the nodes describing the relation - not the nodes 'related to'.
1402: * @param otype the 'type' of relations to return. The type identifies a relation (InsRel-derived) builder, not a reldef object.
1403: * @return An <code>Enumeration</code> containing the nodes
1404: */
1405: public Enumeration<MMObjectNode> getRelations(int otype) {
1406: Enumeration<MMObjectNode> e = getRelations();
1407: Vector<MMObjectNode> result = new Vector<MMObjectNode>();
1408: if (e != null) {
1409: while (e.hasMoreElements()) {
1410: MMObjectNode tnode = e.nextElement();
1411: if (tnode.getOType() == otype) {
1412: result.addElement(tnode);
1413: }
1414: }
1415: }
1416: return result.elements();
1417: }
1418:
1419: /**
1420: * Return the relations of this node, filtered on a specified type.
1421: * Note that this returns the nodes describing the relation - not the nodes 'related to'.
1422: * @param wantedtype the 'type' of relations to return. The type identifies a relation (InsRel-derived) builder, not a reldef object.
1423: * @return An <code>Enumeration</code> containing the nodes
1424: */
1425: public Enumeration<MMObjectNode> getRelations(String wantedtype) {
1426: int otype = parent.mmb.getTypeDef().getIntValue(wantedtype);
1427: if (otype != -1) {
1428: return getRelations(otype);
1429: }
1430: return null;
1431: }
1432:
1433: /**
1434: * Return the number of relations of this node, filtered on a specified type.
1435: * @param wt the 'type' of related nodes (NOT the relations!).
1436: * @return An <code>int</code> indicating the number of nodes found
1437: */
1438: public int getRelationCount(String wt) {
1439: int count = 0;
1440: MMObjectBuilder wantedType = parent.mmb.getBuilder(wt);
1441: if (wantedType != null) {
1442: List<MMObjectNode> relations = getRelationNodes();
1443: if (relations != null) {
1444: for (Enumeration<MMObjectNode> e = Collections
1445: .enumeration(relations); e.hasMoreElements();) {
1446: MMObjectNode tnode = e.nextElement();
1447: int relation_number = tnode.getIntValue("snumber");
1448: int nodetype = 0;
1449:
1450: // bugfix #6432: marcel: determine source of relation, get type, display
1451: // error when nodetype is determined to be -1, which is a possible wrongly inserted relation
1452:
1453: if (relation_number == getNumber()) {
1454: relation_number = tnode.getIntValue("dnumber");
1455: nodetype = parent.getNodeType(relation_number);
1456: } else {
1457: nodetype = parent.getNodeType(relation_number);
1458: }
1459:
1460: // Display situation where snumber or dnumber from a relation-node does not seem to
1461: // exsist in the database. This can be fixed by mannually removing the node out of the insrel-table
1462: if (nodetype == -1) {
1463: log
1464: .warn("Warning: relation_node("
1465: + tnode.getNumber()
1466: + ") has a possible removed relation_number("
1467: + relation_number
1468: + "), manually check its consistency!");
1469: }
1470:
1471: MMObjectBuilder nodeType = parent.mmb
1472: .getBuilder(parent.mmb.getTypeDef()
1473: .getValue(nodetype));
1474: if (nodeType != null
1475: && (nodeType.equals(wantedType) || nodeType
1476: .isExtensionOf(wantedType))) {
1477: count++;
1478: }
1479: }
1480: }
1481: } else {
1482: log
1483: .warn("getRelationCount is requested with an invalid Builder name (otype "
1484: + wt + " does not exist)");
1485: }
1486: return count;
1487: }
1488:
1489: /**
1490: * Return the age of the node, determined using the daymarks builder.
1491: * @return the age in days, or 0 if unknown (daymarks builder not present)
1492: */
1493: public int getAge() {
1494: DayMarkers dayMarkers = ((DayMarkers) parent.mmb
1495: .getBuilder("daymarks"));
1496: if (dayMarkers == null)
1497: return 0;
1498: return dayMarkers.getAge(getNumber());
1499: }
1500:
1501: /**
1502: * Sends a field-changed signal.
1503: * @param fieldName the name of the changed field.
1504: * @return always <code>true</code>
1505: */
1506: public boolean sendFieldChangeSignal(String fieldName) {
1507: return parent.sendFieldChangeSignal(this , fieldName);
1508: }
1509:
1510: /**
1511: * Sets the node's alias.
1512: * The code only sets a (memory) property, it does not actually add the alias to the database.
1513: * Only works for uninserted Nodes. So this is actually only used for application import.
1514: * No need to use this. Use {@link MMObjectBuilder#createAlias}.
1515: */
1516: public void setAlias(String alias) {
1517: if (aliases == null)
1518: aliases = new HashSet<String>();
1519: synchronized (aliases) {
1520: aliases.add(alias);
1521: }
1522: }
1523:
1524: /**
1525: * Returns the node's alias.
1526: * Does not support multiple aliases.
1527: */
1528: void useAliases() {
1529: if (aliases != null) {
1530: synchronized (aliases) {
1531: if (getNumber() <= 0) {
1532: log
1533: .error("Trying to set aliases for uncommited node!!");
1534: return;
1535: }
1536: for (String alias : aliases) {
1537: try {
1538: parent.createAlias(getNumber(), alias,
1539: getStringValue("owner"));
1540: } catch (org.mmbase.storage.StorageException se) {
1541: log.error(se);
1542: }
1543: }
1544: aliases.clear();
1545: }
1546: }
1547: }
1548:
1549: /**
1550: * Get all related nodes. The returned nodes are not the
1551: * nodes directly attached to this node (the relation nodes) but the nodes
1552: * attached to the relation nodes of this node.
1553: *
1554: * XXX: return type of this method make it impossible to make MMObjectNode implements Node, perhaps it needs change
1555: *
1556: * @return a <code>Vector</code> containing <code>MMObjectNode</code>s
1557: */
1558: public Vector<MMObjectNode> getRelatedNodes() {
1559: return getRelatedNodes("object", null,
1560: RelationStep.DIRECTIONS_EITHER);
1561: }
1562:
1563: /**
1564: * Makes number -> MMObjectNode of a list of MMObjectNodes.
1565: * @since MMBase-1.6.2
1566: */
1567: private Map<Integer, MMObjectNode> makeMap(List<MMObjectNode> v) {
1568: Map<Integer, MMObjectNode> result = new HashMap<Integer, MMObjectNode>();
1569: for (MMObjectNode node : v) {
1570: result.put(node.getNumber(), node);
1571: }
1572: return result;
1573: }
1574:
1575: /**
1576: * Get the related nodes of a certain type. The returned nodes are not the
1577: * nodes directly attached to this node (the relation nodes) but the nodes
1578: * attached to the relation nodes of this
1579: *
1580: * XXX: return type of this method make it impossible to make MMObjectNode implements Node, perhaps it needs change
1581: *
1582: * @param type the type of objects to be returned
1583: * @return a <code>Vector</code> containing <code>MMObjectNode</code>s
1584: */
1585: public Vector<MMObjectNode> getRelatedNodes(String type) {
1586: if (log.isDebugEnabled()) {
1587: log.debug("Getting related nodes of " + this + " of type "
1588: + type);
1589: }
1590:
1591: if (InsRel.usesdir) {
1592: return getRelatedNodes(type, RelationStep.DIRECTIONS_BOTH);
1593: } else {
1594: //
1595: // determine related nodes
1596: Map<Integer, MMObjectNode> source = makeMap(getRelatedNodes(
1597: type, RelationStep.DIRECTIONS_SOURCE));
1598: Map<Integer, MMObjectNode> destin = makeMap(getRelatedNodes(
1599: type, RelationStep.DIRECTIONS_DESTINATION));
1600:
1601: if (log.isDebugEnabled()) {
1602: log.debug("source(" + source.size() + ") - destin("
1603: + destin.size() + ")");
1604: }
1605: // remove duplicates (can happen if multirel is being used when no dir on insrel exists)
1606: destin.putAll(source);
1607: return new Vector<MMObjectNode>(destin.values());
1608: }
1609: }
1610:
1611: /**
1612: * If you query from this_node_type(type) (typex, insrel, typey where typex == typey) {
1613: * if the insrel table is directional, use the multirelations.SEARCH_BOTH
1614: * if the insrel table is not directional, use the multirelations.SEARCH_SOURCE + multirelations.SEARCH_DESTINATION
1615: * }
1616: * Otherwise the SEARCH_BOTH will result in an OR on insrel which will never return in
1617: * (huge) databases.
1618: * @param type the type of teh realted node to return
1619: * @param search_type the type of directionality to use
1620: * @since MMBase-1.6.3
1621: */
1622: public Vector<MMObjectNode> getRelatedNodes(String type,
1623: int search_type) {
1624: return getRelatedNodes(type, "insrel", search_type);
1625: }
1626:
1627: /**
1628: * If you query from this_node_type(type) (typex, insrel, typey where typex == typey) {
1629: * if the insrel table is directional, use the multirelations.SEARCH_BOTH
1630: * if the insrel table is not directional, use the multirelations.SEARCH_SOURCE + multirelations.SEARCH_DESTINATION
1631: * }
1632: * Otherwise the SEARCH_BOTH will result in an OR on insrel which will never return in
1633: * (huge) databases.
1634: * @param type the type of teh realted node to return
1635: * @param role the role of the relation (null if no role specified)
1636: * @param search_type the type of directionality to use
1637: * @since MMBase-1.6.3
1638: */
1639: public Vector<MMObjectNode> getRelatedNodes(String type,
1640: String role, int search_type) {
1641: Vector<MMObjectNode> result = null;
1642:
1643: MMObjectBuilder builder = parent.mmb.getBuilder(type);
1644:
1645: // example: we want a thisnode.relatedNodes(mediaparts) where mediaparts are of type
1646: // audioparts and videoparts. This method will return the real nodes (thus of type audio/videoparts)
1647: // when asked to get nodes of type mediaparts.
1648: //
1649: // - get a list of virtual nodes from a multilevel("this.parent.name, type") ordered on otype
1650: // (this will return virtual audio- and/or videoparts ordered on their *real* parent)
1651: // - construct a list of nodes for each parentbuilder seperately
1652: // - ask the parentbuilder for each list of virtual nodes to get a list of the real nodes
1653: if (builder != null) {
1654:
1655: ClusterBuilder clusterBuilder = parent.mmb
1656: .getClusterBuilder();
1657:
1658: // multilevel from table this.parent.name -> type
1659: List<String> tables = new ArrayList<String>();
1660: tables.add(parent.getTableName() + "1");
1661: if (role != null) {
1662: tables.add(role);
1663: }
1664: tables.add(type + "2");
1665:
1666: // return type.number (and otype for sorting)
1667: List<String> fields = new ArrayList<String>();
1668: fields.add(type + "2.number");
1669: fields.add(type + "2.otype");
1670:
1671: // order list UP
1672: List<String> directions = new ArrayList<String>();
1673: directions.add("UP");
1674:
1675: // and order on otype
1676: List<String> ordered = new ArrayList<String>();
1677: ordered.add(type + "2.otype");
1678:
1679: List<String> snodes = new ArrayList<String>();
1680: snodes.add("" + getNumber());
1681:
1682: SearchQuery query = clusterBuilder
1683: .getMultiLevelSearchQuery(snodes, fields, "NO",
1684: tables, null, ordered, directions,
1685: search_type);
1686: RelatedNodesCache relatedCache = RelatedNodesCache
1687: .getCache();
1688: List<MMObjectNode> v = relatedCache.get(query);
1689: if (v == null) {
1690: try {
1691: v = clusterBuilder.getClusterNodes(query);
1692: relatedCache.put(query, v);
1693: } catch (SearchQueryException sqe) {
1694: log.error(sqe.toString());
1695: v = null;
1696: }
1697: }
1698: if (v == null) {
1699: result = new Vector<MMObjectNode>();
1700: } else {
1701: result = new Vector<MMObjectNode>(getRealNodes(v, type
1702: + "2"));
1703: }
1704: } else {
1705: log.error("This type(" + type
1706: + ") is not a valid buildername!");
1707: result = new Vector<MMObjectNode>(); // return empty vector
1708: }
1709:
1710: if (log.isDebugEnabled()) {
1711: log.debug("related(" + parent.getTableName() + "("
1712: + getNumber() + ")) -> " + type + " = size("
1713: + result.size() + ")");
1714: }
1715:
1716: return result;
1717: }
1718:
1719: /**
1720: * Loop through the virtuals vector, group all same nodes based on parent and fetch the real nodes from those parents
1721: *
1722: * @param List of virtual nodes (only type.number and type.otype fields are set)
1723: * @param type, needed to retreive the otype, which is set in node as type + ".otype"
1724: * @returns List of real nodes
1725: *
1726: * @see getRelatedNodes(String type)
1727: * @since MMBase-1.6.2
1728: */
1729: private List<MMObjectNode> getRealNodes(
1730: List<MMObjectNode> virtuals, String type) {
1731:
1732: log.debug("Getting real nodes");
1733: List<MMObjectNode> result = new ArrayList<MMObjectNode>();
1734:
1735: List<MMObjectNode> list = new ArrayList<MMObjectNode>();
1736: int ootype = -1;
1737: List<Integer> virtualNumbers = new ArrayList<Integer>();
1738:
1739: // fill the list
1740: Iterator<MMObjectNode> i = virtuals.iterator();
1741: while (i.hasNext()) {
1742: MMObjectNode node = i.next();
1743: Integer number = node.getIntegerValue(type + ".number");
1744: if (!virtualNumbers.contains(number)) {
1745: virtualNumbers.add(number);
1746:
1747: int otype = node.getIntValue(type + ".otype");
1748:
1749: // convert the nodes of type ootype to real numbers
1750: if (otype != ootype) {
1751: // if we have nodes return real values
1752: if (ootype != -1) {
1753: result.addAll(getRealNodesFromBuilder(list,
1754: ootype));
1755: list = new ArrayList<MMObjectNode>();
1756: }
1757: ootype = otype;
1758: }
1759: // convert current node type.number and type.otype to number and otype
1760: String builderName = parent.mmb.getTypeDef().getValue(
1761: otype);
1762: if (builderName == null) {
1763: log.warn("Could not find builder of node "
1764: + node.getNumber() + " taking 'object'");
1765: builderName = "object";
1766: otype = parent.mmb.getBuilder(builderName)
1767: .getObjectType();
1768: }
1769:
1770: MMObjectNode convert = new MMObjectNode(parent.mmb
1771: .getBuilder(builderName), false);
1772: // parent needs to be set or else mmbase does nag nag nag on a setValue()
1773: convert.setValue(MMObjectBuilder.FIELD_NUMBER, node
1774: .getValue(type + ".number"));
1775: convert.setValue(MMObjectBuilder.FIELD_OBJECT_TYPE,
1776: ootype);
1777: list.add(convert);
1778: }
1779: // first and only list or last list, return real values
1780: if (!i.hasNext()) {
1781: // log.debug("subconverting last "+list.size()+" nodes of type("+otype+")");
1782: result.addAll(getRealNodesFromBuilder(list, ootype));
1783: }
1784: }
1785:
1786: // check that we didnt loose any nodes
1787: if (virtualNumbers.size() != result.size()) {
1788: log
1789: .error("We lost a few nodes during conversion from virtualnodes("
1790: + virtuals.size()
1791: + ") to realnodes("
1792: + result.size() + ")");
1793: StringBuffer vNumbers = new StringBuffer();
1794: for (int j = 0; j < virtualNumbers.size(); j++) {
1795: vNumbers.append(virtualNumbers.get(j)).append(" ");
1796: }
1797: log.error("Virtual node numbers: " + vNumbers.toString());
1798: StringBuffer rNumbers = new StringBuffer();
1799: for (int j = 0; j < result.size(); j++) {
1800: int resultNumber = (result.get(j))
1801: .getIntValue("number");
1802: rNumbers.append(resultNumber).append(" ");
1803: }
1804: log.error("Real node numbers: " + rNumbers.toString());
1805: }
1806:
1807: return result;
1808: }
1809:
1810: /**
1811: * Upgrade a certain list of MMObectNodes to the right type.
1812: * @since MMBase-1.6.2
1813: */
1814: private List<MMObjectNode> getRealNodesFromBuilder(
1815: List<MMObjectNode> list, int otype) {
1816: List<MMObjectNode> result = new ArrayList<MMObjectNode>();
1817: String name = parent.mmb.getTypeDef().getValue(otype);
1818: if (name != null) {
1819: MMObjectBuilder rparent = parent.mmb.getBuilder(name);
1820: if (rparent != null) {
1821: result.addAll(rparent.getNodes(list));
1822: } else {
1823: log.error("This otype(" + otype
1824: + ") does not denote a valid typedef-name("
1825: + name + ")!");
1826: }
1827: } else {
1828: log.error("This otype(" + otype
1829: + ") gives no name from typedef!");
1830: }
1831: return result;
1832: }
1833:
1834: public int getByteSize() {
1835: return getByteSize(new SizeOf());
1836: }
1837:
1838: public int getByteSize(SizeOf sizeof) {
1839: return sizeof.sizeof(values) + sizeof.sizeof(oldValues)
1840: + sizeof.sizeof(sizes) + sizeof.sizeof(properties)
1841: + sizeof.sizeof(changed) + 12 * SizeOf.SZ_REF;
1842: }
1843:
1844: /**
1845: * @since MMBase-1.6.2
1846: */
1847: public int hashCode() {
1848: if (parent != null) {
1849: return parent.hashCode(this );
1850: } else {
1851: return super .hashCode();
1852: }
1853: }
1854:
1855: /**
1856: * @since MMBase-1.6.2
1857: */
1858: public boolean equals(Object o) {
1859: if (o instanceof MMObjectNode) {
1860: MMObjectNode n = (MMObjectNode) o;
1861: if (parent != null) {
1862: return parent.equals(this , n);
1863: } else {
1864: return defaultEquals(n);
1865: }
1866: }
1867: return false;
1868: }
1869:
1870: /**
1871: * @since MMBase-1.6.2
1872: */
1873: public boolean defaultEquals(MMObjectNode n) {
1874: /*
1875: if (getNumber() >= 0) { // we know when real nodes are equal
1876: return n.getNumber() == getNumber();
1877: } else { // I don't know about others
1878: return super.equals(n); // compare as objects.
1879: }
1880: */
1881: return super .equals(n); // compare as objects.
1882: }
1883:
1884: /**
1885: * Custom serialize method for MMObjectNode. The main reason this method exists is
1886: * that the builder for an object will not be serialized, but the tablename for
1887: * the object will be saved instead. During deserialization the builder will
1888: * be recovered using that name.
1889: * @since MMBase-1.8.0
1890: */
1891: private void writeObject(java.io.ObjectOutputStream out)
1892: throws IOException {
1893: out.writeObject(oldValues);
1894: out.writeObject(values);
1895: out.writeObject(sizes);
1896: out.writeBoolean(initializing);
1897: out.writeObject(properties);
1898: out.writeObject(changed);
1899:
1900: // Save parent and builder by name, not by object
1901: if (parent == null) {
1902: out.writeObject(null);
1903: } else {
1904: out.writeObject(parent.getTableName());
1905: }
1906: if (builder == null) {
1907: out.writeObject(null);
1908: } else {
1909: out.writeObject(builder.getTableName());
1910: }
1911: out.writeBoolean(isNew);
1912: out.writeObject(aliases);
1913: out.writeObject(newContext);
1914: }
1915:
1916: /**
1917: * Custom deserialize method for MMObjectNode. The main reason this method exists is
1918: * that the builder for an object will not be serialized, but the tablename for
1919: * the object will be saved instead. During deserialization the builder will
1920: * be recovered using that name.
1921: * @since MMBase-1.8.0
1922: */
1923: @SuppressWarnings("unchecked")
1924: private void readObject(java.io.ObjectInputStream in)
1925: throws IOException, ClassNotFoundException {
1926: oldValues = (Map<String, Object>) in.readObject();
1927: values = (Map<String, Object>) in.readObject();
1928: sizes = (Map<String, Long>) in.readObject();
1929: initializing = in.readBoolean();
1930: properties = (Hashtable<String, MMObjectNode>) in.readObject();
1931: changed = (Set<String>) in.readObject();
1932:
1933: // Retrieve parent and builder by name, not by object
1934: String parentName = (String) in.readObject();
1935: if (parentName != null) {
1936: parent = MMBase.getMMBase().getBuilder(parentName);
1937: }
1938: String builderName = (String) in.readObject();
1939: if (builderName != null) {
1940: builder = MMBase.getMMBase().getBuilder(builderName);
1941: }
1942: isNew = in.readBoolean();
1943: aliases = (Set<String>) in.readObject();
1944: newContext = (String) in.readObject();
1945: }
1946: }
|