Java tutorial
/* Copyright (C) 2007-2011 BlueXML - www.bluexml.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** * */ package com.bluexml.side.Integration.alfresco.xforms.webscript; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.Map.Entry; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.model.FileExistsException; 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.ContentIOException; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.ISO9075; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xml.serialize.OutputFormat; import org.apache.xml.serialize.XMLSerializer; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Adapted from BxDS dataLayer module.<br/> * Amendments are essentially commenting, javadoc-ing and deletion of sections * not used by * AlfrescoController. * * @author Amenel */ public class DataLayer implements DataLayerInterface { public static String defaultStorePath = "/app:company_home/app:dictionary/cm:BlueXMLDATA"; private final static String ALF_CONTENT_URI = "{http://www.alfresco.org/model/content/1.0}"; private static final String BLUEXML_MODEL_URI = "http://www.bluexml.com/model/content/"; private static final String VIEW_FIELD_SEPARATOR = "."; private static final String VIEW_TOKEN_SEPARATOR = "@"; private static Log logger = LogFactory.getLog(DataLayer.class); protected static DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTime(); private ServiceRegistry serviceRegistry; private AuthenticationService authenticationService; private DictionaryService dictionaryService; private NodeService nodeService; private boolean inTransaction; private TreeMap<String, NodeRef> parentFolderCache = null; // format enum for generated properties: affects the BlueXML URI namespace only public enum FormatPattern { // use the uuid NONE("UUID"), // the first text field is used for the label FIRST("FIRST"), // all text fields ALLTEXT("ALLTEXT"), // all fields, including dates and numerical ones. ALL("ALL"); private final String associatedKeyText; private FormatPattern(String keyText) { this.associatedKeyText = keyText; } public String get() { return associatedKeyText; } @Override public String toString() { return get(); } } public DataLayer(ServiceRegistry serviceRegistry) { super(); this.serviceRegistry = serviceRegistry; this.authenticationService = serviceRegistry.getAuthenticationService(); this.dictionaryService = serviceRegistry.getDictionaryService(); this.nodeService = serviceRegistry.getNodeService(); this.inTransaction = false; this.parentFolderCache = null; } /* * (non-Javadoc) * @see * * com.bluexml.side.Integration.alfresco.xforms.webscript.DataLayerInterface# * create(java.lang * .String, org.w3c.dom.Element, java.lang.String) */ @SuppressWarnings("unchecked") public NodeRef create(String where, Element what, String nodeName) throws Exception { XmlParser parser = new XmlParser(); // parse the object to get a Map representation of it Map<String, Object> entry = null; entry = parser.parse(what); // resolve QualifiedName to QName QName nodeTypeQName = getDataTypeQName((String) entry.get("dataType")); // build attribute values Map<QName, Serializable> properties = buildAttributesMap((Map<String, Object>) entry.get("attributes"), nodeTypeQName, false); QName assocTypeQName = ContentModel.ASSOC_CONTAINS; // create the node NodeRef newNode = createNode(where, assocTypeQName, null, nodeTypeQName, nodeName, properties); // create the associations createAssociations(newNode, nodeTypeQName, (List<AssociationBean>) entry.get("associations")); return newNode; } /* * (non-Javadoc) * @see * * com.bluexml.side.Integration.alfresco.xforms.webscript.DataLayerInterface# * update(java.lang * .String, org.w3c.dom.Element) */ @SuppressWarnings("unchecked") public NodeRef update(String nodeId, Element what) throws Exception { NodeRef nodeToUpdate = new NodeRef(nodeId); XmlParser parser = new XmlParser(); // get Map representation of the object Map<String, Object> entry = null; entry = parser.parse(what); // resolve QualifiedName to QName String datatype = (String) entry.get("dataType"); QName nodeTypeQName = getDataTypeQName(datatype); // build the map that contains attribute values Map<QName, Serializable> properties = buildAttributesMap((Map<String, Object>) entry.get("attributes"), nodeTypeQName, true); // update properties updateProperties(nodeToUpdate, properties); // delete previous associations? String associationsAction = (String) entry.get("associationsAction"); boolean deleteAllAssociations = StringUtils.equals(associationsAction, "replace"); // update associations List<AssociationBean> list = (List<AssociationBean>) entry.get("associations"); updateAssociations(nodeToUpdate, nodeTypeQName, list, deleteAllAssociations); return nodeToUpdate; } /** * Mass tagging. Implements applying the same set of properties to several * nodes. */ @SuppressWarnings("unchecked") public String updateMassTagging(String nodeIds, Element what) throws Exception { XmlParser parser = new XmlParser(); // get Map representation of the object Map<String, Object> entry = null; entry = parser.parse(what); // resolve QualifiedName to QName String datatype = (String) entry.get("dataType"); QName nodeTypeQName = getDataTypeQName(datatype); // build the map that contains attribute values Map<QName, Serializable> properties = buildAttributesMap((Map<String, Object>) entry.get("attributes"), nodeTypeQName, true); // StringBuffer result = new StringBuffer(""); String[] ids = StringUtils.split(nodeIds, ','); for (String nodeId : ids) { try { // update properties NodeRef nodeToUpdate = new NodeRef(nodeId); updateProperties(nodeToUpdate, properties); result.append(nodeToUpdate.toString()); } catch (Exception e) { logger.error("Error when updating object " + nodeId, e); } } return result.toString(); } /* * (non-Javadoc) * @see * * com.bluexml.side.Integration.alfresco.xforms.webscript.DataLayerInterface# * request(java.lang * .String) */ public List<NodeRef> request(String xpath) throws Exception { // Amenel: replaced Repository.getStoreRef with a direct reference to // STORE_REF_WORKSPACE_SPACESSTORE // NodeRef root = nodeService.getRootNode(Repository.getStoreRef()); NodeRef root = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); String encodedXpath = xpath; //** #1544 if (encodedXpath.indexOf(' ') != -1) { String[] fragments = xpath.split("/"); String prefix; String local; int pos; for (int i = 0; i < fragments.length; i++) { pos = fragments[i].indexOf(':'); if (pos > 1) { prefix = fragments[i].substring(0, pos); local = fragments[i].substring(pos + 1); fragments[i] = prefix + ":" + ISO9075.encode(local); } } encodedXpath = StringUtils.join(fragments, "/"); } //** #1544 ResultSet result = serviceRegistry.getSearchService().query(root.getStoreRef(), SearchService.LANGUAGE_XPATH, encodedXpath); return result.getNodeRefs(); } /* * (non-Javadoc) * @see * * com.bluexml.side.Integration.alfresco.xforms.webscript.DataLayerInterface# * deleteNode(java * .lang.String) */ public void delete(String objectId) { NodeRef nodeRef = new NodeRef(objectId); List<ChildAssociationRef> assos = this.serviceRegistry.getNodeService().getChildAssocs(nodeRef); for (ChildAssociationRef asso : assos) { QName associationName = asso.getTypeQName(); if (associationName.getNamespaceURI().startsWith(BLUEXML_MODEL_URI)) { NodeRef childRef = asso.getChildRef(); delete(childRef.toString()); } } nodeService.deleteNode(nodeRef); } /* * (non-Javadoc) * @see * * com.bluexml.side.Integration.alfresco.xforms.webscript.DataLayerInterface# * read(java.lang. * String) */ public String read(String objectId) { Document resultDocument = readAsDocument(objectId); String result = convertDocument2String(resultDocument); return result; } /** * Reads an object from the repository. * * @param objectId * the full node reference * @return a DOM representation of the object, including all properties and * associations. */ private Document readAsDocument(String objectId) { NodeRef nodeRef = new NodeRef(objectId); QName type = nodeService.getType(nodeRef); Map<QName, Serializable> properties = nodeService.getProperties(nodeRef); Map<QName, Serializable> convertedproperties = new HashMap<QName, Serializable>(); for (Entry<QName, Serializable> entry : properties.entrySet()) { QName propertyName = entry.getKey(); Serializable convertedValue = entry.getValue(); // must convert value to proper one according to properties definitions PropertyDefinition propertyDef = dictionaryService.getProperty(propertyName); convertedValue = makePropertyValue(propertyDef, convertedValue); if (convertedValue instanceof Date) { convertedValue = dateTimeFormatter.print(((Date) convertedValue).getTime()); } convertedproperties.put(propertyName, convertedValue); } Document resultDocument = XmlBuilder.buildEntry(type, objectId, convertedproperties, getAllAssociations(nodeRef)); return resultDocument; } /** * Creates a folder in the repository. * * @param parentPath * @param nodeName * @return * @throws Exception */ private NodeRef createFolder(String parentPath, String nodeName) throws Exception { String lparentPath = parentPath; if (lparentPath == null) { lparentPath = defaultStorePath; } QName assocTypeQName = ContentModel.ASSOC_CONTAINS; QName assocQName = QName.createQName(ALF_CONTENT_URI + nodeName); QName nodeTypeQName = QName.createQName(ALF_CONTENT_URI + "folder"); NodeRef result = createNode(lparentPath, assocTypeQName, assocQName, nodeTypeQName, nodeName, null); return result; } /** * Create a node using the specified parameters. * * @param where * the parent container * @param assocTypeQName * the relation between the parent and the node to create * @param assocQName * the qualified name of the association * @param nodeTypeQName * the type of the node * @param nodeName * the name we want the node to appear under * @param properties * can be null * @return * @throws Exception */ private NodeRef createNode(String where, QName assocTypeQName, QName assocQName, QName nodeTypeQName, String nodeName, Map<QName, Serializable> properties) throws Exception { String lwhere = where; if (lwhere == null) { lwhere = getDefaultNodePath(nodeTypeQName); } if (logger.isDebugEnabled()) { logger.debug(" createNode: NodePath=" + where); } NodeRef parent = createPath(lwhere); NodeRef newNode = null; QName lassocQName = assocQName; if (lassocQName == null) { lassocQName = QName.createQName(ALF_CONTENT_URI + parent.getId()); } if (properties != null) { newNode = nodeService.createNode(parent, assocTypeQName, lassocQName, nodeTypeQName, properties) .getChildRef(); } else { newNode = nodeService.createNode(parent, assocTypeQName, lassocQName, nodeTypeQName).getChildRef(); } // set NodeName if (nodeName != null) { try { serviceRegistry.getFileFolderService().rename(newNode, nodeName); } catch (FileExistsException e) { logger.debug("Failed to rename", e); } } logger.debug(" NodeProperties :" + nodeService.getProperties(newNode)); return newNode; } /* * (non-Javadoc) * @see * * com.bluexml.side.Integration.alfresco.xforms.webscript.DataLayerInterface# * createPath(java * .lang.String) */ public NodeRef createPath(String where) throws Exception { // fix for bug #931: we may be in a transaction. If so, we MUST not try to create the same // folder twice. Otherwise, the transaction gets marked for rollback! NodeRef parent = (isInTransaction() ? getParentFolderNodeRef(where) : null); if (parent == null) { List<NodeRef> results = request(where); if (results.size() == 0) { // create the missing folder int index = where.lastIndexOf("/"); int indexName = where.lastIndexOf(":"); String folderName = where.substring(indexName + 1); String folderPath = where.substring(0, index); parent = createFolder(folderPath, folderName); } else { parent = results.get(0); } // any parent must find its way into our transaction cache if (isInTransaction()) { addParentFolderNodeRef(where, parent); } } return parent; } /** * Returns the first QName (among available types) whose local name matches * the data type name. * * @param dataTypeName * @return */ private QName getDataTypeQName(String dataTypeName) { Collection<QName> datatypes = dictionaryService.getAllTypes(); for (QName type : datatypes) { if (type.getLocalName().equals(dataTypeName)) { return type; } } logger.error("DataType not found :" + dataTypeName); return null; } /** * Returns the first QName (among available properties from all registered * (and live ?) models) * whose local name matches the property name. * * @param propertyName * @return */ private QName getPropertyQName(String propertyName) { Collection<QName> properties = dictionaryService.getAllProperties(null); for (QName type : properties) { if (type.getLocalName().equals(propertyName)) { return type; } } logger.error("Property not found :" + propertyName); return null; } /** * Returns the first QName (among associations defined for the specified * type) whose local name * matches the association name. Since we are constrained to a type, there * will be, most * probably, only one such QName. * * @param type * @param associationQualifiedName * @return */ private QName getAssociationQName(QName type, String associationQualifiedName) { TypeDefinition def = dictionaryService.getType(type); for (QName key : def.getAssociations().keySet()) { if (key.getLocalName().equals(associationQualifiedName)) { return key; } } logger.error("Association Not Found :" + associationQualifiedName + " for type :" + type); return null; } /** * Builds a Map that complies with the alfresco createNode method. * * @param attributesMap * Map<localName,value> * @param nodeTypeQName * @return * @throws Exception */ private Map<QName, Serializable> buildAttributesMap(Map<String, Object> attributesMap, QName nodeTypeQName, boolean isUpdating) throws Exception { final String CREATOR = ALF_CONTENT_URI + "creator"; final String CREATED = ALF_CONTENT_URI + "created"; Map<QName, PropertyDefinition> properties = dictionaryService.getType(nodeTypeQName).getProperties(); // // build mandatory properties with default values Map<QName, Serializable> mandatoryProperties = new HashMap<QName, Serializable>(); // #1296: if updating, the object exists and all mandatory props are set, so skip this step if (!isUpdating) { mandatoryProperties.put(QName.createQName(CREATOR), authenticationService.getCurrentUserName()); mandatoryProperties.put(QName.createQName(CREATED), new Date()); for (QName propQName : properties.keySet()) { PropertyDefinition def = properties.get(propQName); if (def.isMandatory()) { mandatoryProperties.put(propQName, def.getDefaultValue()); } } } // // resolve qualified names to QNames and convert values Map<QName, Serializable> resultMap = new HashMap<QName, Serializable>(); for (String attName : attributesMap.keySet()) { // resolve Qualified attribute name to QName QName resolvedName = getPropertyQName(attName); // TODO : BEWARE of Multi-valued fields if (resolvedName != null) { // build attribute object ton convert him to write type PropertyDefinition propertyDef = dictionaryService.getProperty(nodeTypeQName, resolvedName); Serializable value = makePropertyValue(propertyDef, (Serializable) attributesMap.get(attName)); resultMap.put(resolvedName, value); } else { logger.error("Attribute Not Found :" + attName); } } // if not present add mandatory properties to the result Map Set<QName> newSet = new HashSet<QName>(); newSet.addAll(mandatoryProperties.keySet()); newSet.addAll(resultMap.keySet()); for (QName prop : newSet) { if (!resultMap.containsKey(prop)) { resultMap.put(prop, mandatoryProperties.get(prop)); } } return resultMap; } /** * Extracted from AbstractNodeService.<-- original DataLayer comment <br/> * * @param propertyDef * @param pValue * @return */ @SuppressWarnings("unchecked") private Serializable makePropertyValue(PropertyDefinition propertyDef, Serializable pValue) { Serializable value = pValue; // get property attributes QName propertyTypeQName = null; if (propertyDef == null) // property not recognised { // allow it for now - persisting excess properties can be useful // sometimes propertyTypeQName = DataTypeDefinition.ANY; } else { propertyTypeQName = propertyDef.getDataType().getName(); // check that multi-valued properties are allowed boolean isMultiValued = propertyDef.isMultiValued(); if (isMultiValued && !(value instanceof Collection)) { if (value != null) { // put the value into a collection // the implementation gives back a Serializable list value = (Serializable) Collections.singletonList(value); } } else if (!isMultiValued && (value instanceof Collection)) { // we only allow this case if the property type is ANY if (!propertyTypeQName.equals(DataTypeDefinition.ANY)) { throw new DictionaryException( "A single-valued property of this type may not be a collection: \n" + " Property: " + propertyDef + "\n" + " Type: " + propertyTypeQName + "\n" + " Value: " + value); } } } try { return convertType(propertyTypeQName, value); } catch (TypeConversionException e) { String classe = "unknown"; if (value != null) { classe = value.getClass().toString(); } throw new TypeConversionException( "The property value is not compatible with the type defined for the property: \n" + " property: " + (propertyDef == null ? "unknown" : propertyDef) + "\n" + " value: " + value + "\n" + " value type: " + classe, e); } } /** * Determines whether the value is legal for the given type by trying to * convert the value to * the type. If not, an exception is thrown. * * @param type * @param candidate * the value to check * @return */ private static Serializable convertType(QName type, Serializable candidate) { if (candidate == null) { return null; } return new PropertyValue(type, candidate).getValue(type); } /** * Create associations on a node.<br/> * Association classes are no longer supported. * * @param nodeRef * @param source * @param list * : a Map that describes the association set to create * <ul> * <li>Key : association QualifiedName</li> * <li>Value : Map<String, String> asso</li> * </ul> * asso :<br> * </br> * <ul> * <li>target : the target NodeRef</li> * <li>targetQualifiedName : the target qualified name</li> * </ul> * @return */ private NodeRef createAssociations(NodeRef nodeRef, QName source, List<AssociationBean> list) { for (AssociationBean associationBean : list) { createAssociation(nodeRef, source, associationBean); } return nodeRef; } /** * Creates a single association originating from a node. Depending on the * information in the * bean, creates a child or simple association. * * @param nodeRef * @param source * @param associationBean */ private synchronized void createAssociation(NodeRef nodeRef, QName source, AssociationBean associationBean) { if (logger.isDebugEnabled()) { logger.debug("createAssociation : NodeRef =" + nodeRef + " NodeType=" + source + " AssoBean=" + associationBean); } // extract datas from assos Map QName targetQualifiedName = getDataTypeQName(associationBean.getTargetQualifiedName()); String targetRef = associationBean.getTargetId(); // get targetNodeRef NodeRef targetNodeRef = new NodeRef(targetRef); QName assoType = getAssociationQName(source, associationBean.getAssociationName()); AssociationDefinition assoDef = dictionaryService.getAssociation(assoType); boolean success; if (assoDef.isChild()) { // association is a ChildAssociation success = createChildAssociation(assoType, nodeRef, targetQualifiedName, targetNodeRef); } else { // association is a simple association success = createSimpleAssociation(nodeRef, assoType, targetNodeRef); } if (success == false) { logger.error("createAssociation: failure."); } else { if (logger.isDebugEnabled()) { logger.debug("createAssociation: success"); } } } /** * Creates a child association. * * @param assoType * @param nodeRef * the parent node * @param targetQualifiedName * @param targetNodeRef * the child node * @return false if an exception occurred */ private boolean createChildAssociation(QName assoType, NodeRef nodeRef, QName targetQualifiedName, NodeRef targetNodeRef) { ChildAssociationRef assoc = new ChildAssociationRef(assoType, nodeRef, targetQualifiedName, targetNodeRef, false, -1); try { this.nodeService.addChild(assoc.getParentRef(), assoc.getChildRef(), assoc.getTypeQName(), assoc.getTypeQName()); } catch (AssociationExistsException e) { logger.warn(e.getMessage()); return false; } return true; } /** * Creates a simple association of the specified type between the node and * the target. * * @param nodeRef * @param assoType * @param targetNodeRef * @return false if an exception occurred */ private boolean createSimpleAssociation(NodeRef nodeRef, QName assoType, NodeRef targetNodeRef) { AssociationRef assoc = new AssociationRef(null, nodeRef, assoType, targetNodeRef); try { this.nodeService.createAssociation(assoc.getSourceRef(), assoc.getTargetRef(), assoc.getTypeQName()); } catch (AssociationExistsException e) { logger.error(e.getMessage(), e); return false; } return true; } /** * Updates properties of a node. * * @param nodeToUpdate * @param properties */ private void updateProperties(NodeRef nodeToUpdate, Map<QName, Serializable> properties) { for (Entry<QName, Serializable> entry : properties.entrySet()) { QName propertyName = entry.getKey(); Serializable convertedValue = entry.getValue(); // must convert value to proper one according to properties definitions PropertyDefinition propertyDef = dictionaryService.getProperty(propertyName); convertedValue = makePropertyValue(propertyDef, convertedValue); // set property value nodeService.setProperty(nodeToUpdate, propertyName, convertedValue); } } /** * Updates the associations on a node. * * @param nodeRef * @param nodeType * @param list * the associations Map see createAssociations() * @param deleteAllAssociations * @return * @throws Exception */ private NodeRef updateAssociations(NodeRef nodeRef, QName nodeType, List<AssociationBean> list, boolean deleteAllAssociations) throws Exception { logger.debug("UpdateAssociations"); if (deleteAllAssociations) { // remove all associations of any Type !! QName removeNodeType = nodeService.getType(nodeRef); removeAllAssociationsForType(nodeRef, removeNodeType); for (AssociationBean associationBean : list) { try { createAssociation(nodeRef, nodeType, associationBean); } catch (Exception e) { logger.error("Failed to process association", e); } } } else { for (AssociationBean associationBean : list) { try { QName assoType = getAssociationQName(nodeType, associationBean.getAssociationName()); AssociationBean.Actions ca = associationBean.getAction(); if (ca.equals(AssociationBean.Actions.ADD)) { createAssociation(nodeRef, nodeType, associationBean); } else if (ca.equals(AssociationBean.Actions.DELETE)) { removeAssociation(nodeRef, nodeType, associationBean); } else if (ca.equals(AssociationBean.Actions.ADD_DELETE_OTHER)) { removeAllAssociationsWithType(nodeRef, assoType); createAssociation(nodeRef, nodeType, associationBean); } else if (ca.equals(AssociationBean.Actions.DELETE_ALL)) { removeAllAssociationsWithType(nodeRef, assoType); } } catch (Exception e) { logger.error("Failed to process association", e); } } } return nodeRef; } /** * Removes all associations of a specified association type where the node * is either the source * or the parent. * * @param nodeRef * @param assoQName * the type of associations to remove */ private void removeAllAssociationsWithType(NodeRef nodeRef, QName assoQName) { AssociationDefinition assoDef = dictionaryService.getAssociation(assoQName); if (!assoDef.isChild()) { // the node is the source of the relationship List<AssociationRef> assos = null; assos = nodeService.getTargetAssocs(nodeRef, assoQName); for (AssociationRef asso : assos) { deleteSimpleAssociation(asso); } } else { // the node is the parent List<ChildAssociationRef> assos = null; assos = nodeService.getChildAssocs(nodeRef); for (ChildAssociationRef asso : assos) { ChildAssociationRef childAssocRef = new ChildAssociationRef(assoQName, asso.getParentRef(), assoQName, asso.getChildRef(), false, -1); nodeService.removeChildAssociation(childAssocRef); } } } /** * Removes all associations that are valid for a specific data type, * including inherited * associations. Any previous associations involving the node as parent or * source are removed. * Assocs as target or child are kept. * * @param nodeRef * @param nodeType */ private void removeAllAssociationsForType(NodeRef nodeRef, QName nodeType) { ClassDefinition nodeClass = dictionaryService.getType(nodeType); // remove all child and source associations defined in content type for (QName assoQName : nodeClass.getAssociations().keySet()) { removeAllAssociationsWithType(nodeRef, assoQName); } // remove remaining assocs with children. Really any such assocs ? List<ChildAssociationRef> assos = nodeService.getChildAssocs(nodeRef); for (ChildAssociationRef asso : assos) { nodeService.removeChild(asso.getParentRef(), asso.getChildRef()); } // remove associations inherited from ancestor content types QName parentName = nodeClass.getParentName(); if (parentName != null) { removeAllAssociationsForType(nodeRef, parentName); } } /** * Removes the single association where this node is the source and the * target has the id * carried by the bean. * * @param nodeRef * @param nodeType * @param associationBean */ private void removeAssociation(NodeRef nodeRef, QName nodeType, AssociationBean associationBean) { logger.debug("RemoveAssociation : NodeRef =" + nodeRef + " NodeType=" + nodeType + " AssoBean=" + associationBean); QName assoType = getAssociationQName(nodeType, associationBean.getAssociationName()); AssociationRef asso = new AssociationRef(null, nodeRef, assoType, new NodeRef(associationBean.getTargetId())); deleteSimpleAssociation(asso); } /** * @param asso * the association to delete */ private void deleteSimpleAssociation(AssociationRef asso) { nodeService.removeAssociation(asso.getSourceRef(), asso.getTargetRef(), asso.getTypeQName()); } /** * @param doc * @return */ private static String convertDocument2String(Document doc) { StringBuilder stringBuilder = new StringBuilder(); try { ByteArrayOutputStream stream = new ByteArrayOutputStream(); OutputFormat outputformat = new OutputFormat(); outputformat.setEncoding("UTF-8"); outputformat.setIndenting(false); outputformat.setPreserveSpace(true); outputformat.setOmitXMLDeclaration(true); outputformat.setOmitDocumentType(true); XMLSerializer serializer = new XMLSerializer(); serializer.setOutputFormat(outputformat); serializer.setOutputByteStream(stream); serializer.asDOMSerializer(); serializer.serialize(doc.getDocumentElement()); stringBuilder = new StringBuilder(stream.toString("UTF-8")); // # 1295 } catch (Exception e) { e.printStackTrace(); } return stringBuilder.toString(); } /** * Collects an AssociationBean object for each item linked to the nodeRef. * * @param nodeRef * @return the list of associations */ private List<AssociationBean> getAllAssociations(NodeRef nodeRef) { List<AssociationBean> associations = new ArrayList<AssociationBean>(); TypeDefinition def = dictionaryService.getType(nodeService.getType(nodeRef)); // get the qnames of associations in the data type definition List<QName> directAssociations = new ArrayList<QName>(def.getAssociations().keySet()); // add associations where the data type is the source for (QName directAssociation : directAssociations) { addAssociations(nodeRef, directAssociation, associations); } // add associations where the data type is the parent List<ChildAssociationRef> assos = nodeService.getChildAssocs(nodeRef); for (ChildAssociationRef asso : assos) { QName associationName = asso.getTypeQName(); if (associationName.getNamespaceURI().startsWith(BLUEXML_MODEL_URI)) { NodeRef childRef = asso.getChildRef(); String targetLabel = getLabelForNode(childRef, FormatPattern.ALLTEXT.get(), false); associations.add(new AssociationBean(associationName.getLocalName(), childRef.toString(), targetLabel, AssociationBean.AssoType.Composition, nodeService.getType(childRef).getLocalName())); } } return associations; } /** * Adds the associations (other than child assocs) that match the given * association name. Only * those in the BlueXML namespace are added. Assocs may be multiple: several * associated items * with the same qname. * * @param nodeRef * @param directAssociation * @param associations */ private void addAssociations(NodeRef nodeRef, QName directAssociation, List<AssociationBean> associations) { List<AssociationRef> assos = nodeService.getTargetAssocs(nodeRef, directAssociation); // there may be multiple items in the list if the assoc is multiple for (AssociationRef asso : assos) { QName associationName = asso.getTypeQName(); if (associationName.getNamespaceURI().startsWith(BLUEXML_MODEL_URI)) { NodeRef childRef = asso.getTargetRef(); String targetLabel = getLabelForNode(childRef, FormatPattern.ALLTEXT.get(), false); associations.add(new AssociationBean(associationName.getLocalName(), childRef.toString(), targetLabel, nodeService.getType(childRef).getLocalName())); } } } /** * Builds a user-friendly label for a node on the basis of its properties. * * @param nodeRef * @param pattern * the pattern for formatting the label for the node. Contains * references to * properties of the node or of an association. This text is * taken "as-is", with no * decoding, encoding, escaping or unescaping done. May be * <code>null</code>. * @param includeSystemProps * if <code>true</code>, system properties are also considered, * in addition to * properties from the data models. * @return */ public String getLabelForNode(NodeRef nodeRef, String pattern, boolean includeSystemProps) { String lpattern = pattern; if (StringUtils.trimToNull(lpattern) == null) { lpattern = FormatPattern.ALLTEXT.get(); } if (StringUtils.equals(lpattern, FormatPattern.NONE.get())) { return nodeRef.toString(); } // filter to exclude system properties if necessary Map<QName, Serializable> properties = collectProperties(nodeRef, includeSystemProps); Set<QName> names = properties.keySet(); // if (StringUtils.equals(lpattern, FormatPattern.FIRST.get())) { return getLabelForNodeFirst(nodeRef, properties, names); } else if (StringUtils.equals(lpattern, FormatPattern.ALLTEXT.get())) { return getLabelForNodeAllText(nodeRef, properties, names); } else if (StringUtils.equals(lpattern, FormatPattern.ALL.get())) { return getLabelForNodeAll(nodeRef, properties, names); } else { // Interpret the format pattern return getNameForNode(lpattern, nodeRef, properties, includeSystemProps); } } /** * Builds a label for the given node from values of its properties or * associations. The format * is a list of tokens separated by the view token separator. A token is * either a property value * (the name of the property is directly given as a placeholder for the * value) or some static * text (enclosed in curly braces). * * @param pattern * the format pattern, in plain text (meaning non URL-encoded) * @param nodeRef * @param properties * the set of properties from which to find property tokens * @param includeSystemProps * <code>true</code> if the * @return */ private String getNameForNode(String pattern, NodeRef nodeRef, Map<QName, Serializable> properties, boolean includeSystemProps) { String result = ""; String view = pattern; StringTokenizer st = new StringTokenizer(view, VIEW_TOKEN_SEPARATOR); boolean lastTokenWasText = false; boolean first = true; while (st.hasMoreTokens()) { String token = st.nextToken().trim(); if (token.startsWith("{") && token.endsWith("}")) { result += token.substring(1, token.length() - 1); first = false; lastTokenWasText = true; } else if (token.contains(VIEW_FIELD_SEPARATOR)) { StringTokenizer st2 = new StringTokenizer(token, VIEW_FIELD_SEPARATOR); String clazz = null; String attribute = null; String association = null; if (st2.hasMoreTokens()) { clazz = st2.nextToken(); } if (st2.hasMoreTokens()) { attribute = st2.nextToken(); } if (st2.hasMoreTokens()) { association = st2.nextToken(); } if (clazz != null) { // QName nodeType = nodeService.getType(nodeRef); if (nodeType.getLocalName().endsWith("_" + clazz)) { if (association == null) { if (attribute != null) { for (QName qname : properties.keySet()) { String key = qname.getLocalName(); if (key.endsWith("_" + attribute)) { Serializable value = properties.get(qname); if (value != null && value.toString().length() > 0) { if (lastTokenWasText == false) { result += " "; } result += value; lastTokenWasText = false; first = false; } } } } } else { if (attribute != null) { Map<String, List<AssociationRef>> nodeAssos = collectAssociations(nodeRef); String associationFirstPartName = nodeType.toString() + "_" + association; String associationName = getAssociationNameStartingBy(associationFirstPartName, nodeAssos); if (associationName.length() > 0) { List<?> assocs = nodeAssos.get(associationName); for (Object o : assocs) { if (o instanceof AssociationRef) { AssociationRef ref = (AssociationRef) o; Map<QName, Serializable> targetProperties = nodeService .getProperties(ref.getTargetRef()); QName targetType = nodeService.getType(ref.getTargetRef()); if (lastTokenWasText == false) { result += " "; } result += getPropertyValue(targetProperties, targetType, attribute); lastTokenWasText = false; first = false; } } } } } } else { logger.error("The class " + clazz + " is not appropriate for this node ('" + nodeRef.toString() + "')."); } } } else { // simple attribute for (QName qname : properties.keySet()) { String key = qname.getLocalName(); if (key.endsWith("_" + token) || (includeSystemProps == true && (key.endsWith(token)))) {// #1529 Serializable value = properties.get(qname); if (value != null && value.toString().length() > 0) { if (lastTokenWasText == false && first == false) { result += " "; } result += value; lastTokenWasText = false; first = false; } } } } } if (result.length() == 0) { return nodeRef.getId(); } // return result.substring(0, result.length() - 1); return result; } /** * Adapted from org.alfresco.web.bean.repository.Node::getAssociations<br/> * Returns the target associations of the node that stem from a generation. * * @param nodeRef * the source node * @return the list of target associations */ private final Map<String, List<AssociationRef>> collectAssociations(NodeRef nodeRef) { Map<String, List<AssociationRef>> result = new HashMap<String, List<AssociationRef>>(); List<AssociationRef> assocs = nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL); for (AssociationRef assocRef : assocs) { QName typeQName = assocRef.getTypeQName(); String assocName = typeQName.toString(); if (typeQName.getNamespaceURI().startsWith(BLUEXML_MODEL_URI)) { List<AssociationRef> list = result.get(assocName); // create the list if this is first association with 'assocName' if (list == null) { list = new ArrayList<AssociationRef>(); result.put(assocName, list); } // add the association to the list list.add(assocRef); } } return result; } /** * Returns a set of properties of the node. * * @param nodeRef * @param includeSystemProps * if <code>true</code>, system properties are not filtered out. * @return */ private final Map<QName, Serializable> collectProperties(NodeRef nodeRef, boolean includeSystemProps) { Map<QName, Serializable> result = new HashMap<QName, Serializable>(); Map<QName, Serializable> properties = nodeService.getProperties(nodeRef); if (includeSystemProps == true) { // #1529 return properties; } Set<QName> qnames = properties.keySet(); for (QName qname : qnames) { if (qname.getNamespaceURI().startsWith(BLUEXML_MODEL_URI)) { result.put(qname, properties.get(qname)); } } return result; } /** * @param associationFirstPartName * @param map * @return */ private String getAssociationNameStartingBy(String associationFirstPartName, Map<String, ?> map) { String associatioName = ""; for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) { String name = i.next(); if (name.startsWith(associationFirstPartName)) { associatioName = name; } } return associatioName; } /** * Return (if found) the value of an attribute * * @param targetProperties * @param targetType * @param attributeName * @return */ private String getPropertyValue(Map<QName, Serializable> targetProperties, QName targetType, String attributeName) { String name = targetType.toString() + "_" + attributeName; QName qname = QName.createQName(name); if (targetProperties.containsKey(qname)) { if (targetProperties.get(qname) != null) { return (String) targetProperties.get(qname); } return ""; } if (dictionaryService.getType(targetType).getParentName() != null) { return getPropertyValue(targetProperties, dictionaryService.getType(targetType).getParentName(), attributeName); } return ""; } /** * Gets the value of the first non-null textual property in the BlueXML * namespace. * * @param nodeRef * @param properties * @param names * @return The first text property value, or the node id if no textual * property is found. */ private String getLabelForNodeFirst(NodeRef nodeRef, Map<QName, Serializable> properties, Set<QName> names) { Serializable propValue; for (QName propertyName : names) { if (propertyName.getNamespaceURI().startsWith(BLUEXML_MODEL_URI)) { PropertyDefinition propertyDef = dictionaryService.getProperty(propertyName); if (propertyDef != null) { // may happen if propertyName was removed from the // current version of the content type String propTypeName = propertyDef.getDataType().getJavaClassName(); if (StringUtils.equalsIgnoreCase(propTypeName, "java.lang.String")) { propValue = makePropertyValue(propertyDef, properties.get(propertyName)); if (propValue != null) { String propStr = propValue.toString(); if (StringUtils.trimToNull(propStr) != null) { return propStr; } } } } } } return nodeRef.getId(); } /** * Gets a label built from all non-null text fields found amongst the * properties in the BlueXML * namespace. Each value is prefixed with the property's title. e.g. * "First Name: John, Last Name: Doe, Hometown: Here-and-There". * * @param nodeRef * @param properties * @param names * @return the computed label, or the node id if the label is empty. */ private String getLabelForNodeAllText(NodeRef nodeRef, Map<QName, Serializable> properties, Set<QName> names) { Serializable propValue; String res = ""; boolean first = true; for (QName propertyName : names) { if (propertyName.getNamespaceURI().startsWith(BLUEXML_MODEL_URI)) { PropertyDefinition propertyDef = dictionaryService.getProperty(propertyName); if (propertyDef != null) { // may happen if propertyName was removed from the // current version of the content type String propTypeName = propertyDef.getDataType().getJavaClassName(); if (StringUtils.equalsIgnoreCase(propTypeName, "java.lang.String")) { propValue = makePropertyValue(propertyDef, properties.get(propertyName)); if (propValue != null) { String propStr = propValue.toString(); if (StringUtils.trimToNull(propStr) != null) { if (first == false) { res += ", "; } res += StringUtils.trimToEmpty(getDisplayLabelForProperty(propertyDef)) + ": " + propStr; first = false; } } } } } } return StringUtils.trimToNull(res) == null ? nodeRef.getId() : res; } /** * Gets a label built from all non-null properties in the BlueXML namespace. * Similar to {@link #getLabelForNodeAllText(Map, Set)} except there's no * restriction to textual fields. * * @param nodeRef * @param properties * @param names * @return the computed label, or the node id if the label is empty. */ private String getLabelForNodeAll(NodeRef nodeRef, Map<QName, Serializable> properties, Set<QName> names) { Serializable propValue; String res = ""; int nb = 0; for (QName propertyName : names) { if (propertyName.getNamespaceURI().startsWith(BLUEXML_MODEL_URI)) { PropertyDefinition propertyDef = dictionaryService.getProperty(propertyName); if (propertyDef != null) { // may happen if propertyName was removed from the // current version of the content type propValue = makePropertyValue(propertyDef, properties.get(propertyName)); if (propValue != null) { String propStr = propValue.toString(); if (StringUtils.trimToNull(propStr) != null) { if (nb > 0) { res += ", "; } res += StringUtils.trimToEmpty(getDisplayLabelForProperty(propertyDef)) + ": " + propStr; nb++; } } } } } return StringUtils.trimToNull(res) == null ? nodeRef.getId() : res; } /** * Computes a label for a property definition in a BlueXML model. * * @param propertyDef * @return the title set in the model, or the name (in case no title was * defined) */ private String getDisplayLabelForProperty(PropertyDefinition propertyDef) { String res = propertyDef.getTitle(); return StringUtils.trimToNull(res) == null ? propertyDef.getName().getLocalName() : res; } /** * @return the inTransaction */ private boolean isInTransaction() { return inTransaction; } /** * Sets the "in transaction" status and <b>clears</b> the folder cache. * * @param inTransaction * the inTransaction to set */ public void setInTransaction(boolean inTransaction) { this.inTransaction = inTransaction; // MANDATORY: clear the folder cache clearParentFolderCache(); } /** * Returns the cached NodeRef for the folder at "where". * <p/> * Part of correction for bug #931. Part of the monitor on the cache. * * @param where * the location of the folder * @return the noderef */ private synchronized NodeRef getParentFolderNodeRef(String where) { if (parentFolderCache == null) { clearParentFolderCache(); } return parentFolderCache.get(where); } /** * Adds a noderef associated with a folder location. * <p/> * Part of correction for bug #931. Also enforces a monitor on the cache. * * @param where * @param parent */ private synchronized void addParentFolderNodeRef(String where, NodeRef parent) { if (parentFolderCache == null) { clearParentFolderCache(); } parentFolderCache.put(where, parent); } /** * Part of correction for bug #931. */ private synchronized void clearParentFolderCache() { parentFolderCache = new TreeMap<String, NodeRef>(); } private String getDefaultNodePath(QName nodeType) { QName lnodeType = getDataTypeQName(nodeType.getLocalName()); return defaultStorePath + "/cm:" + lnodeType.getLocalName(); } /* * (non-Javadoc) * @see * * com.bluexml.side.Integration.alfresco.xforms.webscript.DataLayerInterface# * attachContent(java * .lang.String, java.lang.String) */ public boolean attachContent(String receiver, String filename, String filepath, String mimeType, String contentType, boolean shouldAppendSuffix) throws Exception { NodeRef newNode = new NodeRef(receiver); QName nodeTypeQName = QName.createQName(BLUEXML_MODEL_URI + "1.0", contentType); boolean applyName = (StringUtils.trimToNull(filename) != null); String resultId = uploadContentToNode(newNode, filename, filepath, mimeType, nodeTypeQName, applyName, shouldAppendSuffix); if (StringUtils.trimToNull(resultId) == null) { return false; } return true; } /** * @param newNode * an existing node * @param filename * the display name that should be set on the node if the content * writing succeeds. * NO GUARANTEE is given about whether the name is indeed * applied. * @param filepath * @param mimeType * @param nodeTypeQName * the node content type. Non-BlueXML types are supported. * @param applyName * if false, there will be no attempt to set the file name * @param shouldAppendSuffix * if the renaming is activated, an index [e.g. '(1)'] may be * appended to the * filename. There will be no failure due to an unavailable name. * @return the node ref (protocol, store and id) to the node, or empty * string if exception. */ public String uploadContentToNode(NodeRef newNode, String filename, String filepath, String mimeType, QName nodeTypeQName, boolean applyName, boolean shouldAppendSuffix) { String resultId, FAILURE = ""; ContentWriter writer = serviceRegistry.getContentService().getWriter(newNode, nodeTypeQName, false); // set MIME type property writer.setMimetype(mimeType); // upload the file content to the node long sizeUploaded = writeFileContentIntoNode(filepath, writer); if (sizeUploaded != -1) { // set other properties Map<QName, Serializable> properties = this.serviceRegistry.getNodeService().getProperties(newNode); properties.put(ContentModel.PROP_CONTENT, writer.getContentData()); this.serviceRegistry.getNodeService().setProperties(newNode, properties); resultId = StringEscapeUtils.escapeXml(newNode.toString()); logger.debug(" File '" + filename + "' (size: " + sizeUploaded + ") uploaded to node: " + resultId); // set the node name if (applyName) { if (shouldAppendSuffix) { String currentName = filename; String baseName = getFileNamePart(filename); String ext = getExtensionPart(filename); int idx = 1; // we'll consider "base.ext" equivalent to "base(1).ext" while (idx < 1000) { // is there any need to go beyond this ? try { serviceRegistry.getFileFolderService().rename(newNode, currentName); break; } catch (FileExistsException e) { idx++; currentName = baseName + "(" + idx + ")"; if (ext != null) { currentName += "." + ext; } } catch (org.alfresco.service.cmr.model.FileNotFoundException e) { logger.debug("Failed to rename: the node to rename does not exist!", e); return FAILURE; } // TODO: automatically rename previous versions to extend their index when // the renaming finally succeeds ? i.e. if (1)..(9) exist and the renaming // succeeds at (10), should we rename (1) into (01), (2) into (02), etc. ? } } else { try { serviceRegistry.getFileFolderService().rename(newNode, filename); } catch (FileExistsException e) { logger.debug("Failed to rename: the file already exists!", e); return FAILURE; } catch (org.alfresco.service.cmr.model.FileNotFoundException e) { logger.debug("Failed to rename: the node to rename does not exist!", e); return FAILURE; } } } return resultId; } return FAILURE; } /** * @param filename * @return */ private String getFileNamePart(String filename) { int pos = filename.lastIndexOf('.'); if (pos == -1) { return filename; } return filename.substring(0, pos); } /** * @param filename * @return */ private String getExtensionPart(String filename) { int pos = filename.lastIndexOf('.'); if (pos == -1) { return null; } int length = filename.length(); // pos = pos + 1; if (pos == length) { return ""; } return filename.substring(pos, length); } /** * Writes the content of a file into a node. The file is read as a binary * file. * * @param filename * the name of the node * @param filepath * complete path to the file, including name and extension * @param location * address of a folder in the content repository * @param writer * the appropriate content writer to the node. <b>Must have been * already gotten from * the content service.</b> * @return the length in bytes of the uploaded content if no exception was * thrown during the * process, -1 otherwise */ private long writeFileContentIntoNode(String filepath, ContentWriter writer) { File theFile = new File(filepath); long length = theFile.length(); try { writer.putContent(theFile); } catch (ContentIOException e) { return -1; } return length; } /** * Tells whether this node is referenced (i.e. "pointed to") via a specific * association. Normal * associations (type 'Simple' and 'Aggregation' in the class modeler) and * ChildAssociations * (type 'Composition' in the modeler) are supported. * * @param nodeRef * @param filterAssoc * the qualified name (sequence of packages and local name) of * the association * @param whether * the association is a composition * @return true if an association reference pointing to the node already * exists. */ public boolean isRefencenced(NodeRef nodeRef, String filterAssoc, boolean isComposition) { if (isComposition) { List<ChildAssociationRef> toChildAssoList = serviceRegistry.getNodeService().getParentAssocs(nodeRef); for (ChildAssociationRef assoRef : toChildAssoList) { QName assoTypeQName = assoRef.getTypeQName(); if (assoTypeQName.getNamespaceURI().startsWith(BLUEXML_MODEL_URI) && assoTypeQName.getLocalName().equals(filterAssoc)) { return true; } } } else { List<AssociationRef> toList = serviceRegistry.getNodeService().getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL); for (AssociationRef assoRef : toList) { QName assoTypeQName = assoRef.getTypeQName(); if (assoTypeQName.getNamespaceURI().startsWith(BLUEXML_MODEL_URI) && assoTypeQName.getLocalName().equals(filterAssoc)) { return true; } } } return false; } }