/* * #%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.repo.web.scripts.substitutionsuggestions; 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.Capability; import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.repo.action.parameter.ParameterProcessorComponent; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.namespace.QName; import org.apache.commons.lang.StringUtils; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.DeclarativeWebScript; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptRequest; /** * Implementation for Java backed webscript to get substitution suggestions * given a text fragment (e.g. date.month for 'mon'). * * @author Mark Hibbins * @since 2.2 */ public class RmSubstitutionSuggestionsGet extends DeclarativeWebScript { private static final String FRAGMENT_PARAMETER = "fragment"; private static final String PATH_PARAMETER = "path"; private static final String UNFILED_PARAMETER = "unfiled"; private static final String UNFILED = "true"; private static final String SUBSTITUTIONS_MODEL_KEY = "substitutions"; private static final String CREATE_CAPABILITY = "Create"; private static final String VIEW_CAPABILITY = "ViewRecords"; private static final int DEFAULT_SUBSTITUTION_MINIMUM_FRAGMENT_LENGTH = 0; private static final int DEFAULT_MAXIMUM_NUMBER_PATH_SUGGESTIONS = 10; private int pathSubstitutionMaximumNumberSuggestions = DEFAULT_MAXIMUM_NUMBER_PATH_SUGGESTIONS; private int substitutionMinimumFragmentSize = DEFAULT_SUBSTITUTION_MINIMUM_FRAGMENT_LENGTH; private ParameterProcessorComponent parameterProcessorComponent; private NodeService nodeService; private FilePlanService filePlanService; private CapabilityService capabilityService; /** * Set the parameter processor component bean * * @param parameterProcessorComponent */ public void setParameterProcessorComponent(ParameterProcessorComponent parameterProcessorComponent) { this.parameterProcessorComponent = parameterProcessorComponent; } /** * Set the parameter processor component bean * * @param parameterProcessorComponent */ public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } /** * @param filePlanService file plan service */ public void setFilePlanService(FilePlanService filePlanService) { this.filePlanService = filePlanService; } /** * @param filePlanService file plan service */ public void setCapabilityService(CapabilityService capabilityService) { this.capabilityService = capabilityService; } /** * Set the minimum fragment size to process for suggestion processing * * @param maximumNumberSuggestions */ public void setSubstitutionMinimumFragmentSize(int substitutionMinimumFragmentSize) { this.substitutionMinimumFragmentSize = Math.max(substitutionMinimumFragmentSize, DEFAULT_SUBSTITUTION_MINIMUM_FRAGMENT_LENGTH); } /** * Set the maxmimum number of suggestions returned from the global property * * @param maximumNumberSuggestions */ public void setPathSubstitutionMaximumNumberSuggestions(int pathSubstitutionMaximumNumberSuggestions) { this.pathSubstitutionMaximumNumberSuggestions = (pathSubstitutionMaximumNumberSuggestions <= 0 ? DEFAULT_MAXIMUM_NUMBER_PATH_SUGGESTIONS: pathSubstitutionMaximumNumberSuggestions); } /** * Return a list of substitutions for the given fragment. * * @see org.springframework.extensions.webscripts.DeclarativeWebScript#executeImpl(org.springframework.extensions.webscripts.WebScriptRequest, * org.springframework.extensions.webscripts.Status, * org.springframework.extensions.webscripts.Cache) */ @Override protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache) { String fragment = req.getParameter(FRAGMENT_PARAMETER); String path = req.getParameter(PATH_PARAMETER); String unfiledString = req.getParameter(UNFILED_PARAMETER); boolean unfiled = (unfiledString != null) && UNFILED.equals(unfiledString); List<String> substitutionSuggestions = new ArrayList<String>(); if((fragment != null) && (fragment.length() >= this.substitutionMinimumFragmentSize)) { substitutionSuggestions.addAll(getSubPathSuggestions(req, path, fragment, unfiled)); substitutionSuggestions.addAll(this.parameterProcessorComponent.getSubstitutionSuggestions(fragment)); } Map<String, Object> model = new HashMap<String, Object>(); model.put(SUBSTITUTIONS_MODEL_KEY, substitutionSuggestions); return model; } /** * Return a list of path suggestions for the path fragment supplied. * * @param path * @param fragment * @return */ private List<String> getSubPathSuggestions(WebScriptRequest req, final String path, final String fragment, boolean unfiled) { List<String> pathSuggestions = new ArrayList<String>(); if((path != null) && path.startsWith("/") && (fragment != null)) { String[] pathFragments = path.split("/"); NodeRef currentNode = getFilePlan(req, unfiled); for(String pathFragment : pathFragments) { // ignore empty elements of the path produced by split if(!pathFragment.isEmpty()) { boolean foundThisPathFragment = false; List<ChildAssociationRef> children = nodeService.getChildAssocs(currentNode); for (ChildAssociationRef childAssoc : children) { NodeRef childNodeRef = childAssoc.getChildRef(); String fileName = (String) nodeService.getProperty(childNodeRef, ContentModel.PROP_NAME); if(fileName.equals(pathFragment) && isNodeRefAppropriateForPathSuggestion(childNodeRef, unfiled)) { foundThisPathFragment = true; currentNode = childNodeRef; break; } } if(!foundThisPathFragment) { currentNode = null; break; } } } if(currentNode != null) { String lowerCaseFragment = fragment.toLowerCase(); List<ChildAssociationRef> children = nodeService.getChildAssocs(currentNode); for (ChildAssociationRef childAssoc : children) { NodeRef childNodeRef = childAssoc.getChildRef(); String fileName = (String) nodeService.getProperty(childNodeRef, ContentModel.PROP_NAME); if((fragment.isEmpty() || fileName.toLowerCase().startsWith(lowerCaseFragment)) && isNodeRefAppropriateForPathSuggestion(childNodeRef, unfiled)) { pathSuggestions.add("/" + fileName); if(pathSuggestions.size() >= pathSubstitutionMaximumNumberSuggestions) { break; } } } } } return pathSuggestions; } /** * Utility method to get the file plan from the passed parameters. * * @param req * @return */ protected NodeRef getFilePlan(WebScriptRequest req, boolean unfiled) { NodeRef filePlan = null; Map<String, String> templateVars = req.getServiceMatch().getTemplateVars(); String siteId = templateVars.get("siteid"); if (siteId != null) { filePlan = filePlanService.getFilePlanBySiteId(siteId); } if (filePlan == null) { String storeType = templateVars.get("store_type"); String storeId = templateVars.get("store_id"); String id = templateVars.get("id"); if (!StringUtils.isEmpty(storeType) && !StringUtils.isEmpty(storeId) && !StringUtils.isEmpty(id)) { StoreRef storeRef = new StoreRef(storeType, storeId); NodeRef nodeRef = new NodeRef(storeRef, id); if (filePlanService.isFilePlan(nodeRef)) { filePlan = nodeRef; } } } if (filePlan == null) { // Assume we are in a legacy repository and we will grab the default file plan filePlan = filePlanService.getFilePlanBySiteId(FilePlanService.DEFAULT_RM_SITE_ID); } return unfiled ? filePlanService.getUnfiledContainer(filePlan) : filePlan; } /** * Identifies record category and record folder types of nodeRef * * @param nodeRef Instance of NodeRef to be tested * @return True if the passed NodeRef instance is a record category or record folder */ private boolean isNodeRefAppropriateForPathSuggestion(NodeRef nodeRef, boolean unfiled) { // check node type QName type = nodeService.getType(nodeRef); boolean isCorrectType = (!unfiled && (RecordsManagementModel.TYPE_RECORD_FOLDER.equals(type) || RecordsManagementModel.TYPE_RECORD_CATEGORY .equals(type)) || (unfiled && RecordsManagementModel.TYPE_UNFILED_RECORD_FOLDER.equals(type))); // check permissions boolean canView = false; if(isCorrectType) { Capability createCapability = capabilityService.getCapability(CREATE_CAPABILITY); Capability viewCapability = capabilityService.getCapability(VIEW_CAPABILITY); if ((createCapability != null) && (viewCapability != null)) { List<String> requiredCapabilities = new ArrayList<String>(); requiredCapabilities.add(CREATE_CAPABILITY); requiredCapabilities.add(VIEW_CAPABILITY); Map<Capability, AccessStatus> map = capabilityService.getCapabilitiesAccessState(nodeRef, requiredCapabilities); if (map.containsKey(createCapability) && map.containsKey(viewCapability)) { AccessStatus createAccessStatus = map.get(createCapability); AccessStatus viewAccessStatus = map.get(viewCapability); if (createAccessStatus.equals(AccessStatus.ALLOWED) && viewAccessStatus.equals(AccessStatus.ALLOWED)) { canView = true; } } } } return isCorrectType && canView; } }