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
This commit is contained in:
Kevin Roast
2008-04-25 18:24:04 +00:00
parent 65e498cd2b
commit d1b9417dfc
5 changed files with 501 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
<webscript>
<shortname>Remote AVM Store</shortname>
<description>Remote service mirroring the Store interface - to an AVM store</description>
<url>/remotestore/{method}/{path}</url>
<authentication>guest</authentication>
<format default="">argument</format>
</webscript>

View File

@@ -0,0 +1,7 @@
<webscript>
<shortname>Remote AVM Store</shortname>
<description>Remote service mirroring the Store interface - to an AVM store</description>
<url>/remotestore/{method}/{path}</url>
<authentication>guest</authentication>
<format default="">argument</format>
</webscript>

View File

@@ -213,4 +213,19 @@
<property name="mimetypeService" ref="MimetypeService" /> <property name="mimetypeService" ref="MimetypeService" />
</bean> </bean>
<!-- Remote Store service - AVM -->
<bean id="webscript.org.alfresco.repository.store.remoteavm.get" class="org.alfresco.repo.web.scripts.bean.AVMRemoteStore" parent="webscript">
<property name="mimetypeService" ref="MimetypeService" />
<property name="avmService" ref="AVMService" />
<property name="rootPath"><value>site-data</value></property>
<property name="store"><value>sitestore</value></property>
</bean>
<bean id="webscript.org.alfresco.repository.store.remoteavm.post" class="org.alfresco.repo.web.scripts.bean.AVMRemoteStore" parent="webscript">
<property name="mimetypeService" ref="MimetypeService" />
<property name="avmService" ref="AVMService" />
<property name="rootPath"><value>site-data</value></property>
<property name="store"><value>sitestore</value></property>
</bean>
</beans> </beans>

View File

@@ -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;
}
}

View File

@@ -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:
*
* <servicepath>/<method>/<path>
*
* 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<String> getPathParts(String[] extPaths)
{
List<String> pathParts = new ArrayList<String>(extPaths.length - 1);
for (int i=1; i<extPaths.length; i++)
{
pathParts.add(extPaths[i]);
}
return pathParts;
}
/**
* Gets the last modified timestamp for the document.
*
* @param path document path to an existing document
*/
protected abstract void lastModified(WebScriptResponse res, String path)
throws IOException;
/**
* Determines if the document exists
*
* @param path document path
* @return true => 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
};
}