diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index 48c71437c9..82286f6f7a 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -321,15 +321,30 @@ - - - - - false - - - - - + + + + + false + + + + + + + + + + + + + + + ${spaces.store} + + + /${spaces.company_home.childname} + + diff --git a/config/alfresco/bootstrap/spaces.xml b/config/alfresco/bootstrap/spaces.xml index 0d2d5665c9..d8a593e6b5 100644 --- a/config/alfresco/bootstrap/spaces.xml +++ b/config/alfresco/bootstrap/spaces.xml @@ -58,6 +58,13 @@ ${spaces.savedsearches.name} ${spaces.savedsearches.description} + + + ${spaces.scripts.name} + space-icon-default + ${spaces.scripts.name} + ${spaces.scripts.description} + diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 7ce7343289..bdfcce1566 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -201,6 +201,7 @@ ${spaces.templates.content.childname} ${spaces.templates.email.childname} ${spaces.savedsearches.childname} + ${spaces.scripts.childname} diff --git a/config/alfresco/messages/action-config.properties b/config/alfresco/messages/action-config.properties index 33421691b5..2d7dd75530 100644 --- a/config/alfresco/messages/action-config.properties +++ b/config/alfresco/messages/action-config.properties @@ -72,3 +72,5 @@ export.root.package.description=Alfresco content package for complete Repository export.store.package.description=Alfresco export of store ''{0}''. export.package.error=Failed to find temporary file for export +script.title=Execute a script +script.description=Execute a JavaScript file to perform tasks such as creating new files or folders diff --git a/config/alfresco/messages/bootstrap-spaces.properties b/config/alfresco/messages/bootstrap-spaces.properties index 13c73f403e..c0d7937436 100644 --- a/config/alfresco/messages/bootstrap-spaces.properties +++ b/config/alfresco/messages/bootstrap-spaces.properties @@ -20,3 +20,6 @@ spaces.savedsearches.description=Saved Searches spaces.guest_home.name=Guest Home spaces.guest_home.description=The guest root space + +spaces.scripts.name=Scripts +spaces.scripts.description=JavaScript files diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 0185575810..024b5f6ecf 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -63,3 +63,7 @@ patch.emailTemplatesContent.result=Imported the Email Templates into the default patch.descriptorUpdate.description=Update Repository descriptor patch.descriptorUpdate.result=Repository descriptor updated + +patch.scriptsFolder.description=Ensures the existence of the 'Scripts' folder. +patch.scriptsFolder.result.exists=The scripts folder already exists: {0} +patch.scriptsFolder.result.created=The scripts folder was successfully created: {0} diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index acae9c8121..ad1eecf2c6 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -340,5 +340,20 @@ - + + + patch.scriptsFolder + patch.scriptsFolder.description + 0 + 12 + 13 + + + + + + + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 34350f7ce8..499a5b91f6 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -90,6 +90,7 @@ spaces.templates.childname=app:space_templates spaces.templates.content.childname=app:content_templates spaces.templates.email.childname=app:email_templates spaces.savedsearches.childname=app:saved_searches +spaces.scripts.childname=app:scripts # Folders for storing people diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index b30388b8a6..d237e78d46 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -15,4 +15,4 @@ version.edition=Community Network # Schema number -version.schema=12 +version.schema=13 diff --git a/source/java/org/alfresco/repo/action/executer/ScriptActionExecutor.java b/source/java/org/alfresco/repo/action/executer/ScriptActionExecutor.java new file mode 100644 index 0000000000..4d8bfe5de9 --- /dev/null +++ b/source/java/org/alfresco/repo/action/executer/ScriptActionExecutor.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.action.executer; + +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.jscript.RhinoScriptService; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +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.PersonService; + +/** + * @author Kevin Roast + */ +public class ScriptActionExecutor extends ActionExecuterAbstractBase +{ + public static final String NAME = "script"; + public static final String PARAM_SCRIPTREF = "script-ref"; + + private ServiceRegistry serviceRegistry; + private PersonService personService; + private String companyHomePath; + private StoreRef storeRef; + + /** + * @param serviceRegistry The serviceRegistry to set. + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * @param personService The personService to set. + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setStoreUrl(String storeUrl) + { + this.storeRef = new StoreRef(storeUrl); + } + + public void setCompanyHomePath(String companyHomePath) + { + this.companyHomePath = companyHomePath; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + NodeService nodeService = this.serviceRegistry.getNodeService(); + if (nodeService.exists(actionedUponNodeRef)) + { + NodeRef scriptRef = (NodeRef)action.getParameterValue(PARAM_SCRIPTREF); + + if (nodeService.exists(scriptRef)) + { + // get the references we need to build the default scripting data-model + String userName = this.serviceRegistry.getAuthenticationService().getCurrentUserName(); + NodeRef personRef = this.personService.getPerson(userName); + NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER); + + // the default scripting model provides access to well known objects and searching + // facilities - it also provides basic create/update/delete/copy/move services + Map model = RhinoScriptService.buildDefaultModel( + this.serviceRegistry, + personRef, + getCompanyHome(), + homeSpaceRef, + actionedUponNodeRef, + actionedUponNodeRef); + + // execute the script against the default model + this.serviceRegistry.getScriptService().executeScript( + scriptRef, + ContentModel.PROP_CONTENT, + model); + } + } + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefintions(java.util.List) + */ + protected void addParameterDefintions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_SCRIPTREF, DataTypeDefinition.NODE_REF, true, getParamDisplayLabel(PARAM_SCRIPTREF))); + } + + private NodeRef getCompanyHome() + { + NodeRef companyHomeRef; + + List refs = this.serviceRegistry.getSearchService().selectNodes( + this.serviceRegistry.getNodeService().getRootNode(storeRef), + companyHomePath, + null, + this.serviceRegistry.getNamespaceService(), + false); + if (refs.size() != 1) + { + throw new IllegalStateException("Invalid company home path: " + companyHomePath + " - found: " + refs.size()); + } + companyHomeRef = refs.get(0); + + return companyHomeRef; + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java new file mode 100644 index 0000000000..2c1a688fba --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.admin.patch.impl; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.springframework.context.MessageSource; + +/** + * Ensures that the scripts folder is present. + *

+ * This uses the bootstrap importer to get the paths to look for. If not present, + * the required structures are created. + *

+ * This class should be replaced with a more generic ImporterPatch + * that can do conditional importing into given locations. + *

+ * JIRA: {@link http://www.alfresco.org/jira/browse/AR-342 AR-342} + * + * @author Kevin Roast + */ +public class ScriptsFolderPatch extends AbstractPatch +{ + private static final String MSG_EXISTS = "patch.scriptsFolder.result.exists"; + private static final String MSG_CREATED = "patch.scriptsFolder.result.created"; + + public static final String PROPERTY_COMPANY_HOME_CHILDNAME = "spaces.company_home.childname"; + public static final String PROPERTY_DICTIONARY_CHILDNAME = "spaces.dictionary.childname"; + public static final String PROPERTY_SCRIPTS_FOLDER_CHILDNAME = "spaces.scripts.childname"; + private static final String PROPERTY_SCRIPTS_FOLDER_NAME = "spaces.scripts.name"; + private static final String PROPERTY_SCRIPTS_FOLDER_DESCRIPTION = "spaces.scripts.description"; + private static final String PROPERTY_ICON = "space-icon-default"; + + private ImporterBootstrap importerBootstrap; + private MessageSource messageSource; + + protected NodeRef dictionaryNodeRef; + protected Properties configuration; + protected NodeRef scriptsFolderNodeRef; + + public void setImporterBootstrap(ImporterBootstrap importerBootstrap) + { + this.importerBootstrap = importerBootstrap; + } + + public void setMessageSource(MessageSource messageSource) + { + this.messageSource = messageSource; + } + + /** + * Ensure that required common properties have been set + */ + protected void checkCommonProperties() throws Exception + { + if (importerBootstrap == null) + { + throw new PatchException("'importerBootstrap' property has not been set"); + } + else if (namespaceService == null) + { + throw new PatchException("'namespaceService' property has not been set"); + } + else if (searchService == null) + { + throw new PatchException("'searchService' property has not been set"); + } + else if (nodeService == null) + { + throw new PatchException("'nodeService' property has not been set"); + } + } + + /** + * Extracts pertinent references and properties that are common to execution + * of this and derived patches. + */ + protected void setUp() throws Exception + { + // get the node store that we must work against + StoreRef storeRef = importerBootstrap.getStoreRef(); + if (storeRef == null) + { + throw new PatchException("Bootstrap store has not been set"); + } + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + this.configuration = importerBootstrap.getConfiguration(); + // get the association names that form the path + String companyHomeChildName = configuration.getProperty(PROPERTY_COMPANY_HOME_CHILDNAME); + if (companyHomeChildName == null || companyHomeChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_COMPANY_HOME_CHILDNAME + "' is not present"); + } + String dictionaryChildName = configuration.getProperty(PROPERTY_DICTIONARY_CHILDNAME); + if (dictionaryChildName == null || dictionaryChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_DICTIONARY_CHILDNAME + "' is not present"); + } + String scriptsChildName = configuration.getProperty(PROPERTY_SCRIPTS_FOLDER_CHILDNAME); + if (scriptsChildName == null || scriptsChildName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_SCRIPTS_FOLDER_CHILDNAME + "' is not present"); + } + + // build the search string to get the dictionary node + StringBuilder sb = new StringBuilder(256); + sb.append("/").append(companyHomeChildName) + .append("/").append(dictionaryChildName); + String xpath = sb.toString(); + + // get the dictionary node + List nodeRefs = searchService.selectNodes(storeRootNodeRef, xpath, null, namespaceService, false); + if (nodeRefs.size() == 0) + { + throw new PatchException("XPath didn't return any results: \n" + + " root: " + storeRootNodeRef + "\n" + + " xpath: " + xpath); + } + else if (nodeRefs.size() > 1) + { + throw new PatchException("XPath returned too many results: \n" + + " root: " + storeRootNodeRef + "\n" + + " xpath: " + xpath + "\n" + + " results: " + nodeRefs); + } + this.dictionaryNodeRef = nodeRefs.get(0); + + // Now we have the optional part. Check for the existence of the scripts folder + xpath = scriptsChildName; + nodeRefs = searchService.selectNodes(dictionaryNodeRef, xpath, null, namespaceService, false); + if (nodeRefs.size() > 1) + { + throw new PatchException("XPath returned too many results: \n" + + " dictionary node: " + dictionaryNodeRef + "\n" + + " xpath: " + xpath + "\n" + + " results: " + nodeRefs); + } + else if (nodeRefs.size() == 0) + { + // the node does not exist + this.scriptsFolderNodeRef = null; + } + else + { + // we have the saved searches folder noderef + this.scriptsFolderNodeRef = nodeRefs.get(0); + } + } + + @Override + protected String applyInternal() throws Exception + { + // properties must be set + checkCommonProperties(); + if (messageSource == null) + { + throw new PatchException("'messageSource' property has not been set"); + } + + // get useful values + setUp(); + + String msg = null; + if (scriptsFolderNodeRef == null) + { + // create it + createFolder(); + msg = I18NUtil.getMessage(MSG_CREATED, scriptsFolderNodeRef); + } + else + { + // it already exists + msg = I18NUtil.getMessage(MSG_EXISTS, scriptsFolderNodeRef); + } + // done + return msg; + } + + private void createFolder() + { + // get required properties + String savedSearchesChildName = configuration.getProperty(PROPERTY_SCRIPTS_FOLDER_CHILDNAME); + if (savedSearchesChildName == null) + { + throw new PatchException("Bootstrap property '" + PROPERTY_SCRIPTS_FOLDER_CHILDNAME + "' is not present"); + } + + String savedSearchesName = messageSource.getMessage( + PROPERTY_SCRIPTS_FOLDER_NAME, + null, + I18NUtil.getLocale()); + if (savedSearchesName == null || savedSearchesName.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_SCRIPTS_FOLDER_NAME + "' is not present"); + } + + String savedSearchesDescription = messageSource.getMessage( + PROPERTY_SCRIPTS_FOLDER_DESCRIPTION, + null, + I18NUtil.getLocale()); + if (savedSearchesDescription == null || savedSearchesDescription.length() == 0) + { + throw new PatchException("Bootstrap property '" + PROPERTY_SCRIPTS_FOLDER_DESCRIPTION + "' is not present"); + } + + Map properties = new HashMap(7); + properties.put(ContentModel.PROP_NAME, savedSearchesName); + properties.put(ContentModel.PROP_TITLE, savedSearchesName); + properties.put(ContentModel.PROP_DESCRIPTION, savedSearchesDescription); + properties.put(ContentModel.PROP_ICON, PROPERTY_ICON); + + // create the node + ChildAssociationRef childAssocRef = nodeService.createNode( + dictionaryNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.resolveToQName(namespaceService, savedSearchesChildName), + ContentModel.TYPE_FOLDER, + properties); + scriptsFolderNodeRef = childAssocRef.getChildRef(); + + // add the required aspects + nodeService.addAspect(scriptsFolderNodeRef, ContentModel.ASPECT_UIFACETS, null); + + // done + } +} diff --git a/source/java/org/alfresco/repo/content/MimetypeMap.java b/source/java/org/alfresco/repo/content/MimetypeMap.java index eb7376b892..971ea17542 100644 --- a/source/java/org/alfresco/repo/content/MimetypeMap.java +++ b/source/java/org/alfresco/repo/content/MimetypeMap.java @@ -54,6 +54,7 @@ public class MimetypeMap implements MimetypeService public static final String MIMETYPE_IMAGE_GIF = "image/gif"; public static final String MIMETYPE_IMAGE_JPEG = "image/jpeg"; public static final String MIMETYPE_IMAGE_RGB = "image/x-rgb"; + public static final String MIMETYPE_JAVASCRIPT = "application/x-javascript"; // Open Document public static final String MIMETYPE_OPENDOCUMENT_TEXT = "application/vnd.oasis.opendocument.text"; public static final String MIMETYPE_OPENDOCUMENT_TEXT_TEMPLATE = "application/vnd.oasis.opendocument.text-template"; diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java index bb0d9b58c4..04313100a3 100644 --- a/source/java/org/alfresco/repo/jscript/Node.java +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -38,7 +38,6 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.TemplateImageResolver; -import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.apache.commons.logging.Log; @@ -46,12 +45,11 @@ import org.apache.commons.logging.LogFactory; import org.springframework.util.StringUtils; /** - * Node class implementation specific for use by ScriptService as part of the object model. - *

+ * Node class implementation, specific for use by ScriptService as part of the object model. *

* The class exposes Node properties, children and assocs as dynamically populated maps and lists. * The various collection classes are mirrored as JavaScript properties. So can be accessed using - * standard JavaScript syntax, such as node.children[0].properties.name. + * standard JavaScript property syntax, such as node.children[0].properties.name. *

* Various helper methods are provided to access common and useful node variables such * as the content url and type information. @@ -270,24 +268,6 @@ public final class Node implements Serializable // return childrenByXPath(xpath); //} - /** - * @return A map capable of returning a List of Node objects from an NodeRef to a Saved Search - * object. The Saved Search is executed and the resulting nodes supplied as a sequence. - */ - //public Map getChildrenBySavedSearch() - //{ - // return new SavedSearchResultsMap(this, this.services); - //} - - /** - * @return A map capable of returning a List of Node objects from an NodeRef to a Lucene search - * string. The Saved Search is executed and the resulting nodes supplied as a sequence. - */ - //public Map getChildrenByLuceneSearch() - //{ - // return new LuceneSearchResultsMap(this, this.services); - //} - /** * Return the associations for this Node. As a Map of assoc name to an Array of Nodes. * diff --git a/source/java/org/alfresco/repo/jscript/RhinoScriptService.java b/source/java/org/alfresco/repo/jscript/RhinoScriptService.java index 788dc42275..8945d66111 100644 --- a/source/java/org/alfresco/repo/jscript/RhinoScriptService.java +++ b/source/java/org/alfresco/repo/jscript/RhinoScriptService.java @@ -21,10 +21,12 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; +import java.util.HashMap; import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; @@ -221,4 +223,46 @@ public class RhinoScriptService implements ScriptService cx.exit(); } } + + /** + * Create the default data-model available to scripts as global scope level objects: + *

+ * 'companyhome' - the Company Home node
+ * 'userhome' - the current user home space node
+ * 'person' - the node representing the current user Person
+ * 'document' - document context node (may not be available)
+ * 'space' - space context node (may not be available) + * + * @param services ServiceRegistry + * @param person The current user Person Node + * @param companyHome The CompanyHome ref + * @param userHome The User home space ref + * @param document Optional ref to a document Node + * @param space Optional ref to a space Node + * + * @return A Map of global scope scriptable Node objects + */ + public static Map buildDefaultModel(ServiceRegistry services, + NodeRef person, NodeRef companyHome, NodeRef userHome, NodeRef document, NodeRef space) + { + Map model = new HashMap(); + + // add the well known node wrapper objects + model.put("companyhome", new Node(companyHome, services, null)); + model.put("userhome", new Node(userHome, services, null)); + model.put("person", new Node(person, services, null)); + if (document != null) + { + model.put("document", new Node(document, services, null)); + } + if (space != null) + { + model.put("space", new Node(space, services, null)); + } + + // add other useful util objects + model.put("search", new Search(services, companyHome.getStoreRef(), null)); + + return model; + } } diff --git a/source/java/org/alfresco/repo/jscript/Search.java b/source/java/org/alfresco/repo/jscript/Search.java new file mode 100644 index 0000000000..047aa31250 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/Search.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; + +/** + * Search component for use by the ScriptService. + *

+ * Provides access to Lucene search facilities including saved search objects. The results + * from a search are returned as an array (collection) of scriptable Node wrapper objects. + *

+ * The object is added to the root of the model to provide syntax such as: + * var results = search.luceneSearch(statement); + * and + * var results = search.savedSearch(node); + * + * @author Kevin Roast + */ +public final class Search +{ + private static Log logger = LogFactory.getLog(Search.class); + + private ServiceRegistry services; + private StoreRef storeRef; + private TemplateImageResolver imageResolver; + + + /** + * Constructor + * + * @param services The ServiceRegistry to use + */ + public Search(ServiceRegistry services, StoreRef storeRef, TemplateImageResolver imageResolver) + { + this.services = services; + this.storeRef = storeRef; + this.imageResolver = imageResolver; + } + + /** + * Execute a Lucene search + * + * @param search Lucene search string to execute + * + * @return Node[] of results from the search - can be empty but not null + */ + public Node[] luceneSearch(String search) + { + return query(search); + } + + /** + * Execute a saved Lucene search + * + * @param savedSearch Node that contains the saved lucene search content + * + * @return Node[] of results from the search - can be empty but not null + */ + public Node[] savedSearch(Node savedSearch) + { + String search = null; + + // read the Saved Search XML on the specified node - and get the Lucene search from it + try + { + ContentReader content = this.services.getContentService().getReader( + savedSearch.getNodeRef(), ContentModel.PROP_CONTENT); + if (content != null && content.exists()) + { + // get the root element + SAXReader reader = new SAXReader(); + Document document = reader.read(new StringReader(content.getContentString())); + Element rootElement = document.getRootElement(); + + Element queryElement = rootElement.element("query"); + if (queryElement != null) + { + search = queryElement.getText(); + } + } + } + catch (Throwable err) + { + throw new AlfrescoRuntimeException("Failed to find or load saved Search: " + savedSearch.getNodeRef(), err); + } + + return search != null ? query(search) : new Node[0]; + } + + private Node[] query(String search) + { + Node[] nodes = null; + + // perform the search against the repo + ResultSet results = null; + try + { + results = this.services.getSearchService().query( + this.storeRef, + SearchService.LANGUAGE_LUCENE, + search); + + if (results.length() != 0) + { + nodes = new Node[results.length()]; + int count = 0; + for (ResultSetRow row: results) + { + NodeRef nodeRef = row.getNodeRef(); + nodes[count++] = new Node(nodeRef, services, this.imageResolver); + } + } + } + catch (Throwable err) + { + throw new AlfrescoRuntimeException("Failed to execute search: " + search, err); + } + finally + { + if (results != null) + { + results.close(); + } + } + + return nodes != null ? nodes : new Node[0]; + } +} diff --git a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java index a4cbfee87f..d7e846d429 100644 --- a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java +++ b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java @@ -33,6 +33,7 @@ import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.ScriptService; import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.cmr.search.CategoryService; @@ -302,6 +303,14 @@ public class ServiceDescriptorRegistry */ public FileFolderService getFileFolderService() { - return (FileFolderService) getService(FILE_FOLDER_SERVICE); + return (FileFolderService)getService(FILE_FOLDER_SERVICE); + } + + /* (non-Javadoc) + * @see org.alfresco.service.ServiceRegistry#getScriptService() + */ + public ScriptService getScriptService() + { + return (ScriptService)getService(SCRIPT_SERVICE); } } diff --git a/source/java/org/alfresco/service/ServiceRegistry.java b/source/java/org/alfresco/service/ServiceRegistry.java index 5cd8917780..1e8683848f 100644 --- a/source/java/org/alfresco/service/ServiceRegistry.java +++ b/source/java/org/alfresco/service/ServiceRegistry.java @@ -27,6 +27,7 @@ import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.ScriptService; import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.cmr.search.CategoryService; @@ -79,6 +80,7 @@ public interface ServiceRegistry static final QName AUTHORITY_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "AuthorityService"); static final QName TEMPLATE_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "TemplateService"); static final QName FILE_FOLDER_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "FileFolderService"); + static final QName SCRIPT_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "ScriptService"); /** * Get the list of services provided by the Repository @@ -220,4 +222,9 @@ public interface ServiceRegistry * @return the file-folder manipulation service (or null if one is not provided) */ FileFolderService getFileFolderService(); + + /** + * @return the script execution service (or null if one is not provided) + */ + ScriptService getScriptService(); }