001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.module.core;
011:
012: import java.util.*;
013:
014: import org.mmbase.bridge.Field;
015: import org.mmbase.util.logging.*;
016: import org.mmbase.util.Casting;
017:
018: /**
019: * ClusterNode combines fields of different nodes in a single "virtual" node.
020: * This corresponds to the way that an SQL "join" select statement combines
021: * fields of different tables in result rows.
022: * <p>
023: * The individual fields are retrieved from a set of related nodes using a
024: * multilevel query, i.e. a query joining tables using the relations between
025: * the tables.
026: * <p>
027: * This class overrides a number of methods, allowing direct access to data in
028: * the nodes which form the 'virtual' node.
029: * <br />
030: * In future releases, data will NOT be stored directkly in this node anymore.
031: * Instead, it will be stored in the underlying MMObjectNodes.
032: * For reasons of optiomalization, however, we cannot do this right now.
033: * MMObjectNode will need a status field that allows us to recognize whether
034: * it is fully loaded, partially loaded, or being edited.
035: * This can then be checked in 'retrievevalue'.
036: * In addition, to prevent caching conflicts, nodes will need to maintain
037: * their references. This allows for a secure caching mechanism.
038: * <br />
039: * Among other things, this allows one to change values in a multilevel node,
040: * or to access functionality that would otherwise be restricted to 'real'
041: * nodes.
042: *
043: * @author Pierre van Rooden
044: * @version $Id: ClusterNode.java,v 1.29 2007/07/23 13:51:02 michiel Exp $
045: * @see ClusterBuilder
046: */
047: public class ClusterNode extends VirtualNode {
048:
049: private static final Logger log = Logging
050: .getLoggerInstance(ClusterNode.class);
051:
052: /**
053: * Main contructor.
054: * @param parent the node's parent
055: */
056: public ClusterNode(ClusterBuilder parent) {
057: super (parent);
058: }
059:
060: /**
061: * Main contructor.
062: * @param parent the node's parent
063: * @param nrofnodes Nr of referenced nodes.
064: */
065: public ClusterNode(ClusterBuilder parent, int nrofnodes) {
066: super (parent);
067: }
068:
069: /**
070: * Tests whether the data in a node is valid (throws an exception if this is not the case).
071: * The call is performed on all loaded 'real' nodes. If a 'real' node has not previously been
072: * forcably loaded, it is assumed to be correct.
073: * @throws org.mmbase.module.core.InvalidDataException
074: * If the data was unrecoverably invalid
075: * (the references did not point to existing objects)
076: */
077: public void testValidData() throws InvalidDataException { // why is it public?
078: throw new UnsupportedOperationException("ClusterNode "
079: + this .getClass().getName() + " removed since 1.8");
080: };
081:
082: /**
083: * commit : commits the node to the database or other storage system.
084: * This can only be done on a existing (inserted) node. it will use the
085: * changed Vector as its base of what to commit/changed
086: * @return <code>true</code> if the commit was succesfull, <code>false</code> is it failed
087: */
088: public boolean commit() {
089: throw new UnsupportedOperationException("ClusterNode "
090: + this .getClass().getName() + " removed since 1.8");
091: }
092:
093: /**
094: * Obtain the 'real' nodes, associated with a specified objectbuilder.
095: * @param builderName the name of the builder of the requested node, as known
096: * within the virtual node
097: * @return the node, or <code>null</code> if it does not exist or is unknown
098: */
099: public MMObjectNode getRealNode(String builderName) {
100: if (builderName == null)
101: return null;
102: Integer number = (Integer) retrieveValue(builderName
103: + ".number");
104: if (number != null) {
105: return parent.getNode(number.intValue());
106: }
107: return null;
108: }
109:
110: /**
111: * Stores a value in the values hashtable.
112: * If the value is not stored in the virtualnode,
113: * the 'real' node is used instead.
114: * @param fieldName the name of the field to change
115: * @param fieldValue the value to assign
116: */
117: public void storeValue(String fieldName, Object fieldValue) {
118: super .storeValue(fieldName, fieldValue);
119: }
120:
121: /**
122: * Sets a key/value pair in the main values of this node.
123: * Note that if this node is a node in cache, the changes are immediately visible to
124: * everyone, even if the changes are not committed.
125: * The fieldname is added to the (public) 'changed' vector to track changes.
126: * @param fieldName the name of the field to change
127: * @param fieldValue the value to assign
128: * @return always <code>true</code>
129: */
130: public boolean setValue(String fieldName, Object fieldValue) {
131: // Circument interference by the database during initial loading of the node
132: // This is not pretty, but the alternative is rewriting all support classes...
133: if (initializing) {
134: if (!(parent instanceof ClusterBuilder)) {
135: values.put(ClusterBuilder
136: .getFieldNameFromField(fieldName), fieldValue);
137: } else {
138: values.put(fieldName, fieldValue);
139: }
140: return true;
141: }
142: String builderName = getBuilderName(fieldName);
143:
144: MMObjectNode n = getRealNode(builderName);
145: if (n != null) {
146: String realFieldName = ClusterBuilder
147: .getFieldNameFromField(fieldName);
148: n.setValue(realFieldName, fieldValue);
149: values.remove(fieldName);
150: return true;
151: }
152: log.warn("Could not set field '" + fieldName + "')");
153: return false; // or throw exception?
154: }
155:
156: /**
157: * Determines the builder name of a specified fieldname, i.e.
158: * "news" in "news.title",
159: * @param fieldName the name of the field
160: * @return the buidler name of the field
161: */
162: protected String getBuilderName(String fieldName) {
163: int pos = fieldName.indexOf(".");
164: if (pos == -1) {
165: return null;
166: } else {
167: String builderName = fieldName.substring(0, pos);
168: int pos2 = builderName.lastIndexOf("(");
169: builderName = builderName.substring(pos2 + 1);
170: // XXX: we should check on commas and semicolons too... ?
171: return builderName;
172: }
173: }
174:
175: // MM: special arrangment for if parent is not ClusterBuilder.
176: // could give NPE so this is a fix... (1.7)
177: public MMObjectBuilder getBuilder() {
178: if (parent instanceof ClusterBuilder) {
179: return super .getBuilder();
180: } else {
181: return parent;
182: }
183:
184: }
185:
186: /**
187: * Get a value of a certain field.
188: * @param fieldName the name of the field who's data to return
189: * @return the field's value as an <code>Object</code>
190: */
191: public Object getValue(final String fieldName) {
192: String builderName = getBuilderName(fieldName);
193: if (builderName == null) {
194: // there is no 'builder' specified,
195: // so the fieldname itself is a builder name
196: // -> so return the MMObjectNode for that buidler
197: if (parent instanceof ClusterBuilder) {
198: return getRealNode(fieldName);
199: }
200:
201: }
202: Object o = super .getValue(fieldName);
203: if (o == null) {
204: // the normal approach does not yield results.
205: // get the value from the original builder
206: MMObjectNode n = getRealNode(builderName);
207: if (n != null) {
208: o = n.getValue(ClusterBuilder
209: .getFieldNameFromField(fieldName));
210: } else {
211: // fall back to builder if this node doesn't contain a number to fetch te original
212: MMObjectBuilder bul = parent.mmb
213: .getMMObject(builderName);
214: if (bul != null) {
215: o = bul.getValue(this , fieldName);
216: } else {
217: throw new RuntimeException("Builder with name '"
218: + builderName + "' does not exist");
219: }
220: }
221: }
222: return o;
223: }
224:
225: public long getSize(String fieldName) {
226: String builder = getBuilderName(fieldName);
227: if (builder == null) {
228: return super .getSize(fieldName);
229: } else {
230: MMObjectNode n = getRealNode(builder);
231: if (n != null) {
232: return n.getSize(ClusterBuilder
233: .getFieldNameFromField(fieldName));
234: } else {
235: return super .getSize(fieldName);
236: }
237: }
238: }
239:
240: /**
241: * Get a value of a certain field.
242: * The value is returned as a String. Non-string values are automatically converted to String.
243: * @param fieldName the name of the field who's data to return
244: * @return the field's value as a <code>String</code>
245: */
246: public String getStringValue(String fieldName) {
247:
248: // try to get the value from the values table
249: String tmp = Casting.toString(getValue(fieldName));
250:
251: // check if the object is shorted
252: if (tmp.equals(MMObjectNode.VALUE_SHORTED)) {
253: log.debug("getStringValue(): node=" + this
254: + " -- fieldName " + fieldName);
255: // obtain the database type so we can check if what
256: // kind of object it is. this have be changed for
257: // multiple database support.
258: int type = getDBType(fieldName);
259:
260: log.debug("getStringValue(): fieldName " + fieldName
261: + " has type " + type);
262: // check if for known mapped types
263: if (type == Field.TYPE_STRING) {
264:
265: // determine actual node number for this field
266: // takes into account when in a multilevel node
267: int number = getIntValue(getBuilderName(fieldName)
268: + ".number");
269: tmp = parent.getShortedText(fieldName, parent
270: .getNode(number));
271:
272: // did we get a result then store it in the values for next use
273: if (tmp != null) {
274: // store the unmapped value (replacing the $SHORTED text)
275: storeValue(fieldName, tmp);
276: }
277: }
278: }
279: // return the found value
280: return tmp;
281: }
282:
283: /**
284: * Get a binary value of a certain field.
285: * @param fieldName the name of the field who's data to return
286: * @return the field's value as an <code>byte []</code> (binary/blob field)
287: */
288: public byte[] getByteValue(String fieldName) {
289: // try to get the value from the values table
290: Object obj = getValue(fieldName);
291:
292: // we signal with a empty byte[] that its not obtained yet.
293: if (obj instanceof byte[]) {
294: // was allready unmapped so return the value
295: return (byte[]) obj;
296: } else {
297: // determine actual node number for this field
298: // takes into account when in a multilevel node
299: int number = getIntValue(getBuilderName(fieldName)
300: + ".number");
301: // call our builder with the convert request this will probably
302: // map it to the database we are running.
303: byte[] b = parent.getShortedByte(fieldName, parent
304: .getNode(number));
305:
306: // we could in the future also leave it unmapped in the values
307: // or make this programmable per builder ?
308: storeValue(fieldName, b);
309: // return the unmapped value
310: return b;
311: }
312: }
313:
314: /**
315: * Tests whether one of the values of this node was changed since the last commit/insert.
316: * @return <code>true</code> if changes have been made, <code>false</code> otherwise
317: */
318: public boolean isChanged() {
319: throw new UnsupportedOperationException("ClusterNode "
320: + this .getClass().getName() + " removed since 1.8");
321: }
322:
323: /**
324: * Return the relations of this node.
325: * This is not allowed on a cluster node
326: * @throws <code>RuntimeException</code>
327: */
328: public Enumeration<MMObjectNode> getRelations() {
329: throw new RuntimeException(
330: "Cannot follow relations on a cluster node. ");
331: }
332:
333: }
|