/* * #%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.jscript.app; import static org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel.READ_RECORDS; import static org.alfresco.service.cmr.security.AccessStatus.ALLOWED; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService; import org.alfresco.module.org_alfresco_module_rm.capability.impl.ViewRecordsCapability; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanComponentKind; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.PathUtil; import org.apache.commons.lang.ArrayUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; /** * Extend JSON conversion component to include RM specifics. * * @author Roy Wetherall */ public class JSONConversionComponent extends org.alfresco.repo.jscript.app.JSONConversionComponent implements NodeServicePolicies.OnDeleteNodePolicy, NodeServicePolicies.OnCreateNodePolicy { /** JSON values */ private static final String IS_RM_NODE = "isRmNode"; private static final String RM_NODE = "rmNode"; private static final String IS_RM_SITE_CREATED = "isRmSiteCreated"; private static final String IS_RECORD_CONTRIBUTOR_GROUP_ENABLED = "isRecordContributorGroupEnabled"; private static final String RECORD_CONTRIBUTOR_GROUP_NAME = "recordContributorGroupName"; /** true if record contributor group is enabled, false otherwise */ private boolean isRecordContributorsGroupEnabled = false; /** record contributors group */ private String recordContributorsGroupName = "RECORD_CONTRIBUTORS"; /** Record service */ private RecordService recordService; /** File plan service */ private FilePlanService filePlanService; /** Capability service */ private CapabilityService capabilityService; /** dictionary service */ private DictionaryService dictionaryService; /** site service */ private SiteService siteService; /** Indicators */ private List<BaseEvaluator> indicators = new ArrayList<BaseEvaluator>(); /** Actions */ private List<BaseEvaluator> actions = new ArrayList<BaseEvaluator>(); /** The policy component */ private PolicyComponent policyComponent; /** JSON conversion component cache */ private SimpleCache<String, Object> jsonConversionComponentCache; /** Constants for checking the cache */ private static final String RM_SITE_EXISTS = "rmSiteExists"; /** * @param enabled true if enabled, false otherwise */ public void setRecordContributorsGroupEnabled(boolean enabled) { isRecordContributorsGroupEnabled = enabled; } /** * @param recordContributorsGroupName record contributors group name */ public void setRecordContributorsGroupName(String recordContributorsGroupName) { this.recordContributorsGroupName = recordContributorsGroupName; } /** * @param recordService record service */ public void setRecordService(RecordService recordService) { this.recordService = recordService; } /** * @param filePlanService file plan service */ public void setFilePlanService(FilePlanService filePlanService) { this.filePlanService = filePlanService; } /** * @param capabilityService capability service */ public void setCapabilityService(CapabilityService capabilityService) { this.capabilityService = capabilityService; } /** * @param dictionaryService dictionary service */ public void setDictionaryService(DictionaryService dictionaryService) { this.dictionaryService = dictionaryService; } /** * @param siteService site service */ public void setSiteService(SiteService siteService) { this.siteService = siteService; } /** * @param indicator registered indicator */ public void registerIndicator(BaseEvaluator indicator) { indicators.add(indicator); } /** * @param action registered action */ public void registerAction(BaseEvaluator action) { actions.add(action); } /** * @param policyComponent policy component */ public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; } /** * Gets the json conversion component cache * * @return The json conversion component cache */ protected SimpleCache<String, Object> getJsonConversionComponentCache() { return this.jsonConversionComponentCache; } /** * Sets the json conversion component cache * * @param jsonConversionComponentCache The json conversion component cache */ public void setJsonConversionComponentCache(SimpleCache<String, Object> jsonConversionComponentCache) { this.jsonConversionComponentCache = jsonConversionComponentCache; } /** * The initialise method */ public void init() { policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"), RecordsManagementModel.TYPE_RM_SITE, new JavaBehaviour(this, "onDeleteNode")); policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), RecordsManagementModel.TYPE_RM_SITE, new JavaBehaviour(this, "onCreateNode")); } /** * @see org.alfresco.repo.jscript.app.JSONConversionComponent#setRootValues(org.alfresco.service.cmr.model.FileInfo, * org.json.simple.JSONObject, boolean) */ @SuppressWarnings("unchecked") @Override protected void setRootValues(FileInfo nodeInfo, JSONObject rootJSONObject, boolean useShortQNames) { if (nodeInfo != null) { // Set the base root values super.setRootValues(nodeInfo, rootJSONObject, useShortQNames); // check the exisitance of the RM site checkRmSiteExistence(rootJSONObject); // get the record contributor information rootJSONObject.put(IS_RECORD_CONTRIBUTOR_GROUP_ENABLED, isRecordContributorsGroupEnabled); rootJSONObject.put(RECORD_CONTRIBUTOR_GROUP_NAME, recordContributorsGroupName); // Get the node reference for convenience NodeRef nodeRef = nodeInfo.getNodeRef(); if (AccessStatus.ALLOWED.equals(capabilityService.getCapabilityAccessState(nodeRef, ViewRecordsCapability.NAME))) { // Indicate whether the node is a RM object or not boolean isFilePlanComponent = filePlanService.isFilePlanComponent(nodeRef); rootJSONObject.put(IS_RM_NODE, isFilePlanComponent); if (isFilePlanComponent) { rootJSONObject.put(RM_NODE, setRmNodeValues(nodeRef, useShortQNames)); // FIXME: Is this the right place to add the information? addInfo(nodeInfo, rootJSONObject); } } } } /** * Checks for the existance of the RM site * * @param rootJSONObject the root JSON object */ @SuppressWarnings("unchecked") private void checkRmSiteExistence(JSONObject rootJSONObject) { if (!getJsonConversionComponentCache().contains(RM_SITE_EXISTS)) { SiteInfo site = siteService.getSite(FilePlanService.DEFAULT_RM_SITE_ID); if (site != null) { getJsonConversionComponentCache().put(RM_SITE_EXISTS, true); rootJSONObject.put(IS_RM_SITE_CREATED, true); } else { getJsonConversionComponentCache().put(RM_SITE_EXISTS, false); rootJSONObject.put(IS_RM_SITE_CREATED, false); } } else { rootJSONObject.put(IS_RM_SITE_CREATED, getJsonConversionComponentCache().get(RM_SITE_EXISTS)); } } /** * Helper method to add information about node * * @param nodeInfo node information * @param rootJSONObject root JSON object */ @SuppressWarnings("unchecked") private void addInfo(final FileInfo nodeInfo, JSONObject rootJSONObject) { String itemType = (String) rootJSONObject.get("type"); final QName itemTypeQName = QName.createQName(itemType, namespaceService); NodeRef originatingLocation = AuthenticationUtil.runAsSystem(new RunAsWork<NodeRef>() { public NodeRef doWork() { NodeRef originatingLocation = null; if (dictionaryService.isSubClass(itemTypeQName, ContentModel.TYPE_CONTENT)) { NodeRef nodeRef = nodeInfo.getNodeRef(); List<ChildAssociationRef> parentAssocs = nodeService.getParentAssocs(nodeRef); for (ChildAssociationRef parent : parentAssocs) { // FIXME: What if there is more than a secondary parent? // RM-3930 if (!parent.isPrimary()) { originatingLocation = parent.getParentRef(); // only consider the non-RM parent otherwise we can // run into issues with frozen or transferring records if (!nodeService.hasAspect(originatingLocation, RecordsManagementModel.ASPECT_FILE_PLAN_COMPONENT)) { // assume we have found the correct in-place location // FIXME when we support multiple in-place locations // See RM-3929 break; } } } } return originatingLocation; } }); if (originatingLocation != null) { // add the originating location (if there is one) String pathSeparator = "/"; String displayPath = getDisplayPath(originatingLocation); String[] displayPathElements = displayPath.split(pathSeparator); Object[] subPath = ArrayUtils.subarray(displayPathElements, 5, displayPathElements.length); StringBuilder originatingLocationPath = new StringBuilder(); for (int i = 0; i < subPath.length; i++) { originatingLocationPath.append(pathSeparator).append(subPath[i]); } rootJSONObject.put("originatingLocationPath", originatingLocationPath.toString()); } } /** * Helper method to get the display path. * * @param nodeRef node reference * @return String display path */ private String getDisplayPath(final NodeRef nodeRef) { return AuthenticationUtil.runAs(new RunAsWork<String>() { public String doWork() throws Exception { return PathUtil.getDisplayPath(nodeService.getPath(nodeRef), true); } }, AuthenticationUtil.getAdminUserName()); } /** * Helper method to set the RM node values * * @param nodeRef node reference * @param useShortQName indicates whether the short QName are used or not * @return {@link JSONObject} JSON object containing values */ @SuppressWarnings("unchecked") private JSONObject setRmNodeValues(final NodeRef nodeRef, final boolean useShortQName) { JSONObject rmNodeValues = new JSONObject(); // UI convenience type rmNodeValues.put("uiType", getUIType(nodeRef)); // Get the 'kind' of the file plan component FilePlanComponentKind kind = filePlanService.getFilePlanComponentKind(nodeRef); rmNodeValues.put("kind", kind.toString()); // set the primary parent node reference ChildAssociationRef assoc = nodeService.getPrimaryParent(nodeRef); if (assoc != null) { rmNodeValues.put("primaryParentNodeRef", assoc.getParentRef().toString()); } Map<String, Object> values = AuthenticationUtil.runAsSystem(new RunAsWork<Map<String, Object>>() { public Map<String, Object> doWork() throws Exception { Map<String, Object> result = new HashMap<String, Object>(); // File plan node reference NodeRef filePlan = filePlanService.getFilePlan(nodeRef); if (filePlan != null) { result.put("filePlan", filePlan.toString()); // Unfiled container node reference NodeRef unfiledRecordContainer = filePlanService.getUnfiledContainer(filePlan); if (unfiledRecordContainer != null) { result.put("unfiledRecordContainer", unfiledRecordContainer.toString()); result.put("properties", propertiesToJSON(unfiledRecordContainer, nodeService.getProperties(unfiledRecordContainer), useShortQName)); QName type = fileFolderService.getFileInfo(unfiledRecordContainer).getType(); result.put("type", useShortQName ? type.toPrefixString(namespaceService) : type.toString()); } } return result; } }); rmNodeValues.putAll(values); // Set the indicators array setIndicators(rmNodeValues, nodeRef); // Set the actions array setActions(rmNodeValues, nodeRef); return rmNodeValues; } @SuppressWarnings("unchecked") private void setIndicators(JSONObject rmNodeValues, NodeRef nodeRef) { if (indicators != null && !indicators.isEmpty()) { JSONArray jsonIndicators = new JSONArray(); for (BaseEvaluator indicator : indicators) { if (indicator.evaluate(nodeRef)) { jsonIndicators.add(indicator.getName()); } } rmNodeValues.put("indicators", jsonIndicators); } } @SuppressWarnings("unchecked") private void setActions(JSONObject rmNodeValues, NodeRef nodeRef) { if (actions != null && !actions.isEmpty()) { JSONArray jsonActions = new JSONArray(); for (BaseEvaluator action : actions) { if (action.evaluate(nodeRef)) { jsonActions.add(action.getName()); } } rmNodeValues.put("actions", jsonActions); } } /** * @see org.alfresco.repo.jscript.app.JSONConversionComponent#permissionsToJSON(org.alfresco.service.cmr.repository.NodeRef) */ @SuppressWarnings("unchecked") protected JSONObject permissionsToJSON(final NodeRef nodeRef) { JSONObject permissionsJSON = new JSONObject(); if (!filePlanService.isFilePlanComponent(nodeRef)) { permissionsJSON = super.permissionsToJSON(nodeRef); } else { if (ALLOWED.equals(permissionService.hasPermission(nodeRef, READ_RECORDS))) { permissionsJSON.put("inherited", permissionService.getInheritParentPermissions(nodeRef)); permissionsJSON.put("roles", allSetPermissionsToJSON(nodeRef)); permissionsJSON.put("user", userPermissionsToJSON(nodeRef)); } } return permissionsJSON; } /** * Gets the rm 'type' used as a UI convenience and compatibility flag. */ private String getUIType(NodeRef nodeRef) { String result = "unknown"; FilePlanComponentKind kind = filePlanService.getFilePlanComponentKind(nodeRef); if (kind != null) { switch (kind) { case FILE_PLAN: { result = "fileplan"; break; } case RECORD_CATEGORY: { result = "record-category"; break; } case RECORD_FOLDER: { if (recordService.isMetadataStub(nodeRef)) { result = "metadata-stub-folder"; } else { result = "record-folder"; } break; } case RECORD: { if (recordService.isMetadataStub(nodeRef)) { result = "metadata-stub"; } else { if (recordService.isDeclared(nodeRef)) { result = "record"; } else { result = "undeclared-record"; } } break; } case HOLD: { result = "hold"; break; } case TRANSFER: { result = "transfer-container"; break; } case UNFILED_RECORD_FOLDER: { result = "unfiled-record-folder"; break; } default: { break; } } } return result; } /** * @see org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy#onDeleteNode(org.alfresco.service.cmr.repository.ChildAssociationRef, boolean) */ @Override public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isNodeArchived) { getJsonConversionComponentCache().put(RM_SITE_EXISTS, false); } /** * @see org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy#onCreateNode(org.alfresco.service.cmr.repository.ChildAssociationRef) */ @Override public void onCreateNode(ChildAssociationRef childAssocRef) { getJsonConversionComponentCache().put(RM_SITE_EXISTS, true); } }