From b8b6e0043e8091127c6f90727fc99a3941ae18c3 Mon Sep 17 00:00:00 2001 From: David Caruana Date: Thu, 8 Mar 2007 14:46:22 +0000 Subject: [PATCH] Merge Web Scripts from BRANCHES/DEV/DAVE git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5351 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../web/api/framework/APIContextAware.java | 44 ++ .../web/api/framework/APIDescription.java | 151 +++++ .../web/api/framework/APIDescriptionImpl.java | 273 +++++++++ .../web/api/framework/APIException.java | 57 ++ .../web/api/framework/APIRegistry.java | 90 +++ .../api/framework/APIRepositoryContext.java | 230 +++++++ .../web/api/framework/APIRequest.java | 174 ++++++ .../web/api/framework/APIResponse.java | 56 ++ .../web/api/framework/APIScriptProcessor.java | 111 ++++ .../web/api/framework/APIService.java | 53 ++ .../web/api/framework/APIServiceMatch.java | 50 ++ .../web/api/framework/APIServlet.java | 144 +++++ .../alfresco/web/api/framework/APIStore.java | 81 +++ .../alfresco/web/api/framework/APIStores.java | 179 ++++++ .../api/framework/APITemplateProcessor.java | 150 +++++ .../web/api/framework/AbstractAPIService.java | 320 ++++++++++ .../framework/AlfWebClientAuthenticator.java | 172 ++++++ .../web/api/framework/BasicAuthenticator.java | 194 ++++++ .../web/api/framework/ClassPathAPIStore.java | 238 ++++++++ .../api/framework/DeclarativeAPIRegistry.java | 562 ++++++++++++++++++ .../alfresco/web/api/framework/FormatMap.java | 83 +++ .../web/api/framework/FormatRegistry.java | 158 +++++ .../web/api/framework/MultiScriptLoader.java | 66 ++ .../alfresco/web/api/framework/PathModel.java | 119 ++++ .../web/api/framework/ScriptLoader.java | 46 ++ .../web/api/framework/ScriptedAPIService.java | 222 +++++++ .../web/api/framework/ServerModel.java | 169 ++++++ .../web/api/framework/ServiceLogger.java | 73 +++ .../web/api/framework/ServiceTransaction.java | 100 ++++ .../api/framework/SystemAuthenticator.java | 94 +++ .../web/api/framework/TestAPIServer.java | 311 ++++++++++ .../api/framework/TrustedAuthenticator.java | 91 +++ 32 files changed, 4861 insertions(+) create mode 100644 source/java/org/alfresco/web/api/framework/APIContextAware.java create mode 100644 source/java/org/alfresco/web/api/framework/APIDescription.java create mode 100644 source/java/org/alfresco/web/api/framework/APIDescriptionImpl.java create mode 100644 source/java/org/alfresco/web/api/framework/APIException.java create mode 100644 source/java/org/alfresco/web/api/framework/APIRegistry.java create mode 100644 source/java/org/alfresco/web/api/framework/APIRepositoryContext.java create mode 100644 source/java/org/alfresco/web/api/framework/APIRequest.java create mode 100644 source/java/org/alfresco/web/api/framework/APIResponse.java create mode 100644 source/java/org/alfresco/web/api/framework/APIScriptProcessor.java create mode 100644 source/java/org/alfresco/web/api/framework/APIService.java create mode 100644 source/java/org/alfresco/web/api/framework/APIServiceMatch.java create mode 100644 source/java/org/alfresco/web/api/framework/APIServlet.java create mode 100644 source/java/org/alfresco/web/api/framework/APIStore.java create mode 100644 source/java/org/alfresco/web/api/framework/APIStores.java create mode 100644 source/java/org/alfresco/web/api/framework/APITemplateProcessor.java create mode 100644 source/java/org/alfresco/web/api/framework/AbstractAPIService.java create mode 100644 source/java/org/alfresco/web/api/framework/AlfWebClientAuthenticator.java create mode 100644 source/java/org/alfresco/web/api/framework/BasicAuthenticator.java create mode 100644 source/java/org/alfresco/web/api/framework/ClassPathAPIStore.java create mode 100644 source/java/org/alfresco/web/api/framework/DeclarativeAPIRegistry.java create mode 100644 source/java/org/alfresco/web/api/framework/FormatMap.java create mode 100644 source/java/org/alfresco/web/api/framework/FormatRegistry.java create mode 100644 source/java/org/alfresco/web/api/framework/MultiScriptLoader.java create mode 100644 source/java/org/alfresco/web/api/framework/PathModel.java create mode 100644 source/java/org/alfresco/web/api/framework/ScriptLoader.java create mode 100644 source/java/org/alfresco/web/api/framework/ScriptedAPIService.java create mode 100644 source/java/org/alfresco/web/api/framework/ServerModel.java create mode 100644 source/java/org/alfresco/web/api/framework/ServiceLogger.java create mode 100644 source/java/org/alfresco/web/api/framework/ServiceTransaction.java create mode 100644 source/java/org/alfresco/web/api/framework/SystemAuthenticator.java create mode 100644 source/java/org/alfresco/web/api/framework/TestAPIServer.java create mode 100644 source/java/org/alfresco/web/api/framework/TrustedAuthenticator.java diff --git a/source/java/org/alfresco/web/api/framework/APIContextAware.java b/source/java/org/alfresco/web/api/framework/APIContextAware.java new file mode 100644 index 0000000000..12d33062d6 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIContextAware.java @@ -0,0 +1,44 @@ +/* + * 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.api.framework; + +import javax.servlet.ServletContext; + +/** + * Interface for marking that a service is API Context aware + * + * @author davidc + * + */ +public interface APIContextAware +{ + + /** + * Sets the API Context + * + * @param context api context + */ + public void setAPIContext(ServletContext context); +} diff --git a/source/java/org/alfresco/web/api/framework/APIDescription.java b/source/java/org/alfresco/web/api/framework/APIDescription.java new file mode 100644 index 0000000000..456cdfe1f4 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIDescription.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.api.framework; + + +/** + * API Service Description + * + * @author davidc + */ +public interface APIDescription +{ + /** + * 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(); + + + /** + * API Service 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/api/framework/APIDescriptionImpl.java b/source/java/org/alfresco/web/api/framework/APIDescriptionImpl.java new file mode 100644 index 0000000000..8988a2e4fd --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIDescriptionImpl.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.api.framework; + +import java.util.HashMap; +import java.util.Map; + + +/** + * API Description Implementation + * + * @author davidc + */ +public class APIDescriptionImpl implements APIDescription +{ + 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.api.APIDescription#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.api.APIDescription#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.api.APIDescription#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.api.APIDescription#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.api.APIDescription#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.api.APIDescription#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.api.APIDescription#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.api.APIDescription#getURIs() + */ + public URI[] getURIs() + { + return this.uris; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIDescription#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.api.APIDescription#getDefaultFormat() + */ + public String getDefaultFormat() + { + return this.defaultFormat; + } + + + /** + * API Description URL Implementation + * + * @author davidc + */ + public static class URIImpl implements APIDescription.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.api.APIDescription.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.api.APIDescription.URI#getURI() + */ + public String getURI() + { + return this.uri; + } + } + +} diff --git a/source/java/org/alfresco/web/api/framework/APIException.java b/source/java/org/alfresco/web/api/framework/APIException.java new file mode 100644 index 0000000000..b9a374780d --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIException.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.api.framework; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * API Service Exceptions. + * + * @author David Caruana + */ +public class APIException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -7338963365877285084L; + + public APIException(String msgId) + { + super(msgId); + } + + public APIException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + public APIException(String msgId, Object ... args) + { + super(msgId, args); + } + + public APIException(String msgId, Throwable cause, Object ... args) + { + super(msgId, args, cause); + } +} diff --git a/source/java/org/alfresco/web/api/framework/APIRegistry.java b/source/java/org/alfresco/web/api/framework/APIRegistry.java new file mode 100644 index 0000000000..dc3ca88467 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIRegistry.java @@ -0,0 +1,90 @@ +/* + * 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.api.framework; + +import java.util.Collection; + +import javax.servlet.ServletContext; + + +/** + * API Registry + * + * @author davidc + */ +public interface APIRegistry +{ + /** + * Gets all API Services + * + * @return services + */ + public Collection getServices(); + + /** + * Gets an API Service by Id + * + * @param id service id + * @return service + */ + public APIService getService(String id); + + /** + * Gets an API Service given an HTTP Method and URI + * + * @param method http method + * @param uri uri + * @return service match (pair of service and uri that matched) + */ + public APIServiceMatch findService(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 API Service Template Processor + * + * @return template processor + */ + public APITemplateProcessor getTemplateProcessor(); + + /** + * Gets the API Service Script Processor + * + * @return script processor + */ + public APIScriptProcessor getScriptProcessor(); +} diff --git a/source/java/org/alfresco/web/api/framework/APIRepositoryContext.java b/source/java/org/alfresco/web/api/framework/APIRepositoryContext.java new file mode 100644 index 0000000000..5f1937971d --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIRepositoryContext.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.api.framework; + +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 APIRepositoryContext 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/api/framework/APIRequest.java b/source/java/org/alfresco/web/api/framework/APIRequest.java new file mode 100644 index 0000000000..ff3a5974ca --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIRequest.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.api.framework; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; + + +/** + * API Service Request + * + * @author davidc + */ +public class APIRequest extends HttpServletRequestWrapper +{ + /** Service bound to this request */ + private APIServiceMatch serviceMatch; + + + /** + * Construct + * + * @param req + * @param serviceMatch + */ + /*package*/ APIRequest(HttpServletRequest req, APIServiceMatch serviceMatch) + { + super(req); + this.serviceMatch = serviceMatch; + } + + /** + * Gets the matching API Service for this request + * + * @return the service match + */ + public APIServiceMatch getServiceMatch() + { + return serviceMatch; + } + + /** + * Gets the Alfresco Context URL + * + * @return context url e.g. http://localhost:port/alfresco + */ + public String getPath() + { + return getScheme() + "://" + getServerName() + ":" + getServerPort() + getContextPath(); + } + + /** + * Gets the Alfresco Service URL + * + * @return service url e.g. http://localhost:port/alfresco/service + */ + public String getServicePath() + { + return getPath() + getServletPath(); + } + + /** + * 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; + } + + /** + * Gets the full request URL + * + * @return request url e.g. http://localhost:port/alfresco/service/keyword?q=term + */ + public String getUrl() + { + return getScheme() + "://" + getServerName() + ":" + getServerPort() + getPathInfo() + (getQueryString() != null ? "?" + getQueryString() : ""); + } + + /** + * Gets the currently authenticated username + * + * @return username + */ + public String getAuthenticatedUsername() + { + return AuthenticationUtil.getCurrentUserName(); + } + + /** + * 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/api/framework/APIResponse.java b/source/java/org/alfresco/web/api/framework/APIResponse.java new file mode 100644 index 0000000000..9fcabe9528 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIResponse.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.api.framework; + +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +/** + * API Service Response + * + * @author davidc + */ +public class APIResponse 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 + */ + /*package*/ APIResponse(HttpServletResponse res) + { + super(res); + } + +} diff --git a/source/java/org/alfresco/web/api/framework/APIScriptProcessor.java b/source/java/org/alfresco/web/api/framework/APIScriptProcessor.java new file mode 100644 index 0000000000..634de132d0 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIScriptProcessor.java @@ -0,0 +1,111 @@ +/* + * 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.api.framework; + +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 API + * + * @author davidc + */ +public class APIScriptProcessor +{ + // dependencies + private ScriptService scriptService; + + // api store 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 API 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 api stores + ScriptLocation scriptLocation = findScript(path); + if (scriptLocation == null) + { + throw new APIException("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); + } + +} diff --git a/source/java/org/alfresco/web/api/framework/APIService.java b/source/java/org/alfresco/web/api/framework/APIService.java new file mode 100644 index 0000000000..7ed57fbeb8 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIService.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.api.framework; + +import java.io.IOException; + +/** + * API Service + * + * @author davidc + */ +public interface APIService +{ + /** + * Gets the Service Description + * + * @return service description + */ + public APIDescription getDescription(); + + /** + * Execute service + * + * @param req + * @param res + * @throws IOException + */ + public void execute(APIRequest req, APIResponse res) + throws IOException; + +} diff --git a/source/java/org/alfresco/web/api/framework/APIServiceMatch.java b/source/java/org/alfresco/web/api/framework/APIServiceMatch.java new file mode 100644 index 0000000000..6c7081a4cd --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIServiceMatch.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.api.framework; + + +/** + * Represents a service to uri match + * + * @author davidc + */ +public interface APIServiceMatch +{ + + /** + * Gets the uri path that matched to the service + * + * @return matching uri path + */ + public String getPath(); + + /** + * Gets the matching service + * + * @return service + */ + public APIService getService(); + +} diff --git a/source/java/org/alfresco/web/api/framework/APIServlet.java b/source/java/org/alfresco/web/api/framework/APIServlet.java new file mode 100644 index 0000000000..62efcebd23 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIServlet.java @@ -0,0 +1,144 @@ +/* + * 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.api.framework; + +import java.io.IOException; +import java.util.Map; + +import javax.servlet.ServletContext; +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 based services (REST style) + * + * @author davidc + */ +public class APIServlet extends HttpServlet +{ + private static final long serialVersionUID = 4209892938069597860L; + + // Logger + private static final Log logger = LogFactory.getLog(APIServlet.class); + + // API Services + private DeclarativeAPIRegistry apiServiceRegistry; + + + @Override + public void init() throws ServletException + { + super.init(); + + // Retrieve all web api services and index by http url & http method + ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + initContext(context); + apiServiceRegistry = (DeclarativeAPIRegistry)context.getBean("web.api.framework.registry"); + apiServiceRegistry.initServices(); + } + + +// 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 service + // + // 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() : "")); + + APIServiceMatch match = apiServiceRegistry.findService(req.getMethod(), uri); + if (match != null) + { + // setup service context + APIRequest apiReq = new APIRequest(req, match); + APIResponse apiRes = new APIResponse(res); + + if (logger.isDebugEnabled()) + logger.debug("Agent: " + apiReq.getAgent()); + + // execute service + match.getService().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"); + } + } + + /** + * Initialise any API beans that require a servlet context + * + * @param appContext application context + */ + @SuppressWarnings("unchecked") + private void initContext(ApplicationContext appContext) + { + ServletContext servletContext = getServletContext(); + Map contextAwareMap = appContext.getBeansOfType(APIContextAware.class, false, false); + for (APIContextAware contextAware: contextAwareMap.values()) + { + contextAware.setAPIContext(servletContext); + } + } + +} diff --git a/source/java/org/alfresco/web/api/framework/APIStore.java b/source/java/org/alfresco/web/api/framework/APIStore.java new file mode 100644 index 0000000000..de289cdae4 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIStore.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.api.framework; + +import java.io.IOException; +import java.io.InputStream; + +import freemarker.cache.TemplateLoader; + + +/** + * Store for holding API Service Definitions and Implementations + * + * @author davidc + */ +public interface APIStore +{ + + /** + * Gets the base path of the store + * + * @return base path + */ + public String getBasePath(); + + /** + * Gets the paths of all API Service 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/api/framework/APIStores.java b/source/java/org/alfresco/web/api/framework/APIStores.java new file mode 100644 index 0000000000..37cc173877 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APIStores.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.api.framework; + +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; + + +/** + * API Store Access + * + * @author davidc + */ +public class APIStores implements ApplicationContextAware, ApplicationListener +{ + private ApplicationContext applicationContext; + private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); + private APITemplateProcessor templateProcessor; + private APIScriptProcessor 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(APITemplateProcessor templateProcessor) + { + this.templateProcessor = templateProcessor; + } + + /** + * Sets the script processor + * + * @param scriptProcessor + */ + public void setScriptProcessor(APIScriptProcessor scriptProcessor) + { + this.scriptProcessor = scriptProcessor; + } + + /** + * Gets all API Stores + * + * @return all API Stores + */ + @SuppressWarnings("unchecked") + public Collection getAPIStores() + { + return applicationContext.getBeansOfType(APIStore.class, false, false).values(); + } + + /** + * Gets the Template Processor + * + * @return template processor + */ + public APITemplateProcessor getTemplateProcessor() + { + return templateProcessor; + } + + /** + * Gets the Script Processor + * + * @return script processor + */ + public APIScriptProcessor getScriptProcessor() + { + return scriptProcessor; + } + + /** + * Register template loader from each API Store with Template Processor + */ + protected void initTemplateProcessor() + { + List loaders = new ArrayList(); + for (APIStore apiStore : getAPIStores()) + { + TemplateLoader loader = apiStore.getTemplateLoader(); + if (loader == null) + { + throw new APIException("Unable to retrieve template loader for api store " + apiStore.getBasePath()); + } + loaders.add(loader); + } + MultiTemplateLoader loader = new MultiTemplateLoader(loaders.toArray(new TemplateLoader[loaders.size()])); + templateProcessor.setTemplateLoader(loader); + } + + /** + * Register script loader from each API Store with Script Processor + */ + protected void initScriptProcessor() + { + List loaders = new ArrayList(); + for (APIStore apiStore : getAPIStores()) + { + ScriptLoader loader = apiStore.getScriptLoader(); + if (loader == null) + { + throw new APIException("Unable to retrieve script loader for api 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/api/framework/APITemplateProcessor.java b/source/java/org/alfresco/web/api/framework/APITemplateProcessor.java new file mode 100644 index 0000000000..368bdd1173 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/APITemplateProcessor.java @@ -0,0 +1,150 @@ +/* + * 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.api.framework; + +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 API + * + * Adds the ability to: + * - specify template loaders + * - caching of templates + * + * @author davidc + */ +public class APITemplateProcessor 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; + } + + /** + * 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/api/framework/AbstractAPIService.java b/source/java/org/alfresco/web/api/framework/AbstractAPIService.java new file mode 100644 index 0000000000..c0bd144a9c --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/AbstractAPIService.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.api.framework; + +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.api.framework.APIDescription.RequiredAuthentication; +import org.alfresco.web.api.framework.APIDescription.RequiredTransaction; + + +/** + * Skeleton implementation of an API Service + * + * @author davidc + */ +public abstract class AbstractAPIService implements APIService +{ + // dependencies + private APIRepositoryContext repositoryContext; + private APIRegistry apiRegistry; + private APIDescription apiDescription; + private ServiceRegistry serviceRegistry; + private DescriptorService descriptorService; + + // + // Initialisation + // + + /** + * @param repositoryContext + */ + final public void setRepositoryContext(APIRepositoryContext repositoryContext) + { + this.repositoryContext = repositoryContext; + } + + /** + * @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 apiDescription + */ + final public void setDescription(APIDescription apiDescription) + { + this.apiDescription = apiDescription; + } + + /** + * Initialise Service + * + * @param apiRegistry + */ + public void init(APIRegistry apiRegistry) + { + this.apiRegistry = apiRegistry; + } + + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getServiceDescription() + */ + final public APIDescription getDescription() + { + return this.apiDescription; + } + + + // + // Service Implementation Helpers + // + + /** + * Gets the Repository Context + * + * @return repository context + */ + final public APIRepositoryContext getRepositoryContext() + { + return this.repositoryContext; + } + + /** + * Gets the API Registry + * + * @return API Registry + */ + final public APIRegistry getAPIRegistry() + { + return this.apiRegistry; + } + + /** + * 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 API Request + * + * @param req API Request + * @return argument map + */ + final protected Map createArgModel(APIRequest 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 api request + * @param res api response + * @param customModel custom model entries + * + * @return script model + */ + final protected Map createScriptModel(APIRequest req, APIResponse 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 = repositoryContext.getCompanyHome(); + if (companyHome != null) + { + model.put("companyhome", new Node(repositoryContext.getCompanyHome(), serviceRegistry)); + } + NodeRef person = repositoryContext.getPerson(); + if (person != null) + { + model.put("person", new Node(person, serviceRegistry)); + model.put("userhome", new Node(repositoryContext.getUserHome(person), serviceRegistry)); + } + } + + // add api context + model.put("args", createArgModel(req)); + model.put("guest", req.isGuest()); + model.put("path", new PathModel(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 api request + * @param res api response + * @param customModel custom model entries + * + * @return template model + */ + final protected Map createTemplateModel(APIRequest req, APIResponse 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 = repositoryContext.getCompanyHome(); + if (companyHome != null) + { + model.put("companyhome", new TemplateNode(repositoryContext.getCompanyHome(), serviceRegistry, null)); + } + NodeRef person = repositoryContext.getPerson(); + if (person != null) + { + model.put("person", new TemplateNode(person, serviceRegistry, null)); + model.put("userhome", new TemplateNode(repositoryContext.getUserHome(person), serviceRegistry, null)); + } + } + + // add api context + model.put("args", createArgModel(req)); + model.put("guest", req.isGuest()); + model.put("path", new PathModel(req)); + model.put("server", new ServerModel(descriptorService.getServerDescriptor())); + + // add template support + model.put("xmldate", new ISO8601DateFormatMethod()); + model.put("absurl", new AbsoluteUrlMethod(req.getPath())); + 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) + { + getAPIRegistry().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) + { + getAPIRegistry().getTemplateProcessor().processString(template, model, writer); + } + + /** + * Execute a script + * + * @param location script location + * @param model model + */ + final protected void executeScript(ScriptLocation location, Map model) + { + getAPIRegistry().getScriptProcessor().executeScript(location, model); + } + +} diff --git a/source/java/org/alfresco/web/api/framework/AlfWebClientAuthenticator.java b/source/java/org/alfresco/web/api/framework/AlfWebClientAuthenticator.java new file mode 100644 index 0000000000..b9e84c20fc --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/AlfWebClientAuthenticator.java @@ -0,0 +1,172 @@ +/* + * 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.api.framework; + +import javax.servlet.ServletContext; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.web.api.framework.APIDescription.RequiredAuthentication; +import org.alfresco.web.app.servlet.AuthenticationHelper; +import org.alfresco.web.app.servlet.AuthenticationStatus; +import org.alfresco.web.app.servlet.BaseServlet; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Alfresco Web Client Authentication Interceptor + * + * @author davidc + */ +public class AlfWebClientAuthenticator implements MethodInterceptor, APIContextAware +{ + // Logger + private static final Log logger = LogFactory.getLog(AlfWebClientAuthenticator.class); + + // dependencies + private ServletContext context; + private AuthenticationService authenticationService; + + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIContextAware#setAPIContext(javax.servlet.ServletContext) + */ + public void setAPIContext(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(); + APIRequest request = (APIRequest)args[0]; + APIResponse response = (APIResponse)args[1]; + APIService service = (APIService)invocation.getThis(); + APIDescription 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("Service 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 API service 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/api/framework/BasicAuthenticator.java b/source/java/org/alfresco/web/api/framework/BasicAuthenticator.java new file mode 100644 index 0000000000..662373a204 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/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.api.framework; + +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.api.framework.APIDescription.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(); + APIRequest request = (APIRequest)args[0]; + APIService service = (APIService)invocation.getThis(); + APIDescription 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("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 APIException("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 API service or request authorization + // + + if (authorized) + { + retVal = invocation.proceed(); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Requesting authorization credentials"); + + APIResponse response = (APIResponse)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/api/framework/ClassPathAPIStore.java b/source/java/org/alfresco/web/api/framework/ClassPathAPIStore.java new file mode 100644 index 0000000000..15dbd9187a --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/ClassPathAPIStore.java @@ -0,0 +1,238 @@ +/* + * 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.api.framework; + +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 API Store + * + * @author davidc + */ +public class ClassPathAPIStore implements APIStore, InitializingBean +{ + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + private String classPath; + private 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.api.APIStore#getBasePath() + */ + public String getBasePath() + { + return "classpath:" + classPath; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIStore#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.api.APIStore#getDescriptionDocument(java.lang.String) + */ + public InputStream getDescriptionDocument(String documentPath) + throws IOException + { + File document = new File(fileDir, documentPath); + return new FileInputStream(document); + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIStore#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.api.APIStore#getScriptLoader() + */ + public ScriptLoader getScriptLoader() + { + return new ClassPathScriptLoader(fileDir); + } + + /** + * Class path based script loader + * + * @author davidc + */ + private class ClassPathScriptLoader implements ScriptLoader + { + private File classPath; + + /** + * Construct + * + * @param classPath + */ + public ClassPathScriptLoader(File classPath) + { + this.classPath = classPath; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.ScriptLoader#getScriptLocation(java.lang.String) + */ + public ScriptLocation getScriptLocation(String path) + { + ScriptLocation location = null; + File scriptPath = new File(classPath, 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 APIException("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/api/framework/DeclarativeAPIRegistry.java b/source/java/org/alfresco/web/api/framework/DeclarativeAPIRegistry.java new file mode 100644 index 0000000000..54e43b8b46 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/DeclarativeAPIRegistry.java @@ -0,0 +1,562 @@ +/* + * 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.api.framework; + +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.api.framework.APIDescription.RequiredAuthentication; +import org.alfresco.web.api.framework.APIDescription.RequiredTransaction; +import org.alfresco.web.api.framework.APIDescription.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; + + +/** + * API Service Registry of declarative (scripted/template driven) api services + * + * @author davidc + */ +public class DeclarativeAPIRegistry implements APIRegistry, ApplicationContextAware, APIContextAware +{ + // Logger + private static final Log logger = LogFactory.getLog(DeclarativeAPIRegistry.class); + + private ApplicationContext applicationContext; + private ServletContext servletContext; + private String defaultServiceImpl; + private MethodInterceptor authenticator; + private MethodInterceptor serviceLogger; + private MethodInterceptor serviceTransaction; + private FormatRegistry formatRegistry; + private APIStores stores; + + // map of services by service id + // NOTE: The map is sorted by id (ascending order) + private Map servicesById = new TreeMap(); + + // map of services by url + // NOTE: The map is sorted by url (descending order) + private Map servicesByURL = new TreeMap(Collections.reverseOrder()); + + + // + // Initialisation + // + + /** + * Sets the available API Stores + * + * @param stores + */ + public void setStores(APIStores stores) + { + this.stores = stores; + } + + /** + * Sets the service authenticator + * + * @param authenticator + */ + public void setAuthenticator(MethodInterceptor authenticator) + { + this.authenticator = authenticator; + } + + /** + * Sets the service 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 defaultServiceImpl + */ + public void setDefaultServiceImpl(String defaultServiceImpl) + { + this.defaultServiceImpl = defaultServiceImpl; + } + + /** + * Sets the response format registry + * + * @param formatRegistry + */ + public void setFormatRegistry(FormatRegistry formatRegistry) + { + this.formatRegistry = formatRegistry; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIContextAware#setAPIContext(javax.servlet.ServletContext) + */ + public void setAPIContext(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 API Services + * + * Note: Each invocation of this method resets the list of the services + */ + public void initServices() + { + if (logger.isDebugEnabled()) + logger.debug("Initialising Web API services"); + + // clear currently registered services + servicesById.clear(); + servicesByURL.clear(); + + // register services + for (APIStore apiStore : stores.getAPIStores()) + { + if (logger.isDebugEnabled()) + logger.debug("Locating services within " + apiStore.getBasePath()); + + String basePath = apiStore.getBasePath(); + String[] serviceDescPaths = apiStore.getDescriptionDocumentPaths(); + for (String serviceDescPath : serviceDescPaths) + { + // build service description + APIDescription serviceDesc = null; + InputStream serviceDescIS = null; + try + { + serviceDescIS = apiStore.getDescriptionDocument(serviceDescPath); + serviceDesc = createServiceDescription(basePath, serviceDescPath, serviceDescIS); + } + catch(IOException e) + { + throw new APIException("Failed to read service 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 (servicesById.containsKey(id)) + { + // move to next service + if (logger.isDebugEnabled()) + { + APIService existingService = servicesById.get(id); + logger.debug("Service description document " + existingService.getDescription().getSourceLocation() + " overridden by " + serviceDesc.getSourceLocation()); + } + continue; + } + + // construct service implementation + String serviceImplName = (applicationContext.containsBean("web.api." + id)) ? "web.api." + id : defaultServiceImpl; + AbstractAPIService serviceImpl = (AbstractAPIService)applicationContext.getBean(serviceImplName); + serviceImpl.setDescription(serviceDesc); + serviceImpl.init(this); + + // wrap service implementation in appropriate interceptors (e.g. authentication) + APIService serviceImplIF = (APIService)serviceImpl; + if (serviceLogger != null && serviceTransaction != null && authenticator != null) + { + ProxyFactory authFactory = new ProxyFactory(); + authFactory.addInterface(APIService.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 APIException("Web API Transaction not specified"); + } + RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(".*execute", serviceTransaction); + authFactory.addAdvisor(advisor); + } + + // authentication interceptor + if (serviceDesc.getRequiredAuthentication() != RequiredAuthentication.none) + { + if (authenticator == null) + { + throw new APIException("Web API Authenticator not specified"); + } + RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(".*execute", authenticator); + authFactory.addAdvisor(advisor); + } + + serviceImplIF = (APIService)authFactory.getProxy(); + } + + if (logger.isDebugEnabled()) + logger.debug("Found service " + serviceDescPath + " (id: " + id + ", impl: " + serviceImplName + ", auth: " + serviceDesc.getRequiredAuthentication() + ", trx: " + serviceDesc.getRequiredTransaction() + ")"); + + // register service and its urls + servicesById.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 (servicesByURL.containsKey(uriIdx)) + { + APIService existingService = servicesByURL.get(uriIdx); + if (!existingService.getDescription().getId().equals(serviceDesc.getId())) + { + throw new APIException("Service document " + existingService.getDescription().getSourceLocation() + " already defines the url '" + uriIdx); + } + } + else + { + servicesByURL.put(uriIdx, serviceImplIF); + + if (logger.isDebugEnabled()) + logger.debug("Registered Web API URL '" + uriIdx + "'"); + } + } + } + } + + if (logger.isDebugEnabled()) + logger.debug("Registered " + servicesById.size() + " services; " + servicesByURL.size() + " URLs"); + } + + /** + * Create an API Service Description + * + * @param basePath + * @param serviceDescPath + * @param serviceDoc + * + * @return api service description + */ + private APIDescription createServiceDescription(String basePath, String serviceDescPath, InputStream serviceDoc) + { + SAXReader reader = new SAXReader(); + try + { + Document document = reader.read(serviceDoc); + Element rootElement = document.getRootElement(); + if (!rootElement.getName().equals("servicedescription")) + { + throw new APIException("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 APIException("Unable to establish HTTP Method from service 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 APIException("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 APIException("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 APIException("Expected template attribute on element"); + } + + APIDescriptionImpl.URIImpl uriImpl = new APIDescriptionImpl.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 APIException("Expected value"); + } + reqAuth = RequiredAuthentication.valueOf(reqAuthStr); + if (reqAuth == null) + { + throw new APIException("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 APIException("Expected value"); + } + reqTrx = RequiredTransaction.valueOf(reqTrxStr); + if (reqTrx == null) + { + throw new APIException("Transaction '" + reqTrxStr + "' is not a valid value"); + } + } + + // construct service description + APIDescriptionImpl serviceDesc = new APIDescriptionImpl(); + 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 APIDescription.URI[uris.size()])); + serviceDesc.setDefaultFormat(uris.get(0).getFormat()); + return serviceDesc; + } + catch(DocumentException e) + { + throw new APIException("Failed to parse service description document " + serviceDescPath, e); + } + catch(APIException e) + { + throw new APIException("Failed to parise service description document " + serviceDescPath, e); + } + } + + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIRegistry#getServices() + */ + public Collection getServices() + { + return servicesById.values(); + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIRegistry#getService(java.lang.String) + */ + public APIService getService(String id) + { + return servicesById.get(id); + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIRegistry#findService(java.lang.String, java.lang.String) + */ + public APIServiceMatch findService(String method, String uri) + { + // TODO: Replace with more efficient approach + DeclarativeAPIServiceMatch apiServiceMatch = null; + String match = method.toString().toUpperCase() + ":" + uri; + for (Map.Entry service : servicesByURL.entrySet()) + { + String indexedPath = service.getKey(); + if (match.startsWith(indexedPath)) + { + String matchPath = indexedPath.substring(indexedPath.indexOf(':') +1); + apiServiceMatch = new DeclarativeAPIServiceMatch(matchPath, service.getValue()); + break; + } + } + return apiServiceMatch; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIRegistry#getContext() + */ + public ServletContext getContext() + { + return servletContext; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIRegistry#getFormatRegistry() + */ + public FormatRegistry getFormatRegistry() + { + return this.formatRegistry; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIRegistry#getTemplateProcessor() + */ + public APITemplateProcessor getTemplateProcessor() + { + return this.stores.getTemplateProcessor(); + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIRegistry#getScriptProcessor() + */ + public APIScriptProcessor getScriptProcessor() + { + return this.stores.getScriptProcessor(); + } + + + /** + * API Service Match + * + * @author davidc + */ + public static class DeclarativeAPIServiceMatch implements APIServiceMatch + { + private String path; + private APIService service; + + /** + * Construct + * + * @param path + * @param service + */ + public DeclarativeAPIServiceMatch(String path, APIService service) + { + this.path = path; + this.service = service; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIServiceMatch#getPath() + */ + public String getPath() + { + return path; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIServiceMatch#getService() + */ + public APIService getService() + { + return service; + } + } + +} diff --git a/source/java/org/alfresco/web/api/framework/FormatMap.java b/source/java/org/alfresco/web/api/framework/FormatMap.java new file mode 100644 index 0000000000..d47b021fe9 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/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.api.framework; + +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/api/framework/FormatRegistry.java b/source/java/org/alfresco/web/api/framework/FormatRegistry.java new file mode 100644 index 0000000000..49a34f942f --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/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.api.framework; + +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 API format '" + entry.getKey() + "' (agent: " + agent + ")"); + } + } + + formatsForAgent.put(entry.getKey(), entry.getValue()); + mimetypesForAgent.put(entry.getValue(), entry.getKey()); + + if (logger.isDebugEnabled()) + logger.debug("Registered API 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/api/framework/MultiScriptLoader.java b/source/java/org/alfresco/web/api/framework/MultiScriptLoader.java new file mode 100644 index 0000000000..5fef6b3d10 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/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.api.framework; + +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.api.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/api/framework/PathModel.java b/source/java/org/alfresco/web/api/framework/PathModel.java new file mode 100644 index 0000000000..9f9a8aab36 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/PathModel.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.api.framework; + + +/** + * Script / Template Model representing API paths + * + * @author davidc + */ +public class PathModel +{ + private APIRequest req; + + /** + * Construct + * + * @param req + */ + /*package*/ PathModel(APIRequest req) + { + this.req = req; + } + + /** + * Gets the Context Path + * + * e.g. http://localhost:port/alfresco + * + * @return context path + */ + public String getContext() + { + return req.getPath(); + } + + public String jsGet_context() + { + return getContext(); + } + + /** + * Gets the Service Context Path + * + * e.g. http://localhost:port/alfresco/service + * + * @return service context path + */ + public String getServiceContext() + { + return req.getServicePath(); + } + + public String jsGet_serviceContext() + { + return getServiceContext(); + } + + /** + * Gets the Service Path + * + * e.g. http://localhost:port/alfresco/service/keyword?q=term + * + * @return service path + */ + public String getService() + { + return req.getUrl(); + } + + public String jsGet_service() + { + return getService(); + } + + /** + * 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/api/framework/ScriptLoader.java b/source/java/org/alfresco/web/api/framework/ScriptLoader.java new file mode 100644 index 0000000000..efbc0718b2 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/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.api.framework; + +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/api/framework/ScriptedAPIService.java b/source/java/org/alfresco/web/api/framework/ScriptedAPIService.java new file mode 100644 index 0000000000..b86ce3df94 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/ScriptedAPIService.java @@ -0,0 +1,222 @@ +/* + * 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.api.framework; + +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 API Service + * + * @author davidc + */ +public class ScriptedAPIService extends AbstractAPIService +{ + // Logger + private static final Log logger = LogFactory.getLog(ScriptedAPIService.class); + + private String baseTemplatePath; + private ScriptLocation executeScript; + + + /* (non-Javadoc) + * @see org.alfresco.web.api.AbstractAPIService#init(org.alfresco.web.api.APIRegistry) + */ + @Override + public void init(APIRegistry apiRegistry) + { + super.init(apiRegistry); + baseTemplatePath = getDescription().getId().replace('.', '/'); + + // Test for "execute" script + String scriptPath = baseTemplatePath + ".js"; + executeScript = getAPIRegistry().getScriptProcessor().findScript(scriptPath); + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#execute(org.alfresco.web.api.APIRequest, org.alfresco.web.api.APIResponse) + */ + final public void execute(APIRequest req, APIResponse 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 = getAPIRegistry().getFormatRegistry().getMimeType(req.getAgent(), format); + if (mimetype == null) + { + throw new APIException("API 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 APIException("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 API request + * @param res API response + * @param model basic API model + * @return custom service model + */ + protected Map executeImpl(APIRequest req, APIResponse res) + { + return null; + } + + /** + * Render a template (of given format) to the API 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 service template '" + templatePath + "'"); + + renderTemplate(templatePath, model, writer); + } + +} diff --git a/source/java/org/alfresco/web/api/framework/ServerModel.java b/source/java/org/alfresco/web/api/framework/ServerModel.java new file mode 100644 index 0000000000..e344425715 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/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.api.framework; + +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/api/framework/ServiceLogger.java b/source/java/org/alfresco/web/api/framework/ServiceLogger.java new file mode 100644 index 0000000000..61f43de4ca --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/ServiceLogger.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.api.framework; + +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; + + +/** + * API Service Logger + * + * @author davidc + */ +public class ServiceLogger implements MethodInterceptor +{ + // Logger + private static final Log logger = LogFactory.getLog(ServiceLogger.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()) + { + APIService service = (APIService)invocation.getThis(); + APIDescription description = service.getDescription(); + String user = AuthenticationUtil.getCurrentUserName(); + String locale = I18NUtil.getLocale().toString(); + logger.debug("Invoking service " + description.getId() + (user == null ? " (unauthenticated)" : " (authenticated as " + user + ")" + " (" + locale + ")")); + long start = System.currentTimeMillis(); + retVal = invocation.proceed(); + long end = System.currentTimeMillis(); + logger.debug("Service " + description.getId() + " executed in " + (end - start) + "ms"); + } + else + { + retVal = invocation.proceed(); + } + + return retVal; + } + +} diff --git a/source/java/org/alfresco/web/api/framework/ServiceTransaction.java b/source/java/org/alfresco/web/api/framework/ServiceTransaction.java new file mode 100644 index 0000000000..c4a603bf68 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/ServiceTransaction.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.api.framework; + +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.web.api.framework.APIDescription.RequiredTransaction; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * API Service Transaction + * + * @author davidc + */ +public class ServiceTransaction implements MethodInterceptor +{ + // Logger + protected static final Log logger = LogFactory.getLog(ServiceTransaction.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 + { + APIService service = (APIService)invocation.getThis(); + final APIDescription 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/api/framework/SystemAuthenticator.java b/source/java/org/alfresco/web/api/framework/SystemAuthenticator.java new file mode 100644 index 0000000000..7533a59a5d --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/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.api.framework; + +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/api/framework/TestAPIServer.java b/source/java/org/alfresco/web/api/framework/TestAPIServer.java new file mode 100644 index 0000000000..8efb51e2e4 --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/TestAPIServer.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.api.framework; + +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.repo.transaction.TransactionUtil; +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 API Test Server + * + * @author davidc + */ +public class TestAPIServer +{ + // dependencies + protected TransactionService transactionService; + protected DeclarativeAPIRegistry apiRegistry; + + /** 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 API Registry + * + * @param apiRegistry + */ + public void setAPIRegistry(DeclarativeAPIRegistry apiRegistry) + { + this.apiRegistry = apiRegistry; + } + + + /** + * Initialise the Test API Server + * + * @throws Exception + */ + public void init() throws Exception + { + apiRegistry.initServices(); + 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-api-application-context.xml", "classpath:alfresco/web-api-application-context-test.xml" }; + ApplicationContext context = new ClassPathXmlApplicationContext(CONFIG_LOCATIONS); + TestAPIServer testServer = (TestAPIServer)context.getBean("web.api.framework.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(); + + APIServiceMatch match = apiRegistry.findService(req.getMethod(), uri); + if (match == null) + { + throw new APIException("No service bound to uri '" + uri + "'"); + } + + APIRequest apiReq = new APIRequest(req, match); + APIResponse apiRes = new APIResponse(res); + match.getService().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 path info + req.setPathInfo(iArgIndex == -1 ? uri : uri.substring(0, iArgIndex)); + + // set servlet path + req.setServletPath("/alfresco/service"); + + return req; + } + +} diff --git a/source/java/org/alfresco/web/api/framework/TrustedAuthenticator.java b/source/java/org/alfresco/web/api/framework/TrustedAuthenticator.java new file mode 100644 index 0000000000..197822baee --- /dev/null +++ b/source/java/org/alfresco/web/api/framework/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.api.framework; + +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; + } + +}