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:
011: package org.mmbase.bridge.implementation;
012:
013: import java.util.*;
014:
015: import javax.servlet.*;
016: import javax.servlet.http.*;
017:
018: import org.mmbase.bridge.*;
019: import org.mmbase.bridge.util.Queries;
020: import org.mmbase.core.CoreField;
021: import org.mmbase.storage.search.*;
022: import org.mmbase.module.core.*;
023: import org.mmbase.module.corebuilders.*;
024: import org.mmbase.security.Operation;
025: import org.mmbase.util.PageInfo;
026: import org.mmbase.util.StringTagger;
027: import org.mmbase.util.functions.Function;
028: import org.mmbase.util.logging.*;
029:
030: /**
031: * This class represents a node's type information object - what used to be the 'builder'.
032: * It contains all the field and attribuut information, as well as GUI data for editors and
033: * some information on deribed and deriving types. It also contains some maintenance code - code
034: * to create new nodes, en code to query objects belonging to the same manager.
035: * Since node types are normally maintained through use of config files (and not in the database),
036: * as wel as for security issues, the data of a nodetype cannot be changed except through
037: * the use of an administration module (which is why we do not include setXXX methods here).
038: * @author Rob Vermeulen
039: * @author Pierre van Rooden
040: * @author Michiel Meeuwissen
041: * @version $Id: BasicNodeManager.java,v 1.133 2007/06/21 13:46:51 michiel Exp $
042:
043: */
044: public class BasicNodeManager extends BasicNode implements NodeManager {
045: private static final Logger log = Logging
046: .getLoggerInstance(BasicNodeManager.class);
047:
048: /**
049: * @javadoc
050: */
051: private long internalVersion = -1;
052:
053: // builder on which the type is based
054: protected MMObjectBuilder builder;
055:
056: // field types
057: protected Map<String, Field> fieldTypes = new HashMap<String, Field>();
058:
059: /**
060: * Instantiates a new NodeManager (for insert) based on a newly created node which either represents or references a builder.
061: * Normally this is a TypeDef node, but subclasses (i.e. BasicRelationManager)
062: * may use other nodes, such as nodes from RelDef or TypeRel.
063: * The NodeManager that administrates the node itself is requested from the Cloud.
064: * The Nodemanager cannot be used for administartion tasks until it is isnerted (committed) in the Cloud.
065: * @param node the MMObjectNode to base the NodeManager on.
066: * @param Cloud the cloud to which this node belongs
067: * @param id the id of the node in the temporary cloud
068: */
069: BasicNodeManager(MMObjectNode node, BasicCloud cloud, int nodeid) {
070: super (node, cloud, nodeid);
071: // no initialization - for a new nodes, builder is null.
072: }
073:
074: /**
075: * Instantiates a NodeManager based on a node which either represents or references a builder.
076: * Normally this is a TypeDef node, but subclasses (i.e. BasicRelationManager)
077: * may use other nodes, such as nodes from RelDef or TypeRel.
078: * The NodeManager that administrates the node itself is requested from the Cloud.
079: * @param node the MMObjectNode to base the NodeManager on.
080: * @param Cloud the cloud to which this node belongs
081: */
082: BasicNodeManager(MMObjectNode node, BasicCloud cloud) {
083: super (node, cloud);
084: initManager();
085: }
086:
087: /**
088: * Instantiates a NodeManager based on a builder.
089: * The constructor attempts to retrieve an MMObjectNode (from typedef)
090: * which represents this builder.
091: * @param builder the MMObjectBuilder to base the NodeManager on.
092: * @param Cloud the cloud to which this node belongs
093: */
094: BasicNodeManager(MMObjectBuilder builder, BasicCloud cloud) {
095: super (cloud);
096: noderef = getNodeForBuilder(builder);
097: this .builder = builder;
098: TypeDef typeDef = BasicCloudContext.mmb.getTypeDef();
099: if (builder == typeDef) {
100: nodeManager = this ;
101: } else {
102: nodeManager = cloud.getBasicNodeManager(typeDef);
103: }
104: sync();
105: }
106:
107: /**
108: * This method is only needed to get clearer exception from above constructor in case of problem with builder.
109: * @since MMBase-1.8
110: */
111: private static MMObjectNode getNodeForBuilder(
112: MMObjectBuilder builder) {
113: if (builder.isVirtual()) {
114: return new org.mmbase.module.core.VirtualNode(
115: BasicCloudContext.mmb.getTypeDef());
116: } else {
117: MMObjectNode typedefNode = builder.getNode(builder
118: .getNumber());
119: if (typedefNode == null) {
120: throw new RuntimeException(
121: "Could not find typedef node for builder "
122: + builder + " with otype "
123: + builder.getNumber());
124: }
125: return typedefNode;
126: }
127: }
128:
129: @Override
130: protected void setNodeManager(MMObjectNode node) {
131: int nodeNumber = node.getNumber();
132: if (nodeNumber >= 0
133: && nodeNumber == node.getBuilder().getNumber()) { // this is the typedef itself
134: nodeManager = this ;
135: } else {
136: log
137: .debug("Setting node manager for nodeManager, but no typedef "
138: + node);
139: super .setNodeManager(node);
140: }
141: }
142:
143: @Override
144: public final boolean isNodeManager() {
145: return true;
146: }
147:
148: @Override
149: public final NodeManager toNodeManager() {
150: return this ;
151: }
152:
153: /**
154: * Initializes the NodeManager: determines the MMObjectBuilder if it was not already known,
155: * and fills the fields list.
156: */
157: protected void initManager() {
158: if (builder == null) {
159: if (noderef == null) {
160: throw new BridgeException(
161: "node reference was null, could not continue");
162: }
163: // look which node we represent, and
164: // what the builder is that this
165: // node is representing
166: TypeDef typedef = (TypeDef) noderef.getBuilder();
167: builder = typedef.getBuilder(noderef);
168: if (builder == null) {
169: // builder = null. this muist mean that this is an inactive builder
170: // log warning, then exit.
171: // This will mean the nodemanager can be used as a node, but will be treated as a
172: // non-committed builder.
173: log.warn("could not find nodemanager for node #"
174: + noderef.getNumber() + " ("
175: + noderef.getGUIIndicator() + ")");
176: return;
177: }
178: }
179: sync();
180: }
181:
182: /**
183: * @since MMBase-1.8
184: */
185: protected static void sync(MMObjectBuilder builder,
186: Map<String, Field> fieldTypes, NodeManager nodeManager) {
187: Collection<CoreField> fields = builder.getFields();
188: if (fields != null) { // when is it null?
189: fieldTypes.clear();
190: for (CoreField f : fields) {
191: Field ft = new BasicField(f, nodeManager);
192: if (f.getStoragePosition() > 0) {
193: fieldTypes.put(ft.getName().toLowerCase(), ft);
194: }
195: }
196: }
197: }
198:
199: /**
200: * Syncs the nodemanger with the builder.
201: * Loads the fieldlist from the associated builder if needed.
202: * @since MMBase-1.8
203: */
204: synchronized private void sync() {
205: long builderVersion = builder.getInternalVersion();
206: if (internalVersion < builderVersion) {
207: internalVersion = builderVersion;
208: sync(builder, fieldTypes, this );
209: }
210: }
211:
212: /**
213: * Returns the fieldlist of this nodemanager after making sure the manager is synced with the builder.
214: * @since MMBase-1.8
215: */
216: protected Map<String, Field> getFieldTypes() {
217: sync();
218: return fieldTypes;
219: }
220:
221: /**
222: * Structure to temporary contain an MMObjectNode and a {@link BasicCloud#uniqueId()}
223: * @since MMBase-1.8
224: */
225: protected final class NodeAndId {
226: final MMObjectNode node;
227: final int id;
228:
229: NodeAndId(MMObjectNode n, int i) {
230: node = n;
231: id = i;
232: }
233: }
234:
235: /**
236: * Creates new MMObjectNode for the current node manager.
237: * @return MMObjectNode wrapped in a {@link NodeAndId}
238: * @since MMBase-1.8
239: */
240: protected NodeAndId createMMObjectNode() {
241: // create object as a temporary node
242: int id = BasicCloud.uniqueId();
243: {
244: String currentObjectContext = BasicCloudContext.tmpObjectManager
245: .createTmpNode(getMMObjectBuilder().getTableName(),
246: cloud.getAccount(), "" + id);
247: // if we are in a transaction, add the node to the transaction;
248: cloud.add(currentObjectContext);
249: }
250:
251: MMObjectNode node = BasicCloudContext.tmpObjectManager.getNode(
252: cloud.getAccount(), "" + id);
253: // odd this MMObjectNode does _not_ have the right builder?!
254:
255: // XXX this should somehow be the default value of the owner field!!
256: // set the owner to the owner field as indicated by the user
257: node.setValue("owner", cloud.getUser().getOwnerField());
258:
259: return new NodeAndId(node, id);
260: }
261:
262: /**
263: * BasicNodeManager is garantueed to return BasicNode's. So extendsion must override this, and not {@link #createNode}
264: * @since MMBase-1.8
265: */
266: protected BasicNode createBasicNode() {
267: NodeAndId n = createMMObjectNode();
268: MMObjectBuilder bul = getMMObjectBuilder();
269: if (bul instanceof TypeDef) {
270: // I wonder if this is necessary.
271: return new BasicNodeManager(n.node, cloud, n.id);
272: } else if (bul instanceof TypeRel) {
273: return new BasicRelationManager(n.node, cloud, n.id);
274: } else {
275: return new BasicNode(n.node, cloud, n.id);
276: }
277: }
278:
279: public final Node createNode() {
280: return createBasicNode();
281: }
282:
283: public NodeManager getParent() throws NotFoundException {
284: MMObjectBuilder bul = getMMObjectBuilder().getParentBuilder();
285: if (bul == null) {
286: throw new NotFoundException("Parent of nodemanager "
287: + getName() + "does not exist");
288: } else {
289: return cloud.getNodeManager(bul.getTableName());
290: }
291: }
292:
293: public NodeManagerList getDescendants() {
294: List<MMObjectBuilder> descs = getMMObjectBuilder()
295: .getDescendants();
296: return new BasicNodeManagerList(descs, cloud);
297: }
298:
299: public String getName() {
300: if (builder != null) {
301: return builder.getTableName();
302: } else {
303: return getStringValue("name");
304: }
305: }
306:
307: public String getProperty(String name) {
308: if (builder != null) {
309: return builder.getInitParameter(name);
310: } else {
311: return null;
312: }
313: }
314:
315: public Map<String, String> getProperties() {
316: if (builder != null) {
317: return Collections.unmodifiableMap(builder
318: .getInitParameters());
319: } else {
320: return Collections.emptyMap();
321: }
322: }
323:
324: public String getGUIName() {
325: return getGUIName(NodeManager.GUI_SINGULAR);
326: }
327:
328: public String getGUIName(int plurality) {
329: return getGUIName(plurality, null);
330: }
331:
332: public String getGUIName(int plurality, Locale locale) {
333: if (locale == null)
334: locale = cloud.getLocale();
335: if (builder != null) {
336: if (plurality == NodeManager.GUI_SINGULAR) {
337: return builder.getSingularName(locale.getLanguage());
338: } else {
339: return builder.getPluralName(locale.getLanguage());
340: }
341: } else {
342: return getName();
343: }
344: }
345:
346: public String getDescription() {
347: return getDescription(null);
348: }
349:
350: public String getDescription(Locale locale) {
351: if (locale == null)
352: locale = cloud.getLocale();
353: if (builder != null) {
354: return builder.getDescription(locale.getLanguage());
355: } else {
356: return "";
357: }
358: }
359:
360: public FieldList getFields() {
361: return getFields(NodeManager.ORDER_NONE);
362: }
363:
364: public FieldList getFields(int order) {
365: if (builder != null) {
366: return new BasicFieldList(builder.getFields(order), this );
367: } else {
368: return new BasicFieldList(getFieldTypes().values(), this );
369: }
370: }
371:
372: public Field getField(String fieldName) throws NotFoundException {
373: Field f = getFieldTypes().get(fieldName.toLowerCase());
374: if (f == null)
375: throw new NotFoundException("Field '" + fieldName
376: + "' does not exist in NodeManager '" + getName()
377: + "'.(" + getFieldTypes() + ")");
378: return f;
379: }
380:
381: public boolean hasField(String fieldName) {
382: return fieldName != null
383: && getFieldTypes().containsKey(fieldName.toLowerCase());
384: }
385:
386: // javadoc inherited
387: public NodeQuery createQuery() {
388: return new BasicNodeQuery(this );
389: }
390:
391: public NodeList getList(NodeQuery query) {
392: try {
393: if (query == null)
394: query = createQuery();
395:
396: boolean checked = cloud.setSecurityConstraint(query);
397:
398: boolean useCache = query.getCachePolicy()
399: .checkPolicy(query);
400: List<MMObjectNode> resultList = builder
401: .getStorageConnector().getNodes(query, useCache);
402:
403: BasicNodeList resultNodeList;
404: NodeManager nm = query.getNodeManager();
405: resultNodeList = new BasicNodeList(resultList, cloud);
406:
407: resultNodeList.setProperty(NodeList.QUERY_PROPERTY, query);
408:
409: if (!checked) {
410: cloud.checkNodes(resultNodeList, query);
411: }
412:
413: return resultNodeList;
414: } catch (SearchQueryException sqe) {
415: throw new BridgeException(sqe);
416: }
417: }
418:
419: public NodeList getList(String constraints, String sorted,
420: String directions) {
421: // MMObjectBuilder builder = getMMObjectBuilder();
422:
423: // begin of check invalid search command
424: /*
425: org.mmbase.util.Encode encoder = new org.mmbase.util.Encode("ESCAPE_SINGLE_QUOTE");
426: if(orderby != null) orderby = encoder.encode(orderby);
427: if(directions != null) directions = encoder.encode(directions);
428: if(constraints != null && !cloud.validConstraints(constraints)) {
429: throw new BridgeException("invalid contrain:" + constraints);
430: }
431: */
432: // end of check invalid search command
433:
434: NodeQuery query = createQuery();
435: Queries.addConstraints(query, constraints);
436: Queries.addSortOrders(query, sorted, directions);
437: NodeList list = getList(query);
438: list.setProperty("constraints", constraints);
439: list.setProperty("orderby", sorted);
440: list.setProperty("directions", directions);
441: return list;
442: }
443:
444: public RelationManagerList getAllowedRelations() {
445: return getAllowedRelations((NodeManager) null, null, null);
446: }
447:
448: public RelationManagerList getAllowedRelations(String nodeManager,
449: String role, String direction) {
450: if (nodeManager == null) {
451: return getAllowedRelations((NodeManager) null, role,
452: direction);
453: } else {
454: return getAllowedRelations(cloud
455: .getNodeManager(nodeManager), role, direction);
456: }
457: }
458:
459: public RelationManagerList getAllowedRelations(
460: NodeManager nodeManager, String role, String direction) {
461: int this OType = getMMObjectBuilder().getNumber();
462: int requestedRole = -1;
463: if (role != null) {
464: requestedRole = BasicCloudContext.mmb.getRelDef()
465: .getNumberByName(role);
466: if (requestedRole == -1) {
467: throw new NotFoundException("Could not get role '"
468: + role + "'");
469: }
470: }
471:
472: int dir = ClusterBuilder.getSearchDir(direction);
473:
474: Enumeration<MMObjectNode> typerelNodes;
475: if (nodeManager != null) {
476: int otherOType = nodeManager.getNumber();
477: typerelNodes = BasicCloudContext.mmb.getTypeRel()
478: .getAllowedRelations(this OType, otherOType);
479: } else {
480: typerelNodes = BasicCloudContext.mmb.getTypeRel()
481: .getAllowedRelations(this OType);
482: }
483:
484: List<MMObjectNode> nodes = new ArrayList<MMObjectNode>();
485: while (typerelNodes.hasMoreElements()) {
486: MMObjectNode n = typerelNodes.nextElement();
487: if ((requestedRole == -1)
488: || (requestedRole == n.getIntValue("rnumber"))) {
489: int snumber = n.getIntValue("snumber");
490: int dnumber = n.getIntValue("dnumber");
491: if (snumber != dnumber) { // if types are equal, no need to check direction, it is always ok then..
492: if (this OType == dnumber) {
493: if (dir == RelationStep.DIRECTIONS_DESTINATION) {
494: continue;
495: }
496: } else {
497: if (dir == RelationStep.DIRECTIONS_SOURCE) {
498: continue;
499: }
500: }
501: }
502: nodes.add(n);
503: }
504: }
505: return new BasicRelationManagerList(nodes, cloud);
506: }
507:
508: public String getInfo(String command) {
509: return getInfo(command, null, null);
510: }
511:
512: public String getInfo(String command, ServletRequest req,
513: ServletResponse resp) {
514: MMObjectBuilder builder = getMMObjectBuilder();
515: StringTokenizer tokens = new StringTokenizer(command, "-");
516: return builder.replace(new PageInfo((HttpServletRequest) req,
517: (HttpServletResponse) resp, getCloud()), tokens);
518: }
519:
520: public NodeList getList(String command, Map parameters) {
521: return getList(command, parameters, null, null);
522: }
523:
524: public NodeList getList(String command, Map parameters,
525: ServletRequest req, ServletResponse resp) {
526: MMObjectBuilder builder = getMMObjectBuilder();
527: StringTagger params = new StringTagger("");
528: if (parameters != null) {
529: for (Iterator entries = parameters.entrySet().iterator(); entries
530: .hasNext();) {
531: Map.Entry entry = (Map.Entry) entries.next();
532: String key = (String) entry.getKey();
533: Object o = entry.getValue();
534: if (o instanceof Vector) {
535: params.setValues(key, (Vector) o);
536: } else {
537: params.setValue(key, "" + o);
538: }
539: }
540: }
541: try {
542: StringTokenizer tokens = new StringTokenizer(command, "-");
543: List<String> v = builder.getList(new PageInfo(
544: (HttpServletRequest) req,
545: (HttpServletResponse) resp, getCloud()), params,
546: tokens);
547: if (v == null) {
548: v = new ArrayList<String>();
549: }
550: int items = 1;
551: try {
552: items = Integer.parseInt(params.Value("ITEMS"));
553: } catch (Exception e) {
554: log
555: .warn("parameter 'ITEMS' must be a int value, it was :"
556: + params.Value("ITEMS"));
557: }
558: Vector<String> fieldlist = params.Values("FIELDS");
559: Vector<MMObjectNode> res = new Vector<MMObjectNode>(v
560: .size()
561: / items);
562: for (int i = 0; i < v.size(); i += items) {
563: MMObjectNode node = new org.mmbase.module.core.VirtualNode(
564: builder);
565: for (int j = 0; (j < items) && (j < v.size()); j++) {
566: if ((fieldlist != null) && (j < fieldlist.size())) {
567: node.setValue(fieldlist.get(j), v.get(i + j));
568: } else {
569: node.setValue("item" + (j + 1), v.get(i + j));
570: }
571: }
572: res.add(node);
573: }
574: if (res.size() > 0) {
575: NodeManager tempNodeManager = new VirtualNodeManager(
576: (org.mmbase.module.core.VirtualNode) res.get(0),
577: cloud);
578: return new BasicNodeList(res, tempNodeManager);
579: }
580: return createNodeList();
581: } catch (Exception e) {
582: throw new BridgeException(e);
583: }
584: }
585:
586: public boolean mayCreateNode() {
587: if (builder == null)
588: return false;
589: return cloud.check(Operation.CREATE, builder.getNumber());
590: }
591:
592: MMObjectBuilder getMMObjectBuilder() {
593: if (builder == null) {
594: throw new IllegalStateException(
595: "No functional instantiation exists (yet/any more) for this NodeManager.");
596: }
597: return builder;
598: }
599:
600: // overriding behavior of BasicNode
601:
602: @Override
603: public void commit() {
604: super .commit(); // commit the node - the builder should now be loaded by TypeDef
605: // rebuild builder reference and fieldlist.
606: initManager();
607: }
608:
609: @Override
610: public void delete(boolean deleteRelations) {
611: super .delete(deleteRelations);
612: builder = null; // invalidate (builder does not exist any more)
613: }
614:
615: @Override
616: public Collection<Function<?>> getFunctions() {
617: return builder.getFunctions();
618: }
619:
620: @Override
621: protected Function getNodeFunction(String functionName) {
622: if (log.isDebugEnabled()) {
623: log.debug("Getting function '" + functionName + "' for "
624: + this );
625: }
626:
627: // it may be a node-function on the type-def node then
628: // it may be gui on a typedef node or so.
629: Function function = getNode().getFunction(functionName);
630: if (function == null || functionName.equals("info")
631: || functionName.equals("getFunctions")) {
632: function = builder != null ? builder
633: .getFunction(functionName) : null;
634: }
635: if (function == null) {
636: throw new NotFoundException("Function with name "
637: + functionName + " does not exist in "
638: + builder.getFunctions());
639: }
640: return function;
641:
642: }
643:
644: public FieldList createFieldList() {
645: return new BasicFieldList(Collections.emptyList(), this );
646: }
647:
648: public NodeList createNodeList() {
649: return new BasicNodeList(Collections.emptyList(), this );
650: }
651:
652: public RelationList createRelationList() {
653: return new BasicRelationList(Collections.emptyList(), this);
654: }
655:
656: }
|