From d1b9417dfc7eb2ce9c8da94d2bc52479613075e2 Mon Sep 17 00:00:00 2001 From: Kevin Roast Date: Fri, 25 Apr 2008 18:24:04 +0000 Subject: [PATCH] First attempt at a remote Store API - AVM store impl git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@8932 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../repository/store/remoteavm.get.desc.xml | 7 + .../repository/store/remoteavm.post.desc.xml | 7 + .../web-scripts-application-context.xml | 15 ++ .../repo/web/scripts/bean/AVMRemoteStore.java | 218 +++++++++++++++ .../web/scripts/bean/BaseRemoteStore.java | 254 ++++++++++++++++++ 5 files changed, 501 insertions(+) create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/store/remoteavm.get.desc.xml create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/store/remoteavm.post.desc.xml create mode 100644 source/java/org/alfresco/repo/web/scripts/bean/AVMRemoteStore.java create mode 100644 source/java/org/alfresco/repo/web/scripts/bean/BaseRemoteStore.java diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/remoteavm.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/store/remoteavm.get.desc.xml new file mode 100644 index 0000000000..1332529005 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/remoteavm.get.desc.xml @@ -0,0 +1,7 @@ + + Remote AVM Store + Remote service mirroring the Store interface - to an AVM store + /remotestore/{method}/{path} + guest + argument + \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/remoteavm.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/store/remoteavm.post.desc.xml new file mode 100644 index 0000000000..1332529005 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/remoteavm.post.desc.xml @@ -0,0 +1,7 @@ + + Remote AVM Store + Remote service mirroring the Store interface - to an AVM store + /remotestore/{method}/{path} + guest + argument + \ No newline at end of file diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index a8ecf9b1b3..f142c4dd40 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -212,5 +212,20 @@ + + + + + + site-data + sitestore + + + + + + site-data + sitestore + diff --git a/source/java/org/alfresco/repo/web/scripts/bean/AVMRemoteStore.java b/source/java/org/alfresco/repo/web/scripts/bean/AVMRemoteStore.java new file mode 100644 index 0000000000..82170a07ec --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/bean/AVMRemoteStore.java @@ -0,0 +1,218 @@ +/* + * 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.repo.web.scripts.bean; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.net.SocketException; + +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.web.scripts.WebScriptException; +import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.servlet.WebScriptServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * AVM Remote Store service. + * + * @see BaseRemoteStore for API methods. + * + * @author Kevin Roast + */ +public class AVMRemoteStore extends BaseRemoteStore +{ + private static final Log logger = LogFactory.getLog(AVMRemoteStore.class); + + private String rootPath; + private AVMService avmService; + + + /** + * @param rootPath the root path under which to process store requests + */ + public void setRootPath(String rootPath) + { + this.rootPath = rootPath; + } + + /** + * @param avmService the AVMService to set + */ + public void setAvmService(AVMService avmService) + { + this.avmService = avmService; + } + + + /** + * Gets the last modified timestamp for the document. + * + * @param path document path to an existing document + */ + @Override + protected void lastModified(WebScriptResponse res, String path) + throws IOException + { + String avmPath = buildAVMPath(path); + AVMNodeDescriptor desc = this.avmService.lookup(-1, avmPath); + if (desc == null) + { + throw new WebScriptException("Unable to locate AVM file: " + avmPath); + } + + Writer out = res.getWriter(); + out.write(Long.toString(desc.getModDate())); + out.close(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.scripts.bean.BaseRemoteStore#getDocument(org.alfresco.web.scripts.WebScriptResponse, java.lang.String) + */ + @Override + protected void getDocument(WebScriptResponse res, String path) throws IOException + { + String avmPath = buildAVMPath(path); + AVMNodeDescriptor desc = this.avmService.lookup(-1, avmPath); + if (desc == null) + { + throw new WebScriptException("Unable to locate file: " + avmPath); + } + + ContentReader reader = this.avmService.getContentReader(-1, avmPath); + if (reader == null) + { + throw new WebScriptException("No content found for AVM file: " + avmPath); + } + + // establish mimetype + String mimetype = reader.getMimetype(); + if (mimetype == null || mimetype.length() == 0) + { + mimetype = MimetypeMap.MIMETYPE_BINARY; + int extIndex = path.lastIndexOf('.'); + if (extIndex != -1) + { + String ext = path.substring(extIndex + 1); + String mt = this.mimetypeService.getMimetypesByExtension().get(ext); + if (mt != null) + { + mimetype = mt; + } + } + } + + // set mimetype for the content and the character encoding + length for the stream + WebScriptServletResponse httpRes = (WebScriptServletResponse)res; + httpRes.setContentType(mimetype); + httpRes.getHttpServletResponse().setCharacterEncoding(reader.getEncoding()); + httpRes.getHttpServletResponse().setDateHeader("Last-Modified", desc.getModDate()); + httpRes.setHeader("Content-Length", Long.toString(reader.getSize())); + + // get the content and stream directly to the response output stream + // assuming the repository is capable of streaming in chunks, this should allow large files + // to be streamed directly to the browser response stream. + try + { + reader.getContent(res.getOutputStream()); + } + catch (SocketException e1) + { + // the client cut the connection - our mission was accomplished apart from a little error message + if (logger.isInfoEnabled()) + logger.info("Client aborted stream read:\n\tnode: " + avmPath + "\n\tcontent: " + reader); + } + catch (ContentIOException e2) + { + if (logger.isInfoEnabled()) + logger.info("Client aborted stream read:\n\tnode: " + avmPath + "\n\tcontent: " + reader); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.scripts.bean.BaseRemoteStore#hasDocument(org.alfresco.web.scripts.WebScriptResponse, java.lang.String) + */ + @Override + protected void hasDocument(WebScriptResponse res, String path) throws IOException + { + String avmPath = buildAVMPath(path); + AVMNodeDescriptor desc = this.avmService.lookup(-1, avmPath); + + Writer out = res.getWriter(); + out.write(Boolean.toString(desc != null)); + out.close(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.scripts.bean.BaseRemoteStore#createDocument(org.alfresco.web.scripts.WebScriptResponse, java.lang.String, java.io.InputStream) + */ + @Override + protected void createDocument(WebScriptResponse res, String path, InputStream content) + { + String avmPath = buildAVMPath(path); + AVMNodeDescriptor desc = this.avmService.lookup(-1, avmPath); + if (desc != null) + { + throw new WebScriptException("Unable to create, file already exists: " + avmPath); + } + + String[] parts = AVMNodeConverter.SplitBase(avmPath); + this.avmService.createFile(parts[0], parts[1], content); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.scripts.bean.BaseRemoteStore#updateDocument(org.alfresco.web.scripts.WebScriptResponse, java.lang.String, java.io.InputStream) + */ + @Override + protected void updateDocument(WebScriptResponse res, String path, InputStream content) + { + String avmPath = buildAVMPath(path); + AVMNodeDescriptor desc = this.avmService.lookup(-1, avmPath); + if (desc == null) + { + throw new WebScriptException("Unable to locate file for update: " + avmPath); + } + + ContentWriter writer = this.avmService.getContentWriter(avmPath); + writer.putContent(content); + } + + /** + * @param path root path relative + * + * @return full AVM path to document including store and root path components + */ + private String buildAVMPath(String path) + { + return this.store + ":/" + this.rootPath + "/" + path; + } +} diff --git a/source/java/org/alfresco/repo/web/scripts/bean/BaseRemoteStore.java b/source/java/org/alfresco/repo/web/scripts/bean/BaseRemoteStore.java new file mode 100644 index 0000000000..5de979e3d1 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/bean/BaseRemoteStore.java @@ -0,0 +1,254 @@ +/* + * 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.repo.web.scripts.bean; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.web.scripts.AbstractWebScript; +import org.alfresco.web.scripts.WebScriptException; +import org.alfresco.web.scripts.WebScriptRequest; +import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.servlet.WebScriptServletRequest; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Remote Store service. + * + * Responsible for providing remote HTTP based access to a store. Designed to be accessed + * from a web-tier application to remotely mirror a WebScript Store instance. + * + * Request format: + * + * // + * + * Example: + * + * /service/store/lastmodified/sites/xyz/pages/page.xml + * + * where: /service/store -> service path + * /lastmodified -> method name + * /sites/../page.xml -> document path + * + * Note: path is relative to the root path as configured for this webscript bean + * + * For content create and update the request should be POSTed and the content sent as the + * payload of the request content. + * + * Supported method API: + * GET lastmodified -> return long timestamp of a document + * GET has -> return true/false of existence for a document + * GET get -> return document content - in addition the usual HTTP headers for the + * character encoding, content type, length and modified date will be supplied + * POST create -> create a new document with request content payload + * POST update -> update an existing document with request content payload + * + * @author Kevin Roast + */ +public abstract class BaseRemoteStore extends AbstractWebScript +{ + private static final Log logger = LogFactory.getLog(BaseRemoteStore.class); + + protected String store; + protected ContentService contentService; + protected MimetypeService mimetypeService; + + + /** + * @param store the store name of the store to process document requests against + */ + public void setStore(String store) + { + this.store = store; + } + + /** + * @param contentService the ContentService to set + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @param mimetypeService the MimetypeService to set + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + /** + * Execute the webscript based on the request parameters + */ + public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException + { + // NOTE: This web script must be executed in a HTTP Servlet environment + if (!(req instanceof WebScriptServletRequest)) + { + throw new WebScriptException("Remote Store access must be executed in HTTP Servlet environment"); + } + + HttpServletRequest httpReq = ((WebScriptServletRequest)req).getHttpServletRequest(); + + // break down and validate the request - expecting method name and document path + String extPath = req.getExtensionPath(); + String[] extParts = extPath == null ? new String[0] : extPath.split("/"); + if (extParts.length < 1) + { + throw new WebScriptException("Remote Store expecting method name."); + } + if (extParts.length < 2) + { + throw new WebScriptException("Remote Store expecting document path."); + } + + // build path as a string and as a list of path elements + String path = req.getExtensionPath().substring(extParts[0].length() + 1); + + if (logger.isDebugEnabled()) + logger.debug("Remote store method: " + extParts[0] + " path: " + path); + + // TODO: support storeref name override as argument (i.e. for AVM virtualisation) + + try + { + // generate enum from string method name - so we can use a fast switch table lookup + APIMethod method = APIMethod.valueOf(extParts[0].toUpperCase()); + switch (method) + { + case LASTMODIFIED: + lastModified(res, path); + break; + + case HAS: + hasDocument(res, path); + break; + + case GET: + getDocument(res, path); + break; + + case CREATE: + createDocument(res, path, httpReq.getInputStream()); + break; + + case UPDATE: + updateDocument(res, path, httpReq.getInputStream()); + break; + } + } + catch (IllegalArgumentException enumErr) + { + throw new WebScriptException("Unknown method specified to remote store API: " + extParts[0]); + } + catch (IOException ioErr) + { + throw new WebScriptException("Error during remote store API: " + ioErr.getMessage()); + } + } + + /** + * Helper to break down webscript extension path into path component elements + */ + protected List getPathParts(String[] extPaths) + { + List pathParts = new ArrayList(extPaths.length - 1); + for (int i=1; i exists, false => does not exist + */ + protected abstract void hasDocument(WebScriptResponse res, String path) + throws IOException; + + /** + * Gets a document + * + * @param path document path + * @return input stream onto document + * + * @throws IOException if the document does not exist in the store + */ + protected abstract void getDocument(WebScriptResponse res, String path) + throws IOException; + + /** + * Creates a document. + * + * @param path document path + * @param content content of the document to write + * + * @throws IOException if the document already exists or the create fails + */ + protected abstract void createDocument(WebScriptResponse res, String path, InputStream content); + + /** + * Updates an existing document. + * + * @param path document path + * @param content content to update the document with + * + * @throws IOException if the document does not exist or the update fails + */ + protected abstract void updateDocument(WebScriptResponse res, String path, InputStream content); + + + /** + * Enum representing the API method on the Store. + */ + private enum APIMethod + { + LASTMODIFIED, + HAS, + GET, + CREATE, + UPDATE + }; +}