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:
0011: package org.mmbase.bridge.implementation;
0012:
0013: import java.util.*;
0014:
0015: import org.mmbase.security.*;
0016: import org.mmbase.bridge.*;
0017: import org.mmbase.bridge.util.*;
0018: import org.mmbase.datatypes.DataType;
0019: import org.mmbase.storage.search.*;
0020: import org.mmbase.module.core.*;
0021: import org.mmbase.module.corebuilders.*;
0022: import org.mmbase.util.functions.*;
0023: import org.mmbase.util.logging.*;
0024: import org.mmbase.util.*;
0025:
0026: import org.w3c.dom.Document;
0027:
0028: /**
0029: * Basic implementation of Node. Wraps MMObjectNodes, adds security.
0030: *
0031: * @author Rob Vermeulen
0032: * @author Pierre van Rooden
0033: * @author Michiel Meeuwissen
0034: * @version $Id: BasicNode.java,v 1.226 2008/03/17 10:05:02 michiel Exp $
0035: * @see org.mmbase.bridge.Node
0036: * @see org.mmbase.module.core.MMObjectNode
0037: */
0038: public class BasicNode extends org.mmbase.bridge.util.AbstractNode
0039: implements Node, SizeMeasurable {
0040:
0041: private static final Logger log = Logging
0042: .getLoggerInstance(BasicNode.class);
0043:
0044: /**
0045: * Reference to the NodeManager
0046: */
0047: protected BasicNodeManager nodeManager;
0048:
0049: /**
0050: * Reference to the Cloud.
0051: */
0052: final protected BasicCloud cloud;
0053:
0054: /**
0055: * Reference to actual MMObjectNode object.
0056: */
0057: protected MMObjectNode noderef;
0058:
0059: /**
0060: * Temporary node ID.
0061: * This is necessary since there is otherwise no sure (and quick) way to determine
0062: * whether a node is in 'edit' mode (i.e. has a temporary node).
0063: * Basically, a temporarynodeid is either -1 (invalid), or a negative number smaller than -1
0064: * (a temporary number assigned by the system).
0065: */
0066: protected int temporaryNodeId = -1;
0067:
0068: /**
0069: * The account this node is edited under.
0070: * This is needed to check whether people have not switched users during an edit.
0071: */
0072: protected String account = null;
0073:
0074: BasicNode(BasicCloud cloud) {
0075: this .cloud = cloud;
0076: }
0077:
0078: /**
0079: * Instantiates a node, linking it to a specified node manager.
0080: * Use this constructor if the node you create uses a NodeManager that is not readily available
0081: * from the cloud (such as a temporary nodemanager for a result list).
0082: * @param node the MMObjectNode to base the node on
0083: * @param nodeManager the NodeManager to use for administrating this Node
0084: * @throws IllegalArgumentException If node is null
0085: */
0086: BasicNode(MMObjectNode node, BasicNodeManager nodeManager) {
0087: cloud = nodeManager.cloud;
0088: this .nodeManager = nodeManager;
0089: setNode(node);
0090: init();
0091: }
0092:
0093: /**
0094: * Instantiates a node, linking it to a specified cloud
0095: * The NodeManager for the node is requested from the Cloud.
0096: * @param node the MMObjectNode to base the node on
0097: * @param cloud the cloud to which this node belongs
0098: * @throws IllegalArgumentException If node is null
0099: */
0100: BasicNode(MMObjectNode node, BasicCloud cloud) {
0101: this .cloud = cloud;
0102: setNode(node);
0103: setNodeManager(node);
0104: init();
0105: }
0106:
0107: /**
0108: * Instantiates a new node (for insert), using a specified nodeManager.
0109: * @param node a temporary MMObjectNode that is the base for the node
0110: * @param cloud the cloud to create the node in
0111: * @param id the id of the node in the temporary cloud
0112: */
0113: BasicNode(MMObjectNode node, BasicCloud cloud, int id) {
0114: this .cloud = cloud;
0115: setNode(node);
0116: setNodeManager(node);
0117: temporaryNodeId = id;
0118: init();
0119: checkCreate();
0120: }
0121:
0122: /**
0123: * @since MMBase-1.8
0124: */
0125: protected void setNodeManager(MMObjectNode node) {
0126: nodeManager = cloud.getBasicNodeManager(node.getBuilder());
0127: assert (nodeManager != null);
0128: }
0129:
0130: /**
0131: * Initializes state in case of a transaction.
0132: */
0133: protected void init() {
0134: // check whether the node is currently in transaction
0135: // and intialize temporaryNodeId if that is the case
0136: if (temporaryNodeId == -1 && cloud.contains(getNode())) {
0137: temporaryNodeId = getNode().getNumber();
0138: }
0139: }
0140:
0141: public int getByteSize() {
0142: return getByteSize(new SizeOf());
0143: }
0144:
0145: public int getByteSize(SizeOf sizeof) {
0146: return sizeof.sizeof(getNode());
0147: }
0148:
0149: /**
0150: * Obtains a reference to the underlying MMObjectNode.
0151: * If the underlying node was deleted, this returns a virtual node with
0152: * no info except the (original) node number.
0153: * @return the underlying MMObjectNode
0154: * @throws NotFoundException if no node was specified.
0155: */
0156: protected final MMObjectNode getNode() {
0157: return noderef;
0158: }
0159:
0160: /**
0161: * Invalidates the reference to the underlying MMObjectNode,
0162: * replacing it with a virtual node that only inherits the number field.
0163: * @since MMBase-1.6.4
0164: */
0165: protected void invalidateNode() {
0166: org.mmbase.module.core.VirtualNode n = new org.mmbase.module.core.VirtualNode(
0167: noderef.getBuilder());
0168: n.setValue("number", noderef.getNumber());
0169: n.clearChanged();
0170: noderef = n;
0171: }
0172:
0173: /**
0174: * Sets the reference to the underlying MMObjectNode.
0175: * @param n the node to set a reference to.
0176: * @throws IllegalArgumentException is n is null
0177: * @since MMBase-1.6.4
0178: */
0179: protected void setNode(MMObjectNode n) {
0180: if (n == null) {
0181: throw new IllegalArgumentException("Passed Node is null");
0182: }
0183: noderef = n;
0184: }
0185:
0186: public Cloud getCloud() {
0187: return cloud;
0188: }
0189:
0190: public NodeManager getNodeManager() {
0191: return nodeManager;
0192: }
0193:
0194: @Override
0195: public int getNumber() {
0196: int i = getNode().getNumber();
0197: // new node, thus return temp id.
0198: // note that temp id is equal to "number" if the node is edited
0199: if (i == -1) {
0200: i = temporaryNodeId;
0201: }
0202: return i;
0203: }
0204:
0205: /**
0206: * Returns whether this is a new (not yet committed) node.
0207: * @return is a new node
0208: */
0209: @Override
0210: public boolean isNew() {
0211: return getNode().isNew();
0212: }
0213:
0214: @Override
0215: public boolean isChanged(String fieldName) {
0216: return getNode().getChanged().contains(fieldName);
0217: }
0218:
0219: @Override
0220: public boolean isChanged() {
0221: return getNode().isChanged();
0222: }
0223:
0224: @Override
0225: public Set<String> getChanged() {
0226: return Collections.unmodifiableSet(getNode().getChanged());
0227: }
0228:
0229: protected void checkAccount() {
0230: if (account == null) {
0231: account = cloud.getAccount();
0232: } else if (!account.equals(cloud.getAccount())) {
0233: throw new BridgeException(
0234: "User context changed. Cannot proceed to edit this node .");
0235: }
0236: }
0237:
0238: protected void checkDelete() {
0239: checkAccount();
0240: int realNumber = getNode().getNumber();
0241: if (realNumber != -1) {
0242: cloud.verify(Operation.DELETE, realNumber);
0243: }
0244: if (temporaryNodeId == -1) {
0245: temporaryNodeId = cloud.add(this );
0246: }
0247: }
0248:
0249: /**
0250: * @inheritDoc
0251: */
0252: @Override
0253: protected void checkWrite() {
0254: checkAccount();
0255: int realNumber = getNode().getNumber();
0256: if (realNumber != -1 && temporaryNodeId == -1) {
0257: cloud.verify(Operation.WRITE, realNumber);
0258: }
0259: if (temporaryNodeId == -1) {
0260: temporaryNodeId = cloud.add(this );
0261: }
0262: }
0263:
0264: protected void checkCreate() {
0265: checkAccount();
0266: }
0267:
0268: protected void checkCommit() {
0269: checkAccount();
0270: }
0271:
0272: /**
0273: * Protected method to be able to set rnumber when creating a relation.
0274: * @param fieldName name of field
0275: * @param value new value of field
0276: * @since MMBase-1.7
0277: */
0278: @Override
0279: protected void setValueWithoutChecks(String fieldName, Object value) {
0280: String result = BasicCloudContext.tmpObjectManager
0281: .setObjectField(account, "" + temporaryNodeId,
0282: fieldName, value);
0283: if (TemporaryNodeManager.UNKNOWN == result) {
0284: throw new BridgeException("Can't change unknown field '"
0285: + fieldName + "', of node " + getNumber()
0286: + " of nodeManager '" + getNodeManager().getName()
0287: + "'");
0288: } else if (TemporaryNodeManager.INVALID_VALUE == result) {
0289: log.debug("Storing value");
0290: getNode().setValue(fieldName, value); // commit() will throw that invalid.
0291: }
0292: }
0293:
0294: @Override
0295: protected Integer toNodeNumber(Object v) {
0296: if (v == null) {
0297: return null;
0298: } else if (v instanceof Node) {
0299: return Integer.valueOf(((Node) v).getNumber());
0300: } else if (v instanceof MMObjectNode) {
0301: return Integer.valueOf(((MMObjectNode) v).getNumber());
0302: } else {
0303: // giving up
0304: return Integer.valueOf(cloud.getNode(v.toString())
0305: .getNumber());
0306: }
0307: }
0308:
0309: @Override
0310: protected void setSize(String fieldName, long size) {
0311: getNode().setSize(fieldName, size);
0312: }
0313:
0314: @Override
0315: public boolean isNull(String fieldName) {
0316: return getNode().isNull(fieldName);
0317: }
0318:
0319: public long getSize(String fieldName) {
0320: return getNode().getSize(fieldName);
0321: }
0322:
0323: /**
0324: * Like getObjectValue, but skips any processing that MMBase would normally perform on a field.
0325: * You can use this to get data from a field for validation purposes.
0326: * @param fieldName name of field
0327: * @since MMBase-1.8
0328: */
0329: public Object getValueWithoutProcess(String fieldName) {
0330: // an exception is made for 'owner' field in setValueWithoutProcess, so for symmetry, we
0331: // must make the same exception here (and also in (getStringValue).
0332: if ("owner".equals(fieldName)) {
0333: return getContext();
0334: }
0335: Object result = getNode().getValue(fieldName);
0336: if (result instanceof MMObjectNode) {
0337: MMObjectNode mmnode = (MMObjectNode) result;
0338: result = cloud.makeNode(mmnode, "" + mmnode.getNumber());
0339: }
0340: return result;
0341: }
0342:
0343: //TODO, silly get-methods could be removed (because in AbstractNode), (calling
0344: //getValueWithoutProcess) but they depend on noderef now, so I don't dare to do that right ahead.
0345:
0346: @Override
0347: public boolean getBooleanValue(String fieldName) {
0348: Boolean result = Boolean.valueOf(noderef
0349: .getBooleanValue(fieldName));
0350: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0351: Field field = nodeManager.getField(fieldName);
0352: log.debug(""
0353: + field.getDataType().getProcessor(
0354: DataType.PROCESS_GET, Field.TYPE_STRING));
0355: result = (Boolean) field.getDataType().getProcessor(
0356: DataType.PROCESS_GET, Field.TYPE_BOOLEAN).process(
0357: this , field, result);
0358: }
0359: return result.booleanValue();
0360: }
0361:
0362: @Override
0363: public Date getDateValue(String fieldName) {
0364: Date result = noderef.getDateValue(fieldName);
0365: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0366: Field field = nodeManager.getField(fieldName);
0367: result = (Date) field.getDataType().getProcessor(
0368: DataType.PROCESS_GET, Field.TYPE_DATETIME).process(
0369: this , field, result);
0370: }
0371: return result;
0372: }
0373:
0374: @Override
0375: public List getListValue(String fieldName) {
0376: List result = noderef.getListValue(fieldName);
0377: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0378: Field field = nodeManager.getField(fieldName);
0379: result = (List) field.getDataType().getProcessor(
0380: DataType.PROCESS_GET, Field.TYPE_LIST).process(
0381: this , field, result);
0382: }
0383:
0384: return result;
0385: }
0386:
0387: @Override
0388: public Node getNodeValue(String fieldName) {
0389: if (fieldName == null || fieldName.equals("number")) {
0390: return this ;
0391: }
0392: Node result = null;
0393: MMObjectNode mmobjectNode = getNode().getNodeValue(fieldName);
0394: if (mmobjectNode != null) {
0395: MMObjectBuilder builder = mmobjectNode.getBuilder();
0396: if (builder instanceof TypeDef) {
0397: result = new BasicNodeManager(mmobjectNode, cloud);
0398: } else if (builder instanceof RelDef
0399: || builder instanceof TypeRel) {
0400: result = new BasicRelationManager(mmobjectNode, cloud);
0401: } else if (builder instanceof InsRel) {
0402: result = new BasicRelation(mmobjectNode, cloud); //.getNodeManager(noderes.getBuilder().getTableName()));
0403: } else {
0404: result = cloud.makeNode(mmobjectNode, mmobjectNode
0405: .getStringValue("number")); //.getNodeManager(noderes.getBuilder().getTableName()));
0406: }
0407: }
0408: if (nodeManager.hasField(fieldName)) { // only if this is actually a field of this node-manager, otherewise it might be e.g. a request for an 'element' of a cluster node
0409: Field field = nodeManager.getField(fieldName);
0410: result = (Node) field.getDataType().getProcessor(
0411: DataType.PROCESS_GET, Field.TYPE_NODE).process(
0412: this , field, result);
0413: }
0414:
0415: return result;
0416: }
0417:
0418: @Override
0419: public int getIntValue(String fieldName) {
0420: Integer result = Integer.valueOf(getNode().getIntValue(
0421: fieldName));
0422: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0423: Field field = nodeManager.getField(fieldName);
0424: result = (Integer) field.getDataType().getProcessor(
0425: DataType.PROCESS_GET, Field.TYPE_INTEGER).process(
0426: this , field, result);
0427: }
0428: return result.intValue();
0429:
0430: }
0431:
0432: @Override
0433: public float getFloatValue(String fieldName) {
0434: Float result = getNode().getFloatValue(fieldName);
0435: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0436: Field field = nodeManager.getField(fieldName);
0437: result = (Float) field.getDataType().getProcessor(
0438: DataType.PROCESS_GET, Field.TYPE_FLOAT).process(
0439: this , field, result);
0440: }
0441: return result.floatValue();
0442: }
0443:
0444: @Override
0445: public long getLongValue(String fieldName) {
0446: Long result = getNode().getLongValue(fieldName);
0447: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0448: Field field = nodeManager.getField(fieldName);
0449: result = (Long) field.getDataType().getProcessor(
0450: DataType.PROCESS_GET, Field.TYPE_LONG).process(
0451: this , field, result);
0452: }
0453: return result.longValue();
0454: }
0455:
0456: @Override
0457: public double getDoubleValue(String fieldName) {
0458: Double result = getNode().getDoubleValue(fieldName);
0459: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0460: Field field = nodeManager.getField(fieldName);
0461: result = (Double) field.getDataType().getProcessor(
0462: DataType.PROCESS_GET, Field.TYPE_DOUBLE).process(
0463: this , field, result);
0464: }
0465: return result.doubleValue();
0466: }
0467:
0468: @Override
0469: public byte[] getByteValue(String fieldName) {
0470: byte[] result = getNode().getByteValue(fieldName);
0471: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0472: Field field = nodeManager.getField(fieldName);
0473: result = (byte[]) field.getDataType().getProcessor(
0474: DataType.PROCESS_GET, Field.TYPE_BINARY).process(
0475: this , field, result);
0476: }
0477: return result;
0478: }
0479:
0480: @Override
0481: public java.io.InputStream getInputStreamValue(String fieldName) {
0482: java.io.InputStream result = getNode().getInputStreamValue(
0483: fieldName);
0484: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0485: Field field = nodeManager.getField(fieldName);
0486: result = (java.io.InputStream) field.getDataType()
0487: .getProcessor(DataType.PROCESS_GET,
0488: Field.TYPE_BINARY).process(this , field,
0489: result);
0490: }
0491: return result;
0492: }
0493:
0494: @Override
0495: public String getStringValue(String fieldName) {
0496: if ("owner".equals(fieldName)) {
0497: return getContext();
0498: }
0499: String result = getNode().getStringValue(fieldName);
0500: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0501: Field field = nodeManager.getField(fieldName);
0502: result = (String) field.getDataType().getProcessor(
0503: DataType.PROCESS_GET, Field.TYPE_STRING).process(
0504: this , field, result);
0505: }
0506: return result;
0507: }
0508:
0509: @Override
0510: public Document getXMLValue(String fieldName) {
0511: Document result = getNode().getXMLValue(fieldName);
0512: if (nodeManager.hasField(fieldName)) { // gui(..) stuff could not work.
0513: Field field = nodeManager.getField(fieldName);
0514: result = (Document) field.getDataType().getProcessor(
0515: DataType.PROCESS_GET, Field.TYPE_XML).process(this ,
0516: field, result);
0517: }
0518: return result;
0519: }
0520:
0521: @Override
0522: public void commit() {
0523: if (isNew()) {
0524: cloud.verify(Operation.CREATE, BasicCloudContext.mmb
0525: .getTypeDef().getIntValue(
0526: getNodeManager().getName()));
0527: }
0528: checkCommit();
0529:
0530: Collection<String> errors = validate();
0531: if (errors.size() > 0) {
0532: String mes = "node " + getNumber() + noderef.getChanged()
0533: + ", builder '" + nodeManager.getName() + "' "
0534: + errors.toString();
0535: if (!Casting.toBoolean(getCloud().getProperty(
0536: Cloud.PROP_IGNOREVALIDATION))) {
0537: noderef.cancel();
0538: throw new IllegalArgumentException(mes);
0539: }
0540: }
0541: processCommit();
0542: if (log.isDebugEnabled()) {
0543: log.debug("committing " + noderef.getChanged());
0544: }
0545: // ignore commit in transaction (transaction commits)
0546: if (!(cloud instanceof Transaction)) { // sigh sigh sigh.
0547: log
0548: .debug("not in a transaction so actually committing now");
0549: MMObjectNode node = getNode();
0550: if (isNew()) {
0551: log.debug("new");
0552: node.insert(cloud.getUser());
0553: // cloud.createSecurityInfo(getNumber());
0554: } else {
0555: log.debug("not new");
0556: node.commit(cloud.getUser());
0557: //cloud.updateSecurityInfo(getNumber());
0558: }
0559: // remove the temporary node
0560: BasicCloudContext.tmpObjectManager.deleteTmpNode(account,
0561: "" + temporaryNodeId);
0562: temporaryNodeId = -1;
0563: }
0564: }
0565:
0566: @Override
0567: public void cancel() {
0568: checkCommit();
0569: // when in a transaction, let the transaction cancel
0570: if (cloud instanceof Transaction) {
0571: ((Transaction) cloud).cancel();
0572: } else {
0573: // remove the temporary node
0574: BasicCloudContext.tmpObjectManager.deleteTmpNode(account,
0575: "" + temporaryNodeId);
0576: if (isNew()) {
0577: invalidateNode();
0578: } else {
0579: noderef.cancel();
0580: }
0581: temporaryNodeId = -1;
0582: }
0583: }
0584:
0585: @Override
0586: public void delete(boolean deleteRelations) {
0587: checkDelete();
0588: if (isNew()) {
0589: // remove from the Transaction
0590: // note that the node is immediately destroyed !
0591: // possibly older edits will fail if they refernce this node
0592: cloud.remove("" + temporaryNodeId);
0593:
0594: // remove a temporary node (no true instantion yet, no relations)
0595: BasicCloudContext.tmpObjectManager.deleteTmpNode(account,
0596: "" + temporaryNodeId);
0597: } else {
0598: // remove a node that is edited, i.e. that already exists
0599: // check relations first!
0600: if (deleteRelations) {
0601: // option set, remove relations
0602: deleteRelations(-1);
0603: } else {
0604: // option unset, fail if any relations exit
0605: if (getNode().hasRelations()) {
0606: throw new BridgeException(
0607: "This node ("
0608: + getNode().getNumber()
0609: + ") cannot be deleted. It still has relations attached to it.");
0610: }
0611: }
0612: // remove aliases
0613: deleteAliases(null);
0614: // in transaction:
0615: if (cloud instanceof BasicTransaction) {
0616: // let the transaction remove the node (as well as its temporary counterpart).
0617: // note that the node still exists until the transaction completes
0618: // a getNode() will still retrieve the node and make edits possible
0619: // possibly 'older' edits will fail if they reference this node
0620: ((BasicTransaction) cloud).delete("" + temporaryNodeId);
0621: } else {
0622: // remove the node
0623: if (temporaryNodeId != -1) {
0624: BasicCloudContext.tmpObjectManager.deleteTmpNode(
0625: account, "" + temporaryNodeId);
0626: }
0627: MMObjectNode node = getNode();
0628: //node.getBuilder().removeNode(node);
0629: node.remove(cloud.getUser());
0630: //cloud.removeSecurityInfo(number);
0631: }
0632: }
0633: // the node does not exist anymore, so invalidate all references.
0634: temporaryNodeId = -1;
0635: invalidateNode();
0636: }
0637:
0638: @Override
0639: public String toString() {
0640: //return getNode().toString() + "(" + getNode().getClass().getName() + ")";
0641: return getNode().toString();
0642: //return "" + super.toString() + " " + getNode().getNumber();
0643: }
0644:
0645: /**
0646: * Recursively deletes relations to relations
0647: * @since MMBase-1.8.5
0648: */
0649: private void deleteRelation(MMObjectNode relation) {
0650: // first delete Relations to this this relation.
0651: // SHOULD security not be checked first?
0652: try {
0653: for (MMObjectNode subRelation : BasicCloudContext.mmb
0654: .getInsRel().getRelationNodes(relation.getNumber(),
0655: false)) {
0656: deleteRelation(subRelation);
0657: }
0658: } catch (SearchQueryException sqe) {
0659: log.error(sqe);
0660: }
0661: cloud.remove(relation);
0662: }
0663:
0664: /**
0665: * Removes all relations of a certain type.
0666: *
0667: * @param type the type of relation (-1 = don't care)
0668: */
0669: private void deleteRelations(int type) {
0670: List<MMObjectNode> relations;
0671: try {
0672: if (type == -1) {
0673: relations = BasicCloudContext.mmb.getInsRel()
0674: .getRelationNodes(getNode().getNumber(), false);
0675: } else {
0676: relations = BasicCloudContext.mmb.getInsRel()
0677: .getRelationNodes(getNode().getNumber());
0678: }
0679: } catch (SearchQueryException sqe) {
0680: log.error(sqe.getMessage()); // should not happen
0681: return;
0682: }
0683: // check first
0684: checkAccount();
0685: for (MMObjectNode node : relations) {
0686: cloud.verify(Operation.DELETE, node.getNumber());
0687: }
0688:
0689: // then delete
0690: for (MMObjectNode node : relations) {
0691: if ((type == -1) || (node.getIntValue("rnumber") == type)) {
0692: deleteRelation(node);
0693: }
0694: }
0695:
0696: }
0697:
0698: @Override
0699: public void deleteRelations(String type) throws NotFoundException {
0700: if ("object".equals(type)) {
0701: deleteRelations(-1);
0702: } else {
0703: RelDef reldef = BasicCloudContext.mmb.getRelDef();
0704: int rType = reldef.getNumberByName(type);
0705: if (rType == -1) {
0706: throw new NotFoundException("Relation with role : "
0707: + type + " does not exist.");
0708: } else {
0709: deleteRelations(rType);
0710: }
0711: }
0712: }
0713:
0714: @Override
0715: public RelationList getRelations(String role,
0716: String otherNodeManager) throws NotFoundException {
0717: if (isNew()) {
0718: // new nodes have no relations
0719: return BridgeCollections.EMPTY_RELATIONLIST;
0720: }
0721:
0722: if ("".equals(otherNodeManager))
0723: otherNodeManager = null;
0724: NodeManager otherManager = otherNodeManager == null ? cloud
0725: .getNodeManager("object") : cloud
0726: .getNodeManager(otherNodeManager);
0727:
0728: TypeRel typeRel = BasicCloudContext.mmb.getTypeRel();
0729: RelationList r1 = BridgeCollections.EMPTY_RELATIONLIST;
0730: RelationList r2 = BridgeCollections.EMPTY_RELATIONLIST;
0731: if (role == null) {
0732: int allowedOtherNumber = "object".equals(otherManager
0733: .getName()) ? 0 : otherManager.getNumber();
0734: if (!typeRel.getAllowedRelations(nodeManager.getNumber(),
0735: allowedOtherNumber, 0,
0736: RelationStep.DIRECTIONS_DESTINATION).isEmpty())
0737: r1 = getRelations(role, otherManager, "destination");
0738: if (!typeRel.getAllowedRelations(nodeManager.getNumber(),
0739: allowedOtherNumber, 0,
0740: RelationStep.DIRECTIONS_SOURCE).isEmpty())
0741: r2 = getRelations(role, otherManager, "source");
0742: } else {
0743: RelDef relDef = BasicCloudContext.mmb.getRelDef();
0744: int rnumber = relDef.getNumberByName(role);
0745: if (typeRel.contains(nodeManager.getNumber(), otherManager
0746: .getNumber(), rnumber,
0747: TypeRel.INCLUDE_PARENTS_AND_DESCENDANTS))
0748: r1 = getRelations(role, otherManager, "destination");
0749: if (typeRel.contains(otherManager.getNumber(), nodeManager
0750: .getNumber(), rnumber,
0751: TypeRel.INCLUDE_PARENTS_AND_DESCENDANTS))
0752: r2 = getRelations(role, otherManager, "source");
0753: }
0754:
0755: if (r2.size() == 0) {
0756: return r1;
0757: } else if (r1.size() == 0) {
0758: return r2;
0759: } else {
0760: // perhaps it would be better for performance to have some 'ChainedRelationList' implementation.
0761: RelationList result = cloud.getCloudContext()
0762: .createRelationList();
0763: result.addAll(r1);
0764: result.addAll(r2);
0765: return result;
0766: }
0767: }
0768:
0769: /**
0770: * Returns a list of relations of the given node.
0771: * @param role role of the relation
0772: * @param nodeManager node manager on the other side of the relation
0773: * @param searchDir direction of the relation
0774: * @return list of relations
0775: * @throws NotFoundException
0776: *
0777: * @see Queries#createRelationNodesQuery Should perhaps be implemented with that
0778: */
0779: @Override
0780: public RelationList getRelations(String role,
0781: NodeManager nodeManager, String searchDir)
0782: throws NotFoundException {
0783: if (isNew()) {
0784: // new nodes have no relations
0785: return org.mmbase.bridge.util.BridgeCollections.EMPTY_RELATIONLIST;
0786: }
0787: if (searchDir == null || "BOTH".equalsIgnoreCase(searchDir))
0788: return getRelations(role, nodeManager);
0789: if (nodeManager == null)
0790: nodeManager = cloud.getNodeManager("object");
0791: NodeQuery query = Queries.createRelationNodesQuery(this ,
0792: nodeManager, role, searchDir);
0793: NodeManager nm = query.getNodeManager();
0794: assert query.getNodeStep() instanceof RelationStep;
0795: // assert nm instanceof RelationManager; cannot assert his, because if the role is null, no relation manager can be created (the nodemanager will be insrel).
0796: return new CollectionRelationList(nm.getList(query), cloud);
0797: }
0798:
0799: @Override
0800: public boolean hasRelations() {
0801: return getNode().hasRelations();
0802: }
0803:
0804: @Override
0805: public int countRelatedNodes(NodeManager otherNodeManager,
0806: String role, String direction) {
0807: if (isNew())
0808: return 0;
0809: if (otherNodeManager == null
0810: || otherNodeManager.getName().equals("object")) {
0811: // can be done on only insrel, which is often much quicker.
0812: NodeManager insrel;
0813: if (role != null) {
0814: insrel = cloud.getRelationManager(role);
0815: } else {
0816: insrel = cloud.getNodeManager("insrel");
0817: }
0818: NodeQuery query = insrel.createQuery();
0819:
0820: if (insrel instanceof BasicRelationManager) {
0821: MMObjectNode relDefNode = ((BasicRelationManager) insrel).relDefNode;
0822: if (relDefNode != null) {
0823: StepField rnumber = query.getStepField(insrel
0824: .getField("rnumber"));
0825: query.setConstraint(query.createConstraint(rnumber,
0826: Integer.valueOf(relDefNode.getNumber())));
0827: }
0828: }
0829:
0830: int dir = RelationStep.DIRECTIONS_BOTH;
0831: if (direction != null) {
0832: dir = ClusterBuilder.getSearchDir(direction);
0833: }
0834:
0835: StepField snumber = query.getStepField(insrel
0836: .getField("snumber"));
0837: StepField dnumber = query.getStepField(insrel
0838: .getField("dnumber"));
0839:
0840: Integer number = Integer.valueOf(getNumber());
0841:
0842: switch (dir) {
0843: case RelationStep.DIRECTIONS_DESTINATION: {
0844: Queries.addConstraint(query, query.createConstraint(
0845: snumber, number));
0846: break;
0847: }
0848: case RelationStep.DIRECTIONS_SOURCE: {
0849: Queries.addConstraint(query, query.createConstraint(
0850: dnumber, number));
0851: break;
0852: }
0853: case RelationStep.DIRECTIONS_BOTH:
0854: case RelationStep.DIRECTIONS_EITHER: {
0855: Constraint sourceConstraint = query.createConstraint(
0856: snumber, number);
0857: Constraint destinationConstraint = query
0858: .createConstraint(dnumber, number);
0859: Queries.addConstraint(query, query.createConstraint(
0860: sourceConstraint,
0861: CompositeConstraint.LOGICAL_OR,
0862: destinationConstraint));
0863: break;
0864: }
0865: default:
0866: log.debug("Unknown relation direction" + dir);
0867: break;
0868: }
0869: return Queries.count(query);
0870: } else {
0871: BasicQuery count = (BasicQuery) cloud
0872: .createAggregatedQuery();
0873: count.addStep(nodeManager);
0874: Step step = count.addRelationStep(otherNodeManager, role,
0875: direction, false).getPrevious();
0876: count.addNode(step, this );
0877: count.addAggregatedField(step, nodeManager
0878: .getField("number"),
0879: AggregatedField.AGGREGATION_TYPE_COUNT);
0880: Node result = cloud.getList(count).get(0);
0881: return result.getIntValue("number");
0882: }
0883: }
0884:
0885: /**
0886: * @since MMBase-1.8.2
0887: */
0888: protected NodeList getRelatedNodes(NodeManager otherManager,
0889: String role) {
0890:
0891: NodeList l1 = BridgeCollections.EMPTY_NODELIST;
0892: NodeList l2 = BridgeCollections.EMPTY_NODELIST;
0893:
0894: TypeRel typeRel = BasicCloudContext.mmb.getTypeRel();
0895: if (role == null) {
0896: int allowedOtherNumber = otherManager == null
0897: || "object".equals(otherManager.getName()) ? 0
0898: : otherManager.getNumber();
0899: if (!typeRel.getAllowedRelations(nodeManager.getNumber(),
0900: allowedOtherNumber, 0,
0901: RelationStep.DIRECTIONS_DESTINATION).isEmpty())
0902: l1 = getRelatedNodes(otherManager, role, "destination");
0903: if (!typeRel.getAllowedRelations(nodeManager.getNumber(),
0904: allowedOtherNumber, 0,
0905: RelationStep.DIRECTIONS_SOURCE).isEmpty())
0906: l2 = getRelatedNodes(otherManager, role, "source");
0907: } else {
0908: RelDef relDef = BasicCloudContext.mmb.getRelDef();
0909: int rnumber = relDef.getNumberByName(role);
0910: if (typeRel.contains(nodeManager.getNumber(), otherManager
0911: .getNumber(), rnumber,
0912: TypeRel.INCLUDE_PARENTS_AND_DESCENDANTS))
0913: l1 = getRelatedNodes(otherManager, role, "destination");
0914: if (typeRel.contains(otherManager.getNumber(), nodeManager
0915: .getNumber(), rnumber,
0916: TypeRel.INCLUDE_PARENTS_AND_DESCENDANTS))
0917: l2 = getRelatedNodes(otherManager, role, "source");
0918: }
0919: if (l2.size() == 0) {
0920: return l1;
0921: } else if (l1.size() == 0) {
0922: return l2;
0923: } else {
0924: // perhaps it would be better for performance to have some 'ChainedRelationList' implementation.
0925: NodeList result = cloud.getCloudContext().createNodeList();
0926: result.addAll(l1);
0927: result.addAll(l2);
0928: return result;
0929: }
0930: }
0931:
0932: /**
0933: * @param otherManager node manager on the other side of the relation
0934: * @param role role of the relation
0935: * @param searchDir direction of the relation
0936: * @return List of related nodes
0937: * @see Queries#createRelatedNodesQuery Should perhaps be implemented with that.
0938: * @since MMBase-1.6
0939: */
0940: @Override
0941: public NodeList getRelatedNodes(NodeManager otherManager,
0942: String role, String searchDir) {
0943: if (log.isDebugEnabled()) {
0944: log.debug("type(" + otherManager.getName() + "), role("
0945: + role + "), dir(" + searchDir + ")");
0946: }
0947: if (isNew()) {
0948: // new nodes have no relations
0949: return org.mmbase.bridge.util.BridgeCollections.EMPTY_NODELIST;
0950: }
0951: if (searchDir == null)
0952: searchDir = "BOTH";
0953: if ("BOTH".equalsIgnoreCase(searchDir)) {
0954: return getRelatedNodes(otherManager, role);
0955: }
0956: NodeQuery query = Queries.createRelatedNodesQuery(this ,
0957: otherManager, role, searchDir);
0958: return query.getNodeManager().getList(query);
0959: }
0960:
0961: @Override
0962: public int countRelatedNodes(String type) {
0963: if (isNew())
0964: return 0;
0965: return getNode().getRelationCount(type);
0966: }
0967:
0968: @Override
0969: public StringList getAliases() {
0970: NodeManager oalias = cloud.getNodeManager("oalias");
0971: NodeQuery q = oalias.createQuery();
0972: Constraint c = q
0973: .createConstraint(q.getStepField(oalias
0974: .getField("destination")), Integer
0975: .valueOf(getNumber()));
0976: q.setConstraint(c);
0977: NodeList aliases = oalias.getList(q);
0978: StringList result = new BasicStringList();
0979: NodeIterator i = aliases.nodeIterator();
0980: while (i.hasNext()) {
0981: Node alias = i.nextNode();
0982: result.add(alias.getStringValue("name"));
0983: }
0984:
0985: // There might be aliases in temporary nodes
0986: // This is quite a dirty (and probably also slow) hack
0987: // for bug #6185.
0988: // Usually the temporaryNodes hashtable shall not be
0989: // too full.
0990: if (cloud instanceof Transaction) {
0991: Map<String, MMObjectNode> tnodes = MMObjectBuilder.temporaryNodes;
0992: for (MMObjectNode mynode : tnodes.values()) {
0993: if (mynode.getName().equals("oalias")) {
0994: String dest = mynode.getStringValue("_destination");
0995: if ((account + "_" + temporaryNodeId).equals(dest)) {
0996: result.add(mynode.getStringValue("name"));
0997: }
0998: }
0999: }
1000: }
1001:
1002: return result;
1003: }
1004:
1005: @Override
1006: public void createAlias(String aliasName) {
1007: checkWrite();
1008: if (isNew()) {
1009: cloud.checkAlias(aliasName);
1010: getNode().setAlias(aliasName);
1011: } else {
1012: cloud.createAlias(this , aliasName);
1013: }
1014: }
1015:
1016: /**
1017: * Delete one or all aliases of this node
1018: * @param aliasName the name of the alias (null means all aliases)
1019: */
1020: private void deleteAliases(String aliasName) {
1021: // A new node cannot have any aliases, except when in a transaction.
1022: // However, there is no point in adding aliasses to a ndoe you plan to delete,
1023: // so no attempt has been made to rectify this (cause its not worth all the trouble).
1024: // If people remove a node for which they created aliases in the same transaction, that transaction will fail.
1025: // Live with it.
1026: if (!isNew()) {
1027: NodeManager oalias = cloud.getNodeManager("oalias");
1028: NodeQuery q = oalias.createQuery();
1029: Constraint c = q.createConstraint(q.getStepField(oalias
1030: .getField("destination")), Integer
1031: .valueOf(getNumber()));
1032: if (aliasName != null) {
1033: Constraint c2 = q.createConstraint(q
1034: .getStepField(oalias.getField("name")),
1035: aliasName);
1036: c = q.createConstraint(c,
1037: CompositeConstraint.LOGICAL_AND, c2);
1038: }
1039: q.setConstraint(c);
1040: NodeList aliases = oalias.getList(q);
1041: NodeIterator i = aliases.nodeIterator();
1042: while (i.hasNext()) {
1043: Node alias = i.nextNode();
1044: alias.delete();
1045: }
1046: }
1047: }
1048:
1049: @Override
1050: public void deleteAlias(String aliasName) {
1051: checkWrite();
1052: deleteAliases(aliasName);
1053: }
1054:
1055: // javadoc inherited (from Node)
1056: @Override
1057: public void setContext(String context) {
1058: // set the context on the node (run after insert).
1059: getNode().setContext(cloud.getUser(), context,
1060: temporaryNodeId == -1);
1061: }
1062:
1063: // javadoc inherited (from Node)
1064: @Override
1065: public String getContext() {
1066: return getNode().getContext(cloud.getUser());
1067: }
1068:
1069: // javadoc inherited (from Node)
1070: @Override
1071: public StringList getPossibleContexts() {
1072: return new BasicStringList(getNode().getPossibleContexts(
1073: cloud.getUser()));
1074: }
1075:
1076: @Override
1077: public boolean mayWrite() {
1078: return isNew()
1079: || cloud.check(Operation.WRITE, getNode().getNumber());
1080: }
1081:
1082: @Override
1083: public boolean mayDelete() {
1084: return isNew()
1085: || cloud.check(Operation.DELETE, getNode().getNumber());
1086: }
1087:
1088: @Override
1089: public boolean mayChangeContext() {
1090: return isNew()
1091: || cloud.check(Operation.CHANGE_CONTEXT, getNode()
1092: .getNumber());
1093: }
1094:
1095: /**
1096: * Reverse the buffers, when changed and not stored...
1097: */
1098: @Override
1099: protected void finalize() {
1100: // When not commit-ed or cancelled, and the buffer has changed, the changes must be reversed.
1101: // when not done it results in node-lists with changes which are not performed on the database...
1102: // This is all due to the fact that Node doesnt make a copy of MMObjectNode, while editing...
1103: // my opinion is that this should happen, as soon as edit-ting starts,..........
1104: // when still has modifications.....
1105: if (isChanged()) {
1106: if (!(cloud instanceof Transaction)) {
1107: // cancel the modifications...
1108: cancel();
1109: }
1110: }
1111: }
1112:
1113: public Collection<Function<?>> getFunctions() {
1114: return getNode().getFunctions();
1115: }
1116:
1117: @Override
1118: protected Function<?> getNodeFunction(String functionName) {
1119: return getNode().getFunction(functionName);
1120: }
1121:
1122: @Override
1123: public Parameters createParameters(String functionName) {
1124: return getNode().getFunction(functionName).createParameters();
1125: }
1126:
1127: @Override
1128: protected FieldValue createFunctionValue(Object result) {
1129: return new BasicFunctionValue(getCloud(), result);
1130: }
1131:
1132: /*
1133: public Object getOldValue(String fieldName) {
1134: return getNode().getOldValues().get(fieldName);
1135: }
1136: */
1137:
1138: }
|