diff --git a/source/java/org/alfresco/web/scripts/AbstractWebScript.java b/source/java/org/alfresco/web/scripts/AbstractWebScript.java new file mode 100644 index 0000000000..32cc468f97 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/AbstractWebScript.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.io.Writer; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.repo.jscript.Node; +import org.alfresco.repo.jscript.ScriptableHashMap; +import org.alfresco.repo.template.AbsoluteUrlMethod; +import org.alfresco.repo.template.ISO8601DateFormatMethod; +import org.alfresco.repo.template.UrlEncodeMethod; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.descriptor.DescriptorService; +import org.alfresco.web.scripts.WebScriptDescription.RequiredAuthentication; +import org.alfresco.web.scripts.WebScriptDescription.RequiredTransaction; + + +/** + * Skeleton implementation of a Web Script + * + * @author davidc + */ +public abstract class AbstractWebScript implements WebScript +{ + // dependencies + private WebScriptContext scriptContext; + private WebScriptRegistry scriptRegistry; + private WebScriptDescription description; + private ServiceRegistry serviceRegistry; + private DescriptorService descriptorService; + + // + // Initialisation + // + + /** + * @param scriptContext + */ + final public void setScriptContext(WebScriptContext scriptContext) + { + this.scriptContext = scriptContext; + } + + /** + * @param serviceRegistry + */ + final public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * @param descriptorService + */ + final public void setDescriptorService(DescriptorService descriptorService) + { + this.descriptorService = descriptorService; + } + + /** + * Sets the Service Description + * + * @param description + */ + final public void setDescription(WebScriptDescription description) + { + this.description = description; + } + + /** + * Initialise Web Script + * + * @param scriptRegistry + */ + public void init(WebScriptRegistry scriptRegistry) + { + this.scriptRegistry = scriptRegistry; + } + + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScript#getDescription() + */ + final public WebScriptDescription getDescription() + { + return this.description; + } + + + // + // Service Implementation Helpers + // + + /** + * Gets the Repository Context + * + * @return repository context + */ + final public WebScriptContext getRepositoryContext() + { + return this.scriptContext; + } + + /** + * Gets the Web Script Registry + * + * @return Web Script Registry + */ + final public WebScriptRegistry getWebScriptRegistry() + { + return this.scriptRegistry; + } + + /** + * Gets the Alfresco Service Registry + * + * @return service registry + */ + final public ServiceRegistry getServiceRegistry() + { + return this.serviceRegistry; + } + + /** + * Gets the Alfresco Descriptor + * + * @return descriptor + */ + final public DescriptorService getDescriptorService() + { + return this.descriptorService; + } + + + + // + // Scripting Support + // + + /** + * Create a map of arguments from Web Script Request + * + * @param req Web Script Request + * @return argument map + */ + final protected Map createArgModel(WebScriptRequest req) + { + Map args = new ScriptableHashMap(); + Enumeration names = req.getParameterNames(); + while (names.hasMoreElements()) + { + String name = (String)names.nextElement(); + args.put(name, req.getParameter(name)); + } + return args; + } + + /** + * Create a model for script usage + * + * @param req web script request + * @param res web script response + * @param customModel custom model entries + * + * @return script model + */ + final protected Map createScriptModel(WebScriptRequest req, WebScriptResponse res, Map customModel) + { + // create script model + Map model = new HashMap(7, 1.0f); + + // add repository context (only if authenticated and transaction enabled) + if (getDescription().getRequiredAuthentication() != RequiredAuthentication.none && + getDescription().getRequiredTransaction() != RequiredTransaction.none) + { + NodeRef companyHome = scriptContext.getCompanyHome(); + if (companyHome != null) + { + model.put("companyhome", new Node(scriptContext.getCompanyHome(), serviceRegistry)); + } + NodeRef person = scriptContext.getPerson(); + if (person != null) + { + model.put("person", new Node(person, serviceRegistry)); + model.put("userhome", new Node(scriptContext.getUserHome(person), serviceRegistry)); + } + } + + // add web script context + model.put("args", createArgModel(req)); + model.put("guest", req.isGuest()); + model.put("url", new URLModel(req)); + model.put("server", new ServerModel(descriptorService.getServerDescriptor())); + + // add custom model + if (customModel != null) + { + model.putAll(customModel); + } + + // return model + return model; + } + + /** + * Create a model for template usage + * + * @param req web script request + * @param res web script response + * @param customModel custom model entries + * + * @return template model + */ + final protected Map createTemplateModel(WebScriptRequest req, WebScriptResponse res, Map customModel) + { + // create script model + Map model = new HashMap(7, 1.0f); + + // add repository context + if (getDescription().getRequiredAuthentication() != RequiredAuthentication.none && + getDescription().getRequiredTransaction() != RequiredTransaction.none) + { + NodeRef companyHome = scriptContext.getCompanyHome(); + if (companyHome != null) + { + model.put("companyhome", new TemplateNode(scriptContext.getCompanyHome(), serviceRegistry, null)); + } + NodeRef person = scriptContext.getPerson(); + if (person != null) + { + model.put("person", new TemplateNode(person, serviceRegistry, null)); + model.put("userhome", new TemplateNode(scriptContext.getUserHome(person), serviceRegistry, null)); + } + } + + // add web script context + model.put("args", createArgModel(req)); + model.put("guest", req.isGuest()); + model.put("url", new URLModel(req)); + model.put("server", new ServerModel(descriptorService.getServerDescriptor())); + + // add template support + model.put("xmldate", new ISO8601DateFormatMethod()); + model.put("absurl", new AbsoluteUrlMethod(req.getServerPath())); + model.put("urlencode", new UrlEncodeMethod()); + model.put("date", new Date()); + + // add custom model + if (customModel != null) + { + model.putAll(customModel); + } + + return model; + } + + /** + * Render a template (identified by path) + * + * @param templatePath template path + * @param model model + * @param writer output writer + */ + final protected void renderTemplate(String templatePath, Map model, Writer writer) + { + getWebScriptRegistry().getTemplateProcessor().process(templatePath, model, writer); + } + + /** + * Render a template (contents as string) + * @param template the template + * @param model model + * @param writer output writer + */ + final protected void renderString(String template, Map model, Writer writer) + { + getWebScriptRegistry().getTemplateProcessor().processString(template, model, writer); + } + + /** + * Execute a script + * + * @param location script location + * @param model model + */ + final protected void executeScript(ScriptLocation location, Map model) + { + getWebScriptRegistry().getScriptProcessor().executeScript(location, model); + } + +} diff --git a/source/java/org/alfresco/web/scripts/BasicAuthenticator.java b/source/java/org/alfresco/web/scripts/BasicAuthenticator.java new file mode 100644 index 0000000000..a4e5160524 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/BasicAuthenticator.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.util.Base64; +import org.alfresco.web.scripts.WebScriptDescription.RequiredAuthentication; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * HTTP Basic Authentication Interceptor + * + * @author davidc + */ +public class BasicAuthenticator implements MethodInterceptor +{ + // Logger + private static final Log logger = LogFactory.getLog(BasicAuthenticator.class); + + // dependencies + private AuthenticationService authenticationService; + + /** + * @param authenticationService + */ + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + /* (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + public Object invoke(MethodInvocation invocation) + throws Throwable + { + boolean authorized = false; + String currentUser = null; + Object retVal = null; + Object[] args = invocation.getArguments(); + WebScriptRequest request = (WebScriptRequest)args[0]; + WebScript service = (WebScript)invocation.getThis(); + WebScriptDescription description = service.getDescription(); + + try + { + // + // Determine if user already authenticated + // + + currentUser = AuthenticationUtil.getCurrentUserName(); + if (logger.isDebugEnabled()) + logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + + // + // validate credentials + // + + boolean isGuest = request.isGuest(); + String authorization = request.getHeader("Authorization"); + + if (logger.isDebugEnabled()) + { + logger.debug("Web Service authentication required: " + description.getRequiredAuthentication()); + logger.debug("Guest login: " + isGuest); + logger.debug("Authorization provided (overrides Guest login): " + (authorization != null && authorization.length() > 0)); + } + + // authenticate as guest, if service allows + if (((authorization == null || authorization.length() == 0) || isGuest) + && description.getRequiredAuthentication().equals(RequiredAuthentication.guest)) + { + if (logger.isDebugEnabled()) + logger.debug("Authenticating as Guest"); + + authenticationService.authenticateAsGuest(); + authorized = true; + } + + // authenticate as specified by HTTP Basic Authentication + else if (authorization != null && authorization.length() > 0) + { + try + { + String[] authorizationParts = authorization.split(" "); + if (!authorizationParts[0].equalsIgnoreCase("basic")) + { + throw new WebScriptException("Authorization '" + authorizationParts[0] + "' not supported."); + } + String decodedAuthorisation = new String(Base64.decode(authorizationParts[1])); + String[] parts = decodedAuthorisation.split(":"); + + if (parts.length == 1) + { + if (logger.isDebugEnabled()) + logger.debug("Authenticating ticket " + parts[0]); + + // assume a ticket has been passed + authenticationService.validate(parts[0]); + authorized = true; + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Authenticating user " + parts[0]); + + // assume username and password passed + if (parts[0].equals(AuthenticationUtil.getGuestUserName())) + { + if (description.getRequiredAuthentication().equals(RequiredAuthentication.guest)) + { + authenticationService.authenticateAsGuest(); + authorized = true; + } + } + else + { + authenticationService.authenticate(parts[0], parts[1].toCharArray()); + authorized = true; + } + } + } + catch(AuthenticationException e) + { + // failed authentication + } + } + + // + // execute web script or request authorization + // + + if (authorized) + { + retVal = invocation.proceed(); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Requesting authorization credentials"); + + WebScriptResponse response = (WebScriptResponse)args[1]; + response.setStatus(401); + response.setHeader("WWW-Authenticate", "Basic realm=\"Alfresco\""); + } + } + finally + { + // reset authentication + if (authorized) + { + authenticationService.clearCurrentSecurityContext(); + if (currentUser != null) + { + AuthenticationUtil.setCurrentUser(currentUser); + } + + if (logger.isDebugEnabled()) + logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + } + } + + return retVal; + } + +} diff --git a/source/java/org/alfresco/web/scripts/ClassPathStore.java b/source/java/org/alfresco/web/scripts/ClassPathStore.java new file mode 100644 index 0000000000..ad1c0dc856 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/ClassPathStore.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import freemarker.cache.FileTemplateLoader; +import freemarker.cache.TemplateLoader; + + +/** + * ClassPath based Web Script Store + * + * @author davidc + */ +public class ClassPathStore implements WebScriptStore, InitializingBean +{ + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + protected String classPath; + protected File fileDir; + + + /** + * Sets the class path + * + * @param classPath classpath + */ + public void setClassPath(String classPath) + { + this.classPath = classPath; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() + throws Exception + { + ClassPathResource resource = new ClassPathResource(classPath); + fileDir = resource.getFile(); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptStore#getBasePath() + */ + public String getBasePath() + { + return "classpath:" + classPath; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptStore#getDescriptionDocumentPaths() + */ + public String[] getDescriptionDocumentPaths() + { + String[] paths; + + try + { + int filePathLength = fileDir.getAbsolutePath().length() +1; + List documentPaths = new ArrayList(); + Resource[] resources = resolver.getResources("classpath*:" + classPath + "/**/*_desc.xml"); + for (Resource resource : resources) + { + if (resource instanceof FileSystemResource) + { + String documentPath = resource.getFile().getAbsolutePath().substring(filePathLength); + documentPath = documentPath.replace('\\', '/'); + documentPaths.add(documentPath); + } + } + paths = documentPaths.toArray(new String[documentPaths.size()]); + } + catch(IOException e) + { + // Note: Ignore: no service description documents found + paths = new String[0]; + } + + return paths; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptStore#getDescriptionDocument(java.lang.String) + */ + public InputStream getDescriptionDocument(String documentPath) + throws IOException + { + File document = new File(fileDir, documentPath); + if (!document.exists()) + { + throw new IOException("Description document " + documentPath + " does not exist."); + } + return new FileInputStream(document); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptStore#getTemplateLoader() + */ + public TemplateLoader getTemplateLoader() + { + FileTemplateLoader loader = null; + try + { + loader = new FileTemplateLoader(fileDir); + } + catch (IOException e) + { + // Note: Can't establish loader, so return null + } + return loader; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptStore#getScriptLoader() + */ + public ScriptLoader getScriptLoader() + { + return new ClassPathScriptLoader(); + } + + /** + * Class path based script loader + * + * @author davidc + */ + private class ClassPathScriptLoader implements ScriptLoader + { + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.ScriptLoader#getScriptLocation(java.lang.String) + */ + public ScriptLocation getScriptLocation(String path) + { + ScriptLocation location = null; + File scriptPath = new File(fileDir, path); + if (scriptPath.exists()) + { + location = new ClassPathScriptLocation(scriptPath); + } + return location; + } + } + + /** + * Class path script location + * + * @author davidc + */ + private static class ClassPathScriptLocation implements ScriptLocation + { + private File location; + + /** + * Construct + * + * @param location + */ + public ClassPathScriptLocation(File location) + { + this.location = location; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.repository.ScriptLocation#getInputStream() + */ + public InputStream getInputStream() + { + try + { + return new FileInputStream(location); + } + catch (FileNotFoundException e) + { + throw new WebScriptException("Unable to retrieve input stream for script " + location.getAbsolutePath()); + } + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.repository.ScriptLocation#getReader() + */ + public Reader getReader() + { + return new InputStreamReader(getInputStream()); + } + + @Override + public String toString() + { + return location.getAbsolutePath(); + } + } + +} diff --git a/source/java/org/alfresco/web/scripts/DeclarativeWebScript.java b/source/java/org/alfresco/web/scripts/DeclarativeWebScript.java new file mode 100644 index 0000000000..e7ae9362bb --- /dev/null +++ b/source/java/org/alfresco/web/scripts/DeclarativeWebScript.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.repo.jscript.Node; +import org.alfresco.repo.jscript.ScriptableHashMap; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.alfresco.service.cmr.repository.TemplateException; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.NativeArray; +import org.mozilla.javascript.Wrapper; + + +/** + * Script/template driven based implementation of an Web Script + * + * @author davidc + */ +public class DeclarativeWebScript extends AbstractWebScript +{ + // Logger + private static final Log logger = LogFactory.getLog(DeclarativeWebScript.class); + + private String baseTemplatePath; + private ScriptLocation executeScript; + + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.AbstractWebScript#init(org.alfresco.web.scripts.WebScriptRegistry) + */ + @Override + public void init(WebScriptRegistry apiRegistry) + { + super.init(apiRegistry); + baseTemplatePath = getDescription().getId().replace('.', '/'); + + // Test for "execute" script + String scriptPath = baseTemplatePath + ".js"; + executeScript = getWebScriptRegistry().getScriptProcessor().findScript(scriptPath); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScript#execute(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) + */ + final public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException + { + // construct data model for template + Map model = executeImpl(req, res); + if (model == null) + { + model = new HashMap(7, 1.0f); + } + + // execute script if it exists + if (executeScript != null) + { + if (logger.isDebugEnabled()) + logger.debug("Executing script " + executeScript); + + Map scriptModel = createScriptModel(req, res, model); + // add return model allowing script to add items to template model + Map returnModel = new ScriptableHashMap(); + scriptModel.put("model", returnModel); + executeScript(executeScript, scriptModel); + mergeScriptModelIntoTemplateModel(returnModel, model); + } + + // process requested format + String format = req.getFormat(); + if (format == null || format.length() == 0) + { + format = getDescription().getDefaultFormat(); + } + + String mimetype = getWebScriptRegistry().getFormatRegistry().getMimeType(req.getAgent(), format); + if (mimetype == null) + { + throw new WebScriptException("Web Script format '" + format + "' is not registered"); + } + + // render output + res.setContentType(mimetype + ";charset=UTF-8"); + + if (logger.isDebugEnabled()) + logger.debug("Response content type: " + mimetype); + + try + { + Map templateModel = createTemplateModel(req, res, model); + renderFormatTemplate(format, templateModel, res.getWriter()); + } + catch(TemplateException e) + { + throw new WebScriptException("Failed to process format '" + format + "'", e); + } + } + + /** + * Merge script generated model into template-ready model + * + * @param scriptModel script model + * @param templateModel template model + */ + @SuppressWarnings("unchecked") + final private void mergeScriptModelIntoTemplateModel(Map scriptModel, Map templateModel) + { + for (Map.Entry entry : scriptModel.entrySet()) + { + // retrieve script model value + Object value = entry.getValue(); + + // convert from js to java, if required + if (value instanceof Wrapper) + { + value = ((Wrapper)value).unwrap(); + } + else if (value instanceof NativeArray) + { + value = Context.jsToJava(value, Object[].class); + } + + // convert script node to template node, if required + if (value instanceof Node) + { + value = new TemplateNode(((Node)value).getNodeRef(), getServiceRegistry(), null); + } + else if (value instanceof Collection) + { + Collection coll = (Collection)value; + Collection templateColl = new ArrayList(coll.size()); + for (Object object : coll) + { + if (value instanceof Node) + { + templateColl.add(new TemplateNode(((Node)object).getNodeRef(), getServiceRegistry(), null)); + } + else + { + templateColl.add(object); + } + } + value = templateColl; + } + else if (value instanceof Node[]) + { + Node[] nodes = (Node[])value; + TemplateNode[] templateNodes = new TemplateNode[nodes.length]; + int i = 0; + for (Node node : nodes) + { + templateNodes[i++] = new TemplateNode(node.getNodeRef(), getServiceRegistry(), null); + } + value = templateNodes; + } + templateModel.put(entry.getKey(), value); + } + } + + /** + * Execute custom Java logic + * + * @param req Web Script request + * @param res Web Script response + * @return custom service model + */ + protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + { + return null; + } + + /** + * Render a template (of given format) to the Web Script Response + * + * @param format template format (null, default format) + * @param model data model to render + * @param writer where to output + */ + final protected void renderFormatTemplate(String format, Map model, Writer writer) + { + format = (format == null) ? "" : format; + String templatePath = baseTemplatePath + "_" + format + ".ftl"; + + if (logger.isDebugEnabled()) + logger.debug("Rendering template '" + templatePath + "'"); + + renderTemplate(templatePath, model, writer); + } + +} diff --git a/source/java/org/alfresco/web/scripts/DeclarativeWebScriptRegistry.java b/source/java/org/alfresco/web/scripts/DeclarativeWebScriptRegistry.java new file mode 100644 index 0000000000..99de8a5de2 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/DeclarativeWebScriptRegistry.java @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.servlet.ServletContext; + +import org.alfresco.web.scripts.WebScriptDescription.RequiredAuthentication; +import org.alfresco.web.scripts.WebScriptDescription.RequiredTransaction; +import org.alfresco.web.scripts.WebScriptDescription.URI; +import org.aopalliance.intercept.MethodInterceptor; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.RegexpMethodPointcutAdvisor; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.web.context.ServletContextAware; + + +/** + * Registry of declarative (scripted/template driven) Web Scripts + * + * @author davidc + */ +public class DeclarativeWebScriptRegistry implements WebScriptRegistry, ApplicationContextAware, ServletContextAware +{ + // Logger + private static final Log logger = LogFactory.getLog(DeclarativeWebScriptRegistry.class); + + private ApplicationContext applicationContext; + private ServletContext servletContext; + private String defaultWebScript; + private MethodInterceptor authenticator; + private MethodInterceptor serviceLogger; + private MethodInterceptor serviceTransaction; + private FormatRegistry formatRegistry; + private WebScriptStorage storage; + + // map of web scripts by id + // NOTE: The map is sorted by id (ascending order) + private Map webscriptsById = new TreeMap(); + + // map of web scripts by url + // NOTE: The map is sorted by url (descending order) + private Map webscriptsByURL = new TreeMap(Collections.reverseOrder()); + + + // + // Initialisation + // + + /** + * Sets the available Web Script Stores + * + * @param storage + */ + public void setStorage(WebScriptStorage storage) + { + this.storage = storage; + } + + /** + * Sets the web script authenticator + * + * @param authenticator + */ + public void setAuthenticator(MethodInterceptor authenticator) + { + this.authenticator = authenticator; + } + + /** + * Sets the web script logger + * + * @param serviceLogger + */ + public void setServiceLogger(MethodInterceptor serviceLogger) + { + this.serviceLogger = serviceLogger; + } + + /** + * Sets the service transaction + * + * @param serviceTransaction + */ + public void setServiceTransaction(MethodInterceptor serviceTransaction) + { + this.serviceTransaction = serviceTransaction; + } + + /** + * Sets the default service implementation bean + * + * @param defaultWebScript + */ + public void setDefaultWebScript(String defaultWebScript) + { + this.defaultWebScript = defaultWebScript; + } + + /** + * Sets the response format registry + * + * @param formatRegistry + */ + public void setFormatRegistry(FormatRegistry formatRegistry) + { + this.formatRegistry = formatRegistry; + } + + /* (non-Javadoc) + * @see org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet.ServletContext) + */ + public void setServletContext(ServletContext context) + { + this.servletContext = context; + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } + + + /** + * Initialise Web Scripts + * + * Note: Each invocation of this method resets the list of the services + */ + public void initWebScripts() + { + if (logger.isDebugEnabled()) + logger.debug("Initialising Web Scripts"); + + // clear currently registered services + webscriptsById.clear(); + webscriptsByURL.clear(); + + // register services + for (WebScriptStore apiStore : storage.getStores()) + { + if (logger.isDebugEnabled()) + logger.debug("Locating Web Scripts within " + apiStore.getBasePath()); + + String basePath = apiStore.getBasePath(); + String[] serviceDescPaths = apiStore.getDescriptionDocumentPaths(); + for (String serviceDescPath : serviceDescPaths) + { + // build service description + WebScriptDescription serviceDesc = null; + InputStream serviceDescIS = null; + try + { + serviceDescIS = apiStore.getDescriptionDocument(serviceDescPath); + serviceDesc = createDescription(basePath, serviceDescPath, serviceDescIS); + } + catch(IOException e) + { + throw new WebScriptException("Failed to read Web Script description document " + apiStore.getBasePath() + serviceDescPath, e); + } + finally + { + try + { + if (serviceDescIS != null) serviceDescIS.close(); + } + catch(IOException e) + { + // NOTE: ignore close exception + } + } + + // determine if service description has been registered + String id = serviceDesc.getId(); + if (webscriptsById.containsKey(id)) + { + // move to next service + if (logger.isDebugEnabled()) + { + WebScript existingService = webscriptsById.get(id); + logger.debug("Web Script description document " + serviceDesc.getSourceLocation() + " overridden by " + existingService.getDescription().getSourceLocation()); + } + continue; + } + + // construct service implementation + String serviceImplName = (applicationContext.containsBean("webscript." + id)) ? "webscript." + id : defaultWebScript; + AbstractWebScript serviceImpl = (AbstractWebScript)applicationContext.getBean(serviceImplName); + serviceImpl.setDescription(serviceDesc); + serviceImpl.init(this); + + // wrap service implementation in appropriate interceptors (e.g. authentication) + WebScript serviceImplIF = (WebScript)serviceImpl; + if (serviceLogger != null && serviceTransaction != null && authenticator != null) + { + ProxyFactory authFactory = new ProxyFactory(); + authFactory.addInterface(WebScript.class); + authFactory.setTarget(serviceImplIF); + + // logging interceptor + if (serviceLogger != null) + { + RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(".*execute", serviceLogger); + authFactory.addAdvisor(advisor); + } + + // transaction interceptor + if (serviceDesc.getRequiredTransaction() != RequiredTransaction.none) + { + if (serviceTransaction == null) + { + throw new WebScriptException("Web Script Transaction not specified"); + } + RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(".*execute", serviceTransaction); + authFactory.addAdvisor(advisor); + } + + // authentication interceptor + if (serviceDesc.getRequiredAuthentication() != RequiredAuthentication.none) + { + if (authenticator == null) + { + throw new WebScriptException("Web Script Authenticator not specified"); + } + RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(".*execute", authenticator); + authFactory.addAdvisor(advisor); + } + + serviceImplIF = (WebScript)authFactory.getProxy(); + } + + if (logger.isDebugEnabled()) + logger.debug("Found Web Script " + serviceDescPath + " (id: " + id + ", impl: " + serviceImplName + ", auth: " + serviceDesc.getRequiredAuthentication() + ", trx: " + serviceDesc.getRequiredTransaction() + ")"); + + // register service and its urls + webscriptsById.put(id, serviceImplIF); + for (URI uri : serviceDesc.getURIs()) + { + // establish static part of url template + String uriTemplate = uri.getURI(); + int queryArgIdx = uriTemplate.indexOf('?'); + if (queryArgIdx != -1) + { + uriTemplate = uriTemplate.substring(0, queryArgIdx); + } + int tokenIdx = uriTemplate.indexOf('{'); + if (tokenIdx != -1) + { + uriTemplate = uriTemplate.substring(0, tokenIdx); + } + + // index service by static part of url (ensuring no other service has already claimed the url) + String uriIdx = serviceDesc.getMethod().toString() + ":" + uriTemplate; + if (webscriptsByURL.containsKey(uriIdx)) + { + WebScript existingService = webscriptsByURL.get(uriIdx); + if (!existingService.getDescription().getId().equals(serviceDesc.getId())) + { + String msg = "Web Script document " + serviceDesc.getSourceLocation() + " is attempting to define the url '" + uriIdx + "' already defined by " + existingService.getDescription().getSourceLocation(); + throw new WebScriptException(msg); + } + } + else + { + webscriptsByURL.put(uriIdx, serviceImplIF); + + if (logger.isDebugEnabled()) + logger.debug("Registered Web Script URL '" + uriIdx + "'"); + } + } + } + } + + if (logger.isDebugEnabled()) + logger.debug("Registered " + webscriptsById.size() + " Web Scripts; " + webscriptsByURL.size() + " URLs"); + } + + /** + * Create an Web Script Description + * + * @param basePath + * @param serviceDescPath + * @param serviceDoc + * + * @return web script service description + */ + private WebScriptDescription createDescription(String basePath, String serviceDescPath, InputStream serviceDoc) + { + SAXReader reader = new SAXReader(); + try + { + Document document = reader.read(serviceDoc); + Element rootElement = document.getRootElement(); + if (!rootElement.getName().equals("webscript")) + { + throw new WebScriptException("Expected root element - found <" + rootElement.getName() + ">"); + } + + // retrieve id + String id = serviceDescPath.substring(0, serviceDescPath.lastIndexOf("_desc.xml")).replace('/', '.'); + + // retrieve http method + int methodIdx = id.lastIndexOf('_'); + if (methodIdx == id.length() - 1) + { + throw new WebScriptException("Unable to establish HTTP Method from web script description: naming convention must be __desc.xml"); + } + String method = id.substring(id.lastIndexOf('_') + 1).toUpperCase(); + + // retrieve short name + Element shortNameElement = rootElement.element("shortname"); + if (shortNameElement == null || shortNameElement.getTextTrim() == null || shortNameElement.getTextTrim().length() == 0) + { + throw new WebScriptException("Expected value"); + } + String shortName = shortNameElement.getTextTrim(); + + // retrieve description + Element descriptionElement = rootElement.element("description"); + String description = descriptionElement.getTextTrim(); + + // retrieve urls + List urlElements = rootElement.elements("url"); + if (urlElements == null || urlElements.size() == 0) + { + throw new WebScriptException("Expected at one element"); + } + List uris = new ArrayList(); + Iterator iterElements = urlElements.iterator(); + while(iterElements.hasNext()) + { + // retrieve url element + Element urlElement = (Element)iterElements.next(); + + // retrieve url mimetype + String format = urlElement.attributeValue("format"); + if (format == null) + { + // default to unspecified format + format = ""; + } + + // retrieve url template + String template = urlElement.attributeValue("template"); + if (template == null || template.length() == 0) + { + throw new WebScriptException("Expected template attribute on element"); + } + + WebScriptDescriptionImpl.URIImpl uriImpl = new WebScriptDescriptionImpl.URIImpl(); + uriImpl.setFormat(format); + uriImpl.setUri(template); + uris.add(uriImpl); + } + + // retrieve authentication + RequiredAuthentication reqAuth = RequiredAuthentication.none; + Element authElement = rootElement.element("authentication"); + if (authElement != null) + { + String reqAuthStr = authElement.getTextTrim(); + if (reqAuthStr == null || reqAuthStr.length() == 0) + { + throw new WebScriptException("Expected value"); + } + reqAuth = RequiredAuthentication.valueOf(reqAuthStr); + if (reqAuth == null) + { + throw new WebScriptException("Authentication '" + reqAuthStr + "' is not a valid value"); + } + } + + // retrieve transaction + RequiredTransaction reqTrx = (reqAuth == RequiredAuthentication.none) ? RequiredTransaction.none : RequiredTransaction.required; + Element trxElement = rootElement.element("transaction"); + if (trxElement != null) + { + String reqTrxStr = trxElement.getTextTrim(); + if (reqTrxStr == null || reqTrxStr.length() == 0) + { + throw new WebScriptException("Expected value"); + } + reqTrx = RequiredTransaction.valueOf(reqTrxStr); + if (reqTrx == null) + { + throw new WebScriptException("Transaction '" + reqTrxStr + "' is not a valid value"); + } + } + + // construct service description + WebScriptDescriptionImpl serviceDesc = new WebScriptDescriptionImpl(); + serviceDesc.setSourceLocation(basePath + "/" + serviceDescPath); + serviceDesc.setId(id); + serviceDesc.setShortName(shortName); + serviceDesc.setDescription(description); + serviceDesc.setRequiredAuthentication(reqAuth); + serviceDesc.setRequiredTransaction(reqTrx); + serviceDesc.setMethod(method); + serviceDesc.setUris(uris.toArray(new WebScriptDescription.URI[uris.size()])); + serviceDesc.setDefaultFormat(uris.get(0).getFormat()); + return serviceDesc; + } + catch(DocumentException e) + { + throw new WebScriptException("Failed to parse web script description document " + serviceDescPath, e); + } + catch(WebScriptException e) + { + throw new WebScriptException("Failed to parse web script description document " + serviceDescPath, e); + } + } + + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptRegistry#getWebScripts() + */ + public Collection getWebScripts() + { + return webscriptsById.values(); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptRegistry#getWebScript(java.lang.String) + */ + public WebScript getWebScript(String id) + { + return webscriptsById.get(id); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptRegistry#findWebScript(java.lang.String, java.lang.String) + */ + public WebScriptMatch findWebScript(String method, String uri) + { + // TODO: Replace with more efficient approach + DeclarativeWebScriptMatch apiServiceMatch = null; + String match = method.toString().toUpperCase() + ":" + uri; + for (Map.Entry service : webscriptsByURL.entrySet()) + { + String indexedPath = service.getKey(); + if (match.startsWith(indexedPath)) + { + String matchPath = indexedPath.substring(indexedPath.indexOf(':') +1); + apiServiceMatch = new DeclarativeWebScriptMatch(matchPath, service.getValue()); + break; + } + } + return apiServiceMatch; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptRegistry#getContext() + */ + public ServletContext getContext() + { + return servletContext; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptRegistry#getFormatRegistry() + */ + public FormatRegistry getFormatRegistry() + { + return this.formatRegistry; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptRegistry#getTemplateProcessor() + */ + public TemplateProcessor getTemplateProcessor() + { + return this.storage.getTemplateProcessor(); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptRegistry#getScriptProcessor() + */ + public ScriptProcessor getScriptProcessor() + { + return this.storage.getScriptProcessor(); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptRegistry#reset() + */ + public void reset() + { + getTemplateProcessor().resetCache(); + getScriptProcessor().resetCache(); + initWebScripts(); + } + + + /** + * Web Script Match + * + * @author davidc + */ + public static class DeclarativeWebScriptMatch implements WebScriptMatch + { + private String path; + private WebScript service; + + /** + * Construct + * + * @param path + * @param service + */ + public DeclarativeWebScriptMatch(String path, WebScript service) + { + this.path = path; + this.service = service; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptMatch#getPath() + */ + public String getPath() + { + return path; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptMatch#getWebScript() + */ + public WebScript getWebScript() + { + return service; + } + } + +} diff --git a/source/java/org/alfresco/web/scripts/FormatMap.java b/source/java/org/alfresco/web/scripts/FormatMap.java new file mode 100644 index 0000000000..d1b72990c7 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/FormatMap.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.util.Map; + +import org.springframework.beans.factory.InitializingBean; + + +/** + * A map of mimetypes indexed by format. + * + * @author davidc + */ +public class FormatMap implements InitializingBean +{ + private FormatRegistry registry; + private String agent; + private Map formats; + + + /** + * Sets the Format Registry + * + * @param registry + */ + public void setRegistry(FormatRegistry registry) + { + this.registry = registry; + } + + /** + * Sets the User Agent for which the formats apply + * + * @param agent + */ + public void setAgent(String agent) + { + this.agent = agent; + } + + /** + * Sets the formats + * + * @param formats + */ + public void setFormats(Map formats) + { + this.formats = formats; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + // Add formats to format registry + registry.addFormats(agent, formats); + } + +} diff --git a/source/java/org/alfresco/web/scripts/FormatRegistry.java b/source/java/org/alfresco/web/scripts/FormatRegistry.java new file mode 100644 index 0000000000..a1a621afde --- /dev/null +++ b/source/java/org/alfresco/web/scripts/FormatRegistry.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Maintains a registry of mimetypes (indexed by format and user agent) + * + * @author davidc + */ +public class FormatRegistry +{ + // Logger + private static final Log logger = LogFactory.getLog(FormatRegistry.class); + + private Map formats; + private Map mimetypes; + private Map> agentFormats; + private Map> agentMimetypes; + + + /** + * Construct + */ + public FormatRegistry() + { + formats = new HashMap(); + mimetypes = new HashMap(); + agentFormats = new HashMap>(); + agentMimetypes = new HashMap>(); + } + + /** + * Add formats + * + * @param agent + * @param formatsToAdd + */ + public void addFormats(String agent, Map formatsToAdd) + { + // retrieve formats list for agent + Map formatsForAgent = formats; + Map mimetypesForAgent = mimetypes; + if (agent != null) + { + formatsForAgent = agentFormats.get(agent); + if (formatsForAgent == null) + { + formatsForAgent = new HashMap(); + mimetypesForAgent = new HashMap(); + agentFormats.put(agent, formatsForAgent); + agentMimetypes.put(agent, mimetypesForAgent); + } + } + + for (Map.Entry entry : formatsToAdd.entrySet()) + { + if (logger.isWarnEnabled()) + { + String mimetype = formatsForAgent.get(entry.getKey()); + if (mimetype != null) + { + logger.warn("Replacing mime type '" + mimetype + "' with '" + entry.getValue() + "' for Web Script format '" + entry.getKey() + "' (agent: " + agent + ")"); + } + } + + formatsForAgent.put(entry.getKey(), entry.getValue()); + mimetypesForAgent.put(entry.getValue(), entry.getKey()); + + if (logger.isDebugEnabled()) + logger.debug("Registered Web Script format '" + entry.getKey() + "' with mime type '" + entry.getValue() + "' (agent: " + agent + ")"); + } + } + + /** + * Gets the mimetype for the specified user agent and format + * + * @param agent + * @param format + * @return mimetype (or null, if one is not registered) + */ + public String getMimeType(String agent, String format) + { + String mimetype = null; + + if (agent != null) + { + Map formatsForAgent = agentFormats.get(agent); + if (formatsForAgent != null) + { + mimetype = formatsForAgent.get(format); + } + } + + if (mimetype == null) + { + mimetype = formats.get(format); + } + + return mimetype; + } + + /** + * Gets the format for the specified user agent and mimetype + * + * @param agent + * @param mimetype + * @return format (or null, if one is not registered) + */ + public String getFormat(String agent, String mimetype) + { + String format = null; + + if (agent != null) + { + Map mimetypesForAgent = agentMimetypes.get(agent); + if (mimetypesForAgent != null) + { + format = mimetypesForAgent.get(mimetype); + } + } + + if (format == null) + { + format = mimetypes.get(mimetype); + } + + return format; + } + +} diff --git a/source/java/org/alfresco/web/scripts/MultiScriptLoader.java b/source/java/org/alfresco/web/scripts/MultiScriptLoader.java new file mode 100644 index 0000000000..1f3ae148d9 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/MultiScriptLoader.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import org.alfresco.service.cmr.repository.ScriptLocation; + + +/** + * Composite script loader + * + * @author davidc + */ +public class MultiScriptLoader implements ScriptLoader +{ + private ScriptLoader[] loaders; + + /** + * Construct + * + * @param loaders + */ + public MultiScriptLoader(ScriptLoader[] loaders) + { + this.loaders = loaders; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.ScriptLoader#getScriptLocation(java.lang.String) + */ + public ScriptLocation getScriptLocation(String path) + { + ScriptLocation location = null; + for (ScriptLoader loader : loaders) + { + location = loader.getScriptLocation(path); + if (location != null) + { + break; + } + } + return location; + } + +} diff --git a/source/java/org/alfresco/web/scripts/RepoStore.java b/source/java/org/alfresco/web/scripts/RepoStore.java new file mode 100644 index 0000000000..fb7dad3fb7 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/RepoStore.java @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.TransactionUtil; +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.NodeService; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.AbstractLifecycleBean; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; + +import freemarker.cache.TemplateLoader; + + +/** + * Repository based Web Script Store + * + * @author davidc + */ +public class RepoStore implements WebScriptStore, ApplicationContextAware, ApplicationListener +{ + private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); + protected StoreRef repoStore; + protected String repoPath; + protected NodeRef baseNodeRef; + protected String baseDir; + + // dependencies + protected TransactionService transactionService; + protected SearchService searchService; + protected NodeService nodeService; + protected ContentService contentService; + protected NamespaceService namespaceService; + + + /** + * Sets transaction service + * + * @param transactionService + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Sets the search service + * + * @param searchService + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Sets the node service + * + * @param nodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the content service + * + * @param contentService + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Sets the namespace service + * + * @param namespaceService + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Sets the repo store + * + * @param repoStore + */ + public void setStore(String repoStore) + { + this.repoStore = new StoreRef(repoStore); + } + + /** + * Sets the repo path + * + * @param repoPath repoPath + */ + public void setPath(String repoPath) + { + this.repoPath = repoPath; + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + lifecycle.setApplicationContext(applicationContext); + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + */ + public void onApplicationEvent(ApplicationEvent event) + { + lifecycle.onApplicationEvent(event); + } + + /** + * Hooks into Spring Application Lifecycle + */ + private class ProcessorLifecycle extends AbstractLifecycleBean + { + @Override + protected void onBootstrap(ApplicationEvent event) + { + init(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + protected void init() + { + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + return TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + String query = "PATH:\"" + repoPath + "\""; + ResultSet resultSet = searchService.query(repoStore, SearchService.LANGUAGE_LUCENE, query); + if (resultSet.length() == 0) + { + throw new WebScriptException("Unable to locate repository path " + repoStore.toString() + repoPath); + } + if (resultSet.length() > 1) + { + throw new WebScriptException("Multiple repository paths found for " + repoStore.toString() + repoPath); + } + baseNodeRef = resultSet.getNodeRef(0); + baseDir = getPath(baseNodeRef); + return null; + } + }); + } + }, AuthenticationUtil.getSystemUserName()); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptStore#getBasePath() + */ + public String getBasePath() + { + return repoStore.toString() + repoPath; + } + + /** + * Gets the display path for the specified node + * + * @param nodeRef + * @return display path + */ + protected String getPath(NodeRef nodeRef) + { + return nodeService.getPath(nodeRef).toDisplayPath(nodeService) + "/" + nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + } + + /** + * Gets the node ref for the specified path within this repo store + * + * @param documentPath + * @return node ref + */ + protected NodeRef findNodeRef(String documentPath) + { + StringBuilder xpath = new StringBuilder(documentPath.length() << 1); + for (StringTokenizer t = new StringTokenizer(documentPath, "/"); t.hasMoreTokens(); /**/) + { + if (xpath.length() != 0) + { + xpath.append('/'); + } + xpath.append("*[@cm:name='").append(t.nextToken()).append("']"); + } + + List nodes = searchService.selectNodes(baseNodeRef, xpath.toString(), null, namespaceService, false); + return (nodes.size() == 1) ? nodes.get(0) : null; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptStore#getDescriptionDocumentPaths() + */ + public String[] getDescriptionDocumentPaths() + { + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public String[] doWork() throws Exception + { + return TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + { + public String[] doWork() throws Exception + { + int baseDirLength = baseDir.length() +1; + List documentPaths = new ArrayList(); + + String query = "PATH:\"" + repoPath + "//*\" AND @cm\\:name:\"*_desc.xml\""; + ResultSet resultSet = searchService.query(repoStore, SearchService.LANGUAGE_LUCENE, query); + List nodes = resultSet.getNodeRefs(); + for (NodeRef nodeRef : nodes) + { + String nodeDir = getPath(nodeRef); + String documentPath = nodeDir.substring(baseDirLength); + documentPaths.add(documentPath); + } + + return documentPaths.toArray(new String[documentPaths.size()]); + } + }); + } + }, AuthenticationUtil.getSystemUserName()); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptStore#getDescriptionDocument(java.lang.String) + */ + public InputStream getDescriptionDocument(final String documentPath) + throws IOException + { + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public InputStream doWork() throws Exception + { + return TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + { + public InputStream doWork() throws Exception + { + NodeRef nodeRef = findNodeRef(documentPath); + if (nodeRef == null) + { + throw new IOException("Description document " + documentPath + " does not exist."); + } + ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + return reader.getContentInputStream(); + } + }); + } + }, AuthenticationUtil.getSystemUserName()); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptStore#getTemplateLoader() + */ + public TemplateLoader getTemplateLoader() + { + return new RepoTemplateLoader(); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptStore#getScriptLoader() + */ + public ScriptLoader getScriptLoader() + { + return new RepoScriptLoader(); + } + + /** + * Repository path based template loader + * + * @author davidc + */ + private class RepoTemplateLoader implements TemplateLoader + { + /* (non-Javadoc) + * @see freemarker.cache.TemplateLoader#findTemplateSource(java.lang.String) + */ + public Object findTemplateSource(final String name) + throws IOException + { + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + return TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + RepoTemplateSource source = null; + NodeRef nodeRef = findNodeRef(name); + if (nodeRef != null) + { + source = new RepoTemplateSource(nodeRef); + } + return source; + } + }); + } + }, AuthenticationUtil.getSystemUserName()); + } + + /* (non-Javadoc) + * @see freemarker.cache.TemplateLoader#getLastModified(java.lang.Object) + */ + public long getLastModified(Object templateSource) + { + return ((RepoTemplateSource)templateSource).lastModified(); + } + + /* (non-Javadoc) + * @see freemarker.cache.TemplateLoader#getReader(java.lang.Object, java.lang.String) + */ + public Reader getReader(Object templateSource, String encoding) throws IOException + { + return ((RepoTemplateSource)templateSource).getReader(); + } + + /* (non-Javadoc) + * @see freemarker.cache.TemplateLoader#closeTemplateSource(java.lang.Object) + */ + public void closeTemplateSource(Object arg0) throws IOException + { + } + } + + /** + * Repository (content) node template source + * + * @author davidc + */ + private class RepoTemplateSource + { + protected final NodeRef nodeRef; + + /** + * Construct + * + * @param ref + */ + private RepoTemplateSource(NodeRef ref) + { + this.nodeRef = ref; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) + { + if (o instanceof RepoTemplateSource) + { + return nodeRef.equals(((RepoTemplateSource)o).nodeRef); + } + else + { + return false; + } + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + return nodeRef.hashCode(); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + return nodeRef.toString(); + } + + /** + * Gets the last modified time of the content + * + * @return last modified time + */ + public long lastModified() + { + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Long doWork() throws Exception + { + return TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + { + public Long doWork() throws Exception + { + ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + return reader.getLastModified(); + } + }); + } + }, AuthenticationUtil.getSystemUserName()); + } + + /** + * Gets the content reader + * + * @return content reader + * @throws IOException + */ + public Reader getReader() throws IOException + { + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Reader doWork() throws Exception + { + return TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + { + public Reader doWork() throws Exception + { + ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + return new InputStreamReader(reader.getContentInputStream()); + } + }); + } + }, AuthenticationUtil.getSystemUserName()); + } + } + + /** + * Repository path based script loader + * + * @author davidc + */ + private class RepoScriptLoader implements ScriptLoader + { + /* (non-Javadoc) + * @see org.alfresco.web.scripts.ScriptLoader#getScriptLocation(java.lang.String) + */ + public ScriptLocation getScriptLocation(final String path) + { + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public ScriptLocation doWork() throws Exception + { + return TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + { + public ScriptLocation doWork() throws Exception + { + ScriptLocation location = null; + NodeRef nodeRef = findNodeRef(path); + if (nodeRef != null) + { + location = new RepoScriptLocation(path, nodeRef); + } + return location; + } + }); + } + }, AuthenticationUtil.getSystemUserName()); + } + } + + /** + * Repo path script location + * + * @author davidc + */ + private class RepoScriptLocation implements ScriptLocation + { + protected String path; + protected NodeRef nodeRef; + + /** + * Construct + * + * @param location + */ + public RepoScriptLocation(String path, NodeRef nodeRef) + { + this.path = path; + this.nodeRef = nodeRef; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.repository.ScriptLocation#getInputStream() + */ + public InputStream getInputStream() + { + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public InputStream doWork() throws Exception + { + return TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + { + public InputStream doWork() throws Exception + { + ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + return reader.getContentInputStream(); + } + }); + } + }, AuthenticationUtil.getSystemUserName()); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.repository.ScriptLocation#getReader() + */ + public Reader getReader() + { + return new InputStreamReader(getInputStream()); + } + + @Override + public String toString() + { + return repoStore + "/" + baseDir + "/" + path; + } + } + +} diff --git a/source/java/org/alfresco/web/scripts/ScriptLoader.java b/source/java/org/alfresco/web/scripts/ScriptLoader.java new file mode 100644 index 0000000000..e3d674f72a --- /dev/null +++ b/source/java/org/alfresco/web/scripts/ScriptLoader.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import org.alfresco.service.cmr.repository.ScriptLocation; + + +/** + * Locate a script + * + * @author davidc + */ +public interface ScriptLoader +{ + + /** + * Gets the script location for the script at the specified path + * + * @param path path within api store + * @return script location (or null, if script does not exist at path) + */ + ScriptLocation getScriptLocation(String path); + +} diff --git a/source/java/org/alfresco/web/scripts/ScriptProcessor.java b/source/java/org/alfresco/web/scripts/ScriptProcessor.java new file mode 100644 index 0000000000..a27b4c963b --- /dev/null +++ b/source/java/org/alfresco/web/scripts/ScriptProcessor.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.util.Map; + +import org.alfresco.service.cmr.repository.ScriptException; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.alfresco.service.cmr.repository.ScriptService; + + +/** + * Script Processor for use in Web Script + * + * @author davidc + */ +public class ScriptProcessor +{ + // dependencies + private ScriptService scriptService; + + // script loader + private ScriptLoader scriptLoader; + + /** + * Sets the script service + * + * @param scriptService + */ + public void setScriptService(ScriptService scriptService) + { + this.scriptService = scriptService; + } + + /** + * Sets the script loader + * + * @param scriptLoader + */ + public void setScriptLoader(ScriptLoader scriptLoader) + { + this.scriptLoader = scriptLoader; + } + + /** + * Find a script at the specified path (within registered Web Script stores) + * + * @param path script path + * @return script location (or null, if not found) + */ + public ScriptLocation findScript(String path) + { + return scriptLoader.getScriptLocation(path); + } + + /** + * Execute script + * + * @param path script path + * @param model model + * @return script result + * @throws ScriptException + */ + public Object executeScript(String path, Map model) + throws ScriptException + { + // locate script within web script stores + ScriptLocation scriptLocation = findScript(path); + if (scriptLocation == null) + { + throw new WebScriptException("Unable to locate script " + path); + } + // execute script + return executeScript(scriptLocation, model); + } + + /** + * Execute script + * + * @param location script location + * @param model model + * @return script result + */ + public Object executeScript(ScriptLocation location, Map model) + { + return scriptService.executeScript(location, model); + } + + /** + * Reset script cache + */ + public void resetCache() + { + // NOOP + } + +} diff --git a/source/java/org/alfresco/web/scripts/ServerModel.java b/source/java/org/alfresco/web/scripts/ServerModel.java new file mode 100644 index 0000000000..c7ce52b2e6 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/ServerModel.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import org.alfresco.service.descriptor.Descriptor; + + +/** + * Script / Template Model representing Repository Server meta-data + * + * @author davidc + */ +public class ServerModel +{ + private Descriptor serverDescriptor; + + /** + * Construct + * + * @param serverDescriptor + */ + /*package*/ ServerModel(Descriptor serverDescriptor) + { + this.serverDescriptor = serverDescriptor; + } + + /** + * Gets the major version number, e.g. 1.2.3 + * + * @return major version number + */ + public String getVersionMajor() + { + return serverDescriptor.getVersionMajor(); + } + + public String jsGet_versionMajor() + { + return getVersionMajor(); + } + + /** + * Gets the minor version number, e.g. 1.2.3 + * + * @return minor version number + */ + public String getVersionMinor() + { + return serverDescriptor.getVersionMinor(); + } + + public String jsGet_versionMinor() + { + return getVersionMinor(); + } + + /** + * Gets the version revision number, e.g. 1.2.3 + * + * @return revision number + */ + public String getVersionRevision() + { + return serverDescriptor.getVersionRevision(); + } + + public String jsGet_versionRevision() + { + return getVersionRevision(); + } + + /** + * Gets the version label + * + * @return the version label + */ + public String getVersionLabel() + { + return serverDescriptor.getVersionLabel(); + } + + public String jsGet_versionLabel() + { + return getVersionLabel(); + } + + /** + * Gets the build number + * + * @return the build number i.e. build-1 + */ + public String getVersionBuild() + { + return serverDescriptor.getVersionBuild(); + } + + public String jsGet_versionBuild() + { + return getVersionBuild(); + } + + /** + * Gets the full version number + * + * @return full version number as major.minor.revision (label) + */ + public String getVersion() + { + return serverDescriptor.getVersion(); + } + + public String jsGet_version() + { + return getVersion(); + } + + /** + * Gets the edition + * + * @return the edition + */ + public String getEdition() + { + return serverDescriptor.getEdition(); + } + + public String jsGet_edition() + { + return getEdition(); + } + + /** + * Gets the schema number + * + * @return a positive integer + */ + public int getSchema() + { + return serverDescriptor.getSchema(); + } + + public int jsGet_schema() + { + return getSchema(); + } + +} diff --git a/source/java/org/alfresco/web/scripts/SystemAuthenticator.java b/source/java/org/alfresco/web/scripts/SystemAuthenticator.java new file mode 100644 index 0000000000..f59815e43c --- /dev/null +++ b/source/java/org/alfresco/web/scripts/SystemAuthenticator.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * System Authentication Interceptor + * + * @author davidc + */ +public class SystemAuthenticator implements MethodInterceptor +{ + // Logger + private static final Log logger = LogFactory.getLog(SystemAuthenticator.class); + + + /* (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + public Object invoke(MethodInvocation invocation) + throws Throwable + { + String currentUser = null; + Object retVal = null; + + try + { + // + // Determine if user already authenticated + // + + currentUser = AuthenticationUtil.getCurrentUserName(); + if (logger.isDebugEnabled()) + logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + + // + // Force system user + // + + if (logger.isDebugEnabled()) + logger.debug("Authenticating as System"); + + AuthenticationUtil.setSystemUserAsCurrentUser(); + + // + // Invoke service + // + + retVal = invocation.proceed(); + } + finally + { + AuthenticationUtil.clearCurrentSecurityContext(); + if (currentUser != null) + { + AuthenticationUtil.setCurrentUser(currentUser); + } + + if (logger.isDebugEnabled()) + logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + } + + return retVal; + } + +} diff --git a/source/java/org/alfresco/web/scripts/TemplateProcessor.java b/source/java/org/alfresco/web/scripts/TemplateProcessor.java new file mode 100644 index 0000000000..0ba2219172 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/TemplateProcessor.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import org.alfresco.repo.template.FreeMarkerProcessor; +import org.alfresco.repo.template.QNameAwareObjectWrapper; +import org.alfresco.util.AbstractLifecycleBean; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; + +import freemarker.cache.MruCacheStorage; +import freemarker.cache.TemplateLoader; +import freemarker.template.Configuration; +import freemarker.template.TemplateExceptionHandler; + + +/** + * FreeMarker Processor for use in Web Scripts + * + * Adds the ability to: + * - specify template loaders + * - caching of templates + * + * @author davidc + */ +public class TemplateProcessor extends FreeMarkerProcessor implements ApplicationContextAware, ApplicationListener +{ + private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); + private TemplateLoader templateLoader = null; + private String defaultEncoding; + private Configuration templateConfig; + + + /* (non-Javadoc) + * @see org.alfresco.repo.template.FreeMarkerProcessor#setDefaultEncoding(java.lang.String) + */ + public void setDefaultEncoding(String defaultEncoding) + { + this.defaultEncoding = defaultEncoding; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.template.FreeMarkerProcessor#getConfig() + */ + @Override + protected Configuration getConfig() + { + return templateConfig; + } + + /** + * Sets the Template Loader + * + * @param templateLoader template loader + */ + public void setTemplateLoader(TemplateLoader templateLoader) + { + this.templateLoader = templateLoader; + } + + /** + * Reset template cache + */ + public void resetCache() + { + if (templateConfig != null) + { + templateConfig.clearTemplateCache(); + } + } + + /** + * Initialise FreeMarker Configuration + */ + protected void initConfig() + { + Configuration config = new Configuration(); + + // setup template cache + config.setCacheStorage(new MruCacheStorage(20, 100)); + + // setup template loaders + config.setTemplateLoader(templateLoader); + + // use our custom object wrapper that can deal with QNameMap objects directly + config.setObjectWrapper(new QNameAwareObjectWrapper()); + + // rethrow any exception so we can deal with them + config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + // set template encoding + if (defaultEncoding != null) + { + config.setDefaultEncoding(defaultEncoding); + } + + // set output encoding + config.setOutputEncoding("UTF-8"); + + templateConfig = config; + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + lifecycle.setApplicationContext(applicationContext); + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + */ + public void onApplicationEvent(ApplicationEvent event) + { + lifecycle.onApplicationEvent(event); + } + + /** + * Hooks into Spring Application Lifecycle + */ + private class ProcessorLifecycle extends AbstractLifecycleBean + { + @Override + protected void onBootstrap(ApplicationEvent event) + { + initConfig(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } + } + +} diff --git a/source/java/org/alfresco/web/scripts/TestWebScriptServer.java b/source/java/org/alfresco/web/scripts/TestWebScriptServer.java new file mode 100644 index 0000000000..6010d1fd25 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/TestWebScriptServer.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.transaction.TransactionService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + + +/** + * Stand-alone Web Script Test Server + * + * @author davidc + */ +public class TestWebScriptServer +{ + // dependencies + protected TransactionService transactionService; + protected DeclarativeWebScriptRegistry registry; + + /** The reader for interaction. */ + private BufferedReader fIn; + + /** Last command issued */ + private String lastCommand = null; + + /** Current user */ + private String username = "admin"; + + + /** + * Sets the transaction service + * + * @param transactionService + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Sets the Web Script Registry + * + * @param registry + */ + public void setRegistry(DeclarativeWebScriptRegistry registry) + { + this.registry = registry; + } + + + /** + * Initialise the Test Web Script Server + * + * @throws Exception + */ + public void init() throws Exception + { + registry.initWebScripts(); + fIn = new BufferedReader(new InputStreamReader(System.in)); + } + + /** + * Main entry point. + */ + public static void main(String[] args) + { + try + { + String[] CONFIG_LOCATIONS = new String[] { "classpath:alfresco/application-context.xml", "classpath:alfresco/web-scripts-application-context.xml", "classpath:alfresco/web-scripts-application-context-test.xml" }; + ApplicationContext context = new ClassPathXmlApplicationContext(CONFIG_LOCATIONS); + TestWebScriptServer testServer = (TestWebScriptServer)context.getBean("webscripts.test"); + testServer.init(); + testServer.rep(); + } + catch(Throwable e) + { + StringWriter strWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(strWriter); + e.printStackTrace(printWriter); + System.out.println(strWriter.toString()); + } + finally + { + System.exit(0); + } + } + + /** + * A Read-Eval-Print loop. + */ + public void rep() + { + // accept commands + while (true) + { + System.out.print("ok> "); + try + { + // get command + final String line = fIn.readLine(); + if (line.equals("exit") || line.equals("quit")) + { + return; + } + + // execute command in context of currently selected user + long startms = System.currentTimeMillis(); + System.out.print(interpretCommand(line)); + System.out.println("" + (System.currentTimeMillis() - startms) + "ms"); + } + catch (Exception e) + { + e.printStackTrace(System.err); + System.out.println(""); + } + } + } + + /** + * Interpret a single command using the BufferedReader passed in for any data needed. + * + * @param line The unparsed command + * @return The textual output of the command. + */ + public String interpretCommand(final String line) + throws IOException + { + // execute command in context of currently selected user + return AuthenticationUtil.runAs(new RunAsWork() + { + public String doWork() throws Exception + { + return executeCommand(line); + } + }, username); + } + + /** + * Execute a single command using the BufferedReader passed in for any data needed. + * + * TODO: Use decent parser! + * + * @param line The unparsed command + * @return The textual output of the command. + */ + protected String executeCommand(String line) + throws IOException + { + String[] command = line.split(" "); + if (command.length == 0) + { + command = new String[1]; + command[0] = line; + } + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + + // repeat last command? + if (command[0].equals("r")) + { + if (lastCommand == null) + { + return "No command entered yet."; + } + return "repeating command " + lastCommand + "\n\n" + interpretCommand(lastCommand); + } + + // remember last command + lastCommand = line; + + // execute command + if (command[0].equals("help")) + { + // TODO: + String helpFile = I18NUtil.getMessage("test_service.help"); + ClassPathResource helpResource = new ClassPathResource(helpFile); + byte[] helpBytes = new byte[500]; + InputStream helpStream = helpResource.getInputStream(); + try + { + int read = helpStream.read(helpBytes); + while (read != -1) + { + bout.write(helpBytes, 0, read); + read = helpStream.read(helpBytes); + } + } + finally + { + helpStream.close(); + } + } + + else if (command[0].equals("user")) + { + if (command.length == 2) + { + username = command[1]; + } + out.println("using user " + username); + } + + else if (command[0].equals("get")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + + String uri = command[1]; + MockHttpServletRequest req = createRequest("get", uri); + MockHttpServletResponse res = new MockHttpServletResponse(); + + WebScriptMatch match = registry.findWebScript(req.getMethod(), uri); + if (match == null) + { + throw new WebScriptException("No service bound to uri '" + uri + "'"); + } + + WebScriptRequest apiReq = new WebScriptRequest(req, match); + WebScriptResponse apiRes = new WebScriptResponse(res); + match.getWebScript().execute(apiReq, apiRes); + bout.write(res.getContentAsByteArray()); + out.println(); + } + + else + { + return "Syntax Error.\n"; + } + + out.flush(); + String retVal = new String(bout.toByteArray()); + out.close(); + return retVal; + } + + /** + * Create a Mock HTTP Servlet Request + * + * @param method + * @param uri + * @return mock http servlet request + */ + private MockHttpServletRequest createRequest(String method, String uri) + { + MockHttpServletRequest req = new MockHttpServletRequest("get", uri); + + // set parameters + int iArgIndex = uri.indexOf('?'); + if (iArgIndex != -1 && iArgIndex != uri.length() -1) + { + String uriArgs = uri.substring(iArgIndex +1); + String[] args = uriArgs.split("&"); + for (String arg : args) + { + String[] parts = arg.split("="); + req.addParameter(parts[0], (parts.length == 2) ? parts[1] : null); + } + } + + // set paths + req.setContextPath("/alfresco"); + req.setServletPath("/service"); + req.setPathInfo(iArgIndex == -1 ? uri : uri.substring(0, iArgIndex)); + + return req; + } + +} diff --git a/source/java/org/alfresco/web/scripts/TrustedAuthenticator.java b/source/java/org/alfresco/web/scripts/TrustedAuthenticator.java new file mode 100644 index 0000000000..e6890a9d4d --- /dev/null +++ b/source/java/org/alfresco/web/scripts/TrustedAuthenticator.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Trusted Authentication Interceptor + * + * Just use the currently authenticated user + * + * @author davidc + */ +public class TrustedAuthenticator implements MethodInterceptor +{ + // Logger + private static final Log logger = LogFactory.getLog(TrustedAuthenticator.class); + + + /* (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + public Object invoke(MethodInvocation invocation) + throws Throwable + { + String currentUser = null; + Object retVal = null; + + try + { + // + // Determine if user already authenticated + // + + currentUser = AuthenticationUtil.getCurrentUserName(); + if (logger.isDebugEnabled()) + logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + + // + // Use current authentication + // + + // + // Invoke service + // + + retVal = invocation.proceed(); + } + finally + { + AuthenticationUtil.clearCurrentSecurityContext(); + if (currentUser != null) + { + AuthenticationUtil.setCurrentUser(currentUser); + } + + if (logger.isDebugEnabled()) + logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + } + + return retVal; + } + +} diff --git a/source/java/org/alfresco/web/scripts/URLModel.java b/source/java/org/alfresco/web/scripts/URLModel.java new file mode 100644 index 0000000000..5f7a2ef540 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/URLModel.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + + +/** + * Script / Template Model representing Web Script URLs + * + * @author davidc + */ +public class URLModel +{ + private WebScriptRequest req; + + /** + * Construct + * + * @param req + */ + URLModel(WebScriptRequest req) + { + this.req = req; + } + + /** + * Gets the Context Path + * + * e.g. /alfresco + * + * @return context path + */ + public String getContext() + { + return req.getContextPath(); + } + + public String jsGet_context() + { + return getContext(); + } + + /** + * Gets the Service Context Path + * + * e.g. /alfresco/service + * + * @return service context path + */ + public String getServiceContext() + { + return req.getServiceContextPath(); + } + + public String jsGet_serviceContext() + { + return getServiceContext(); + } + + /** + * Gets the Service Path + * + * e.g. /alfresco/service/search/keyword + * + * @return service path + */ + public String getService() + { + return req.getServicePath(); + } + + public String jsGet_service() + { + return getService(); + } + + /** + * Gets the full path + * + * e.g. /alfresco/service/search/keyword?q=term + * + * @return service path + */ + public String getFull() + { + return req.getURL(); + } + + public String jsGet_full() + { + return getFull(); + } + + /** + * Gets the matching service path + * + * e.g. + * a) service registered path = /search/engine + * b) request path = /search/engine/external + * + * => /search/engine + * + * @return matching path + */ + public String getMatch() + { + return getServiceContext() + req.getServiceMatch().getPath(); + } + + public String jsGet_match() + { + return getMatch(); + } + + /** + * Gets the Service Extension Path + * + * e.g. + * a) service registered path = /search/engine + * b) request path = /search/engine/external + * + * => /external + * + * @return extension path + */ + public String getExtension() + { + return req.getExtensionPath(); + } + + public String jsGet_extension() + { + return getExtension(); + } + +} diff --git a/source/java/org/alfresco/web/scripts/WebClientAuthenticator.java b/source/java/org/alfresco/web/scripts/WebClientAuthenticator.java new file mode 100644 index 0000000000..b4db1db94b --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebClientAuthenticator.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import javax.servlet.ServletContext; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.web.app.servlet.AuthenticationHelper; +import org.alfresco.web.app.servlet.AuthenticationStatus; +import org.alfresco.web.app.servlet.BaseServlet; +import org.alfresco.web.scripts.WebScriptDescription.RequiredAuthentication; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.context.ServletContextAware; + + +/** + * Alfresco Web Client Authentication Interceptor + * + * @author davidc + */ +public class WebClientAuthenticator implements MethodInterceptor, ServletContextAware +{ + // Logger + private static final Log logger = LogFactory.getLog(WebClientAuthenticator.class); + + // dependencies + private ServletContext context; + private AuthenticationService authenticationService; + + + /* (non-Javadoc) + * @see org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet.ServletContext) + */ + public void setServletContext(ServletContext context) + { + this.context = context; + } + + /** + * @param authenticationService + */ + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + /* (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + public Object invoke(MethodInvocation invocation) + throws Throwable + { + String currentUser = null; + Object retVal = null; + Object[] args = invocation.getArguments(); + WebScriptRequest request = (WebScriptRequest)args[0]; + WebScriptResponse response = (WebScriptResponse)args[1]; + WebScript service = (WebScript)invocation.getThis(); + WebScriptDescription description = service.getDescription(); + AuthenticationStatus status = null; + + try + { + + // + // Determine if user already authenticated + // + + currentUser = AuthenticationUtil.getCurrentUserName(); + if (logger.isDebugEnabled()) + logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + + // + // validate credentials + // + + String ticket = request.getParameter("ticket"); + boolean isGuest = request.isGuest(); + + if (logger.isDebugEnabled()) + { + logger.debug("Web Script authentication required: " + description.getRequiredAuthentication()); + logger.debug("Guest login: " + isGuest); + logger.debug("Ticket provided: " + (ticket != null && ticket.length() > 0)); + } + + if (ticket != null && ticket.length() > 0) + { + if (logger.isDebugEnabled()) + logger.debug("Authenticating ticket " + ticket); + + status = AuthenticationHelper.authenticate(context, request, response, ticket); + } + else + { + if (isGuest && description.getRequiredAuthentication() == RequiredAuthentication.guest) + { + if (logger.isDebugEnabled()) + logger.debug("Authenticating as Guest"); + + status = AuthenticationHelper.authenticate(context, request, response, true); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Authenticating session"); + + status = AuthenticationHelper.authenticate(context, request, response, false); + } + } + + // + // execute web script or request authorization + // + + if (status != null && status != AuthenticationStatus.Failure) + { + retVal = invocation.proceed(); + } + else + { + // authentication failed - now need to display the login page to the user, if asked to + if (logger.isDebugEnabled()) + logger.debug("Redirecting to Alfresco Login"); + + BaseServlet.redirectToLoginPage(request, response, context); + } + } + finally + { + if (status != null && status != AuthenticationStatus.Failure) + { + authenticationService.clearCurrentSecurityContext(); + if (currentUser != null) + { + AuthenticationUtil.setCurrentUser(currentUser); + } + + if (logger.isDebugEnabled()) + logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + } + } + + return retVal; + } + +} diff --git a/source/java/org/alfresco/web/scripts/WebScript.java b/source/java/org/alfresco/web/scripts/WebScript.java new file mode 100644 index 0000000000..70a9cfc58e --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScript.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.io.IOException; + +/** + * Web Script + * + * @author davidc + */ +public interface WebScript +{ + /** + * Gets the Service Description + * + * @return service description + */ + public WebScriptDescription getDescription(); + + /** + * Execute service + * + * @param req + * @param res + * @throws IOException + */ + public void execute(WebScriptRequest req, WebScriptResponse res) + throws IOException; + +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptContext.java b/source/java/org/alfresco/web/scripts/WebScriptContext.java new file mode 100644 index 0000000000..1a3b2a75b8 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptContext.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.TransactionUtil; +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.search.SearchService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.AbstractLifecycleBean; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; + + +/** + * Provision of Repository Context + * + * @author davidc + */ +public class WebScriptContext implements ApplicationContextAware, ApplicationListener +{ + private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); + + // dependencies + private TransactionService transactionService; + private NamespaceService namespaceService; + private SearchService searchService; + private NodeService nodeService; + private PersonService personService; + + // company home + private StoreRef companyHomeStore; + private String companyHomePath; + private NodeRef companyHome; + + + /** + * Sets the Company Home Store + * + * @param companyHomeStore + */ + public void setCompanyHomeStore(String companyHomeStore) + { + this.companyHomeStore = new StoreRef(companyHomeStore); + } + + /** + * Sets the Company Home Path + * + * @param companyHomePath + */ + public void setCompanyHomePath(String companyHomePath) + { + this.companyHomePath = companyHomePath; + } + + /** + * Sets the transaction service + * + * @param transactionService + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Sets the namespace service + * + * @param namespaceService + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Sets the search service + * + * @param searchService + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Sets the node service + * + * @param nodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the person service + * + * @param personService + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + lifecycle.setApplicationContext(applicationContext); + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + */ + public void onApplicationEvent(ApplicationEvent event) + { + lifecycle.onApplicationEvent(event); + } + + /** + * Hooks into Spring Application Lifecycle + */ + private class ProcessorLifecycle extends AbstractLifecycleBean + { + @Override + protected void onBootstrap(ApplicationEvent event) + { + initContext(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } + } + + /** + * Initialise Repository Context + */ + protected void initContext() + { + TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork() + { + @SuppressWarnings("synthetic-access") + public Object doWork() throws Exception + { + List refs = searchService.selectNodes(nodeService.getRootNode(companyHomeStore), companyHomePath, null, namespaceService, false); + if (refs.size() != 1) + { + throw new IllegalStateException("Invalid company home path: " + companyHomePath + " - found: " + refs.size()); + } + companyHome = refs.get(0); + return null; + } + }); + } + + /** + * Gets the Company Home + * + * @return company home node ref + */ + public NodeRef getCompanyHome() + { + return companyHome; + } + + /** + * Gets the currently authenticated person + * + * @return person node ref + */ + public NodeRef getPerson() + { + NodeRef person = null; + String currentUserName = AuthenticationUtil.getCurrentUserName(); + if (personService.personExists(currentUserName)) + { + person = personService.getPerson(currentUserName); + } + return person; + } + + /** + * Gets the user home of the currently authenticated person + * + * @param person person + * @return user home of person + */ + public NodeRef getUserHome(NodeRef person) + { + return (NodeRef)nodeService.getProperty(person, ContentModel.PROP_HOMEFOLDER); + } + +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptDescription.java b/source/java/org/alfresco/web/scripts/WebScriptDescription.java new file mode 100644 index 0000000000..f7ab22f8a7 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptDescription.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + + +/** + * Web Script Description + * + * @author davidc + */ +public interface WebScriptDescription +{ + /** + * Enumeration of "required" Authentication level + */ + public enum RequiredAuthentication + { + none, + guest, + user + } + + /** + * Enumeration of "required" Transaction level + */ + public enum RequiredTransaction + { + none, + required, + requiresnew + } + + + /** + * Gets the source document location of this service description + * + * @return document location (path) + */ + public String getSourceLocation(); + + /** + * Gets the id of this service + * + * @return service id + */ + public String getId(); + + /** + * Gets the short name of this service + * + * @return service short name + */ + public String getShortName(); + + /** + * Gets the description of this service + */ + public String getDescription(); + + /** + * Gets the required authentication level for execution of this service + * + * @return the required authentication level + */ + public RequiredAuthentication getRequiredAuthentication(); + + /** + * Gets the required transaction level + * + * @return the required transaction level + */ + public RequiredTransaction getRequiredTransaction(); + + /** + * Gets the HTTP method this service is bound to + * + * @return HTTP method + */ + public String getMethod(); + + /** + * Gets the URIs this service supports + * + * @return array of URIs in order specified in service description document + */ + public URI[] getURIs(); + + /** + * Gets a URI by format + * + * @param format the format + * @return the URI (or null, if no URI registered for the format) + */ + public URI getURI(String format); + + /** + * Gets the default response format + * + * Note: the default response format is the first listed in the service + * description document + * + * @return default response format + */ + public String getDefaultFormat(); + + + /** + * Web Script URL + * + * @author davidc + */ + public interface URI + { + /** + * Gets the URI response format + * + * @return format + */ + public String getFormat(); + + /** + * Gets the URI + * + * @return uri + */ + public String getURI(); + } + +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptDescriptionImpl.java b/source/java/org/alfresco/web/scripts/WebScriptDescriptionImpl.java new file mode 100644 index 0000000000..4ec8ed5f2f --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptDescriptionImpl.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Implementation of a Web Script Description + * + * @author davidc + */ +public class WebScriptDescriptionImpl implements WebScriptDescription +{ + private String sourceLocation; + private String id; + private String shortName; + private String description; + private RequiredAuthentication requiredAuthentication; + private RequiredTransaction requiredTransaction; + private String httpMethod; + private URI[] uris; + private String defaultFormat; + private Map uriByFormat; + + + /** + * Sets the source location + * + * @param sourceLocation + */ + public void setSourceLocation(String sourceLocation) + { + this.sourceLocation = sourceLocation; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription#getSourceLocation() + */ + public String getSourceLocation() + { + return this.sourceLocation; + } + + /** + * Sets the service id + * + * @param id + */ + public void setId(String id) + { + this.id = id; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription#getId() + */ + public String getId() + { + return this.id; + } + + /** + * Sets the service short name + * + * @param shortName + */ + public void setShortName(String shortName) + { + this.shortName = shortName; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription#getShortName() + */ + public String getShortName() + { + return this.shortName; + } + + /** + * Sets the service description + * + * @param description + */ + public void setDescription(String description) + { + this.description = description; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription#getDescription() + */ + public String getDescription() + { + return this.description; + } + + /** + * Sets the required level of authentication + * + * @param requiredAuthentication + */ + public void setRequiredAuthentication(RequiredAuthentication requiredAuthentication) + { + this.requiredAuthentication = requiredAuthentication; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription#getRequiredAuthentication() + */ + public RequiredAuthentication getRequiredAuthentication() + { + return this.requiredAuthentication; + } + + /** + * Sets the required level of transaction + * + * @param requiredTransaction + */ + public void setRequiredTransaction(RequiredTransaction requiredTransaction) + { + this.requiredTransaction = requiredTransaction; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription#getRequiredTransaction() + */ + public RequiredTransaction getRequiredTransaction() + { + return this.requiredTransaction; + } + + /** + * Sets the service http method + * + * @param httpMethod + */ + public void setMethod(String httpMethod) + { + this.httpMethod = httpMethod; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription#getMethod() + */ + public String getMethod() + { + return this.httpMethod; + } + + /** + * Sets the service URIs + * + * @param uris + */ + public void setUris(URI[] uris) + { + this.uriByFormat = new HashMap(uris.length); + for (URI uri : uris) + { + this.uriByFormat.put(uri.getFormat(), uri); + } + this.uris = uris; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription#getURIs() + */ + public URI[] getURIs() + { + return this.uris; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription#getURI(java.lang.String) + */ + public URI getURI(String format) + { + return this.uriByFormat.get(format); + } + + /** + * Sets the default response format + * + * @param defaultFormat + */ + public void setDefaultFormat(String defaultFormat) + { + this.defaultFormat = defaultFormat; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription#getDefaultFormat() + */ + public String getDefaultFormat() + { + return this.defaultFormat; + } + + + /** + * Web Script URL Implementation + * + * @author davidc + */ + public static class URIImpl implements WebScriptDescription.URI + { + private String format; + private String uri; + + /** + * Sets the URI response format + * + * @param format + */ + public void setFormat(String format) + { + this.format = format; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription.URI#getFormat() + */ + public String getFormat() + { + return this.format; + } + + /** + * Sets the URI + * + * @param uri + */ + public void setUri(String uri) + { + this.uri = uri; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScriptDescription.URI#getURI() + */ + public String getURI() + { + return this.uri; + } + } + +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptException.java b/source/java/org/alfresco/web/scripts/WebScriptException.java new file mode 100644 index 0000000000..f7c336e226 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptException.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Web Script Exceptions. + * + * @author David Caruana + */ +public class WebScriptException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -7338963365877285084L; + + public WebScriptException(String msgId) + { + super(msgId); + } + + public WebScriptException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + public WebScriptException(String msgId, Object ... args) + { + super(msgId, args); + } + + public WebScriptException(String msgId, Throwable cause, Object ... args) + { + super(msgId, args, cause); + } +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptLogger.java b/source/java/org/alfresco/web/scripts/WebScriptLogger.java new file mode 100644 index 0000000000..46b2e05a51 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptLogger.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Web Script Logger + * + * @author davidc + */ +public class WebScriptLogger implements MethodInterceptor +{ + // Logger + private static final Log logger = LogFactory.getLog(WebScriptLogger.class); + + /* (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + public Object invoke(MethodInvocation invocation) + throws Throwable + { + Object retVal = null; + + if (logger.isDebugEnabled()) + { + WebScript service = (WebScript)invocation.getThis(); + WebScriptDescription description = service.getDescription(); + String user = AuthenticationUtil.getCurrentUserName(); + String locale = I18NUtil.getLocale().toString(); + logger.debug("Invoking Web Script " + description.getId() + (user == null ? " (unauthenticated)" : " (authenticated as " + user + ")" + " (" + locale + ")")); + long start = System.currentTimeMillis(); + retVal = invocation.proceed(); + long end = System.currentTimeMillis(); + logger.debug("Web Script " + description.getId() + " executed in " + (end - start) + "ms"); + } + else + { + retVal = invocation.proceed(); + } + + return retVal; + } + +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptMatch.java b/source/java/org/alfresco/web/scripts/WebScriptMatch.java new file mode 100644 index 0000000000..325368f568 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptMatch.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + + +/** + * Represents a URL to Web Script match + * + * @author davidc + */ +public interface WebScriptMatch +{ + + /** + * Gets the part of the request URL that matched the Web Script URL Template + * + * @return matching url path + */ + public String getPath(); + + /** + * Gets the matching web script + * + * @return service + */ + public WebScript getWebScript(); + +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptRegistry.java b/source/java/org/alfresco/web/scripts/WebScriptRegistry.java new file mode 100644 index 0000000000..7d859d2ccf --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptRegistry.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.util.Collection; + +import javax.servlet.ServletContext; + + +/** + * Web Scripts Registry + * + * @author davidc + */ +public interface WebScriptRegistry +{ + /** + * Gets all Web Scripts + * + * @return web scripts + */ + public Collection getWebScripts(); + + /** + * Gets a Web Script by Id + * + * @param id web script id + * @return web script + */ + public WebScript getWebScript(String id); + + /** + * Gets a Web Script given an HTTP Method and URI + * + * @param method http method + * @param uri uri + * @return service match (pair of service and uri that matched) + */ + public WebScriptMatch findWebScript(String method, String uri); + + /** + * Gets the Servlet Context + * + * @return servlet context + */ + public ServletContext getContext(); + + /** + * Gets the response format registry + * + * @return response format registry + */ + public FormatRegistry getFormatRegistry(); + + /** + * Gets the Template Processor + * + * @return template processor + */ + public TemplateProcessor getTemplateProcessor(); + + /** + * Gets the Script Processor + * + * @return script processor + */ + public ScriptProcessor getScriptProcessor(); + + /** + * Resets the Registry + */ + public void reset(); +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptRequest.java b/source/java/org/alfresco/web/scripts/WebScriptRequest.java new file mode 100644 index 0000000000..e85278a0c1 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptRequest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + + +/** + * Web Script Request + * + * @author davidc + */ +public class WebScriptRequest extends HttpServletRequestWrapper +{ + /** Service bound to this request */ + private WebScriptMatch serviceMatch; + + + /** + * Construct + * + * @param req + * @param serviceMatch + */ + WebScriptRequest(HttpServletRequest req, WebScriptMatch serviceMatch) + { + super(req); + this.serviceMatch = serviceMatch; + } + + /** + * Gets the matching API Service for this request + * + * @return the service match + */ + public WebScriptMatch getServiceMatch() + { + return serviceMatch; + } + + /** + * Get server portion of the request + * + * e.g. scheme://host:port + * + * @return server path + */ + public String getServerPath() + { + return getScheme() + "://" + getServerName() + ":" + getServerPort(); + } + + /** + * Gets the Alfresco Web Script Context Path + * + * @return service url e.g. /alfresco/service + */ + public String getServiceContextPath() + { + return getContextPath() + getServletPath(); + } + + /** + * Gets the Alfresco Service Path + * + * @return service url e.g. /alfresco/service/search/keyword + */ + public String getServicePath() + { + return getServiceContextPath() + getPathInfo(); + } + + /** + * Gets the full request URL + * + * @return request url e.g. /alfresco/service/search/keyword?q=term + */ + public String getURL() + { + return getServicePath() + (getQueryString() != null ? "?" + getQueryString() : ""); + } + + /** + * Gets the path extension beyond the path registered for this service + * + * e.g. + * a) service registered path = /search/engine + * b) request path = /search/engine/external + * + * => /external + * + * @return extension path + */ + public String getExtensionPath() + { + String servicePath = serviceMatch.getPath(); + String extensionPath = getPathInfo(); + int extIdx = extensionPath.indexOf(servicePath); + if (extIdx != -1) + { + int extLength = (servicePath.endsWith("/") ? servicePath.length() : servicePath.length() + 1); + extensionPath = extensionPath.substring(extIdx + extLength); + } + return extensionPath; + } + + /** + * Determine if Guest User? + * + * @return true => guest user + */ + public boolean isGuest() + { + return Boolean.valueOf(getParameter("guest")); + } + + /** + * Get Requested Format + * + * @return content type requested + */ + public String getFormat() + { + String format = getParameter("format"); + return (format == null || format.length() == 0) ? "" : format; + } + + /** + * Get User Agent + * + * TODO: Expand on known agents + * + * @return MSIE / Firefox + */ + public String getAgent() + { + String userAgent = getHeader("user-agent"); + if (userAgent != null) + { + if (userAgent.indexOf("Firefox/") != -1) + { + return "Firefox"; + } + else if (userAgent.indexOf("MSIE") != -1) + { + return "MSIE"; + } + } + return null; + } +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptResponse.java b/source/java/org/alfresco/web/scripts/WebScriptResponse.java new file mode 100644 index 0000000000..7866c05488 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptResponse.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +/** + * API Service Response + * + * @author davidc + */ +public class WebScriptResponse extends HttpServletResponseWrapper +{ + // API Formats + public static final String HTML_FORMAT = "html"; + public static final String ATOM_FORMAT = "atom"; + public static final String RSS_FORMAT = "rss"; + public static final String XML_FORMAT = "xml"; + public static final String JSON_FORMAT = "json"; + public static final String OPENSEARCH_DESCRIPTION_FORMAT = "opensearchdescription"; + + + /** + * Construct + * + * @param res + */ + WebScriptResponse(HttpServletResponse res) + { + super(res); + } + +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptServlet.java b/source/java/org/alfresco/web/scripts/WebScriptServlet.java new file mode 100644 index 0000000000..9f79609830 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptServlet.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + + +/** + * Entry point for Web Scripts + * + * @author davidc + */ +public class WebScriptServlet extends HttpServlet +{ + private static final long serialVersionUID = 4209892938069597860L; + + // Logger + private static final Log logger = LogFactory.getLog(WebScriptServlet.class); + + // Web Scripts + private DeclarativeWebScriptRegistry registry; + + + @Override + public void init() throws ServletException + { + super.init(); + ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + registry = (DeclarativeWebScriptRegistry)context.getBean("webscripts.registry"); + registry.initWebScripts(); + } + + +// TODO: +// - authentication (as suggested in http://www.xml.com/pub/a/2003/12/17/dive.html) + + + /* + * (non-Javadoc) + * + * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse) + */ + protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException + { + long start = System.currentTimeMillis(); + + try + { + // + // Execute appropriate web scripy + // + // TODO: Handle errors (with appropriate HTTP error responses) + + String uri = req.getPathInfo(); + + if (logger.isDebugEnabled()) + logger.debug("Processing request (" + req.getMethod() + ") " + req.getRequestURL() + (req.getQueryString() != null ? "?" + req.getQueryString() : "")); + + WebScriptMatch match = registry.findWebScript(req.getMethod(), uri); + if (match != null) + { + // setup web script context + WebScriptRequest apiReq = new WebScriptRequest(req, match); + WebScriptResponse apiRes = new WebScriptResponse(res); + + if (logger.isDebugEnabled()) + logger.debug("Agent: " + apiReq.getAgent()); + + // execute service + match.getWebScript().execute(apiReq, apiRes); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Request does not map to service."); + + res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + // TODO: add appropriate error detail + } + } + // TODO: exception handling + finally + { + long end = System.currentTimeMillis(); + if (logger.isDebugEnabled()) + logger.debug("Processed request (" + req.getMethod() + ") " + req.getRequestURL() + (req.getQueryString() != null ? "?" + req.getQueryString() : "") + " in " + (end - start) + "ms"); + } + } + +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptStorage.java b/source/java/org/alfresco/web/scripts/WebScriptStorage.java new file mode 100644 index 0000000000..930391c89d --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptStorage.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.alfresco.util.AbstractLifecycleBean; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; + +import freemarker.cache.MultiTemplateLoader; +import freemarker.cache.TemplateLoader; + + +/** + * Web Script Storage + * + * @author davidc + */ +public class WebScriptStorage implements ApplicationContextAware, ApplicationListener +{ + private ApplicationContext applicationContext; + private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); + private TemplateProcessor templateProcessor; + private ScriptProcessor scriptProcessor; + + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + this.lifecycle.setApplicationContext(applicationContext); + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + */ + public void onApplicationEvent(ApplicationEvent event) + { + lifecycle.onApplicationEvent(event); + } + + /** + * Sets the template processor + * + * @param templateProcessor + */ + public void setTemplateProcessor(TemplateProcessor templateProcessor) + { + this.templateProcessor = templateProcessor; + } + + /** + * Sets the script processor + * + * @param scriptProcessor + */ + public void setScriptProcessor(ScriptProcessor scriptProcessor) + { + this.scriptProcessor = scriptProcessor; + } + + /** + * Gets all Web Script Stores + * + * @return all Web Script Stores + */ + @SuppressWarnings("unchecked") + public Collection getStores() + { + return applicationContext.getBeansOfType(WebScriptStore.class, false, false).values(); + } + + /** + * Gets the Template Processor + * + * @return template processor + */ + public TemplateProcessor getTemplateProcessor() + { + return templateProcessor; + } + + /** + * Gets the Script Processor + * + * @return script processor + */ + public ScriptProcessor getScriptProcessor() + { + return scriptProcessor; + } + + /** + * Register template loader from each Web Script Store with Template Processor + */ + protected void initTemplateProcessor() + { + List loaders = new ArrayList(); + for (WebScriptStore apiStore : getStores()) + { + TemplateLoader loader = apiStore.getTemplateLoader(); + if (loader == null) + { + throw new WebScriptException("Unable to retrieve template loader for Web Script store " + apiStore.getBasePath()); + } + loaders.add(loader); + } + MultiTemplateLoader loader = new MultiTemplateLoader(loaders.toArray(new TemplateLoader[loaders.size()])); + templateProcessor.setTemplateLoader(loader); + } + + /** + * Register script loader from each Web Script Store with Script Processor + */ + protected void initScriptProcessor() + { + List loaders = new ArrayList(); + for (WebScriptStore apiStore : getStores()) + { + ScriptLoader loader = apiStore.getScriptLoader(); + if (loader == null) + { + throw new WebScriptException("Unable to retrieve script loader for Web Script store " + apiStore.getBasePath()); + } + loaders.add(loader); + } + MultiScriptLoader loader = new MultiScriptLoader(loaders.toArray(new ScriptLoader[loaders.size()])); + scriptProcessor.setScriptLoader(loader); + } + + /** + * Hooks into Spring Application Lifecycle + */ + private class ProcessorLifecycle extends AbstractLifecycleBean + { + @Override + protected void onBootstrap(ApplicationEvent event) + { + initTemplateProcessor(); + initScriptProcessor(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } + } + +} diff --git a/source/java/org/alfresco/web/scripts/WebScriptStore.java b/source/java/org/alfresco/web/scripts/WebScriptStore.java new file mode 100644 index 0000000000..ee2f24cbe0 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptStore.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import java.io.IOException; +import java.io.InputStream; + +import freemarker.cache.TemplateLoader; + + +/** + * Store for holding Web Script Definitions and Implementations + * + * @author davidc + */ +public interface WebScriptStore +{ + + /** + * Gets the base path of the store + * + * @return base path + */ + public String getBasePath(); + + /** + * Gets the paths of all Web Script description documents in this store + * + * @return array of description document paths + */ + public String[] getDescriptionDocumentPaths(); + + /** + * Gets a description document + * + * @param documentPath description document path + * @return input stream onto description document + * + * @throws IOException + */ + public InputStream getDescriptionDocument(String documentPath) + throws IOException; + + /** + * Gets the template loader for this store + * + * @return template loader + */ + public TemplateLoader getTemplateLoader(); + + /** + * Gets the script loader for this store + * + * @return script loader + */ + public ScriptLoader getScriptLoader(); + +} + diff --git a/source/java/org/alfresco/web/scripts/WebScriptTransaction.java b/source/java/org/alfresco/web/scripts/WebScriptTransaction.java new file mode 100644 index 0000000000..15fcdb6362 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/WebScriptTransaction.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts; + +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.web.scripts.WebScriptDescription.RequiredTransaction; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Web Script Transaction + * + * @author davidc + */ +public class WebScriptTransaction implements MethodInterceptor +{ + // Logger + protected static final Log logger = LogFactory.getLog(WebScriptTransaction.class); + + // dependencies + private TransactionService transactionService; + + + /** + * Sets the transaction service + * + * @param transactionService + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + + /* (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + public Object invoke(final MethodInvocation invocation) + throws Throwable + { + WebScript service = (WebScript)invocation.getThis(); + final WebScriptDescription description = service.getDescription(); + + // encapsulate service call within transaction + TransactionUtil.TransactionWork work = new TransactionUtil.TransactionWork() + { + public Object doWork() throws Throwable + { + if (logger.isDebugEnabled()) + logger.debug("Begin transaction: " + description.getRequiredTransaction()); + + Object retVal = invocation.proceed(); + + if (logger.isDebugEnabled()) + logger.debug("End transaction: " + description.getRequiredTransaction()); + + return retVal; + } + }; + + // execute call within transaction + Object retVal; + if (description.getRequiredTransaction() == RequiredTransaction.required) + { + retVal = TransactionUtil.executeInUserTransaction(transactionService, work); + } + else + { + retVal = TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, work); + } + return retVal; + } + +} diff --git a/source/java/org/alfresco/web/scripts/bean/Index.java b/source/java/org/alfresco/web/scripts/bean/Index.java new file mode 100644 index 0000000000..535384de3f --- /dev/null +++ b/source/java/org/alfresco/web/scripts/bean/Index.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts.bean; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.web.scripts.DeclarativeWebScript; +import org.alfresco.web.scripts.WebScriptRequest; +import org.alfresco.web.scripts.WebScriptResponse; + + +/** + * Retrieves the list of available Web APIs + * + * @author davidc + */ +public class Index extends DeclarativeWebScript +{ + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) + */ + @Override + protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + { + Map model = new HashMap(7, 1.0f); + model.put("webscripts", getWebScriptRegistry().getWebScripts()); + return model; + } + +} diff --git a/source/java/org/alfresco/web/scripts/bean/IndexUpdate.java b/source/java/org/alfresco/web/scripts/bean/IndexUpdate.java new file mode 100644 index 0000000000..a43ba464f8 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/bean/IndexUpdate.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts.bean; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.web.scripts.DeclarativeWebScript; +import org.alfresco.web.scripts.WebScriptRequest; +import org.alfresco.web.scripts.WebScriptResponse; + + +/** + * Retrieves the list of available Web APIs + * + * @author davidc + */ +public class IndexUpdate extends DeclarativeWebScript +{ + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) + */ + @Override + protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + { + List tasks = new ArrayList(); + + // reset index + String reset = req.getParameter("reset"); + if (reset != null && reset.equals("on")) + { + int previousCount = getWebScriptRegistry().getWebScripts().size(); + getWebScriptRegistry().reset(); + tasks.add("Reset Web Scripts Registry; found " + getWebScriptRegistry().getWebScripts().size() + " Web Scripts. Previously, there were " + previousCount + "."); + } + + // create model for rendering + Map model = new HashMap(7, 1.0f); + model.put("tasks", tasks); + model.put("webscripts", getWebScriptRegistry().getWebScripts()); + return model; + } + +} diff --git a/source/java/org/alfresco/web/scripts/bean/KeywordSearch.java b/source/java/org/alfresco/web/scripts/bean/KeywordSearch.java new file mode 100644 index 0000000000..696e116c48 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/bean/KeywordSearch.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts.bean; + +import java.io.StringWriter; +import java.io.Writer; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.alfresco.i18n.I18NUtil; +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.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.util.GUID; +import org.alfresco.util.ParameterCheck; +import org.alfresco.web.scripts.DeclarativeWebScript; +import org.alfresco.web.scripts.WebScriptException; +import org.alfresco.web.scripts.WebScriptRequest; +import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Alfresco Keyword (simple) Search Service + * + * @author davidc + */ +public class KeywordSearch extends DeclarativeWebScript +{ + // Logger + private static final Log logger = LogFactory.getLog(KeywordSearch.class); + + // search parameters + // TODO: allow configuration of search store + protected static final StoreRef SEARCH_STORE = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + protected static final int DEFAULT_ITEMS_PER_PAGE = 10; + protected static final String QUERY_FORMAT = "query_"; + + // dependencies + protected SearchService searchService; + + // icon resolver + protected TemplateImageResolver iconResolver = new TemplateImageResolver() + { + public String resolveImagePathForName(String filename, boolean small) + { + return Utils.getFileTypeImage(getWebScriptRegistry().getContext(), filename, small); + } + }; + + /** + * @param searchService + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) + */ + @Override + protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + { + // + // process arguments + // + + String searchTerms = req.getParameter("q"); + ParameterCheck.mandatoryString("q", searchTerms); + String startPageArg = req.getParameter("p"); + int startPage = 1; + try + { + startPage = new Integer(startPageArg); + } + catch(NumberFormatException e) + { + // NOTE: use default startPage + } + String itemsPerPageArg = req.getParameter("c"); + int itemsPerPage = DEFAULT_ITEMS_PER_PAGE; + try + { + itemsPerPage = new Integer(itemsPerPageArg); + } + catch(NumberFormatException e) + { + // NOTE: use default itemsPerPage + } + Locale locale = I18NUtil.getLocale(); + String language = req.getParameter("l"); + if (language != null && language.length() > 0) + { + // NOTE: Simple conversion from XML Language Id to Java Locale Id + locale = new Locale(language.replace("-", "_")); + } + + // + // execute the search + // + + SearchResult results = search(searchTerms, startPage, itemsPerPage, locale, req); + + // + // create model + // + + Map model = new HashMap(7, 1.0f); + model.put("search", results); + return model; + } + + /** + * Execute the search + * + * @param searchTerms + * @param startPage + * @return + */ + private SearchResult search(String searchTerms, int startPage, int itemsPerPage, Locale locale, WebScriptRequest req) + { + SearchResult searchResult = null; + ResultSet results = null; + + try + { + // construct search statement + String[] terms = searchTerms.split(" "); + Map statementModel = new HashMap(7, 1.0f); + statementModel.put("args", createArgModel(req)); + statementModel.put("terms", terms); + Writer queryWriter = new StringWriter(1024); + renderFormatTemplate(QUERY_FORMAT, statementModel, queryWriter); + String query = queryWriter.toString(); + + // execute query + if (logger.isDebugEnabled()) + { + logger.debug("Search parameters: searchTerms=" + searchTerms + ", startPage=" + startPage + ", itemsPerPage=" + itemsPerPage + ", search locale=" + locale.toString()); + logger.debug("Issuing lucene search: " + query); + } + + SearchParameters parameters = new SearchParameters(); + parameters.addStore(SEARCH_STORE); + parameters.setLanguage(SearchService.LANGUAGE_LUCENE); + parameters.setQuery(query); + if (locale != null) + { + parameters.addLocale(locale); + } + results = searchService.query(parameters); + int totalResults = results.length(); + + if (logger.isDebugEnabled()) + logger.debug("Results: " + totalResults + " rows (limited: " + results.getResultSetMetaData().getLimitedBy() + ")"); + + // are we out-of-range + int totalPages = (totalResults / itemsPerPage); + totalPages += (totalResults % itemsPerPage != 0) ? 1 : 0; + if (totalPages != 0 && (startPage < 1 || startPage > totalPages)) + { + throw new WebScriptException("Start page " + startPage + " is outside boundary of " + totalPages + " pages"); + } + + // construct search result + searchResult = new SearchResult(); + searchResult.setSearchTerms(searchTerms); + searchResult.setLocale(locale); + searchResult.setItemsPerPage(itemsPerPage); + searchResult.setStartPage(startPage); + searchResult.setTotalPages(totalPages); + searchResult.setTotalResults(totalResults); + searchResult.setStartIndex(((startPage -1) * itemsPerPage) + 1); + searchResult.setTotalPageItems(Math.min(itemsPerPage, totalResults - searchResult.getStartIndex() + 1)); + SearchTemplateNode[] nodes = new SearchTemplateNode[searchResult.getTotalPageItems()]; + for (int i = 0; i < searchResult.getTotalPageItems(); i++) + { + NodeRef node = results.getNodeRef(i + searchResult.getStartIndex() - 1); + float score = results.getScore(i + searchResult.getStartIndex() - 1); + nodes[i] = new SearchTemplateNode(node, score); + } + searchResult.setResults(nodes); + return searchResult; + } + finally + { + if (results != null) + { + results.close(); + } + } + } + + /** + * Search Result + * + * @author davidc + */ + public static class SearchResult + { + private String id; + private String searchTerms; + private Locale locale; + private int itemsPerPage; + private int totalPages; + private int totalResults; + private int totalPageItems; + private int startPage; + private int startIndex; + private SearchTemplateNode[] results; + + + public int getItemsPerPage() + { + return itemsPerPage; + } + + /*package*/ void setItemsPerPage(int itemsPerPage) + { + this.itemsPerPage = itemsPerPage; + } + + public TemplateNode[] getResults() + { + return results; + } + + /*package*/ void setResults(SearchTemplateNode[] results) + { + this.results = results; + } + + public int getStartIndex() + { + return startIndex; + } + + /*package*/ void setStartIndex(int startIndex) + { + this.startIndex = startIndex; + } + + public int getStartPage() + { + return startPage; + } + + /*package*/ void setStartPage(int startPage) + { + this.startPage = startPage; + } + + public int getTotalPageItems() + { + return totalPageItems; + } + + /*package*/ void setTotalPageItems(int totalPageItems) + { + this.totalPageItems = totalPageItems; + } + + public int getTotalPages() + { + return totalPages; + } + + /*package*/ void setTotalPages(int totalPages) + { + this.totalPages = totalPages; + } + + public int getTotalResults() + { + return totalResults; + } + + /*package*/ void setTotalResults(int totalResults) + { + this.totalResults = totalResults; + } + + public String getSearchTerms() + { + return searchTerms; + } + + /*package*/ void setSearchTerms(String searchTerms) + { + this.searchTerms = searchTerms; + } + + public Locale getLocale() + { + return locale; + } + + /** + * @return XML 1.0 Language Identification + */ + public String getLocaleId() + { + return locale.toString().replace('_', '-'); + } + + /*package*/ void setLocale(Locale locale) + { + this.locale = locale; + } + + public String getId() + { + if (id == null) + { + id = GUID.generate(); + } + return id; + } + } + + /** + * Search result row template node + */ + public class SearchTemplateNode extends TemplateNode + { + private static final long serialVersionUID = -1791913270786140012L; + private float score; + + /** + * Construct + * + * @param nodeRef + * @param score + */ + public SearchTemplateNode(NodeRef nodeRef, float score) + { + super(nodeRef, getServiceRegistry(), iconResolver); + this.score = score; + } + + /** + * Gets the result row score + * + * @return score + */ + public float getScore() + { + return score; + } + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/web/scripts/bean/SearchEngines.java b/source/java/org/alfresco/web/scripts/bean/SearchEngines.java new file mode 100644 index 0000000000..cb013c3e29 --- /dev/null +++ b/source/java/org/alfresco/web/scripts/bean/SearchEngines.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts.bean; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigService; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.web.config.OpenSearchConfigElement; +import org.alfresco.web.scripts.DeclarativeWebScript; +import org.alfresco.web.scripts.WebScriptRequest; +import org.alfresco.web.scripts.WebScriptResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * List of (server-side) registered Search Engines + * + * @author davidc + */ +public class SearchEngines extends DeclarativeWebScript +{ + // url argument values + public static final String URL_ARG_DESCRIPTION = "description"; + public static final String URL_ARG_TEMPLATE = "template"; + public static final String URL_ARG_ALL = "all"; + + // Logger + private static final Log logger = LogFactory.getLog(SearchEngines.class); + + // dependencies + protected ConfigService configService; + protected SearchProxy searchProxy; + + /** + * @param configService + */ + public void setConfigService(ConfigService configService) + { + this.configService = configService; + } + + /** + * @param searchProxy + */ + public void setSearchProxy(SearchProxy searchProxy) + { + this.searchProxy = searchProxy; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) + */ + @Override + protected Map executeImpl(WebScriptRequest req, WebScriptResponse res) + { + String urlType = req.getParameter("type"); + if (urlType == null || urlType.length() == 0) + { + urlType = URL_ARG_DESCRIPTION; + } + else if (!urlType.equals(URL_ARG_DESCRIPTION) && !urlType.equals(URL_ARG_TEMPLATE) && !urlType.equals(URL_ARG_ALL)) + { + urlType = URL_ARG_DESCRIPTION; + } + + // + // retrieve open search engines configuration + // + + Set urls = getUrls(urlType); + Map model = new HashMap(7, 1.0f); + model.put("urltype", urlType); + model.put("engines", urls); + return model; + } + + /** + * Retrieve registered search engines + * + * @return set of search engines + */ + private Set getUrls(String urlType) + { + if (logger.isDebugEnabled()) + logger.debug("Search Engine parameters: urltype=" + urlType); + + Set urls = new HashSet(); + Config config = configService.getConfig("OpenSearch"); + + OpenSearchConfigElement searchConfig = (OpenSearchConfigElement)config.getConfigElement(OpenSearchConfigElement.CONFIG_ELEMENT_ID); + for (OpenSearchConfigElement.EngineConfig engineConfig : searchConfig.getEngines()) + { + Map engineUrls = engineConfig.getUrls(); + for (Map.Entry engineUrl : engineUrls.entrySet()) + { + String type = engineUrl.getKey(); + String url = searchProxy.createUrl(engineConfig, type); + + if ((urlType.equals(URL_ARG_ALL)) || + (urlType.equals(URL_ARG_DESCRIPTION) && type.equals(MimetypeMap.MIMETYPE_OPENSEARCH_DESCRIPTION)) || + (urlType.equals(URL_ARG_TEMPLATE) && !type.equals(MimetypeMap.MIMETYPE_OPENSEARCH_DESCRIPTION))) + { + String label = engineConfig.getLabel(); + String labelId = engineConfig.getLabelId(); + if (labelId != null && labelId.length() > 0) + { + String i18nLabel = I18NUtil.getMessage(labelId); + if (i18nLabel == null && label == null) + { + label = (i18nLabel == null) ? "$$" + labelId + "$$" : i18nLabel; + } + } + urls.add(new UrlTemplate(label, type, url)); + } + + // TODO: Extract URL templates from OpenSearch description + else if (urlType.equals(URL_ARG_TEMPLATE) && + type.equals(MimetypeMap.MIMETYPE_OPENSEARCH_DESCRIPTION)) + { + } + } + } + + if (logger.isDebugEnabled()) + logger.debug("Retrieved " + urls.size() + " engine registrations"); + + return urls; + } + + /** + * Model object for representing a registered search engine + */ + public static class UrlTemplate + { + private String type; + private String label; + private String url; + private UrlTemplate engine; + + public UrlTemplate(String label, String type, String url) + { + this.label = label; + this.type = type; + this.url = url; + this.engine = null; + } + + public UrlTemplate(String label, String type, String url, UrlTemplate engine) + { + this(label, type, url); + this.engine = engine; + } + + public String getLabel() + { + return label; + } + + public String getType() + { + return type; + } + + public String getUrl() + { + return url; + } + + public String getUrlType() + { + return (type.equals(MimetypeMap.MIMETYPE_OPENSEARCH_DESCRIPTION) ? "description" : "template"); + } + + public UrlTemplate getEngine() + { + return engine; + } + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/web/scripts/bean/SearchProxy.java b/source/java/org/alfresco/web/scripts/bean/SearchProxy.java new file mode 100644 index 0000000000..5bbf068d5f --- /dev/null +++ b/source/java/org/alfresco/web/scripts/bean/SearchProxy.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.scripts.bean; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigService; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.web.app.servlet.HTTPProxy; +import org.alfresco.web.config.OpenSearchConfigElement; +import org.alfresco.web.config.OpenSearchConfigElement.EngineConfig; +import org.alfresco.web.config.OpenSearchConfigElement.ProxyConfig; +import org.alfresco.web.scripts.AbstractWebScript; +import org.alfresco.web.scripts.FormatRegistry; +import org.alfresco.web.scripts.WebScriptException; +import org.alfresco.web.scripts.WebScriptRequest; +import org.alfresco.web.scripts.WebScriptResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.Attribute; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.XPath; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.SAXReader; +import org.dom4j.io.XMLWriter; +import org.springframework.beans.factory.InitializingBean; + + +/** + * Alfresco OpenSearch Proxy Service + * + * Provides the ability to submit a request to a registered search engine + * via the Alfresco server. + * + * @author davidc + */ +public class SearchProxy extends AbstractWebScript implements InitializingBean +{ + // Logger + private static final Log logger = LogFactory.getLog(SearchProxy.class); + + // dependencies + protected FormatRegistry formatRegistry; + protected ConfigService configService; + protected OpenSearchConfigElement searchConfig; + protected String proxyPath; + + /** + * @param formatRegistry + */ + public void setFormatRegistry(FormatRegistry formatRegistry) + { + this.formatRegistry = formatRegistry; + } + + /** + * @param configService + */ + public void setConfigService(ConfigService configService) + { + this.configService = configService; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + Config config = configService.getConfig("OpenSearch"); + searchConfig = (OpenSearchConfigElement)config.getConfigElement(OpenSearchConfigElement.CONFIG_ELEMENT_ID); + if (searchConfig == null) + { + throw new WebScriptException("OpenSearch configuration not found"); + } + ProxyConfig proxyConfig = searchConfig.getProxy(); + if (proxyConfig == null) + { + throw new WebScriptException("OpenSearch proxy configuration not found"); + } + proxyPath = proxyConfig.getUrl(); + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.WebScript#execute(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) + */ + public void execute(WebScriptRequest req, WebScriptResponse res) + throws IOException + { + String extensionPath = req.getExtensionPath(); + String[] extensionPaths = extensionPath.split("/"); + if (extensionPaths.length != 2) + { + throw new WebScriptException("OpenSearch engine has not been specified as /{engine}/{format}"); + } + + // retrieve search engine configuration + String engine = extensionPaths[0]; + EngineConfig engineConfig = searchConfig.getEngine(engine); + if (engineConfig == null) + { + throw new WebScriptException("OpenSearch engine '" + engine + "' does not exist"); + } + + // retrieve engine url as specified by format + String format = extensionPaths[1]; + String mimetype = formatRegistry.getMimeType(null, format); + if (mimetype == null) + { + throw new WebScriptException("Format '" + format + "' does not map to a registered mimetype"); + } + Map engineUrls = engineConfig.getUrls(); + String engineUrl = engineUrls.get(mimetype); + if (engineUrl == null) + { + throw new WebScriptException("Url mimetype '" + mimetype + "' does not exist for engine '" + engine + "'"); + } + + // replace template url arguments with actual arguments specified on request + int engineUrlArgIdx = engineUrl.indexOf("?"); + if (engineUrlArgIdx != -1) + { + engineUrl = engineUrl.substring(0, engineUrlArgIdx); + } + if (req.getQueryString() != null) + { + engineUrl += "?" + req.getQueryString(); + } + + if (logger.isDebugEnabled()) + logger.debug("Mapping engine '" + engine + "' (mimetype '" + mimetype + "') to url '" + engineUrl + "'"); + + // issue request against search engine + SearchEngineHttpProxy proxy = new SearchEngineHttpProxy(req.getServicePath() + "/" + req.getContextPath(), engine, engineUrl, res); + proxy.service(); + } + + /** + * OpenSearch HTTPProxy + * + * This proxy remaps OpenSearch links (e.g. previous, next) found in search results. + * + * @author davidc + */ + private class SearchEngineHttpProxy extends HTTPProxy + { + private final static String ATOM_NS_URI = "http://www.w3.org/2005/Atom"; + private final static String ATOM_NS_PREFIX = "atom"; + private final static String ATOM_LINK_XPATH = "atom:link[@rel=\"first\" or @rel=\"last\" or @rel=\"next\" or @rel=\"previous\" or @rel=\"self\" or @rel=\"alternate\"]"; + private String engine; + private String rootPath; + + /** + * Construct + * + * @param requestUrl + * @param response + * @throws MalformedURLException + */ + public SearchEngineHttpProxy(String rootPath, String engine, String engineUrl, HttpServletResponse response) + throws MalformedURLException + { + super(engineUrl.startsWith("/") ? rootPath + engineUrl : engineUrl, response); + this.engine = engine; + this.rootPath = rootPath; + } + + /* (non-Javadoc) + * @see org.alfresco.web.app.servlet.HTTPProxy#writeResponse(java.io.InputStream, java.io.OutputStream) + */ + @Override + protected void writeResponse(InputStream input, OutputStream output) + throws IOException + { + if (response.getContentType().startsWith(MimetypeMap.MIMETYPE_ATOM) || + response.getContentType().startsWith(MimetypeMap.MIMETYPE_RSS)) + { + // Only post-process ATOM and RSS feeds + // Replace all navigation links with "proxied" versions + SAXReader reader = new SAXReader(); + try + { + Document document = reader.read(input); + Element rootElement = document.getRootElement(); + + XPath xpath = rootElement.createXPath(ATOM_LINK_XPATH); + Map uris = new HashMap(); + uris.put(ATOM_NS_PREFIX, ATOM_NS_URI); + xpath.setNamespaceURIs(uris); + + List nodes = xpath.selectNodes(rootElement); + Iterator iter = nodes.iterator(); + while (iter.hasNext()) + { + Element element = (Element)iter.next(); + Attribute hrefAttr = element.attribute("href"); + String mimetype = element.attributeValue("type"); + if (mimetype == null || mimetype.length() == 0) + { + mimetype = MimetypeMap.MIMETYPE_HTML; + } + String url = createUrl(engine, hrefAttr.getValue(), mimetype); + if (url.startsWith("/")) + { + url = rootPath + url; + } + hrefAttr.setValue(url); + } + + OutputFormat outputFormat = OutputFormat.createPrettyPrint(); + XMLWriter writer = new XMLWriter(output, outputFormat); + writer.write(rootElement); + writer.flush(); + } + catch(DocumentException e) + { + throw new IOException(e.toString()); + } + } + else + { + super.writeResponse(input, output); + } + } + } + + /** + * Construct a "proxied" search engine url + * + * @param engine engine name (as identified by ) + * @param mimetype url to proxy (as identified by mimetype) + * @return "proxied" url + */ + public String createUrl(OpenSearchConfigElement.EngineConfig engine, String mimetype) + { + Map urls = engine.getUrls(); + String url = urls.get(mimetype); + if (url != null) + { + String proxy = engine.getProxy(); + if (proxy != null && !mimetype.equals(MimetypeMap.MIMETYPE_OPENSEARCH_DESCRIPTION)) + { + url = createUrl(proxy, url, mimetype); + } + } + return url; + } + + /** + * Construct a "proxied" search engine url + * + * @param engine engine name (as identified by ) + * @param url engine url + * @param mimetype mimetype of url + * @return "proxied" url + */ + public String createUrl(String engine, String url, String mimetype) + { + String format = formatRegistry.getFormat(null, mimetype); + if (format == null) + { + throw new WebScriptException("Mimetype '" + mimetype + "' is not registered."); + } + + String proxyUrl = null; + int argIdx = url.indexOf("?"); + if (argIdx == -1) + { + proxyUrl = proxyPath + "/" + engine + "/" + format; + } + else + { + proxyUrl = proxyPath + "/" + engine + "/" + format + url.substring(argIdx); + } + return proxyUrl; + } + +} \ No newline at end of file