/* * #%L * Alfresco Records Management Module * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * - * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * - * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Alfresco 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 Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.module.org_alfresco_module_rm.admin; import static org.springframework.extensions.surf.util.ParameterCheck.mandatory; import static org.springframework.extensions.surf.util.ParameterCheck.mandatoryString; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.caveat.RMListOfValuesConstraint; import org.alfresco.module.org_alfresco_module_rm.caveat.RMListOfValuesConstraint.MatchLogic; import org.alfresco.module.org_alfresco_module_rm.compatibility.CompatibilityModel; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementCustomModel; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.relationship.RelationshipDefinition; import org.alfresco.module.org_alfresco_module_rm.relationship.RelationshipDisplayName; import org.alfresco.module.org_alfresco_module_rm.relationship.RelationshipService; import org.alfresco.repo.dictionary.IndexTokenisationMode; import org.alfresco.repo.dictionary.M2Aspect; import org.alfresco.repo.dictionary.M2Constraint; import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.dictionary.M2Property; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.policy.annotation.Behaviour; import org.alfresco.repo.policy.annotation.BehaviourBean; import org.alfresco.repo.policy.annotation.BehaviourKind; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authority.RMAuthority; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.GUID; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.Ordered; import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.surf.util.URLDecoder; /** * Records Management AdminService Implementation. * * @author Neil McErlean, janv */ @BehaviourBean public class RecordsManagementAdminServiceImpl extends RecordsManagementAdminBase implements RecordsManagementAdminService, RecordsManagementCustomModel, NodeServicePolicies.OnAddAspectPolicy, NodeServicePolicies.OnRemoveAspectPolicy, NodeServicePolicies.OnCreateNodePolicy, ApplicationListener<ContextRefreshedEvent>, Ordered { /** I18N messages*/ private static final String MSG_SERVICE_NOT_INIT = "rm.admin.service-not-init"; private static final String MSG_PROP_EXIST = "rm.admin.prop-exist"; private static final String MSG_CUSTOM_PROP_EXIST = "rm.admin.custom-prop-exist"; private static final String MSG_UNKNOWN_ASPECT = "rm.admin.unknown-aspect"; private static final String MSG_CONSTRAINT_EXISTS = "rm.admin.constraint-exists"; private static final String MSG_CANNOT_FIND_CONSTRAINT = "rm.admin.contraint-cannot-find"; private static final String MSG_UNEXPECTED_TYPE_CONSTRAINT = "rm.admin.unexpected_type_constraint"; private static final String MSG_ERROR_CLIENT_ID = "rm.admin.error-client-id"; /** Constants */ private static final String CUSTOM_CONSTRAINT_TYPE = org.alfresco.module.org_alfresco_module_rm.caveat.RMListOfValuesConstraint.class.getName(); private static final String CAPATIBILITY_CUSTOM_CONTRAINT_TYPE = org.alfresco.module.org_alfresco_module_dod5015.caveat.RMListOfValuesConstraint.class.getName(); private static final String PARAM_ALLOWED_VALUES = "allowedValues"; private static final String PARAM_CASE_SENSITIVE = "caseSensitive"; private static final String PARAM_MATCH_LOGIC = "matchLogic"; /** Relationship service */ private RelationshipService relationshipService; /** Transaction service */ private TransactionService transactionService; /** List of types that can be customisable */ private List<QName> pendingCustomisableTypes; private Map<QName, QName> customisableTypes; /** indicates whether the custom map has been initialised or not */ private boolean isCustomMapInit = false; /** * @param transactionService transaction service */ public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; } /** * @param relationshipService The relationship service instance */ public void setRelationshipService(RelationshipService relationshipService) { this.relationshipService = relationshipService; } /** * Gets the relationship service instance * * @return The relationship service instance */ protected RelationshipService getRelationshipService() { return this.relationshipService; } /** * Indicate that this application content listener must be executed with the lowest * precedence. (ie last) * * @see Ordered#getOrder() */ @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } /** * Load the custom properties map * * @see ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) */ @Override public void onApplicationEvent(ContextRefreshedEvent event) { if(!isCustomMapInit && getDictionaryService().getAllModels().contains(RM_CUSTOM_MODEL)) { // run as System on bootstrap AuthenticationUtil.runAs(new RunAsWork<Object>() { public Object doWork() { RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>() { public Void execute() { // initialise custom properties initCustomMap(); return null; } }; transactionService.getRetryingTransactionHelper().doInTransaction(callback); return null; } }, AuthenticationUtil.getSystemUserName()); } } /** * Helper method to indicate whether the custom map is initialised or not. * * @return boolean true if initialised, false otherwise */ public boolean isCustomMapInit() { return isCustomMapInit; } /** * @see org.alfresco.repo.node.NodeServicePolicies.OnAddAspectPolicy#onAddAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) */ @Override @Behaviour ( kind = BehaviourKind.CLASS, isService = true, notificationFrequency = NotificationFrequency.FIRST_EVENT ) public void onAddAspect(final NodeRef nodeRef, final QName aspectTypeQName) { if (isCustomMapInit) { AuthenticationUtil.runAs(new RunAsWork<Void>() { @Override public Void doWork() { if (getNodeService().exists(nodeRef) && getDictionaryService().getAllModels().contains(RM_CUSTOM_MODEL) && isCustomisable(aspectTypeQName)) { QName customPropertyAspect = getCustomAspect(aspectTypeQName); getNodeService().addAspect(nodeRef, customPropertyAspect, null); } return null; } }, AuthenticationUtil.getSystemUserName()); } } /** * @see org.alfresco.repo.node.NodeServicePolicies.OnRemoveAspectPolicy#onRemoveAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) */ @Override @Behaviour ( kind = BehaviourKind.CLASS, isService = true, notificationFrequency = NotificationFrequency.FIRST_EVENT ) public void onRemoveAspect(final NodeRef nodeRef, final QName aspectTypeQName) { if (isCustomMapInit) { AuthenticationUtil.runAs(new RunAsWork<Void>() { @Override public Void doWork() { if (getNodeService().exists(nodeRef) && isCustomisable(aspectTypeQName)) { QName customPropertyAspect = getCustomAspect(aspectTypeQName); getNodeService().removeAspect(nodeRef, customPropertyAspect); } return null; } }, AuthenticationUtil.getSystemUserName()); } } /** * Make sure any custom property aspects are applied to newly created nodes. * * @see org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy#onCreateNode(org.alfresco.service.cmr.repository.ChildAssociationRef) */ @Override @Behaviour ( kind = BehaviourKind.CLASS, isService = true, notificationFrequency = NotificationFrequency.FIRST_EVENT ) public void onCreateNode(final ChildAssociationRef childAssocRef) { if (isCustomMapInit) { AuthenticationUtil.runAs(new RunAsWork<Void>() { @Override public Void doWork() { if (getDictionaryService().getAllModels().contains(RecordsManagementCustomModel.RM_CUSTOM_MODEL)) { NodeRef nodeRef = childAssocRef.getChildRef(); QName type = getNodeService().getType(nodeRef); while (type != null && !ContentModel.TYPE_CMOBJECT.equals(type)) { if (isCustomisable(type)) { QName customPropertyAspect = getCustomAspect(type); getNodeService().addAspect(nodeRef, customPropertyAspect, null); } TypeDefinition def = getDictionaryService().getType(type); if (def != null) { type = def.getParentName(); } else { type = null; } } } return null; } }, AuthenticationUtil.getSystemUserName()); } } /** * @param customisableTypes list of string representations of the type qnames that are customisable */ public void setCustomisableTypes(List<String> customisableTypes) { mandatory("customisableTypes", customisableTypes); pendingCustomisableTypes = new ArrayList<QName>(); for (String customisableType : customisableTypes) { pendingCustomisableTypes.add(QName.createQName(customisableType, getNamespaceService())); } } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#getCustomisable() */ public Set<QName> getCustomisable() { return getCustomisableMap().keySet(); } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#getCustomisable(org.alfresco.service.cmr.repository.NodeRef) */ @Override public Set<QName> getCustomisable(NodeRef nodeRef) { mandatory("nodeRef", nodeRef); Set<QName> result = new HashSet<QName>(5); // Check the nodes hierarchy for customisable types QName type = getNodeService().getType(nodeRef); while (type != null && !ContentModel.TYPE_CMOBJECT.equals(type)) { // Add to the list if the type is customisable if (isCustomisable(type)) { result.add(type); } // Type and get the types parent TypeDefinition def = getDictionaryService().getType(type); if (def != null) { type = def.getParentName(); } else { type = null; } } // Get all the nodes aspects Set<QName> aspects = getNodeService().getAspects(nodeRef); for (QName aspect : aspects) { QName tempAspect = QName.createQName(aspect.toString()); while (tempAspect != null) { // Add to the list if the aspect is customisable if (isCustomisable(tempAspect)) { result.add(tempAspect); } // Try and get the parent aspect AspectDefinition aspectDef = getDictionaryService().getAspect(tempAspect); if (aspectDef != null) { tempAspect = aspectDef.getParentName(); } else { tempAspect = null; } } } return result; } /** * Initialise custom type map */ private void initCustomMap() { customisableTypes = new HashMap<QName, QName>(7); Collection<QName> aspects = getDictionaryService().getAspects(RM_CUSTOM_MODEL); for (QName aspect : aspects) { AspectDefinition aspectDef = getDictionaryService().getAspect(aspect); String name = aspectDef.getName().getLocalName(); if (name.endsWith("Properties")) { QName type = null; String prefixString = aspectDef.getDescription(getDictionaryService()); if (prefixString == null) { // Backward compatibility from previous RM V1.0 custom models if (CompatibilityModel.NAME_CUSTOM_RECORD_PROPERTIES.equals(name)) { type = RecordsManagementModel.ASPECT_RECORD; } else if (CompatibilityModel.NAME_CUSTOM_RECORD_FOLDER_PROPERTIES.equals(name)) { type = RecordsManagementModel.TYPE_RECORD_FOLDER; } else if (CompatibilityModel.NAME_CUSTOM_RECORD_CATEGORY_PROPERTIES.equals(name)) { type = RecordsManagementModel.TYPE_RECORD_CATEGORY; } else if (CompatibilityModel.NAME_CUSTOM_RECORD_SERIES_PROPERTIES.equals(name) && // Only add the deprecated record series type as customisable if // a v1.0 installation has added custom properties aspectDef.getProperties().size() != 0) { type = CompatibilityModel.TYPE_RECORD_SERIES; } } else { type = QName.createQName(prefixString, getNamespaceService()); } // Add the customisable type to the map if (type != null) { customisableTypes.put(type, aspect); // Remove customisable type from the pending list if (pendingCustomisableTypes != null && pendingCustomisableTypes.contains(type)) { pendingCustomisableTypes.remove(type); } } } } // Deal with any pending types left over if (pendingCustomisableTypes != null && pendingCustomisableTypes.size() != 0) { NodeRef modelRef = getCustomModelRef(RecordsManagementModel.RM_CUSTOM_URI); M2Model model = readCustomContentModel(modelRef); try { for (QName customisableType : pendingCustomisableTypes) { QName customAspect = getCustomAspectImpl(customisableType); // Create the new aspect to hold the custom properties M2Aspect aspect = model.createAspect(customAspect.toPrefixString(getNamespaceService())); aspect.setDescription(customisableType.toPrefixString(getNamespaceService())); // Make a record of the customisable type customisableTypes.put(customisableType, customAspect); } } finally { writeCustomContentModel(modelRef, model); } } // indicate map is initialised isCustomMapInit = true; } /** * Gets a map containing all the customisable types * * @return map from the customisable type to its custom aspect */ private Map<QName, QName> getCustomisableMap() { if (customisableTypes == null) { throw AlfrescoRuntimeException.create("Customisable map has not been initialised correctly."); } return customisableTypes; } /** * Gets the QName of the custom aspect given the customisable type QName * * @param customisableType * @return */ private QName getCustomAspect(QName customisableType) { Map<QName, QName> map = getCustomisableMap(); QName result = map.get(customisableType); if (result == null) { result = getCustomAspectImpl(customisableType); } return result; } /** * Builds a custom aspect QName from a customisable type/aspect QName * * @param customisableType * @return */ private QName getCustomAspectImpl(QName customisableType) { String localName = customisableType.toPrefixString(getNamespaceService()).replace(":", ""); localName = MessageFormat.format("{0}CustomProperties", localName); return QName.createQName(RM_CUSTOM_URI, localName); } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#isCustomisable(org.alfresco.service.namespace.QName) */ @Override public boolean isCustomisable(QName type) { mandatory("type", type); return getCustomisable().contains(type); } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#makeCustomisable(org.alfresco.service.namespace.QName) */ @Override public void makeCustomisable(QName type) { mandatory("type", type); if (customisableTypes == null) { // Add the type to the pending list pendingCustomisableTypes.add(type); } else { QName customAspect = getCustomAspect(type); if (getDictionaryService().getAspect(customAspect) == null) { NodeRef modelRef = getCustomModelRef(customAspect.getNamespaceURI()); M2Model model = readCustomContentModel(modelRef); try { // Create the new aspect to hold the custom properties M2Aspect aspect = model.createAspect(customAspect.toPrefixString(getNamespaceService())); aspect.setDescription(type.toPrefixString(getNamespaceService())); } finally { writeCustomContentModel(modelRef, model); } customisableTypes.put(type, customAspect); } } } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#unmakeCustomisable(org.alfresco.service.namespace.QName) */ @Override public void unmakeCustomisable(QName type) { mandatory("type", type); if (customisableTypes == null) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_SERVICE_NOT_INIT)); } QName customAspect = getCustomAspect(type); if (getDictionaryService().getAspect(customAspect) != null) { // TODO need to confirm that the custom properties are not being used! NodeRef modelRef = getCustomModelRef(customAspect.getNamespaceURI()); M2Model model = readCustomContentModel(modelRef); try { // Create the new aspect to hold the custom properties model.removeAspect(customAspect.toPrefixString(getNamespaceService())); } finally { writeCustomContentModel(modelRef, model); } customisableTypes.remove(type); } } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#existsCustomProperty(org.alfresco.service.namespace.QName) */ @Override public boolean existsCustomProperty(QName propertyName) { mandatory("propertyName", propertyName); boolean result = false; if (RM_CUSTOM_URI.equals(propertyName.getNamespaceURI()) && getDictionaryService().getProperty(propertyName) != null) { result = true; } return result; } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#getCustomPropertyDefinitions() */ public Map<QName, PropertyDefinition> getCustomPropertyDefinitions() { Map<QName, PropertyDefinition> result = new HashMap<QName, PropertyDefinition>(); for (QName customisableType : getCustomisable()) { Map<QName, PropertyDefinition> props = getCustomPropertyDefinitions(customisableType); if (props != null) { result.putAll(props); } } return result; } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#getCustomPropertyDefinitions(org.alfresco.module.org_alfresco_module_rm.CustomisableRmElement) */ public Map<QName, PropertyDefinition> getCustomPropertyDefinitions(QName customisableType) { mandatory("customisableType", customisableType); Map<QName, PropertyDefinition> propDefns = null; QName relevantAspectQName = getCustomAspect(customisableType); AspectDefinition aspectDefn = getDictionaryService().getAspect(relevantAspectQName); if (aspectDefn != null) { propDefns = aspectDefn.getProperties(); } return propDefns; } /** * @throws CustomMetadataException * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#addCustomPropertyDefinition(org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, java.lang.String, org.alfresco.service.namespace.QName, java.lang.String, java.lang.String) */ public QName addCustomPropertyDefinition(QName propId, QName aspectName, String label, QName dataType, String title, String description) throws CustomMetadataException { return addCustomPropertyDefinition(propId, aspectName, label, dataType, title, description, null, false, false, false, null); } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#addCustomPropertyDefinition(org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, java.lang.String, org.alfresco.service.namespace.QName, java.lang.String, java.lang.String, java.lang.String, boolean, boolean, boolean, org.alfresco.service.namespace.QName) */ public QName addCustomPropertyDefinition(QName propId, QName aspectName, String label, QName dataType, String title, String description, String defaultValue, boolean multiValued, boolean mandatory, boolean isProtected, QName lovConstraint) throws CustomMetadataException { if (!isCustomisable(aspectName)) { throw new NotCustomisableMetadataException(aspectName.toPrefixString(getNamespaceService())); } // title parameter is currently ignored. Intentionally. if (propId == null) { // Generate a propId propId = this.generateQNameFor(label); } mandatory("aspectName", aspectName); mandatory("label", label); mandatory("dataType", dataType); NodeRef modelRef = getCustomModelRef(propId.getNamespaceURI()); M2Model deserializedModel = readCustomContentModel(modelRef); QName customAspect = getCustomAspect(aspectName); M2Aspect customPropsAspect = deserializedModel.getAspect(customAspect.toPrefixString(getNamespaceService())); if (customPropsAspect == null) { throw new InvalidCustomAspectMetadataException(customAspect, aspectName.toPrefixString(getNamespaceService())); } String propIdAsString = propId.toPrefixString(getNamespaceService()); M2Property customProp = customPropsAspect.getProperty(propIdAsString); if (customProp != null) { throw new PropertyAlreadyExistsMetadataException(propIdAsString); } M2Property newProp = customPropsAspect.createProperty(propIdAsString); newProp.setName(propIdAsString); newProp.setType(dataType.toPrefixString(getNamespaceService())); // Note that the title is used to store the RM 'label'. newProp.setTitle(label); newProp.setDescription(description); newProp.setDefaultValue(defaultValue); newProp.setMandatory(mandatory); newProp.setProtected(isProtected); newProp.setMultiValued(multiValued); newProp.setIndexed(true); newProp.setIndexedAtomically(true); newProp.setStoredInIndex(false); newProp.setIndexTokenisationMode(IndexTokenisationMode.FALSE); if (lovConstraint != null) { if (! dataType.equals(DataTypeDefinition.TEXT)) { throw new CannotApplyConstraintMetadataException(lovConstraint, propIdAsString, dataType); } String lovConstraintQNameAsString = lovConstraint.toPrefixString(getNamespaceService()); newProp.addConstraintRef(lovConstraintQNameAsString); } writeCustomContentModel(modelRef, deserializedModel); if (logger.isInfoEnabled()) { logger.info("addCustomPropertyDefinition: "+label+ "=" + propIdAsString + " to aspect: "+aspectName); } return propId; } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#updateCustomPropertyDefinitionName(org.alfresco.service.namespace.QName, java.lang.String) */ public QName updateCustomPropertyDefinitionName(QName propQName, String newName) throws CustomMetadataException { mandatory("propQName", propQName); PropertyDefinition propDefn = getDictionaryService().getProperty(propQName); if (propDefn == null) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_PROP_EXIST, propQName)); } if (newName == null) { return propQName; } QName newPropQName = getQNameForClientId(newName); if (newPropQName != null) { PropertyDefinition newPropDefn = getDictionaryService().getProperty(newPropQName); if (newPropDefn != null && !propDefn.equals(newPropDefn)) { // The requested QName is already in use String propIdAsString = newPropQName.toPrefixString(getNamespaceService()); throw new PropertyAlreadyExistsMetadataException(propIdAsString); } } NodeRef modelRef = getCustomModelRef(propQName.getNamespaceURI()); M2Model deserializedModel = readCustomContentModel(modelRef); M2Property targetProperty = findProperty(propQName, deserializedModel); targetProperty.setName(new StringBuilder().append(RecordsManagementCustomModel.RM_CUSTOM_PREFIX).append(QName.NAMESPACE_PREFIX).append(newName).toString()); targetProperty.setTitle(URLDecoder.decode(newName)); writeCustomContentModel(modelRef, deserializedModel); if (logger.isInfoEnabled()) { logger.info("setCustomPropertyDefinitionLabel: "+propQName+ "=" + newName); } return propQName; } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#setCustomPropertyDefinitionLabel(org.alfresco.service.namespace.QName, java.lang.String) */ public QName setCustomPropertyDefinitionLabel(QName propQName, String newLabel) { mandatory("propQName", propQName); PropertyDefinition propDefn = getDictionaryService().getProperty(propQName); if (propDefn == null) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_PROP_EXIST, propQName)); } if (newLabel == null) { return propQName; } NodeRef modelRef = getCustomModelRef(propQName.getNamespaceURI()); M2Model deserializedModel = readCustomContentModel(modelRef); M2Property targetProperty = findProperty(propQName, deserializedModel); targetProperty.setTitle(newLabel); writeCustomContentModel(modelRef, deserializedModel); if (logger.isInfoEnabled()) { logger.info("setCustomPropertyDefinitionLabel: "+propQName+ "=" + newLabel); } return propQName; } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#setCustomPropertyDefinitionConstraint(org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) */ public QName setCustomPropertyDefinitionConstraint(QName propQName, QName newLovConstraint) { mandatory("propQName", propQName); mandatory("newLovConstraint", newLovConstraint); PropertyDefinition propDefn = getDictionaryService().getProperty(propQName); if (propDefn == null) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_PROP_EXIST, propQName)); } NodeRef modelRef = getCustomModelRef(propQName.getNamespaceURI()); M2Model deserializedModel = readCustomContentModel(modelRef); M2Property targetProp = findProperty(propQName, deserializedModel); String dataType = targetProp.getType(); if (! dataType.equals(DataTypeDefinition.TEXT.toPrefixString(getNamespaceService()))) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(CannotApplyConstraintMetadataException.MSG_CANNOT_APPLY_CONSTRAINT, newLovConstraint, targetProp.getName(), dataType)); } String lovConstraintQNameAsString = newLovConstraint.toPrefixString(getNamespaceService()); // Add the constraint - if it isn't already there. String refOfExistingConstraint = null; for (M2Constraint c : targetProp.getConstraints()) { // There should only be one constraint. refOfExistingConstraint = c.getRef(); break; } if (refOfExistingConstraint != null) { targetProp.removeConstraintRef(refOfExistingConstraint); } targetProp.addConstraintRef(lovConstraintQNameAsString); writeCustomContentModel(modelRef, deserializedModel); if (logger.isInfoEnabled()) { logger.info("addCustomPropertyDefinitionConstraint: "+lovConstraintQNameAsString); } return propQName; } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#removeCustomPropertyDefinitionConstraints(org.alfresco.service.namespace.QName) */ public QName removeCustomPropertyDefinitionConstraints(QName propQName) { mandatory("propQName", propQName); PropertyDefinition propDefn = getDictionaryService().getProperty(propQName); if (propDefn == null) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_PROP_EXIST, propQName)); } NodeRef modelRef = getCustomModelRef(propQName.getNamespaceURI()); M2Model deserializedModel = readCustomContentModel(modelRef); M2Property targetProperty = findProperty(propQName, deserializedModel); // Need to count backwards to remove constraints for (int i = targetProperty.getConstraints().size() - 1; i >= 0; i--) { String ref = targetProperty.getConstraints().get(i).getRef(); targetProperty.removeConstraintRef(ref); } writeCustomContentModel(modelRef, deserializedModel); if (logger.isInfoEnabled()) { logger.info("removeCustomPropertyDefinitionConstraints: "+propQName); } return propQName; } /** * * @param propQName * @param deserializedModel * @return */ private M2Property findProperty(QName propQName, M2Model deserializedModel) { List<M2Aspect> aspects = deserializedModel.getAspects(); // Search through the aspects looking for the custom property for (M2Aspect aspect : aspects) { for (M2Property prop : aspect.getProperties()) { if (propQName.toPrefixString(getNamespaceService()).equals(prop.getName())) { return prop; } } } throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_CUSTOM_PROP_EXIST, propQName)); } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#removeCustomPropertyDefinition(org.alfresco.service.namespace.QName) */ public void removeCustomPropertyDefinition(QName propQName) { mandatory("propQName", propQName); NodeRef modelRef = getCustomModelRef(propQName.getNamespaceURI()); M2Model deserializedModel = readCustomContentModel(modelRef); String propQNameAsString = propQName.toPrefixString(getNamespaceService()); String aspectName = null; boolean found = false; // Need to select the correct aspect in the customModel from which we'll // attempt to delete the property definition. for (QName customisableType : getCustomisable()) { aspectName = getCustomAspect(customisableType).toPrefixString(getNamespaceService()); M2Aspect customPropsAspect = deserializedModel.getAspect(aspectName); if (customPropsAspect == null) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_UNKNOWN_ASPECT, aspectName)); } M2Property prop = customPropsAspect.getProperty(propQNameAsString); if (prop != null) { if (logger.isDebugEnabled()) { StringBuilder msg = new StringBuilder(); msg.append("Attempting to delete custom property: "); msg.append(propQNameAsString); logger.debug(msg.toString()); } found = true; customPropsAspect.removeProperty(propQNameAsString); break; } } if (!found) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_PROP_EXIST, propQNameAsString)); } writeCustomContentModel(modelRef, deserializedModel); if (logger.isInfoEnabled()) { logger.info("deleteCustomPropertyDefinition: "+propQNameAsString+" from aspect: "+aspectName); } } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#getCustomReferenceDefinitions() */ public Map<QName, AssociationDefinition> getCustomReferenceDefinitions() { return getCustomAssociations(); } /** * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAdminService#addCustomReference(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) */ public void addCustomReference(NodeRef fromNode, NodeRef toNode, QName refId) { mandatory("fromNode", fromNode); mandatory("toNode", toNode); mandatory("refId", refId); getRelationshipService().addRelationship(refId.getLocalName(), fromNode, toNode); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#removeCustomReference(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) */ public void removeCustomReference(final NodeRef fromNode, final NodeRef toNode, final QName assocId) { mandatory("fromNode", fromNode); mandatory("toNode", toNode); mandatory("assocId",assocId); getRelationshipService().removeRelationship(assocId.getLocalName(), fromNode, toNode); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#getCustomReferencesFrom(org.alfresco.service.cmr.repository.NodeRef) */ public List<AssociationRef> getCustomReferencesFrom(NodeRef node) { mandatory("node", node); return getNodeService().getTargetAssocs(node, RegexQNamePattern.MATCH_ALL); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#getCustomChildReferences(org.alfresco.service.cmr.repository.NodeRef) */ public List<ChildAssociationRef> getCustomChildReferences(NodeRef node) { mandatory("node", node); return getNodeService().getChildAssocs(node); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#getCustomReferencesTo(org.alfresco.service.cmr.repository.NodeRef) */ public List<AssociationRef> getCustomReferencesTo(NodeRef node) { mandatory("node", node); return getNodeService().getSourceAssocs(node, RegexQNamePattern.MATCH_ALL); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#getCustomParentReferences(org.alfresco.service.cmr.repository.NodeRef) */ public List<ChildAssociationRef> getCustomParentReferences(NodeRef node) { mandatory("node", node); return getNodeService().getParentAssocs(node); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#addCustomAssocDefinition(java.lang.String) * * note: currently RMC custom assocs only */ public QName addCustomAssocDefinition(String label) { mandatoryString("label", label); return addCustomChildAssocDefinition(label, label); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#addCustomChildAssocDefinition(java.lang.String, java.lang.String) * * note: currently RMC custom assocs only */ public QName addCustomChildAssocDefinition(String source, String target) { mandatoryString("source", source); mandatoryString("target", target); RelationshipDisplayName displayName = new RelationshipDisplayName(source, target); RelationshipDefinition relationshipDefinition = getRelationshipService().createRelationshipDefinition(displayName); return QName.createQName(RM_CUSTOM_PREFIX, relationshipDefinition.getUniqueName(), getNamespaceService()); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#updateCustomChildAssocDefinition(org.alfresco.service.namespace.QName, java.lang.String, java.lang.String) * * note: currently RMC custom assocs only */ public QName updateCustomChildAssocDefinition(QName refQName, String newSource, String newTarget) { mandatory("refQName", refQName); mandatoryString("newSource", newSource); mandatoryString("newTarget", newTarget); RelationshipDisplayName displayName = new RelationshipDisplayName(newSource, newTarget); String localName = refQName.getLocalName(); RelationshipDefinition relationshipDefinition = getRelationshipService().updateRelationshipDefinition(localName, displayName); return QName.createQName(RM_CUSTOM_PREFIX, relationshipDefinition.getUniqueName(), getNamespaceService()); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#updateCustomAssocDefinition(org.alfresco.service.namespace.QName, java.lang.String) * * note: currently RMC custom assocs only */ public QName updateCustomAssocDefinition(QName refQName, String newLabel) { mandatory("refQName", refQName); mandatoryString("newLabel", newLabel); return updateCustomChildAssocDefinition(refQName, newLabel, newLabel); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#addCustomConstraintDefinition(org.alfresco.service.namespace.QName, java.lang.String, boolean, java.util.List, org.alfresco.module.org_alfresco_module_rm.caveat.RMListOfValuesConstraint.MatchLogic) */ public void addCustomConstraintDefinition(QName constraintName, String title, boolean caseSensitive, List<String> allowedValues, MatchLogic matchLogic) { mandatory("constraintName", constraintName); mandatoryString("title", title); mandatory("allowedValues", allowedValues); mandatory("matchLogic", matchLogic); NodeRef modelRef = getCustomModelRef(constraintName.getNamespaceURI()); M2Model deserializedModel = readCustomContentModel(modelRef); String constraintNameAsPrefixString = constraintName.toPrefixString(getNamespaceService()); M2Constraint customConstraint = deserializedModel.getConstraint(constraintNameAsPrefixString); if (customConstraint != null) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_CONSTRAINT_EXISTS, constraintNameAsPrefixString)); } M2Constraint newCon = deserializedModel.createConstraint(constraintNameAsPrefixString, CUSTOM_CONSTRAINT_TYPE); newCon.setTitle(title); newCon.createParameter(PARAM_ALLOWED_VALUES, allowedValues); newCon.createParameter(PARAM_CASE_SENSITIVE, caseSensitive ? "true" : "false"); newCon.createParameter(PARAM_MATCH_LOGIC, matchLogic.toString()); writeCustomContentModel(modelRef, deserializedModel); if (logger.isInfoEnabled()) { logger.info("addCustomConstraintDefinition: "+constraintNameAsPrefixString+" (valueCnt: "+allowedValues.size()+")"); } } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#changeCustomConstraintValues(org.alfresco.service.namespace.QName, java.util.List) */ public void changeCustomConstraintValues(QName constraintName, List<String> newAllowedValues) { mandatory("constraintName", constraintName); mandatory("newAllowedValues", newAllowedValues); NodeRef modelRef = getCustomModelRef(constraintName.getNamespaceURI()); M2Model deserializedModel = readCustomContentModel(modelRef); String constraintNameAsPrefixString = constraintName.toPrefixString(getNamespaceService()); M2Constraint customConstraint = deserializedModel.getConstraint(constraintNameAsPrefixString); if (customConstraint == null) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_CANNOT_FIND_CONSTRAINT, constraintNameAsPrefixString)); } String type = customConstraint.getType(); if (type == null || (!type.equals(CUSTOM_CONSTRAINT_TYPE) && !type.equals(CAPATIBILITY_CUSTOM_CONTRAINT_TYPE))) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_UNEXPECTED_TYPE_CONSTRAINT, type, constraintNameAsPrefixString, CUSTOM_CONSTRAINT_TYPE)); } customConstraint.removeParameter(PARAM_ALLOWED_VALUES); customConstraint.createParameter(PARAM_ALLOWED_VALUES, newAllowedValues); writeCustomContentModel(modelRef, deserializedModel); if (logger.isInfoEnabled()) { logger.info("changeCustomConstraintValues: "+constraintNameAsPrefixString+" (valueCnt: "+newAllowedValues.size()+")"); } } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#changeCustomConstraintTitle(org.alfresco.service.namespace.QName, java.lang.String) */ public void changeCustomConstraintTitle(QName constraintName, String title) { mandatory("constraintName", constraintName); mandatoryString("title", title); NodeRef modelRef = getCustomModelRef(constraintName.getNamespaceURI()); M2Model deserializedModel = readCustomContentModel(modelRef); String constraintNameAsPrefixString = constraintName.toPrefixString(getNamespaceService()); M2Constraint customConstraint = deserializedModel.getConstraint(constraintNameAsPrefixString); if (customConstraint == null) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_CANNOT_FIND_CONSTRAINT, constraintNameAsPrefixString)); } String type = customConstraint.getType(); if ((type == null) || (! type.equals(CUSTOM_CONSTRAINT_TYPE))) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_UNEXPECTED_TYPE_CONSTRAINT, type, constraintNameAsPrefixString, CUSTOM_CONSTRAINT_TYPE)); } customConstraint.setTitle(title); writeCustomContentModel(modelRef, deserializedModel); if (logger.isInfoEnabled()) { logger.info("changeCustomConstraintTitle: "+constraintNameAsPrefixString+" (title: "+title+")"); } } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#getCustomConstraintDefinitions(org.alfresco.service.namespace.QName) */ public List<ConstraintDefinition> getCustomConstraintDefinitions(QName modelQName) { mandatory("modelQName", modelQName); Collection<ConstraintDefinition> conDefs = getDictionaryService().getConstraints(modelQName, true); for (ConstraintDefinition conDef : conDefs) { Constraint con = conDef.getConstraint(); if (! (con instanceof RMListOfValuesConstraint)) { conDefs.remove(conDef); } } return new ArrayList<ConstraintDefinition>(conDefs); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#removeCustomConstraintDefinition(org.alfresco.service.namespace.QName) */ public void removeCustomConstraintDefinition(QName constraintName) { mandatory("constraintName", constraintName); NodeRef modelRef = getCustomModelRef(constraintName.getNamespaceURI()); M2Model deserializedModel = readCustomContentModel(modelRef); String constraintNameAsPrefixString = constraintName.toPrefixString(getNamespaceService()); M2Constraint customConstraint = deserializedModel.getConstraint(constraintNameAsPrefixString); if (customConstraint == null) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_CANNOT_FIND_CONSTRAINT, constraintNameAsPrefixString)); } deserializedModel.removeConstraint(constraintNameAsPrefixString); writeCustomContentModel(modelRef, deserializedModel); if (logger.isInfoEnabled()) { logger.info("deleteCustomConstraintDefinition: "+constraintNameAsPrefixString); } } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#getQNameForClientId(java.lang.String) */ public QName getQNameForClientId(String localName) { //TODO 1. After certification. This implementation currently does not support reference, // property, constraints definitions with the same names, which is technically allowed by Alfresco. //TODO 2. Note the implicit assumption here that all custom references will have // unique titles. This is, in fact, not guaranteed. QName propertyResult = null; for (QName qn : getCustomPropertyDefinitions().keySet()) { if (localName != null && localName.equals(qn.getLocalName())) { propertyResult = qn; } } if (propertyResult != null) { return propertyResult; } QName referenceResult = null; for (QName refQn : getCustomReferenceDefinitions().keySet()) { if (localName != null && localName.equals(refQn.getLocalName())) { referenceResult = refQn; } } // TODO Handle the case where both are not null return referenceResult; } /** * @param clientId * @return */ private QName generateQNameFor(String clientId) { if (getQNameForClientId(clientId) != null) { // TODO log it's already taken. What to do? throw new IllegalArgumentException(I18NUtil.getMessage(MSG_ERROR_CLIENT_ID, clientId)); } String newGUID = GUID.generate(); QName newQName = QName.createQName(RM_CUSTOM_PREFIX, newGUID, getNamespaceService()); return newQName; } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#splitSourceTargetId(java.lang.String) */ public String[] splitSourceTargetId(String sourceTargetId) { mandatoryString("sourceTargetId", sourceTargetId); return splitAssociationDefinitionTitle(sourceTargetId); } /** * @see org.alfresco.module.org_alfresco_module_rm.admin.RecordsManagementAdminService#getCompoundIdFor(java.lang.String, java.lang.String) */ public String getCompoundIdFor(String sourceId, String targetId) { mandatoryString("sourceId", sourceId); mandatoryString("targetId", targetId); return composeAssociationDefinitionTitle(sourceId, targetId); } }