/* * Copyright (C) 2006 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the * License. */ package org.alfresco.repo.avm; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.node.AbstractNodeServiceImpl; import org.alfresco.service.cmr.avm.AVMException; import org.alfresco.service.cmr.avm.AVMExistsException; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMNotFoundException; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMStoreDescriptor; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.InvalidAspectException; import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.AssociationExistsException; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.CyclicChildRelationshipException; import org.alfresco.service.cmr.repository.InvalidChildAssociationRefException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidStoreRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreExistsException; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNamePattern; import org.apache.log4j.Logger; /** * NodeService implementing facade over AVMService. * @author britt */ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeService { private static Logger fgLogger = Logger.getLogger(AVMNodeService.class); /** * Reference to AVMService. */ private AVMService fAVMService; /** * Set the AVMService. For Spring. * @param service The AVMService instance. */ public void setAvmService(AVMService service) { fAVMService = service; } /** * Default constructor. */ public AVMNodeService() { } /** * Gets a list of all available node store references * * @return Returns a list of store references */ public List getStores() { // For AVM stores we fake up StoreRefs. List stores = fAVMService.getAVMStores(); List result = new ArrayList(); for (AVMStoreDescriptor desc : stores) { String name = desc.getName(); result.add(new StoreRef(StoreRef.PROTOCOL_AVM, name)); } return result; } /** * Create a new AVM store. * @param protocol the implementation protocol * @param identifier the protocol-specific identifier * @return Returns a reference to the store * @throws StoreExistsException */ public StoreRef createStore(String protocol, String identifier) throws StoreExistsException { StoreRef result = new StoreRef(StoreRef.PROTOCOL_AVM, identifier); invokeBeforeCreateStore(ContentModel.TYPE_STOREROOT, result); try { fAVMService.createAVMStore(identifier); NodeRef rootRef = getRootNode(result); addAspect(rootRef, ContentModel.ASPECT_ROOT, Collections.emptyMap()); invokeOnCreateStore(rootRef); return result; } catch (AVMExistsException e) { throw new StoreExistsException("AVMStore exists", result); } } /** * Does the indicated store exist? * @param storeRef a reference to the store to look for * @return Returns true if the store exists, otherwise false */ public boolean exists(StoreRef storeRef) { return fAVMService.getAVMStore(storeRef.getIdentifier()) != null; } /** * @param nodeRef a reference to the node to look for * @return Returns true if the node exists, otherwise false */ public boolean exists(NodeRef nodeRef) { Object [] avmInfo = AVMNodeConverter.ToAVMVersionPath(nodeRef); int version = (Integer)avmInfo[0]; String avmPath = (String)avmInfo[1]; return fAVMService.lookup(version, avmPath) != null; } /** * Gets the ID of the last transaction that caused the node to change. This includes * deletions, so it is possible that the node being referenced no longer exists. * If the node never existed, then null is returned. * * @param nodeRef a reference to a current or previously existing node * @return Returns the status of the node, or null if the node never existed */ public NodeRef.Status getNodeStatus(NodeRef nodeRef) { // TODO Need to find out if this is important and if so // need to capture Transaction IDs. return new NodeRef.Status("Unknown", !exists(nodeRef)); } /** * @param storeRef a reference to an existing store * @return Returns a reference to the root node of the store * @throws InvalidStoreRefException if the store could not be found */ public NodeRef getRootNode(StoreRef storeRef) throws InvalidStoreRefException { String storeName = storeRef.getIdentifier(); if (fAVMService.getAVMStore(storeName) != null) { return AVMNodeConverter.ToNodeRef(-1, storeName + ":/"); } else { throw new InvalidStoreRefException("Not Found.", storeRef); } } /** * @see #createNode(NodeRef, QName, QName, QName, Map) */ public ChildAssociationRef createNode( NodeRef parentRef, QName assocTypeQName, QName assocQName, QName nodeTypeQName) throws InvalidNodeRefException, InvalidTypeException { return createNode(parentRef, assocTypeQName, assocQName, nodeTypeQName, new HashMap()); } /** * Creates a new, non-abstract, real node as a primary child of the given parent node. * * @param parentRef the parent node * @param assocTypeQName the type of the association to create. This is used * for verification against the data dictionary. * @param assocQName the qualified name of the association * @param nodeTypeQName a reference to the node type * @param properties optional map of properties to keyed by their qualified names * @return Returns a reference to the newly created child association * @throws InvalidNodeRefException if the parent reference is invalid * @throws InvalidTypeException if the node type reference is not recognised * * @see org.alfresco.service.cmr.dictionary.DictionaryService */ public ChildAssociationRef createNode( NodeRef parentRef, QName assocTypeQName, QName assocQName, QName nodeTypeQName, Map properties) throws InvalidNodeRefException, InvalidTypeException { // AVM stores only allow simple child associations. if (!assocTypeQName.equals(ContentModel.ASSOC_CONTAINS)) { throw new InvalidTypeException(assocTypeQName); } String nodeName = assocQName.getLocalName(); Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(parentRef); int version = ((Integer)avmVersionPath[0]); if (version >= 0) { throw new InvalidNodeRefException("Read only store.", parentRef); } String avmPath = (String)avmVersionPath[1]; // Invoke policy behavior. invokeBeforeUpdateNode(parentRef); invokeBeforeCreateNode(parentRef, assocTypeQName, assocQName, nodeTypeQName); // Look up the type definition in the dictionary. TypeDefinition nodeTypeDef = dictionaryService.getType(nodeTypeQName); // Do the creates for supported types, or error out. try { if (nodeTypeQName.equals(ContentModel.TYPE_AVM_PLAIN_FOLDER) || nodeTypeQName.equals(ContentModel.TYPE_FOLDER)) { fAVMService.createDirectory(avmPath, nodeName); } else if (nodeTypeQName.equals(ContentModel.TYPE_AVM_PLAIN_CONTENT) ||nodeTypeQName.equals(ContentModel.TYPE_CONTENT)) { fAVMService.createFile(avmPath, nodeName); } else if (nodeTypeQName.equals(ContentModel.TYPE_AVM_LAYERED_CONTENT)) { NodeRef indirection = (NodeRef)properties.get(ContentModel.PROP_AVM_FILE_INDIRECTION); if (indirection == null) { throw new InvalidTypeException("No Indirection Property", nodeTypeQName); } Object [] indVersionPath = AVMNodeConverter.ToAVMVersionPath(indirection); fAVMService.createLayeredFile((String)indVersionPath[1], avmPath, nodeName); } else if (nodeTypeQName.equals(ContentModel.TYPE_AVM_LAYERED_FOLDER)) { NodeRef indirection = (NodeRef)properties.get(ContentModel.PROP_AVM_DIR_INDIRECTION); if (indirection == null) { throw new InvalidTypeException("No Indirection Property.", nodeTypeQName); } Object [] indVersionPath = AVMNodeConverter.ToAVMVersionPath(indirection); fAVMService.createLayeredDirectory((String)indVersionPath[1], avmPath, nodeName); } else { throw new InvalidTypeException("Invalid node type for AVM.", nodeTypeQName); } addDefaultPropertyValues(nodeTypeDef, properties); addDefaultAspects(nodeTypeDef, avmPath, properties); } catch (AVMNotFoundException e) { throw new InvalidNodeRefException("Not Found.", parentRef); } catch (AVMExistsException e) { throw new InvalidNodeRefException("Child " + nodeName + " exists", parentRef); } String newAVMPath = AVMNodeConverter.ExtendAVMPath(avmPath, nodeName); NodeRef childRef = AVMNodeConverter.ToNodeRef(-1, newAVMPath); addDefaultPropertyValues(nodeTypeDef, properties); addDefaultAspects(nodeTypeDef, newAVMPath, properties); Map props = new HashMap(); for (QName qname : properties.keySet()) { if (isBuiltInProperty(qname)) { continue; } props.put(qname, new PropertyValue(null, properties.get(qname))); } fAVMService.setNodeProperties(newAVMPath, props); ChildAssociationRef ref = new ChildAssociationRef(assocTypeQName, parentRef, assocQName, childRef, true, -1); invokeOnCreateNode(ref); invokeOnUpdateNode(parentRef); if (properties.size() != 0) { invokeOnUpdateProperties(childRef, new HashMap(), properties); } return ref; } /** * Moves the primary location of the given node. *

* This involves changing the node's primary parent and possibly the name of the * association referencing it. *

* If the new parent is in a different store from the original, then the entire * node hierarchy is moved to the new store. Inter-store associations are not * affected. * * @param nodeToMoveRef the node to move * @param newParentRef the new parent of the moved node * @param assocTypeQName the type of the association to create. This is used * for verification against the data dictionary. * @param assocQName the qualified name of the new child association * @return Returns a reference to the newly created child association * @throws InvalidNodeRefException if either the parent node or move node reference is invalid * @throws CyclicChildRelationshipException if the child partakes in a cyclic relationship after the add * * @see #getPrimaryParent(NodeRef) */ public ChildAssociationRef moveNode( NodeRef nodeToMoveRef, NodeRef newParentRef, QName assocTypeQName, QName assocQName) throws InvalidNodeRefException { // AVM stores only allow simple child associations. if (!assocTypeQName.equals(ContentModel.ASSOC_CONTAINS)) { throw new InvalidTypeException(assocTypeQName); } // Extract the parts from the source. Object [] src = AVMNodeConverter.ToAVMVersionPath(nodeToMoveRef); int srcVersion = (Integer)src[0]; if (srcVersion >= 0) { throw new InvalidNodeRefException("Read Only Store.", nodeToMoveRef); } String srcPath = (String)src[0]; String [] splitSrc = null; try { splitSrc = AVMNodeConverter.SplitBase(srcPath); } catch (AVMException e) { throw new InvalidNodeRefException("Invalid src path.", nodeToMoveRef); } String srcParent = splitSrc[0]; if (srcParent == null) { throw new InvalidNodeRefException("Cannot rename root node.", nodeToMoveRef); } String srcName = splitSrc[1]; // Extract and setup the parts of the destination. Object[] dst = AVMNodeConverter.ToAVMVersionPath(newParentRef); if ((Integer)dst[0] >= 0) { throw new InvalidNodeRefException("Read Only Store.", newParentRef); } String dstParent = (String)dst[1]; String dstName = assocQName.getLocalName(); // TODO Invoke policy behavior. Not quite sure how to translate this. NodeRef oldParentRef = AVMNodeConverter.ToNodeRef(-1, srcParent); ChildAssociationRef oldAssocRef = new ChildAssociationRef(assocTypeQName, oldParentRef, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, srcName), nodeToMoveRef, true, -1); invokeBeforeDeleteChildAssociation(oldAssocRef); String dstPath = AVMNodeConverter.ExtendAVMPath(dstParent, dstName); NodeRef newChildRef = AVMNodeConverter.ToNodeRef(-1, dstPath); invokeBeforeCreateChildAssociation(newParentRef, newChildRef, assocTypeQName, assocQName); invokeBeforeUpdateNode(oldParentRef); invokeBeforeUpdateNode(newParentRef); // Actually perform the rename and return a pseudo // ChildAssociationRef. try { fAVMService.rename(srcParent, srcName, dstParent, dstName); ChildAssociationRef newAssocRef = new ChildAssociationRef(assocTypeQName, newParentRef, assocQName, newChildRef, true, -1); invokeOnCreateChildAssociation(newAssocRef); invokeOnDeleteChildAssociation(oldAssocRef); invokeOnUpdateNode(oldParentRef); invokeOnUpdateNode(newParentRef); return newAssocRef; } catch (AVMNotFoundException e) { throw new InvalidNodeRefException("Non existent node.", nodeToMoveRef); } catch (AVMExistsException e) { throw new InvalidNodeRefException("Target already exists.", newParentRef); } catch (AVMException e) { throw new InvalidNodeRefException("Illegal move.", nodeToMoveRef); } } /** * Set the ordering index of the child association. This affects the ordering of * of the return values of methods that return a set of children or child * associations. * * @param childAssocRef the child association that must be moved in the order * @param index an arbitrary index that will affect the return order * * @see #getChildAssocs(NodeRef) * @see #getChildAssocs(NodeRef, QNamePattern, QNamePattern) * @see ChildAssociationRef#getNthSibling() */ public void setChildAssociationIndex( ChildAssociationRef childAssocRef, int index) throws InvalidChildAssociationRefException { // TODO We'll keep this a no-op unless there's a // compelling reason to implement this capability // for the AVM repository. } /** * @param nodeRef * @return Returns the type name * @throws InvalidNodeRefException if the node could not be found * * @see org.alfresco.service.cmr.dictionary.DictionaryService */ public QName getType(NodeRef nodeRef) throws InvalidNodeRefException { Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); AVMNodeDescriptor desc = fAVMService.lookup((Integer)avmVersionPath[0], (String)avmVersionPath[1]); if (desc == null) { throw new InvalidNodeRefException("Not Found.", nodeRef); } if (desc.isPlainDirectory()) { return ContentModel.TYPE_AVM_PLAIN_FOLDER; } else if (desc.isPlainFile()) { return ContentModel.TYPE_AVM_PLAIN_CONTENT; } else if (desc.isLayeredDirectory()) { return ContentModel.TYPE_AVM_LAYERED_FOLDER; } else { return ContentModel.TYPE_AVM_LAYERED_CONTENT; } } /** * Re-sets the type of the node. Can be called in order specialise a node to a sub-type. * * This should be used with caution since calling it changes the type of the node and thus * implies a different set of aspects, properties and associations. It is the calling codes * responsibility to ensure that the node is in a approriate state after changing the type. * * @param nodeRef the node reference * @param typeQName the type QName * * @since 1.1 */ public void setType(NodeRef nodeRef, QName typeQName) throws InvalidNodeRefException { throw new UnsupportedOperationException("AVM Types are immutable."); } /** * Applies an aspect to the given node. After this method has been called, * the node with have all the aspect-related properties present * * @param nodeRef * @param aspectTypeQName the aspect to apply to the node * @param aspectProperties a minimum of the mandatory properties required for * the aspect * @throws InvalidNodeRefException * @throws InvalidAspectException if the class reference is not to a valid aspect * * @see org.alfresco.service.cmr.dictionary.DictionaryService#getAspect(QName) * @see org.alfresco.service.cmr.dictionary.ClassDefinition#getProperties() */ public void addAspect( NodeRef nodeRef, QName aspectTypeQName, Map aspectProperties) throws InvalidNodeRefException, InvalidAspectException { // Check that the aspect exists. AspectDefinition aspectDef = this.dictionaryService.getAspect(aspectTypeQName); if (aspectDef == null) { throw new InvalidAspectException("The aspect is invalid: " + aspectTypeQName, aspectTypeQName); } // Invoke policy behaviors. invokeBeforeUpdateNode(nodeRef); invokeBeforeAddAspect(nodeRef, aspectTypeQName); // Crack the nodeRef. Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); int version = (Integer)avmVersionPath[0]; if (version >= 0) { throw new InvalidNodeRefException("Read Only node.", nodeRef); } String avmPath = (String)avmVersionPath[1]; // Get the current node properties. Map properties = getProperties(nodeRef); // Add the supplied properties. if (aspectProperties != null) { properties.putAll(aspectProperties); } // Now set any unspecified default properties for the aspect. addDefaultPropertyValues(aspectDef, properties); // Now add any cascading aspects. addDefaultAspects(aspectDef, avmPath, properties); // Set the property values on the AVM Node. setProperties(nodeRef, properties); if (isBuiltinAspect(aspectTypeQName)) { // No more work to do in this case. return; } try { fAVMService.addAspect(avmPath, aspectTypeQName); // Invoke policy behaviors. invokeOnUpdateNode(nodeRef); invokeOnAddAspect(nodeRef, aspectTypeQName); } catch (AVMNotFoundException e) { throw new InvalidNodeRefException(nodeRef); } } /** * Add any aspects that are mandatory for the ClassDefinition. * @param classDef The ClassDefinition. * @param path The path to the AVMNode. * @param properties The in/out map of accumulated properties. */ private void addDefaultAspects(ClassDefinition classDef, String path, Map properties) { NodeRef nodeRef = AVMNodeConverter.ToNodeRef(-1, path); // Get mandatory aspects. List defaultAspectDefs = classDef.getDefaultAspects(); // add all the aspects (and there dependent aspects recursively). for (AspectDefinition def : defaultAspectDefs) { invokeBeforeAddAspect(nodeRef, def.getName()); addAspect(nodeRef, def.getName(), Collections.emptyMap()); addDefaultPropertyValues(def, properties); invokeOnAddAspect(nodeRef, def.getName()); // recurse addDefaultAspects(def, path, properties); } } /** * Remove an aspect and all related properties from a node * * @param nodeRef * @param aspectTypeQName the type of aspect to remove * @throws InvalidNodeRefException if the node could not be found * @throws InvalidAspectException if the the aspect is unknown or if the * aspect is mandatory for the class of the node */ public void removeAspect(NodeRef nodeRef, QName aspectTypeQName) throws InvalidNodeRefException, InvalidAspectException { // Invoke policy behaviors. invokeBeforeUpdateNode(nodeRef); invokeBeforeRemoveAspect(nodeRef, aspectTypeQName); AspectDefinition def = dictionaryService.getAspect(aspectTypeQName); if (def == null) { throw new InvalidAspectException(aspectTypeQName); } if (isBuiltinAspect(aspectTypeQName)) { // TODO shouldn't we be throwing some kind of exception here. return; } Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); int version = (Integer)avmVersionPath[0]; if (version >= 0) { throw new InvalidNodeRefException("Read Only Node.", nodeRef); } String path = (String)avmVersionPath[1]; try { if (fAVMService.hasAspect(-1, path, aspectTypeQName)) { fAVMService.removeAspect(path, aspectTypeQName); Map propDefs = def.getProperties(); for (QName propertyName : propDefs.keySet()) { fAVMService.deleteNodeProperty(path, propertyName); } } // Invoke policy behaviors. invokeOnUpdateNode(nodeRef); invokeOnRemoveAspect(nodeRef, aspectTypeQName); } catch (AVMNotFoundException e) { throw new InvalidNodeRefException(nodeRef); } } /** * Determines if a given aspect is present on a node. Aspects may only be * removed if they are NOT mandatory. * * @param nodeRef * @param aspectTypeQName * @return Returns true if the aspect has been applied to the given node, * otherwise false * @throws InvalidNodeRefException if the node could not be found * @throws InvalidAspectException if the aspect reference is invalid */ public boolean hasAspect(NodeRef nodeRef, QName aspectTypeQName) throws InvalidNodeRefException, InvalidAspectException { Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); int version = (Integer)avmVersionPath[0]; String path = (String)avmVersionPath[1]; if (isBuiltinAspect(aspectTypeQName)) { return true; } try { return fAVMService.hasAspect(version, path, aspectTypeQName); } catch (AVMNotFoundException e) { throw new InvalidNodeRefException(nodeRef); } } private static QName [] fgBuiltinAspects = new QName[] { ContentModel.ASPECT_AUDITABLE, ContentModel.ASPECT_REFERENCEABLE }; private boolean isBuiltinAspect(QName aspectQName) { for (QName builtin : fgBuiltinAspects) { if (builtin.equals(aspectQName)) { return true; } } return false; } /** * @param nodeRef * @return Returns a set of all aspects applied to the node, including mandatory * aspects * @throws InvalidNodeRefException if the node could not be found */ public Set getAspects(NodeRef nodeRef) throws InvalidNodeRefException { Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); int version = (Integer)avmVersionPath[0]; String path = (String)avmVersionPath[1]; Set result = new HashSet(); // Add the builtin ones. for (QName name : fgBuiltinAspects) { result.add(name); } try { List aspects = fAVMService.getAspects(version, path); for (QName name : aspects) { result.add(name); } return result; } catch (AVMNotFoundException e) { throw new InvalidNodeRefException(nodeRef); } } /** * Deletes the given node. *

* All associations (both children and regular node associations) * will be deleted, and where the given node is the primary parent, * the children will also be cascade deleted. * * @param nodeRef reference to a node within a store * @throws InvalidNodeRefException if the reference given is invalid */ public void deleteNode(NodeRef nodeRef) throws InvalidNodeRefException { // Invoke policy behaviors. invokeBeforeDeleteNode(nodeRef); Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); if ((Integer)avmVersionPath[0] != -1) { throw new InvalidNodeRefException("Read only store.", nodeRef); } String [] avmPathBase = AVMNodeConverter.SplitBase((String)avmVersionPath[1]); if (avmPathBase[0] == null) { throw new InvalidNodeRefException("Cannot delete root node.", nodeRef); } try { QName nodeTypeQName = getType(nodeRef); Set aspects = getAspects(nodeRef); fAVMService.removeNode(avmPathBase[0], avmPathBase[1]); ChildAssociationRef childAssocRef = new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, AVMNodeConverter.ToNodeRef(-1, avmPathBase[0]), QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, avmPathBase[1]), nodeRef); invokeOnDeleteNode(childAssocRef, nodeTypeQName, aspects, false); } catch (AVMNotFoundException e) { throw new InvalidNodeRefException("Not Found.", nodeRef); } } /** * Makes a parent-child association between the given nodes. Both nodes must belong to the same store. *

* * @param parentRef * @param childRef * @param assocTypeQName the qualified name of the association type as defined in the datadictionary * @param qname the qualified name of the association * @return Returns a reference to the newly created child association * @throws InvalidNodeRefException if the parent or child nodes could not be found * @throws CyclicChildRelationshipException if the child partakes in a cyclic relationship after the add */ public ChildAssociationRef addChild( NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName qname) throws InvalidNodeRefException { // TODO This can be supported theoretically. I'm not sure if // the link operation is semantically equivalent. throw new UnsupportedOperationException("addChild: unsupported"); } /** * Severs all parent-child relationships between two nodes. *

* The child node will be cascade deleted if one of the associations was the * primary association, i.e. the one with which the child node was created. * * @param parentRef the parent end of the association * @param childRef the child end of the association * @throws InvalidNodeRefException if the parent or child nodes could not be found */ public void removeChild(NodeRef parentRef, NodeRef childRef) throws InvalidNodeRefException { // Invoke policy behaviors. // TODO Have to fake up ChildAssocRef. Object [] parentVersionPath = AVMNodeConverter.ToAVMVersionPath(parentRef); if ((Integer)parentVersionPath[0] >= 0) { throw new InvalidNodeRefException("Read only store.", parentRef); } Object [] childVersionPath = AVMNodeConverter.ToAVMVersionPath(parentRef); if ((Integer)childVersionPath[0] >= 0) { throw new InvalidNodeRefException("Read only store.", childRef); } String parentPath = (String)parentVersionPath[1]; String childPath = (String)childVersionPath[1]; String [] childPathBase = AVMNodeConverter.SplitBase(childPath); if (childPathBase[0] == null || !childPathBase[0].equals(parentPath)) { throw new InvalidNodeRefException("Not a child.", childRef); } try { ChildAssociationRef assocRef = new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, AVMNodeConverter.ToNodeRef(-1, parentPath), QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, childPathBase[1]), AVMNodeConverter.ToNodeRef(-1, childPath)); invokeBeforeDeleteChildAssociation(assocRef); fAVMService.removeNode(childPathBase[0], childPathBase[1]); invokeOnDeleteChildAssociation(assocRef); invokeOnUpdateNode(AVMNodeConverter.ToNodeRef(-1, parentPath)); } catch (AVMNotFoundException e) { throw new InvalidNodeRefException("Not found.", childRef); } } /** * @param nodeRef * @return Returns all properties keyed by their qualified name * @throws InvalidNodeRefException if the node could not be found */ public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException { Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); Map props = null; AVMNodeDescriptor desc = fAVMService.lookup((Integer)avmVersionPath[0], (String)avmVersionPath[1]); try { props = fAVMService.getNodeProperties((Integer)avmVersionPath[0], (String)avmVersionPath[1]); } catch (AVMNotFoundException e) { throw new InvalidNodeRefException("Not Found.", nodeRef); } Map result = new HashMap(); for (QName qName : props.keySet()) { PropertyValue value = props.get(qName); PropertyDefinition def = dictionaryService.getProperty(qName); result.put(qName, makeSerializableValue(def, value)); } // Now spoof properties that are built in. result.put(ContentModel.PROP_CREATED, new Date(desc.getCreateDate())); result.put(ContentModel.PROP_CREATOR, desc.getCreator()); result.put(ContentModel.PROP_MODIFIED, new Date(desc.getModDate())); result.put(ContentModel.PROP_MODIFIER, desc.getLastModifier()); result.put(ContentModel.PROP_OWNER, desc.getOwner()); result.put(ContentModel.PROP_NAME, desc.getName()); result.put(ContentModel.PROP_NODE_UUID, "UNKNOWN"); result.put(ContentModel.PROP_NODE_DBID, new Long(desc.getId())); result.put(ContentModel.PROP_STORE_PROTOCOL, "avm"); result.put(ContentModel.PROP_STORE_IDENTIFIER, nodeRef.getStoreRef().getIdentifier()); if (desc.isLayeredDirectory()) { result.put(ContentModel.PROP_AVM_DIR_INDIRECTION, AVMNodeConverter.ToNodeRef(-1, desc.getIndirection())); } if (desc.isLayeredFile()) { result.put(ContentModel.PROP_AVM_FILE_INDIRECTION, AVMNodeConverter.ToNodeRef(-1, desc.getIndirection())); } if (desc.isFile()) { try { ContentData contentData = fAVMService.getContentDataForRead((Integer)avmVersionPath[0], (String)avmVersionPath[1]); result.put(ContentModel.PROP_CONTENT, contentData); } catch (AVMException e) { // TODO For now ignore. } } return result; } /** * @param nodeRef * @param qname the qualified name of the property * @return Returns the value of the property, or null if not yet set * @throws InvalidNodeRefException if the node could not be found */ public Serializable getProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException { Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); if (isBuiltInProperty(qname)) { return getBuiltInProperty(avmVersionPath, qname, nodeRef); } try { PropertyValue value = fAVMService.getNodeProperty((Integer)avmVersionPath[0], (String)avmVersionPath[1], qname); if (value == null) { return null; } PropertyDefinition def = this.dictionaryService.getProperty(qname); return makeSerializableValue(def, value); } catch (AVMNotFoundException e) { throw new InvalidNodeRefException("Not Found.", nodeRef); } } /** * A Helper to spoof built in properties. * @param avmVersionPath The broken out version and path from a NodeRef. * @param qName The name of the property to retrieve. * @param nodeRef The original NodeRef (for error reporting). * @return The property value. */ private Serializable getBuiltInProperty(Object [] avmVersionPath, QName qName, NodeRef nodeRef) { if (qName.equals(ContentModel.PROP_CONTENT)) { try { ContentData contentData = fAVMService.getContentDataForRead((Integer)avmVersionPath[0], (String)avmVersionPath[1]); return contentData; } catch (AVMException e) { // TODO This seems very wrong. Do something better // sooner rather than later. return null; } } AVMNodeDescriptor desc = fAVMService.lookup((Integer)avmVersionPath[0], (String)avmVersionPath[1]); if (desc == null) { throw new InvalidNodeRefException("Not Found.", nodeRef); } if (qName.equals(ContentModel.PROP_CREATED)) { return new Date(desc.getCreateDate()); } else if (qName.equals(ContentModel.PROP_CREATOR)) { return desc.getCreator(); } else if (qName.equals(ContentModel.PROP_MODIFIED)) { return new Date(desc.getModDate()); } else if (qName.equals(ContentModel.PROP_MODIFIER)) { return desc.getLastModifier(); } else if (qName.equals(ContentModel.PROP_OWNER)) { return desc.getOwner(); } else if (qName.equals(ContentModel.PROP_NAME)) { return desc.getName(); } else if (qName.equals(ContentModel.PROP_NODE_UUID)) { return "UNKNOWN"; } else if (qName.equals(ContentModel.PROP_NODE_DBID)) { return new Long(desc.getId()); } else if (qName.equals(ContentModel.PROP_STORE_PROTOCOL)) { return "avm"; } else if (qName.equals(ContentModel.PROP_STORE_IDENTIFIER)) { return nodeRef.getStoreRef().getIdentifier(); } else if (qName.equals(ContentModel.PROP_AVM_DIR_INDIRECTION)) { if (desc.isLayeredDirectory()) { return AVMNodeConverter.ToNodeRef(-1, desc.getIndirection()); } return null; } else if (qName.equals(ContentModel.PROP_AVM_FILE_INDIRECTION)) { if (desc.isLayeredFile()) { return AVMNodeConverter.ToNodeRef(-1, desc.getIndirection()); } return null; } else { fgLogger.error("Invalid Built In Property: " + qName); return null; } } /** * Set the values of all properties to be an Serializable instances. * The properties given must still fulfill the requirements of the class and * aspects relevant to the node. *

* NOTE: Null values are allowed. * * @param nodeRef * @param properties all the properties of the node keyed by their qualified names * @throws InvalidNodeRefException if the node could not be found */ public void setProperties(NodeRef nodeRef, Map properties) throws InvalidNodeRefException { Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); if ((Integer)avmVersionPath[0] >= 0) { throw new InvalidNodeRefException("Read only store.", nodeRef); } // TODO Not sure this try block is necessary. try { // Invoke policy behaviors. invokeBeforeUpdateNode(nodeRef); Map oldProps = getProperties(nodeRef); fAVMService.deleteNodeProperties((String)avmVersionPath[1]); Map values = new HashMap(); for (QName qName : properties.keySet()) { // For AVM nodes is in place. if (isBuiltInProperty(qName)) { if (qName.equals(ContentModel.PROP_CONTENT)) { AVMNodeDescriptor desc = fAVMService.lookup(-1, (String)avmVersionPath[1]); if (desc == null) { throw new InvalidNodeRefException("Not Found.", nodeRef); } if (desc.isPlainFile()) { fAVMService.setContentData((String)avmVersionPath[1], (ContentData)properties.get(qName)); } } } values.put(qName, new PropertyValue(null, properties.get(qName))); } fAVMService.setNodeProperties((String)avmVersionPath[1], values); // Invoke policy behaviors. invokeOnUpdateNode(nodeRef); invokeOnUpdateProperties(nodeRef, oldProps, properties); } catch (AVMNotFoundException e) { throw new InvalidNodeRefException("Not Found.", nodeRef); } } static QName [] fgBuiltinProperties = new QName [] { ContentModel.PROP_CREATED, ContentModel.PROP_CREATOR, ContentModel.PROP_MODIFIED, ContentModel.PROP_MODIFIER, ContentModel.PROP_OWNER, ContentModel.PROP_CONTENT, ContentModel.PROP_NAME, ContentModel.PROP_NODE_UUID, ContentModel.PROP_NODE_DBID, ContentModel.PROP_STORE_PROTOCOL, ContentModel.PROP_STORE_IDENTIFIER, ContentModel.PROP_AVM_FILE_INDIRECTION, ContentModel.PROP_AVM_DIR_INDIRECTION }; /** * Helper to distinguish built-in from generic properties. * @param qName The name of the property to check. * @return Whether qName is a built-in propety. */ private boolean isBuiltInProperty(QName qName) { for (QName name : fgBuiltinProperties) { if (name.equals(qName)) { return true; } } return false; } /** * Sets the value of a property to be any Serializable instance. * To remove a property value, use {@link #getProperties(NodeRef)}, remove the * value and call {@link #setProperties(NodeRef, Map)}. *

* NOTE: Null values are allowed. * * @param nodeRef * @param qname the fully qualified name of the property * @param propertyValue the value of the property - never null * @throws InvalidNodeRefException if the node could not be found */ public void setProperty(NodeRef nodeRef, QName qname, Serializable value) throws InvalidNodeRefException { // Invoke policy behaviors. invokeBeforeUpdateNode(nodeRef); Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); if ((Integer)avmVersionPath[0] >= 0) { throw new InvalidNodeRefException("Read only store.", nodeRef); } if (isBuiltInProperty(qname)) { if (qname.equals(ContentModel.PROP_CONTENT)) { try { fAVMService.setContentData((String)avmVersionPath[1], (ContentData)value); } catch (ClassCastException e) { throw new AVMException("Invalid ContentData."); } } return; } try { Map propsBefore = getProperties(nodeRef); fAVMService.setNodeProperty((String)avmVersionPath[1], qname, new PropertyValue(null, value)); Map propsAfter = getProperties(nodeRef); // Invoke policy behaviors. invokeOnUpdateNode(nodeRef); invokeOnUpdateProperties(nodeRef, propsBefore, propsAfter); } catch (AVMNotFoundException e) { throw new InvalidNodeRefException("Not Found.", nodeRef); } } /** * @param nodeRef the child node * @return Returns a list of all parent-child associations that exist where the given * node is the child * @throws InvalidNodeRefException if the node could not be found * * @see #getParentAssocs(NodeRef, QNamePattern, QNamePattern) */ public List getParentAssocs(NodeRef nodeRef) throws InvalidNodeRefException { // TODO OK, for now we'll simply return the single parent that corresponds // to the path stuffed in the NodeRef. Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); String path = (String)avmVersionPath[1]; List result = new ArrayList(); String [] splitPath = AVMNodeConverter.SplitBase(path); if (splitPath[0] == null) { return result; } result.add(new ChildAssociationRef(ContentModel.ASSOC_CONTAINS, AVMNodeConverter.ToNodeRef((Integer)avmVersionPath[0], splitPath[0]), QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, splitPath[1]), nodeRef, true, -1)); return result; } /** * Gets all parent associations where the pattern of the association qualified * name is a match *

* The resultant list is ordered by (a) explicit index and (b) association creation time. * * @param nodeRef the child node * @param typeQNamePattern the pattern that the type qualified name of the association must match * @param qnamePattern the pattern that the qnames of the assocs must match * @return Returns a list of all parent-child associations that exist where the given * node is the child * @throws InvalidNodeRefException if the node could not be found * * @see ChildAssociationRef#getNthSibling() * @see #setChildAssociationIndex(ChildAssociationRef, int) * @see QName * @see org.alfresco.service.namespace.RegexQNamePattern#MATCH_ALL */ public List getParentAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) throws InvalidNodeRefException { if (!typeQNamePattern.isMatch(ContentModel.ASSOC_CONTAINS)) { return new ArrayList(); } List result = getParentAssocs(nodeRef); if (result.size() == 0) { return result; } if (qnamePattern.isMatch(result.get(0).getQName())) { return result; } return new ArrayList(); } /** * Get all child associations of the given node. *

* The resultant list is ordered by (a) explicit index and (b) association creation time. * * @param nodeRef the parent node - usually a container * @return Returns a collection of ChildAssocRef instances. If the * node is not a container then the result will be empty. * @throws InvalidNodeRefException if the node could not be found * * @see #getChildAssocs(NodeRef, QNamePattern, QNamePattern) * @see #setChildAssociationIndex(ChildAssociationRef, int) * @see ChildAssociationRef#getNthSibling() */ public List getChildAssocs(NodeRef nodeRef) throws InvalidNodeRefException { Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); int version = (Integer)avmVersionPath[0]; String path = (String)avmVersionPath[1]; List result = new ArrayList(); SortedMap children = null; try { children = fAVMService.getDirectoryListing(version, path); } catch (AVMNotFoundException e) { return result; } for (String name : children.keySet()) { result.add(new ChildAssociationRef(ContentModel.ASSOC_CONTAINS, nodeRef, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), AVMNodeConverter.ToNodeRef( version, AVMNodeConverter.ExtendAVMPath(path, name)), true, -1)); } return result; } /** * Gets all child associations where the pattern of the association qualified * name is a match. * * @param nodeRef the parent node - usually a container * @param typeQNamePattern the pattern that the type qualified name of the association must match * @param qnamePattern the pattern that the qnames of the assocs must match * @return Returns a list of ChildAssocRef instances. If the * node is not a container then the result will be empty. * @throws InvalidNodeRefException if the node could not be found * * @see QName * @see org.alfresco.service.namespace.RegexQNamePattern#MATCH_ALL */ public List getChildAssocs( NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) throws InvalidNodeRefException { List result = new ArrayList(); if (!typeQNamePattern.isMatch(ContentModel.ASSOC_CONTAINS)) { return result; } List all = getChildAssocs(nodeRef); for (ChildAssociationRef child : all) { if (!qnamePattern.isMatch(child.getQName())) { continue; } result.add(child); } return result; } /** * Get a child NodeRef by name. * @param nodeRef The parent node. * @param assocTypeQName The type of the Child Association. * @param childName The name of the child to get. */ public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName) { if (!assocTypeQName.equals(ContentModel.ASSOC_CONTAINS)) { return null; } Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); try { AVMNodeDescriptor child = fAVMService.lookup((Integer)avmVersionPath[0], (String)avmVersionPath[1]); if (child == null) { return null; } return AVMNodeConverter.ToNodeRef((Integer)avmVersionPath[0], child.getPath()); } catch (AVMException e) { return null; } } /** * Fetches the primary parent-child relationship. *

* For a root node, the parent node reference will be null. * * @param nodeRef * @return Returns the primary parent-child association of the node * @throws InvalidNodeRefException if the node could not be found */ public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException { List parents = getParentAssocs(nodeRef); if (parents.size() == 0) { return new ChildAssociationRef(null, null, null, nodeRef); } return parents.get(0); } /** * * @param sourceRef a reference to a real node * @param targetRef a reference to a node * @param assocTypeQName the qualified name of the association type * @return Returns a reference to the new association * @throws InvalidNodeRefException if either of the nodes could not be found * @throws AssociationExistsException */ public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) throws InvalidNodeRefException, AssociationExistsException { throw new UnsupportedOperationException("AVM does not support arbitrary associations."); } /** * * @param sourceRef the associaton source node * @param targetRef the association target node * @param assocTypeQName the qualified name of the association type * @throws InvalidNodeRefException if either of the nodes could not be found */ public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) throws InvalidNodeRefException { throw new UnsupportedOperationException("AVM does not support arbitrary associations."); } /** * Fetches all associations from the given source where the associations' * qualified names match the pattern provided. * * @param sourceRef the association source * @param qnamePattern the association qname pattern to match against * @return Returns a list of NodeAssocRef instances for which the * given node is a source * @throws InvalidNodeRefException if the source node could not be found * * @see QName * @see org.alfresco.service.namespace.RegexQNamePattern#MATCH_ALL */ public List getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern) throws InvalidNodeRefException { return new ArrayList(); } /** * Fetches all associations to the given target where the associations' * qualified names match the pattern provided. * * @param targetRef the association target * @param qnamePattern the association qname pattern to match against * @return Returns a list of NodeAssocRef instances for which the * given node is a target * @throws InvalidNodeRefException * * @see QName * @see org.alfresco.service.namespace.RegexQNamePattern#MATCH_ALL */ public List getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern) throws InvalidNodeRefException { return new ArrayList(); } /** * The root node has an entry in the path(s) returned. For this reason, there * will always be at least one path element in the returned path(s). * The first element will have a null parent reference and qname. * * @param nodeRef * @return Returns the path to the node along the primary node path * @throws InvalidNodeRefException if the node could not be found * * @see #getPaths(NodeRef, boolean) */ public Path getPath(NodeRef nodeRef) throws InvalidNodeRefException { // TODO Review later. This may be wrong. Path path = new Path(); Object [] avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); int version = (Integer)avmVersionPath[0]; String currPath = (String)avmVersionPath[1]; while (!currPath.endsWith("/")) { String [] splitPath = AVMNodeConverter.SplitBase(currPath); String parentPath = splitPath[0]; String name = splitPath[1]; ChildAssociationRef caRef = new ChildAssociationRef(ContentModel.ASSOC_CONTAINS, AVMNodeConverter.ToNodeRef(version, parentPath), QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), AVMNodeConverter.ToNodeRef(version, currPath), true, -1); path.prepend(new Path.ChildAssocElement(caRef)); currPath = parentPath; } ChildAssociationRef caRef = new ChildAssociationRef(null, null, null, AVMNodeConverter.ToNodeRef(version, currPath)); path.prepend(new Path.ChildAssocElement(caRef)); return path; } /** * The root node has an entry in the path(s) returned. For this reason, there * will always be at least one path element in the returned path(s). * The first element will have a null parent reference and qname. * * @param nodeRef * @param primaryOnly true if only the primary path must be retrieved. If true, the * result will have exactly one entry. * @return Returns a List of all possible paths to the given node * @throws InvalidNodeRefException if the node could not be found */ public List getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException { List result = new ArrayList(); result.add(getPath(nodeRef)); return result; } /** * Get the node where archived items will have gone when deleted from the given store. * * @param storeRef the store that items were deleted from * @return Returns the archive node parent */ public NodeRef getStoreArchiveNode(StoreRef storeRef) { throw new UnsupportedOperationException("AVM does not support this operation."); } /** * Restore an individual node (along with its sub-tree nodes) to the target location. * The archived node must have the {@link org.alfresco.model.ContentModel#ASPECT_ARCHIVED archived aspect} * set against it. * * @param archivedNodeRef the archived node * @param destinationParentNodeRef the parent to move the node into * or null to use the original * @param assocTypeQName the primary association type name to use in the new location * or null to use the original * @param assocQName the primary association name to use in the new location * or null to use the original * @return Returns the reference to the newly created node */ public NodeRef restoreNode( NodeRef archivedNodeRef, NodeRef destinationParentNodeRef, QName assocTypeQName, QName assocQName) { throw new UnsupportedOperationException("AVM does not support this operation."); } }