/* * #%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.search; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.ISO9075; import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.springframework.extensions.surf.util.I18NUtil; /** * Records management search service implementation * * @author Roy Wetherall */ public class RecordsManagementSearchServiceImpl implements RecordsManagementSearchService { private static final String SITES_SPACE_QNAME_PATH = "/app:company_home/st:sites/"; /** Name of the main site container used to store the saved searches within */ private static final String SEARCH_CONTAINER = "Saved Searches"; /** File folder service */ private FileFolderService fileFolderService; /** Search service */ private SearchService searchService; /** Site service */ private SiteService siteService; /** Namespace service */ private NamespaceService namespaceService; /** List of report details */ private List<ReportDetails> reports = new ArrayList<ReportDetails>(13); /** * @param fileFolderService file folder service */ public void setFileFolderService(FileFolderService fileFolderService) { this.fileFolderService = fileFolderService; } /** * @param searchService search service */ public void setSearchService(SearchService searchService) { this.searchService = searchService; } /** * @param siteService site service */ public void setSiteService(SiteService siteService) { this.siteService = siteService; } /** * @param namespaceService namespace service */ public void setNamespaceService(NamespaceService namespaceService) { this.namespaceService = namespaceService; } /** * @param reportsJSON */ public void setReportsJSON(String reportsJSON) { try { JSONArray jsonArray = new JSONArray(reportsJSON); for (int i=0; i < jsonArray.length(); i++) { JSONObject report = jsonArray.getJSONObject(i); // Get the name if (!report.has(SavedSearchDetails.NAME)) { throw new AlfrescoRuntimeException("Unable to load report details because name has not been specified. \n" + reportsJSON); } String name = report.getString(SavedSearchDetails.NAME); String translatedName = I18NUtil.getMessage(name); if (translatedName != null) { name = translatedName; } // Get the query if (!report.has(SavedSearchDetails.SEARCH)) { throw new AlfrescoRuntimeException("Unable to load report details because search has not been specified for report " + name + ". \n" + reportsJSON); } String query = report.getString(SavedSearchDetails.SEARCH); // Get the description String description = ""; if (report.has(SavedSearchDetails.DESCRIPTION)) { description = report.getString(SavedSearchDetails.DESCRIPTION); String translatedDescription = I18NUtil.getMessage(description); if (translatedDescription != null) { description = translatedDescription; } } RecordsManagementSearchParameters searchParameters = new RecordsManagementSearchParameters(); if (report.has("searchparams")) { searchParameters = RecordsManagementSearchParameters.createFromJSON(report.getJSONObject("searchparams"), namespaceService); } // Create the report details and add to list ReportDetails reportDetails = new ReportDetails(name, description, query, searchParameters); reports.add(reportDetails); } } catch (JSONException exception) { throw new AlfrescoRuntimeException("Unable to load report details.\n" + reportsJSON, exception); } } /** * @see org.alfresco.module.org_alfresco_module_rm.search.RecordsManagementSearchService#search(java.lang.String, java.lang.String, org.alfresco.module.org_alfresco_module_rm.search.RecordsManagementSearchParameters) */ @Override public List<Pair<NodeRef, NodeRef>> search(String siteId, String query, RecordsManagementSearchParameters rmSearchParameters) { // build the full RM query StringBuilder fullQuery = new StringBuilder(1024); fullQuery.append("PATH:\"") .append(SITES_SPACE_QNAME_PATH) .append("cm:").append(ISO9075.encode(siteId)).append("/cm:documentLibrary//*\"") .append(" AND (") .append(buildQueryString(query, rmSearchParameters)) .append(")"); // create the search parameters SearchParameters searchParameters = new SearchParameters(); searchParameters.setQuery(fullQuery.toString()); searchParameters.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); searchParameters.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); searchParameters.setMaxItems(rmSearchParameters.getMaxItems()); searchParameters.setNamespace(RecordsManagementModel.RM_URI); // set sort for(SortItem entry : rmSearchParameters.getSortOrder()) { searchParameters.addSort(entry.property.toPrefixString(namespaceService), entry.assc); } // set templates for (Entry<String, String> entry : rmSearchParameters.getTemplates().entrySet()) { searchParameters.addQueryTemplate(entry.getKey(), entry.getValue()); } // execute query ResultSet resultSet = searchService.query(searchParameters); // process results List<Pair<NodeRef, NodeRef>> result = new ArrayList<Pair<NodeRef, NodeRef>>(resultSet.length()); for (ChildAssociationRef childAssoc : resultSet.getChildAssocRefs()) { result.add(new Pair<NodeRef, NodeRef>(childAssoc.getParentRef(), childAssoc.getChildRef())); } // return results return result; } /** * * @param queryTerm * @param aspects * @param types * @return */ /*package*/ String buildQueryString(String queryTerm, RecordsManagementSearchParameters searchParameters) { StringBuilder aspectQuery = new StringBuilder(); if (searchParameters.isIncludeRecords()) { appendAspect(aspectQuery, "rma:record"); if (!searchParameters.isIncludeUndeclaredRecords()) { appendAspect(aspectQuery, "rma:declaredRecord"); } if (searchParameters.isIncludeVitalRecords()) { appendAspect(aspectQuery, "rma:vitalRecord"); } } StringBuilder typeQuery = new StringBuilder(); if (searchParameters.isIncludeRecordFolders()) { appendType(typeQuery, "rma:recordFolder"); } List<QName> includedContainerTypes = searchParameters.getIncludedContainerTypes(); if (includedContainerTypes != null && includedContainerTypes.size() != 0) { for (QName includedContainerType : includedContainerTypes) { appendType(typeQuery, includedContainerType.toPrefixString(namespaceService)); } } StringBuilder query = new StringBuilder(); if (queryTerm == null || queryTerm.length() == 0) { // Default to search for everything query.append("ISNODE:T"); } else { if (isComplexQueryTerm(queryTerm)) { query.append(queryTerm); } else { query.append("keywords:\"" + queryTerm + "\""); } } StringBuilder fullQuery = new StringBuilder(1024); if (aspectQuery.length() != 0 || typeQuery.length() != 0) { if (aspectQuery.length() != 0 && typeQuery.length() != 0) { fullQuery.append("("); } if (aspectQuery.length() != 0) { fullQuery.append("(").append(aspectQuery).append(") "); } if (typeQuery.length() != 0) { fullQuery.append("(").append(typeQuery).append(")"); } if (aspectQuery.length() != 0 && typeQuery.length() != 0) { fullQuery.append(")"); } } if (searchParameters.isIncludeFrozen()) { appendAspect(fullQuery, "rma:frozen"); } else { appendNotAspect(fullQuery, "rma:frozen"); } if (searchParameters.isIncludeCutoff()) { appendAspect(fullQuery, "rma:cutOff"); } if (fullQuery.length() != 0) { fullQuery.append(" AND "); } fullQuery.append(query).append(" AND NOT ASPECT:\"rma:versionedRecord\""); return fullQuery.toString(); } private boolean isComplexQueryTerm(String query) { return query.matches(".*[\":].*"); } /** * * @param sb * @param aspect */ private void appendAspect(StringBuilder sb, String aspect) { appendWithJoin(sb, " AND ", "ASPECT:\"", aspect, "\""); } private void appendNotAspect(StringBuilder sb, String aspect) { appendWithJoin(sb, " AND ", "NOT ASPECT:\"", aspect, "\""); } /** * * @param sb * @param type */ private void appendType(StringBuilder sb, String type) { appendWithJoin(sb, " ", "TYPE:\"", type, "\""); } /** * * @param sb * @param withJoin * @param prefix * @param value * @param postfix */ private void appendWithJoin(StringBuilder sb, String withJoin, String prefix, String value, String postfix) { if (sb.length() != 0) { sb.append(withJoin); } sb.append(prefix).append(value).append(postfix); } /** * @see org.alfresco.module.org_alfresco_module_rm.search.RecordsManagementSearchService#getSavedSearches(java.lang.String) */ @Override public List<SavedSearchDetails> getSavedSearches(String siteId) { List<SavedSearchDetails> result = new ArrayList<SavedSearchDetails>(17); NodeRef container = siteService.getContainer(siteId, SEARCH_CONTAINER); if (container != null) { // add the details of all the public saved searches List<FileInfo> searches = fileFolderService.listFiles(container); for (FileInfo search : searches) { addSearchDetailsToList(result, search.getNodeRef()); } // add the details of any "private" searches for the current user String userName = AuthenticationUtil.getFullyAuthenticatedUser(); NodeRef userContainer = fileFolderService.searchSimple(container, userName); if (userContainer != null) { List<FileInfo> userSearches = fileFolderService.listFiles(userContainer); for (FileInfo userSearch : userSearches) { addSearchDetailsToList(result, userSearch.getNodeRef()); } } } return result; } /** * Add the search details to the list. * @param searches list of search details * @param searchNode search node */ private void addSearchDetailsToList(List<SavedSearchDetails> searches, NodeRef searchNode) { ContentReader reader = fileFolderService.getReader(searchNode); String jsonString = reader.getContentString(); SavedSearchDetails savedSearchDetails = SavedSearchDetails.createFromJSON(jsonString, namespaceService, this, searchNode); searches.add(savedSearchDetails); } /** * @see org.alfresco.module.org_alfresco_module_rm.search.RecordsManagementSearchService#getSavedSearch(java.lang.String, java.lang.String) */ @Override public SavedSearchDetails getSavedSearch(String siteId, String name) { // check for mandatory parameters ParameterCheck.mandatory("siteId", siteId); ParameterCheck.mandatory("name", name); SavedSearchDetails result = null; // get the saved search node NodeRef searchNode = getSearchNodeRef(siteId, name); if (searchNode != null) { // get the json content ContentReader reader = fileFolderService.getReader(searchNode); String jsonString = reader.getContentString(); // create the saved search details result = SavedSearchDetails.createFromJSON(jsonString, namespaceService, this, searchNode); } return result; } /** * @see org.alfresco.module.org_alfresco_module_rm.search.RecordsManagementSearchService#saveSearch(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean) */ @Override public SavedSearchDetails saveSearch(String siteId, String name, String description, String query, RecordsManagementSearchParameters searchParameters, boolean isPublic) { // Check for mandatory parameters ParameterCheck.mandatory("siteId", siteId); ParameterCheck.mandatory("name", name); ParameterCheck.mandatory("query", query); ParameterCheck.mandatory("searchParameters", searchParameters); // Create saved search details SavedSearchDetails savedSearchDetails = new SavedSearchDetails(siteId, name, description, query, searchParameters, isPublic, false, namespaceService, this); // Save search details return saveSearch(savedSearchDetails); } /** * @see org.alfresco.module.org_alfresco_module_rm.search.RecordsManagementSearchService#saveSearch(org.alfresco.module.org_alfresco_module_rm.search.SavedSearchDetails) */ @Override public SavedSearchDetails saveSearch(final SavedSearchDetails savedSearchDetails) { // Check for mandatory parameters ParameterCheck.mandatory("savedSearchDetails", savedSearchDetails); // Get the root saved search container final String siteId = savedSearchDetails.getSiteId(); NodeRef container = siteService.getContainer(siteId, SEARCH_CONTAINER); if (container == null) { container = AuthenticationUtil.runAs(new RunAsWork<NodeRef>() { @Override public NodeRef doWork() { return siteService.createContainer(siteId, SEARCH_CONTAINER, null, null); } }, AuthenticationUtil.getSystemUserName()); } // Get the private container for the current user if (!savedSearchDetails.isPublic()) { final String userName = AuthenticationUtil.getFullyAuthenticatedUser(); NodeRef userContainer = fileFolderService.searchSimple(container, userName); if (userContainer == null) { final NodeRef parentContainer = container; userContainer = AuthenticationUtil.runAs(new RunAsWork<NodeRef>() { @Override public NodeRef doWork() { return fileFolderService.create(parentContainer, userName, ContentModel.TYPE_FOLDER).getNodeRef(); } }, AuthenticationUtil.getSystemUserName()); } container = userContainer; } // Get the saved search node NodeRef searchNode = fileFolderService.searchSimple(container, savedSearchDetails.getName()); if (searchNode == null) { final NodeRef searchContainer = container; searchNode = AuthenticationUtil.runAs(new RunAsWork<NodeRef>() { @Override public NodeRef doWork() { return fileFolderService.create(searchContainer, savedSearchDetails.getName(), ContentModel.TYPE_CONTENT).getNodeRef(); } }, AuthenticationUtil.getSystemUserName()); } // Write the JSON content to search node final NodeRef writableSearchNode = searchNode; AuthenticationUtil.runAs(new RunAsWork<Void>() { @Override public Void doWork() { ContentWriter writer = fileFolderService.getWriter(writableSearchNode); writer.setEncoding("UTF-8"); writer.setMimetype(MimetypeMap.MIMETYPE_JSON); writer.putContent(savedSearchDetails.toJSONString()); return null; } }, AuthenticationUtil.getSystemUserName()); return savedSearchDetails; } /** * @see org.alfresco.module.org_alfresco_module_rm.search.RecordsManagementSearchService#deleteSavedSearch(java.lang.String, java.lang.String) */ @Override public void deleteSavedSearch(String siteId, String name) { // Check parameters ParameterCheck.mandatory("siteId", siteId); ParameterCheck.mandatory("name", name); // Get the search node for the saved query NodeRef searchNode = getSearchNodeRef(siteId, name); if (searchNode != null && fileFolderService.exists(searchNode)) { fileFolderService.delete(searchNode); } } /** * @see org.alfresco.module.org_alfresco_module_rm.search.RecordsManagementSearchService#deleteSavedSearch(org.alfresco.module.org_alfresco_module_rm.search.SavedSearchDetails) */ @Override public void deleteSavedSearch(SavedSearchDetails savedSearchDetails) { // Check parameters ParameterCheck.mandatory("savedSearchDetails", savedSearchDetails); // Delete the saved search deleteSavedSearch(savedSearchDetails.getSiteId(), savedSearchDetails.getName()); } /** * Get the saved search node reference. * @param siteId site id * @param name search name * @return {@link NodeRef} search node reference */ private NodeRef getSearchNodeRef(String siteId, String name) { NodeRef searchNode = null; // Get the root saved search container NodeRef container = siteService.getContainer(siteId, SEARCH_CONTAINER); if (container != null) { // try and find the search node searchNode = fileFolderService.searchSimple(container, name); // can't find it so check the users container if (searchNode == null) { String userName = AuthenticationUtil.getFullyAuthenticatedUser(); NodeRef userContainer = fileFolderService.searchSimple(container, userName); if (userContainer != null) { searchNode = fileFolderService.searchSimple(userContainer, name); } } } return searchNode; } /** * @see org.alfresco.module.org_alfresco_module_rm.search.RecordsManagementSearchService#addReports(java.lang.String) */ @Override public void addReports(String siteId) { for (ReportDetails report : reports) { // Create saved search details SavedSearchDetails savedSearchDetails = new SavedSearchDetails( siteId, report.getName(), report.getDescription(), report.getSearch(), report.getSearchParameters(), true, true, namespaceService, this); // Save search details saveSearch(savedSearchDetails); } } }