diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml index 9dea1037dd..7e86ee5f93 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml @@ -684,7 +684,6 @@ Review Period d:period - true none|0 diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-patch-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-patch-context.xml index 4822dc0072..3ba031ec0c 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-patch-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-patch-context.xml @@ -64,6 +64,19 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + org.alfresco.cache.caveatConfigCache + + + + + + + + + + + + + org.alfresco.caveatConfigTransactionalCache + + + + + + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/RecordsManagementAdminServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/RecordsManagementAdminServiceImpl.java index 8540efc651..5381b22235 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/RecordsManagementAdminServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/RecordsManagementAdminServiceImpl.java @@ -121,9 +121,10 @@ public class RecordsManagementAdminServiceImpl implements RecordsManagementAdmin private static final String MSG_ERROR_SPLIT_ID = "rm.admin.error-split-id"; /** Constants */ - public static final String RMC_CUSTOM_ASSOCS = RecordsManagementCustomModel.RM_CUSTOM_PREFIX + ":customAssocs"; + public static final String RMC_CUSTOM_ASSOCS = RecordsManagementCustomModel.RM_CUSTOM_PREFIX + ":customAssocs"; private static final String CUSTOM_CONSTRAINT_TYPE = org.alfresco.module.org_alfresco_module_rm.caveat.RMListOfValuesConstraint.class.getName(); - private static final NodeRef RM_CUSTOM_MODEL_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "records_management_custom_model"); + private static final String CAPATIBILITY_CUSTOM_CONTRAINT_TYPE = org.alfresco.module.org_alfresco_module_dod5015.caveat.RMListOfValuesConstraint.class.getName(); + private static final NodeRef RM_CUSTOM_MODEL_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "records_management_custom_model"); private static final String PARAM_ALLOWED_VALUES = "allowedValues"; private static final String PARAM_CASE_SENSITIVE = "caseSensitive"; private static final String PARAM_MATCH_LOGIC = "matchLogic"; @@ -1413,7 +1414,9 @@ public class RecordsManagementAdminServiceImpl implements RecordsManagementAdmin } String type = customConstraint.getType(); - if ((type == null) || (! type.equals(CUSTOM_CONSTRAINT_TYPE))) + if (type == null || + (type.equals(CUSTOM_CONSTRAINT_TYPE) == false && + type.equals(CAPATIBILITY_CUSTOM_CONTRAINT_TYPE) == false)) { throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_UNEXPECTED_TYPE_CONSTRAINT, type, constraintNameAsPrefixString, CUSTOM_CONSTRAINT_TYPE)); } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/caveat/RMCaveatConfigComponentImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/caveat/RMCaveatConfigComponentImpl.java index 30dc9fd22f..759a88f2ca 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/caveat/RMCaveatConfigComponentImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/caveat/RMCaveatConfigComponentImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -22,17 +22,22 @@ import java.io.File; import java.io.InputStream; import java.io.Serializable; 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 java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.caveat.RMListOfValuesConstraint.MatchLogic; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.content.ContentServicePolicies; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.node.NodeServicePolicies; @@ -65,7 +70,7 @@ import org.json.JSONObject; /** * RM Caveat Config component impl - * + * * @author janv */ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnContentUpdatePolicy, @@ -74,7 +79,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon RMCaveatConfigComponent { private static Log logger = LogFactory.getLog(RMCaveatConfigComponentImpl.class); - + private PolicyComponent policyComponent; private ContentService contentService; private DictionaryService dictionaryService; @@ -82,81 +87,90 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon private AuthorityService authorityService; private PersonService personService; private NodeService nodeService; - + // Default private StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); - + private List caveatAspectURINames = new ArrayList(0); private List caveatAspectQNames = new ArrayList(0); - + private List caveatModelURINames = new ArrayList(0); private List caveatModelQNames = new ArrayList(0); - + private static final String CAVEAT_CONFIG_NAME = "caveatConfig.json"; - + private static final QName DATATYPE_TEXT = DataTypeDefinition.TEXT; - - + + /** + * Lock objects + */ + private ReadWriteLock lock = new ReentrantReadWriteLock(); + private Lock readLock = lock.readLock(); + private Lock writeLock = lock.writeLock(); + /* - * Caveat Config + * Caveat Config (Shared) config * first string is property name * second string is authority name (user or group full name) - * third string is list of values of property + * third string is list of values of property */ - - // TODO - convert to SimpleCache to be cluster-aware (for dynamic changes to caveat config across a cluster) - private Map>> caveatConfig = new ConcurrentHashMap>>(2); - + private SimpleCache>> caveatConfig; + + public void setCaveatConfig(SimpleCache>> caveatConfig) + { + this.caveatConfig = caveatConfig; + } + public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; } - + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } - + public void setContentService(ContentService contentService) { this.contentService = contentService; } - + public void setDictionaryService(DictionaryService dictionaryService) { this.dictionaryService = dictionaryService; } - + public void setNamespaceService(NamespaceService namespaceService) { this.namespaceService = namespaceService; } - + public void setAuthorityService(AuthorityService authorityService) { this.authorityService = authorityService; } - + public void setPersonService(PersonService personService) { this.personService = personService; } - + public void setStoreRef(String storeRef) { this.storeRef = new StoreRef(storeRef); } - + public void setCaveatAspects(List caveatAspectNames) { this.caveatAspectURINames = caveatAspectNames; } - + public void setCaveatModels(List caveatModelNames) { this.caveatModelURINames = caveatModelNames; } - + /** * Initialise behaviours and caveat config cache */ @@ -167,26 +181,26 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon ContentServicePolicies.OnContentUpdatePolicy.QNAME, RecordsManagementModel.TYPE_CAVEAT_CONFIG, new JavaBehaviour(this, "onContentUpdate")); - + // Register interest in the beforeDeleteNode policy policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), RecordsManagementModel.TYPE_CAVEAT_CONFIG, new JavaBehaviour(this, "beforeDeleteNode")); - + // Register interest in the onCreateNode policy policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), RecordsManagementModel.TYPE_CAVEAT_CONFIG, new JavaBehaviour(this, "onCreateNode")); - + if (caveatAspectURINames.size() > 0) { for (String caveatAspectURIName : caveatAspectURINames) { caveatAspectQNames.add(QName.createQName(caveatAspectURIName)); } - + if (logger.isInfoEnabled()) { logger.info("Caveat aspects configured "+caveatAspectQNames); @@ -196,14 +210,14 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { logger.warn("No caveat aspects configured - caveats will not be applied"); } - + if (caveatModelURINames.size() > 0) { for (String caveatModelURIName : caveatModelURINames) { caveatModelQNames.add(QName.createQName(caveatModelURIName)); } - + if (logger.isInfoEnabled()) { logger.info("Caveat models configured "+caveatModelQNames); @@ -213,44 +227,50 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { logger.info("No caveat models configured - all models will be checked"); } - + NodeRef caveatConfigNodeRef = getCaveatConfigNode(); if (caveatConfigNodeRef != null) { validateAndReset(caveatConfigNodeRef); } } - + public void onContentUpdate(NodeRef nodeRef, boolean newContent) { if (logger.isInfoEnabled()) { logger.info("onContentUpdate: "+nodeRef+", "+newContent); } - + validateAndReset(nodeRef); } - + public void beforeDeleteNode(NodeRef nodeRef) { if (logger.isInfoEnabled()) { logger.info("beforeDeleteNode: "+nodeRef); } - + validateAndReset(nodeRef); } - + public void onCreateNode(ChildAssociationRef childAssocRef) { if (logger.isInfoEnabled()) { logger.info("onCreateNode: "+childAssocRef); } - + validateAndReset(childAssocRef.getChildRef()); } - + + /** + * Validate the caveat config and optionally update the cache. + * + * @param nodeRef The nodeRef of the config + * @param updateCache Set to true to update the cache + */ @SuppressWarnings("unchecked") protected void validateAndReset(NodeRef nodeRef) { @@ -259,7 +279,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { // TODO - check who can change caveat config ! // TODO - locking (or checkout/checkin) - + String caveatConfigData = cr.getContentString(); if (caveatConfigData != null) { @@ -268,18 +288,18 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { throw new AlfrescoRuntimeException("Cannot create more than one caveat config (existing="+existing+", new="+nodeRef+")"); } - + try { if (logger.isTraceEnabled()) { logger.trace(caveatConfigData); } - + Set models = new HashSet(1); Set props = new HashSet(10); Set expectedPrefixes = new HashSet(10); - + if (caveatModelQNames.size() > 0) { models.addAll(caveatModelQNames); @@ -288,18 +308,18 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { models.addAll(dictionaryService.getAllModels()); } - + if (logger.isTraceEnabled()) { logger.trace("validateAndReset: models to check "+models); } - + for (QName model : models) { props.addAll(dictionaryService.getProperties(model, DATATYPE_TEXT)); expectedPrefixes.addAll(namespaceService.getPrefixes(model.getNamespaceURI())); } - + if (props.size() == 0) { logger.warn("validateAndReset: no caveat properties found"); @@ -311,15 +331,15 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon logger.trace("validateAndReset: properties to check "+props); } } - + Map caveatConfigMap = JSONtoFmModel.convertJSONObjectToMap(caveatConfigData); - + for (Map.Entry conEntry : caveatConfigMap.entrySet()) { String conStr = conEntry.getKey(); - + QName conQName = QName.resolveToQName(namespaceService, conStr); - + // check prefix String conPrefix = QName.splitPrefixedQName(conStr)[0]; boolean prefixFound = false; @@ -330,17 +350,17 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon prefixFound = true; } } - + if (! prefixFound) { throw new AlfrescoRuntimeException("Unexpected prefix: "+ conPrefix + " (" + conStr +") expected one of "+expectedPrefixes+")"); } - + Map> caveatMap = (Map>)conEntry.getValue(); - + List allowedValues = null; boolean found = false; - + for (QName propertyName : props) { PropertyDefinition propDef = dictionaryService.getProperty(propertyName); @@ -361,31 +381,31 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon return ((RMListOfValuesConstraint)con).getAllowedValues(); } }, AuthenticationUtil.getSystemUserName()); - + found = true; break; } } } } - + if (! found) { //throw new AlfrescoRuntimeException("Constraint does not exist (or is not used): "+conStr); } - + if (allowedValues != null) { if (logger.isInfoEnabled()) { logger.info("Processing constraint: "+conQName); } - + for (Map.Entry> caveatEntry : caveatMap.entrySet()) { String authorityName = caveatEntry.getKey(); List caveatList = caveatEntry.getValue(); - + // validate authority (user or group) - note: groups are configured with fullname (ie. GROUP_xxx) if ((! authorityService.authorityExists(authorityName) && ! personService.personExists(authorityName))) { @@ -393,7 +413,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon String msg = "User/group does not exist: "+authorityName+" (constraint="+conStr+")"; logger.warn(msg); } - + // validate caveat list for (String value : caveatList) { @@ -407,16 +427,30 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon } } } - - // Valid, so update - caveatConfig.clear(); - - for (Map.Entry conEntry : caveatConfigMap.entrySet()) + + try { - String conStr = conEntry.getKey(); - Map> caveatMap = (Map>)conEntry.getValue(); - - caveatConfig.put(conStr, caveatMap); + writeLock.lock(); + // we can't just clear the cache, as all puts to the cache afterwards in this transaction will be ignored + // first delete all keys that are now not in the config + caveatConfig.getKeys().retainAll(caveatConfigMap.keySet()); + + for (Map.Entry conEntry : caveatConfigMap.entrySet()) + { + String conStr = conEntry.getKey(); + Map> caveatMap = (Map>)conEntry.getValue(); + + Map> cacheValue = caveatConfig.get(conStr); + if (cacheValue == null || !cacheValue.equals(caveatMap)) + { + // update the cache + caveatConfig.put(conStr, caveatMap); + } + } + } + finally + { + writeLock.unlock(); } } catch (JSONException e) @@ -426,53 +460,53 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon } } } - + private NodeRef getCaveatConfigNode() { NodeRef rootNode = nodeService.getRootNode(storeRef); return nodeService.getChildByName(rootNode, RecordsManagementModel.ASSOC_CAVEAT_CONFIG, CAVEAT_CONFIG_NAME); } - - + + public NodeRef updateOrCreateCaveatConfig(InputStream is) { NodeRef caveatConfig = getOrCreateCaveatConfig(); - + // Update the content ContentWriter writer = this.contentService.getWriter(caveatConfig, ContentModel.PROP_CONTENT, true); writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); writer.setEncoding("UTF-8"); writer.putContent(is); - + return caveatConfig; } - + public NodeRef updateOrCreateCaveatConfig(File jsonFile) { NodeRef caveatConfig = getOrCreateCaveatConfig(); - + // Update the content ContentWriter writer = this.contentService.getWriter(caveatConfig, ContentModel.PROP_CONTENT, true); writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); writer.setEncoding("UTF-8"); writer.putContent(jsonFile); - + return caveatConfig; } - + public NodeRef updateOrCreateCaveatConfig(String jsonString) { NodeRef caveatConfig = getOrCreateCaveatConfig(); - + // Update the content ContentWriter writer = this.contentService.getWriter(caveatConfig, ContentModel.PROP_CONTENT, true); writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); writer.setEncoding("UTF-8"); writer.putContent(jsonString); - + return caveatConfig; } - + private NodeRef getOrCreateCaveatConfig() { NodeRef caveatConfig = getCaveatConfigNode(); @@ -480,30 +514,40 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { NodeRef rootNode = nodeService.getRootNode(storeRef); nodeService.addAspect(rootNode, VersionModel.ASPECT_VERSION_STORE_ROOT, null); - + // Create caveat config caveatConfig = nodeService.createNode(rootNode, RecordsManagementModel.ASSOC_CAVEAT_CONFIG, QName.createQName(RecordsManagementModel.RM_URI, CAVEAT_CONFIG_NAME), RecordsManagementModel.TYPE_CAVEAT_CONFIG).getChildRef(); - + nodeService.setProperty(caveatConfig, ContentModel.PROP_NAME, CAVEAT_CONFIG_NAME); } - + return caveatConfig; } - + // Get list of all caveat qualified names - public Set getRMConstraintNames() + public Collection getRMConstraintNames() { - return caveatConfig.keySet(); + Collection rmConstraintNames = Collections.emptySet(); + try + { + readLock.lock(); + rmConstraintNames = caveatConfig.getKeys(); + } + finally + { + readLock.unlock(); + } + return Collections.unmodifiableCollection(rmConstraintNames); } - + // Get allowed values for given caveat (for current user) public List getRMAllowedValues(String constraintName) { List allowedValues = new ArrayList(0); - + String userName = AuthenticationUtil.getRunAsUser(); if (userName != null) { @@ -511,21 +555,31 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { // note: userName and userGroupNames must not be null caveatConfig.get(constraintName); + Set userGroupFullNames = authorityService.getAuthoritiesForUser(userName); allowedValues = getRMAllowedValues(userName, userGroupFullNames, constraintName); } } - + return allowedValues; } - + private List getRMAllowedValues(String userName, Set userGroupFullNames, String constraintName) { SetallowedValues = new HashSet(); - + // note: userName and userGroupNames must not be null - Map> caveatConstraintDef = caveatConfig.get(constraintName); - + Map> caveatConstraintDef = null; + try + { + readLock.lock(); + caveatConstraintDef = caveatConfig.get(constraintName); + } + finally + { + readLock.unlock(); + } + if (caveatConstraintDef != null) { List direct = caveatConstraintDef.get(userName); @@ -533,7 +587,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { allowedValues.addAll(direct); } - + for (String group : userGroupFullNames) { List values = caveatConstraintDef.get(group); @@ -543,15 +597,15 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon } } } - + Listret = new ArrayList(); ret.addAll(allowedValues); - return ret; + return Collections.unmodifiableList(ret); } - + /** * Check whether access to 'record component' node is vetoed for current user due to caveat(s) - * + * * @param nodeRef * @return false, if caveat(s) veto access otherwise return true */ @@ -562,7 +616,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { return true; } - + boolean found = false; for (QName caveatAspectQName : caveatAspectQNames) { @@ -572,7 +626,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon break; } } - + if (! found) { // no caveat aspect @@ -590,7 +644,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { QName propName = entry.getKey(); PropertyDefinition propDef = dictionaryService.getProperty(propName); - + if ((propDef != null) && (propDef.getDataType().getName().equals(DATATYPE_TEXT))) { List conDefs = propDef.getConstraints(); @@ -602,7 +656,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon RMListOfValuesConstraint rmCon = ((RMListOfValuesConstraint)con); String conName = rmCon.getShortName(); MatchLogic matchLogic = rmCon.getMatchLogicEnum(); - Map> caveatConstraintDef = caveatConfig.get(conName); + Map> caveatConstraintDef = caveatConfig.get(conName); if (caveatConstraintDef == null) { continue; @@ -611,7 +665,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { Set userGroupNames = authorityService.getAuthoritiesForUser(userName); List allowedValues = getRMAllowedValues(userName, userGroupNames, conName); - + List propValues = null; Object val = entry.getValue(); if (val instanceof String) @@ -623,7 +677,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { propValues = (List)val; } - + if (propValues != null && !isAllowed(propValues, allowedValues, matchLogic)) { if (logger.isDebugEnabled()) @@ -638,11 +692,11 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon } } } - + return true; } } - + private boolean isAllowed(List propValues, List userGroupValues, MatchLogic matchLogic) { if (matchLogic.equals(MatchLogic.AND)) @@ -656,11 +710,11 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon { logger.trace("Not allowed: "+propValues+", "+userGroupValues+", "+matchLogic); } - + return false; } } - + return true; } else if (matchLogic.equals(MatchLogic.OR)) @@ -673,43 +727,73 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon return true; } } - + if (logger.isTraceEnabled()) { logger.trace("Not allowed: "+propValues+", "+userGroupValues+", "+matchLogic); } - + return false; } - + logger.error("Unexpected match logic type: "+matchLogic); return false; } - + /** * Add a single value to an authority in a list. The existing values of the list remain. - * + * * @param listName the name of the RMConstraintList * @param authorityName - * @param values + * @param value * @throws AlfrescoRuntimeException if either the list or the authority do not already exist. */ public void addRMConstraintListValue(String listName, String authorityName, String value) { - Map> members = caveatConfig.get(listName); - if(members == null) + Map> members = null; + try { - throw new AlfrescoRuntimeException("unable to add to list, list not defined:"+ listName); + readLock.lock(); + members = caveatConfig.get(listName); + if(members == null) + { + throw new AlfrescoRuntimeException("unable to add to list, list not defined:"+ listName); + } + + try + { + readLock.unlock(); + writeLock.lock(); + // check again + members = caveatConfig.get(listName); + if(members == null) + { + throw new AlfrescoRuntimeException("unable to add to list, list not defined:"+ listName); + } + + List values = members.get(authorityName); + if(values == null) + { + throw new AlfrescoRuntimeException("Unable to add to authority in list. Authority not member listName: "+ listName + " authorityName:" +authorityName); + } + values.add(value); + + caveatConfig.put(listName, members); + updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); + } + finally + { + readLock.lock(); + writeLock.unlock(); + } + } - List values = members.get(authorityName); - if(values == null) + finally { - throw new AlfrescoRuntimeException("Unable to add to authority in list. Authority not member listName: "+ listName + " authorityName:" +authorityName); + readLock.unlock(); } - values.add(value); - updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); } - + /** * Get the member details of the specified list * @param listName @@ -717,107 +801,96 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon */ public Map> getListDetails(String listName) { - return caveatConfig.get(listName); + Map> listDetails = null; + try + { + readLock.lock(); + listDetails = caveatConfig.get(listName); + } + finally + { + readLock.unlock(); + } + if (listDetails == null) + { + return Collections.emptyMap(); + } + else + { + return Collections.unmodifiableMap(listDetails); + } } - + public List getRMCaveatModels() { return caveatModelQNames; } - + /** - * Replace the values for an authority in a list. + * Replace the values for an authority in a list. * The existing values are removed. - * + * * If the authority does not already exist in the list, it will be added - * + * * @param listName the name of the RMConstraintList * @param authorityName * @param values */ public void updateRMConstraintListAuthority(String listName, String authorityName, Listvalues) { - Map> members = caveatConfig.get(listName); - if(members == null) + Map> members = null; + try { - // Create the new list, with the authority name - Map> constraint = new HashMap>(0); - constraint.put(authorityName, values); - caveatConfig.put(listName, constraint); + writeLock.lock(); + members = caveatConfig.get(listName); + if(members == null) + { + // Create the new list, with the authority name + Map> constraint = new HashMap>(0); + constraint.put(authorityName, new ArrayList(values)); + members = constraint; + } + else + { + members.put(authorityName, new ArrayList(values)); + } + + caveatConfig.put(listName, members); + updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); } - else + finally { - members.put(authorityName, values); + writeLock.unlock(); } - - updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); } - + /** * Replace the authorities for a value in a list - * + * * @param listName * @param valueName * @param authorities */ public void updateRMConstraintListValue(String listName, String valueName, Listauthorities) { - - // members contains member, values[] + Map> members = caveatConfig.get(listName); - - if(members == null) + try { - // Members List does not exist - Map> emptyConstraint = new HashMap>(0); - caveatConfig.put(listName, emptyConstraint); - members = emptyConstraint; - - } - // authorities contains authority, values[] - // pivot contains value, members[] - Map> pivot = PivotUtil.getPivot(members); - - // remove all authorities which have this value - List existingAuthorities = pivot.get(valueName); - if(existingAuthorities != null) - { - for(String authority : existingAuthorities) + writeLock.lock(); + + if(members == null) { - List vals = members.get(authority); - vals.remove(valueName); + // Members List does not exist + Map> emptyConstraint = new HashMap>(0); + caveatConfig.put(listName, emptyConstraint); + members = emptyConstraint; + } - } - // add the new authorities for this value - for(String authority : authorities) - { - List vals = members.get(authority); - if(vals == null) - { - vals= new ArrayList(); - members.put(authority, vals); - } - vals.add(valueName); - } - - updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); - } - - public void removeRMConstraintListValue(String listName, String valueName) - { - // members contains member, values[] - Map> members = caveatConfig.get(listName); - - if(members == null) - { - // list does not exist - } - else - { // authorities contains authority, values[] // pivot contains value, members[] Map> pivot = PivotUtil.getPivot(members); - + // remove all authorities which have this value List existingAuthorities = pivot.get(valueName); if(existingAuthorities != null) @@ -828,52 +901,137 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon vals.remove(valueName); } } - + // add the new authorities for this value + for(String authority : authorities) + { + List vals = members.get(authority); + if(vals == null) + { + vals= new ArrayList(); + members.put(authority, vals); + } + vals.add(valueName); + } + caveatConfig.put(listName, members); updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); } + finally + { + writeLock.unlock(); + } } - + + public void removeRMConstraintListValue(String listName, String valueName) + { + Map> members = null; + try + { + readLock.lock(); + + members = caveatConfig.get(listName); + if(members == null) + { + // list does not exist + } + else + { + try + { + readLock.unlock(); + writeLock.lock(); + // check again + members = caveatConfig.get(listName); + if(members == null) + { + // list does not exist + } + else + { + // authorities contains authority, values[] + // pivot contains value, members[] + Map> pivot = PivotUtil.getPivot(members); + + // remove all authorities which have this value + List existingAuthorities = pivot.get(valueName); + if(existingAuthorities != null) + { + for(String authority : existingAuthorities) + { + List vals = members.get(authority); + vals.remove(valueName); + } + caveatConfig.put(listName, members); + } + } + + updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); + } + finally + { + readLock.lock(); + writeLock.unlock(); + } + + } + } + finally + { + readLock.unlock(); + } + } + /** * Remove an authority from a list - * + * * @param listName the name of the RMConstraintList * @param authorityName * @param values */ public void removeRMConstraintListAuthority(String listName, String authorityName) { - Map> members = caveatConfig.get(listName); - if(members != null) + Map> members = null; + try { - members.remove(listName); + writeLock.lock(); + members = caveatConfig.get(listName); + if(members != null) + { + members.remove(listName); + } + + caveatConfig.put(listName, members); + updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); + } - - updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); - } - + finally + { + writeLock.unlock(); + } +} + /** * @param config the configuration to convert * @return a String containing the JSON representation of the configuration. */ - private String convertToJSONString(Map>> config) + private String convertToJSONString(SimpleCache>> config) { JSONObject obj = new JSONObject(); - - try + + try { - Set listNames = config.keySet(); + Collection listNames = config.getKeys(); for(String listName : listNames) { Map> members = config.get(listName); - + Set authorityNames = members.keySet(); JSONObject listMembers = new JSONObject(); - + for(String authorityName : authorityNames) { listMembers.put(authorityName, members.get(authorityName)); } - + obj.put(listName, listMembers); } } @@ -883,7 +1041,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon } return obj.toString(); } - + /** * Get an RMConstraintInfo * @param listQName @@ -898,7 +1056,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon if (con instanceof RMListOfValuesConstraint) { final RMListOfValuesConstraint def = (RMListOfValuesConstraint)con; - + RMConstraintInfo info = new RMConstraintInfo(); info.setName(listQName.toPrefixString()); info.setTitle(con.getTitle()); @@ -909,7 +1067,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon return def.getAllowedValues(); } }, AuthenticationUtil.getSystemUserName()); - + info.setAllowedValues(allowedValues.toArray(new String[allowedValues.size()])); info.setCaseSensitive(def.isCaseSensitive()); return info; @@ -920,7 +1078,7 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon /** * Get RM Constraint detail. - * + * * @return the constraintInfo or null */ public RMConstraintInfo getRMConstraint(String listName) @@ -928,17 +1086,33 @@ public class RMCaveatConfigComponentImpl implements ContentServicePolicies.OnCon QName listQName = QName.createQName(listName, namespaceService); return getRMConstraint(listQName); } - + public void deleteRMConstraint(String listName) { - caveatConfig.remove(listName); - updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); + try + { + writeLock.lock(); + caveatConfig.remove(listName); + updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); + } + finally + { + writeLock.unlock(); + } } - + public void addRMConstraint(String listName) { - Map> emptyConstraint = new HashMap>(0); - caveatConfig.put(listName, emptyConstraint); - updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); + try + { + writeLock.lock(); + Map> emptyConstraint = new HashMap>(0); + caveatConfig.put(listName, emptyConstraint); + updateOrCreateCaveatConfig(convertToJSONString(caveatConfig)); + } + finally + { + writeLock.unlock(); + } } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/forms/RecordsManagementTypeFormFilter.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/forms/RecordsManagementTypeFormFilter.java index 915cca99cc..f8495049ca 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/forms/RecordsManagementTypeFormFilter.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/forms/RecordsManagementTypeFormFilter.java @@ -113,11 +113,20 @@ public class RecordsManagementTypeFormFilter extends RecordsManagementFormFilter for (FieldDefinition fieldDef : fieldDefs) { String prefixName = fieldDef.getName(); - if (prefixName.equals("rma:identifier")) + if (prefixName.equals("rma:identifier") == true) { String defaultId = identifierService.generateIdentifier(typeName, null); fieldDef.setDefaultValue(defaultId); } + // NOTE: we set these defaults in the form for backwards compatibility reasons (RM-753) + else if (prefixName.equals("rma:vitalRecordIndicator") == true) + { + fieldDef.setDefaultValue(Boolean.FALSE.toString()); + } + else if (prefixName.equals("rma:reviewPeriod") == true) + { + fieldDef.setDefaultValue("none|0"); + } } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/RMv2FilePlanNodeRefPatch.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/RMv2FilePlanNodeRefPatch.java index 526b4756cb..bd8175f8dd 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/RMv2FilePlanNodeRefPatch.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/RMv2FilePlanNodeRefPatch.java @@ -18,11 +18,16 @@ */ package org.alfresco.module.org_alfresco_module_rm.patch; +import java.io.Serializable; import java.util.List; +import org.alfresco.module.org_alfresco_module_rm.FilePlanComponentKind; import org.alfresco.module.org_alfresco_module_rm.RecordsManagementService; +import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; import org.alfresco.module.org_alfresco_module_rm.dod5015.DOD5015Model; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.security.RecordsManagementSecurityService; +import org.alfresco.module.org_alfresco_module_rm.security.Role; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.patch.PatchDAO; import org.alfresco.repo.domain.qname.QNameDAO; @@ -30,6 +35,8 @@ import org.alfresco.repo.module.AbstractModuleComponent; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Period; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; @@ -53,6 +60,8 @@ public class RMv2FilePlanNodeRefPatch extends AbstractModuleComponent private PatchDAO patchDAO; private NodeDAO nodeDAO; private QNameDAO qnameDAO; + private PermissionService permissionService; + private RecordsManagementSecurityService recordsManagementSecurityService; public void setNodeService(NodeService nodeService) { @@ -84,6 +93,22 @@ public class RMv2FilePlanNodeRefPatch extends AbstractModuleComponent this.qnameDAO = qnameDAO; } + /** + * @param recordsManagementSecurityService records management security service + */ + public void setRecordsManagementSecurityService(RecordsManagementSecurityService recordsManagementSecurityService) + { + this.recordsManagementSecurityService = recordsManagementSecurityService; + } + + /** + * @param permissionService permission service + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + /** * @see org.alfresco.repo.module.AbstractModuleComponent#executeInternal() */ @@ -98,24 +123,53 @@ public class RMv2FilePlanNodeRefPatch extends AbstractModuleComponent Pair aspectPair = qnameDAO.getQName(ASPECT_FILE_PLAN_COMPONENT); if (aspectPair != null) { - List records = patchDAO.getNodesByAspectQNameId(aspectPair.getFirst(), 0L, patchDAO.getMaxAdmNodeID()); + List filePlanComponents = patchDAO.getNodesByAspectQNameId(aspectPair.getFirst(), 0L, patchDAO.getMaxAdmNodeID()); if (logger.isDebugEnabled() == true) { - logger.debug(" ... updating " + records.size() + " items" ); + logger.debug(" ... updating " + filePlanComponents.size() + " items" ); } behaviourFilter.disableBehaviour(); try { - for (Long record : records) + for (Long filePlanComponent : filePlanComponents) { - Pair recordPair = nodeDAO.getNodePair(record); - NodeRef recordNodeRef = recordPair.getSecond(); + Pair recordPair = nodeDAO.getNodePair(filePlanComponent); + NodeRef filePlanComponentNodeRef = recordPair.getSecond(); - if (nodeService.getProperty(recordNodeRef, PROP_ROOT_NODEREF) == null) + NodeRef filePlan = recordsManagementService.getFilePlan(filePlanComponentNodeRef); + + // set the file plan node reference + if (nodeService.getProperty(filePlanComponentNodeRef, PROP_ROOT_NODEREF) == null) { - nodeService.setProperty(recordNodeRef, PROP_ROOT_NODEREF, recordsManagementService.getFilePlan(recordNodeRef)); + nodeService.setProperty(filePlanComponentNodeRef, PROP_ROOT_NODEREF, filePlan); + } + + // only set the rmadmin permissions on record categories, record folders and records + FilePlanComponentKind kind = recordsManagementService.getFilePlanComponentKind(filePlanComponentNodeRef); + if (FilePlanComponentKind.RECORD_CATEGORY.equals(kind) == true || + FilePlanComponentKind.RECORD_FOLDER.equals(kind) == true || + FilePlanComponentKind.RECORD.equals(kind) == true ) + { + // ensure the that the records management role has read and file on the node + Role adminRole = recordsManagementSecurityService.getRole(filePlan, "Administrator"); + if (adminRole != null) + { + permissionService.setPermission(filePlanComponentNodeRef, adminRole.getRoleGroupName(), RMPermissionModel.FILING, true); + } + + // ensure that the default vital record default values have been set (RM-753) + Serializable vitalRecordIndicator = nodeService.getProperty(filePlanComponentNodeRef, PROP_VITAL_RECORD_INDICATOR); + if (vitalRecordIndicator == null) + { + nodeService.setProperty(filePlanComponentNodeRef, PROP_VITAL_RECORD_INDICATOR, false); + } + Serializable reviewPeriod = nodeService.getProperty(filePlanComponentNodeRef, PROP_REVIEW_PERIOD); + if (reviewPeriod == null) + { + nodeService.setProperty(filePlanComponentNodeRef, PROP_REVIEW_PERIOD, new Period("none|0")); + } } } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/RMv2SavedSearchPatch.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/RMv2SavedSearchPatch.java new file mode 100644 index 0000000000..076ec3860c --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/RMv2SavedSearchPatch.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.module.org_alfresco_module_rm.patch; + +import java.util.List; + +import org.alfresco.module.org_alfresco_module_rm.dod5015.DOD5015Model; +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.search.RecordsManagementSearchService; +import org.alfresco.module.org_alfresco_module_rm.search.SavedSearchDetails; +import org.alfresco.repo.module.AbstractModuleComponent; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanNameAware; + +/** + * RM v2.0 Saved Search Patch + * + * + * @author Roy Wetherall + */ +public class RMv2SavedSearchPatch extends AbstractModuleComponent + implements BeanNameAware, RecordsManagementModel, DOD5015Model +{ + /** Logger */ + private static Log logger = LogFactory.getLog(RMv2SavedSearchPatch.class); + + /** RM site id */ + private static final String RM_SITE_ID = "rm"; + + /** Records management search service */ + private RecordsManagementSearchService recordsManagementSearchService; + + /** + * @param recordsManagementSearchService records management search service + */ + public void setRecordsManagementSearchService(RecordsManagementSearchService recordsManagementSearchService) + { + this.recordsManagementSearchService = recordsManagementSearchService; + } + + /** + * @see org.alfresco.repo.module.AbstractModuleComponent#executeInternal() + */ + @Override + protected void executeInternal() throws Throwable + { + if (logger.isDebugEnabled() == true) + { + logger.debug("RM Module RMv2SavedSearchPatch ..."); + } + + // get the saved searches + List savedSearches = recordsManagementSearchService.getSavedSearches(RM_SITE_ID); + + if (logger.isDebugEnabled() == true) + { + logger.debug(" ... updating " + savedSearches.size() + " saved searches"); + } + + for (SavedSearchDetails savedSearchDetails : savedSearches) + { + // re-save each search so that the query is regenerated correctly + recordsManagementSearchService.deleteSavedSearch(RM_SITE_ID, savedSearchDetails.getName()); + recordsManagementSearchService.saveSearch(RM_SITE_ID, + savedSearchDetails.getName(), + savedSearchDetails.getDescription(), + savedSearchDetails.getSearch(), + savedSearchDetails.getSearchParameters(), + savedSearchDetails.isPublic()); + } + + if (logger.isDebugEnabled() == true) + { + logger.debug(" ... complete"); + } + + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/search/SavedSearchDetails.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/search/SavedSearchDetails.java index 53d6923a03..8be1c3d2a6 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/search/SavedSearchDetails.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/search/SavedSearchDetails.java @@ -95,7 +95,7 @@ public class SavedSearchDetails extends ReportDetails private String siteId; /** Indicates whether the saved search is public or not */ - private boolean isPublic; + private boolean isPublic = true; /** Indicates whether the saved search is a report */ private boolean isReport = false; @@ -103,8 +103,10 @@ public class SavedSearchDetails extends ReportDetails /** Namespace service */ NamespaceService namespaceService; + /** Records management search service */ RecordsManagementSearchServiceImpl searchService; + /** Saves search details compatibility */ private SavedSearchDetailsCompatibility compatibility; /** @@ -178,7 +180,7 @@ public class SavedSearchDetails extends ReportDetails } // Determine whether the saved query is public or not - boolean isPublic = false; + boolean isPublic = true; if (search.has(PUBLIC) == true) { isPublic = search.getBoolean(PUBLIC); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/BroadcastVitalRecordDefinitionAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/BroadcastVitalRecordDefinitionAction.java index fd14cc1597..e84d8fb8a1 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/BroadcastVitalRecordDefinitionAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/BroadcastVitalRecordDefinitionAction.java @@ -78,7 +78,15 @@ public class BroadcastVitalRecordDefinitionAction extends RMActionExecuterAbstra private void propagateChangeToChildrenOf(NodeRef actionedUponNodeRef) { Map parentProps = nodeService.getProperties(actionedUponNodeRef); - boolean parentVri = (Boolean) parentProps.get(PROP_VITAL_RECORD_INDICATOR); + + // parent vital record indicator, default to null if not set + boolean parentVri = false; + Boolean parentVriValue = (Boolean) parentProps.get(PROP_VITAL_RECORD_INDICATOR); + if (parentVriValue != null) + { + parentVri = parentVriValue.booleanValue(); + } + Period parentReviewPeriod = (Period) parentProps.get(PROP_REVIEW_PERIOD); List assocs = this.nodeService.getChildAssocs(actionedUponNodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/VitalRecordDefinitionImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/VitalRecordDefinitionImpl.java index 571992b247..2315ddf2bf 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/VitalRecordDefinitionImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/VitalRecordDefinitionImpl.java @@ -63,6 +63,10 @@ public class VitalRecordDefinitionImpl implements VitalRecordDefinition, Records /* package */ static VitalRecordDefinition create(NodeService nodeService, NodeRef nodeRef) { Boolean enabled = (Boolean)nodeService.getProperty(nodeRef, PROP_VITAL_RECORD_INDICATOR); + if (enabled == null) + { + enabled = Boolean.FALSE; + } Period reviewPeriod = (Period)nodeService.getProperty(nodeRef, PROP_REVIEW_PERIOD); return new VitalRecordDefinitionImpl(enabled, reviewPeriod); }