mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-21 18:09:20 +00:00
Swift – SE.S21 Share - DM Remote Store - WIP
- not hooked in as the default yet - fleshing out methods and impl git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28338 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
<webscript>
|
||||||
|
<shortname>Remote ADM Store</shortname>
|
||||||
|
<description>Remote service mirroring the Store interface - to an ADM store</description>
|
||||||
|
<url>/remoteadm/{method}/{path}</url>
|
||||||
|
<authentication>user</authentication>
|
||||||
|
<transaction>required</transaction>
|
||||||
|
<format default="">argument</format>
|
||||||
|
</webscript>
|
@@ -0,0 +1,9 @@
|
|||||||
|
<webscript>
|
||||||
|
<shortname>Remote ADM Store</shortname>
|
||||||
|
<description>Remote service mirroring the Store interface - to an ADM store</description>
|
||||||
|
<url>/remoteadm/{method}</url>
|
||||||
|
<url>/remoteadm/{method}/{path}</url>
|
||||||
|
<authentication>none</authentication>
|
||||||
|
<transaction allow="readonly">required</transaction>
|
||||||
|
<format default="">argument</format>
|
||||||
|
</webscript>
|
@@ -0,0 +1,8 @@
|
|||||||
|
<webscript>
|
||||||
|
<shortname>Remote ADM Store</shortname>
|
||||||
|
<description>Remote service mirroring the Store interface - to an ADM store</description>
|
||||||
|
<url>/remoteadm/{method}/{path}</url>
|
||||||
|
<authentication>user</authentication>
|
||||||
|
<transaction>required</transaction>
|
||||||
|
<format default="">argument</format>
|
||||||
|
</webscript>
|
@@ -474,6 +474,33 @@
|
|||||||
<property name="searchService" ref="SearchService" />
|
<property name="searchService" ref="SearchService" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<!-- Remote Store service - ADM -->
|
||||||
|
<bean id="webscript.org.alfresco.repository.store.remoteadm.get" class="org.alfresco.repo.web.scripts.bean.ADMRemoteStore" parent="webscript">
|
||||||
|
<property name="nodeService" ref="NodeService" />
|
||||||
|
<property name="unprotectedNodeService" ref="nodeService" />
|
||||||
|
<property name="mimetypeService" ref="MimetypeService" />
|
||||||
|
<property name="fileFolderService" ref="FileFolderService"/>
|
||||||
|
<property name="contentService" ref="ContentService"/>
|
||||||
|
<property name="siteService" ref="SiteService" />
|
||||||
|
</bean>
|
||||||
|
<bean id="webscript.org.alfresco.repository.store.remoteadm.post" class="org.alfresco.repo.web.scripts.bean.ADMRemoteStore" parent="webscript">
|
||||||
|
<property name="nodeService" ref="NodeService" />
|
||||||
|
<property name="unprotectedNodeService" ref="nodeService" />
|
||||||
|
<property name="mimetypeService" ref="MimetypeService" />
|
||||||
|
<property name="fileFolderService" ref="FileFolderService"/>
|
||||||
|
<property name="contentService" ref="ContentService"/>
|
||||||
|
<property name="siteService" ref="SiteService" />
|
||||||
|
</bean>
|
||||||
|
<bean id="webscript.org.alfresco.repository.store.remoteadm.delete" class="org.alfresco.repo.web.scripts.bean.ADMRemoteStore" parent="webscript">
|
||||||
|
<property name="nodeService" ref="NodeService" />
|
||||||
|
<property name="unprotectedNodeService" ref="nodeService" />
|
||||||
|
<property name="mimetypeService" ref="MimetypeService" />
|
||||||
|
<property name="fileFolderService" ref="FileFolderService"/>
|
||||||
|
<property name="contentService" ref="ContentService"/>
|
||||||
|
<property name="siteService" ref="SiteService" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
|
||||||
<!-- Authenticated Web Framework AVM Store service -->
|
<!-- Authenticated Web Framework AVM Store service -->
|
||||||
<bean id="webscript.org.alfresco.webframework.avmstore.get" class="org.alfresco.repo.web.scripts.bean.AVMRemoteStore" parent="webscript">
|
<bean id="webscript.org.alfresco.webframework.avmstore.get" class="org.alfresco.repo.web.scripts.bean.AVMRemoteStore" parent="webscript">
|
||||||
<property name="mimetypeService" ref="MimetypeService" />
|
<property name="mimetypeService" ref="MimetypeService" />
|
||||||
|
@@ -0,0 +1,795 @@
|
|||||||
|
/*
|
||||||
|
* 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.Serializable;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.repo.content.MimetypeMap;
|
||||||
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||||
|
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
||||||
|
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
||||||
|
import org.alfresco.repo.security.permissions.noop.PermissionServiceNOOPImpl;
|
||||||
|
import org.alfresco.service.cmr.model.FileExistsException;
|
||||||
|
import org.alfresco.service.cmr.model.FileFolderService;
|
||||||
|
import org.alfresco.service.cmr.model.FileFolderUtil;
|
||||||
|
import org.alfresco.service.cmr.model.FileInfo;
|
||||||
|
import org.alfresco.service.cmr.model.FileNotFoundException;
|
||||||
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||||
|
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||||
|
import org.alfresco.service.cmr.repository.ContentReader;
|
||||||
|
import org.alfresco.service.cmr.repository.ContentService;
|
||||||
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
|
import org.alfresco.service.cmr.repository.Path;
|
||||||
|
import org.alfresco.service.cmr.site.SiteInfo;
|
||||||
|
import org.alfresco.service.cmr.site.SiteService;
|
||||||
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.extensions.webscripts.Status;
|
||||||
|
import org.springframework.extensions.webscripts.WebScriptException;
|
||||||
|
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ADM Remote Store service.
|
||||||
|
* <p>
|
||||||
|
* This implementation of the RemoteStore is tied to the current SiteService implementation.
|
||||||
|
* <p>
|
||||||
|
* It remaps incoming generic document path requests to the appropriate folder structure
|
||||||
|
* in the Sites folder. Dashboard pages and component bindings are remapped to take advantage
|
||||||
|
* of inherited permissions in the appropriate root site folder, ensuring that only valid
|
||||||
|
* users can write to the appropriate configuration objects.
|
||||||
|
* <p>
|
||||||
|
* System folders are used to hide configuration from general folder browsing UI.
|
||||||
|
*
|
||||||
|
* @see BaseRemoteStore for the available API methods.
|
||||||
|
*
|
||||||
|
* @author Kevin Roast
|
||||||
|
*/
|
||||||
|
public class ADMRemoteStore extends BaseRemoteStore
|
||||||
|
{
|
||||||
|
private static final Log logger = LogFactory.getLog(ADMRemoteStore.class);
|
||||||
|
|
||||||
|
private static final String SURF_CONFIG = "surf-config";
|
||||||
|
|
||||||
|
private static final Pattern USER_PATTERN_1 = Pattern.compile(".*/components/.*\\.user~(.*)~.*");
|
||||||
|
private static final Pattern USER_PATTERN_2 = Pattern.compile(".*/pages/user/(.*?)(/.*)?$");
|
||||||
|
private static final Pattern SITE_PATTERN_1 = Pattern.compile(".*/components/.*\\.site~(.*)~.*");
|
||||||
|
private static final Pattern SITE_PATTERN_2 = Pattern.compile(".*/pages/site/(.*?)(/.*)?$");
|
||||||
|
|
||||||
|
private NodeService nodeService;
|
||||||
|
private NodeService unprotNodeService;
|
||||||
|
private FileFolderService fileFolderService;
|
||||||
|
private NamespaceService namespaceService;
|
||||||
|
private SiteService siteService;
|
||||||
|
private ContentService contentService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param nodeService the NodeService to set
|
||||||
|
*/
|
||||||
|
public void setNodeService(NodeService nodeService)
|
||||||
|
{
|
||||||
|
this.nodeService = nodeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param nodeService the NodeService to set
|
||||||
|
*/
|
||||||
|
public void setUnprotectedNodeService(NodeService nodeService)
|
||||||
|
{
|
||||||
|
this.unprotNodeService = nodeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fileFolderService the FileFolderService to set
|
||||||
|
*/
|
||||||
|
public void setFileFolderService(FileFolderService fileFolderService)
|
||||||
|
{
|
||||||
|
this.fileFolderService = fileFolderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param namespaceService the NamespaceService to set
|
||||||
|
*/
|
||||||
|
public void setNamespaceService(NamespaceService namespaceService)
|
||||||
|
{
|
||||||
|
this.namespaceService = namespaceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param siteService the SiteService to set
|
||||||
|
*/
|
||||||
|
public void setSiteService(SiteService siteService)
|
||||||
|
{
|
||||||
|
this.siteService = siteService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param contentService the ContentService to set
|
||||||
|
*/
|
||||||
|
public void setContentService(ContentService contentService)
|
||||||
|
{
|
||||||
|
this.contentService = contentService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the last modified timestamp for the document.
|
||||||
|
*
|
||||||
|
* The output will be the last modified date as a long toString().
|
||||||
|
*
|
||||||
|
* @param store the store id
|
||||||
|
* @param path document path to an existing document
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void lastModified(final WebScriptResponse res, final String store, final String path)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
AuthenticationUtil.runAs(new RunAsWork<Object>()
|
||||||
|
{
|
||||||
|
@SuppressWarnings("synthetic-access")
|
||||||
|
public Object doWork() throws Exception
|
||||||
|
{
|
||||||
|
final FileInfo fileInfo = resolveFilePath(path);
|
||||||
|
if (fileInfo == null)
|
||||||
|
{
|
||||||
|
throw new WebScriptException("Unable to locate file: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Writer out = res.getWriter();
|
||||||
|
out.write(Long.toString(fileInfo.getModifiedDate().getTime()));
|
||||||
|
out.close();
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("lastModified: " + Long.toString(fileInfo.getModifiedDate().getTime()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, AuthenticationUtil.getSystemUserName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a document.
|
||||||
|
*
|
||||||
|
* The output will be the document content stream.
|
||||||
|
*
|
||||||
|
* @param store the store id
|
||||||
|
* @param path document path
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void getDocument(final WebScriptResponse res, final String store, final String path)
|
||||||
|
{
|
||||||
|
// TODO: could only allow appropriate users to read config docs i.e. site specific...
|
||||||
|
// but currently GET requests need to run unauthenticated before user login occurs.
|
||||||
|
AuthenticationUtil.runAs(new RunAsWork<Object>()
|
||||||
|
{
|
||||||
|
@SuppressWarnings("synthetic-access")
|
||||||
|
public Object doWork() throws Exception
|
||||||
|
{
|
||||||
|
final FileInfo fileInfo = resolveFilePath(path);
|
||||||
|
if (fileInfo == null || fileInfo.isFolder())
|
||||||
|
{
|
||||||
|
res.setStatus(Status.STATUS_NOT_FOUND);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ContentReader reader;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
reader = contentService.getReader(fileInfo.getNodeRef(), ContentModel.PROP_CONTENT);
|
||||||
|
if (reader == null || !reader.exists())
|
||||||
|
{
|
||||||
|
throw new WebScriptException("No content found for file: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = mimetypeService.getMimetypesByExtension().get(ext);
|
||||||
|
if (mt != null)
|
||||||
|
{
|
||||||
|
mimetype = mt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set mimetype for the content and the character encoding + length for the stream
|
||||||
|
res.setContentType(mimetype);
|
||||||
|
res.setContentEncoding(reader.getEncoding());
|
||||||
|
res.setHeader("Last-Modified", Long.toString(fileInfo.getModifiedDate().getTime()));
|
||||||
|
res.setHeader("Content-Length", Long.toString(reader.getSize()));
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("getDocument: " + fileInfo.toString());
|
||||||
|
|
||||||
|
// 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.isDebugEnabled())
|
||||||
|
logger.debug("Client aborted stream read:\n\tnode: " + path + "\n\tcontent: " + reader);
|
||||||
|
}
|
||||||
|
catch (ContentIOException e2)
|
||||||
|
{
|
||||||
|
if (logger.isInfoEnabled())
|
||||||
|
logger.info("Client aborted stream read:\n\tnode: " + path + "\n\tcontent: " + reader);
|
||||||
|
}
|
||||||
|
catch (Throwable err)
|
||||||
|
{
|
||||||
|
if (err.getCause() instanceof SocketException)
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("Client aborted stream read:\n\tnode: " + path + "\n\tcontent: " + reader);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (logger.isInfoEnabled())
|
||||||
|
logger.info(err.getMessage());
|
||||||
|
res.setStatus(Status.STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (AccessDeniedException ae)
|
||||||
|
{
|
||||||
|
res.setStatus(Status.STATUS_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, AuthenticationUtil.getSystemUserName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the document exists.
|
||||||
|
*
|
||||||
|
* The output will be either the string "true" or the string "false".
|
||||||
|
*
|
||||||
|
* @param store the store id
|
||||||
|
* @param path document path
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void hasDocument(final WebScriptResponse res, final String store, final String path) throws IOException
|
||||||
|
{
|
||||||
|
AuthenticationUtil.runAs(new RunAsWork<Object>()
|
||||||
|
{
|
||||||
|
@SuppressWarnings("synthetic-access")
|
||||||
|
public Object doWork() throws Exception
|
||||||
|
{
|
||||||
|
final FileInfo fileInfo = resolveFilePath(path);
|
||||||
|
|
||||||
|
Writer out = res.getWriter();
|
||||||
|
out.write(Boolean.toString(fileInfo != null && !fileInfo.isFolder()));
|
||||||
|
out.close();
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("hasDocument: " + Boolean.toString(fileInfo != null && !fileInfo.isFolder()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, AuthenticationUtil.getSystemUserName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a document.
|
||||||
|
* <p>
|
||||||
|
* Create methods are user authenticated, so the creation of site config must be
|
||||||
|
* allowed for the current user.
|
||||||
|
*
|
||||||
|
* @param store the store id
|
||||||
|
* @param path document path
|
||||||
|
* @param content content of the document to write
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void createDocument(final WebScriptResponse res, final String store, final String path, final InputStream content)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// TODO: don't need to support filenames at the root?
|
||||||
|
final int off = path.lastIndexOf('/');
|
||||||
|
if (off != -1)
|
||||||
|
{
|
||||||
|
FileInfo parentFolder = resolveNodePath(path, true, false);
|
||||||
|
FileInfo fileInfo = this.fileFolderService.create(
|
||||||
|
parentFolder.getNodeRef(), encodePath(path.substring(off + 1)), ContentModel.TYPE_CONTENT);
|
||||||
|
this.contentService.getWriter(
|
||||||
|
fileInfo.getNodeRef(), ContentModel.PROP_CONTENT, true).putContent(content);
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("createDocument: " + fileInfo.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FileExistsException feeErr)
|
||||||
|
{
|
||||||
|
res.setStatus(Status.STATUS_CONFLICT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates multiple XML documents encapsulated in a single one.
|
||||||
|
*
|
||||||
|
* @param store the store id
|
||||||
|
* @param path document path
|
||||||
|
* @param content content of the document to write
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void createDocuments(WebScriptResponse res, String store, InputStream content)
|
||||||
|
{
|
||||||
|
// no implementation currently
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing document.
|
||||||
|
* <p>
|
||||||
|
* Update methods are user authenticated, so the modification of site config must be
|
||||||
|
* allowed for the current user.
|
||||||
|
*
|
||||||
|
* @param store the store id
|
||||||
|
* @param path document path
|
||||||
|
* @param content content to update the document with
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void updateDocument(final WebScriptResponse res, String store, final String path, final InputStream content)
|
||||||
|
{
|
||||||
|
final FileInfo fileInfo = resolveFilePath(path);
|
||||||
|
if (fileInfo == null || fileInfo.isFolder())
|
||||||
|
{
|
||||||
|
res.setStatus(Status.STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentWriter writer = contentService.getWriter(fileInfo.getNodeRef(), ContentModel.PROP_CONTENT, true);
|
||||||
|
writer.putContent(content);
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("updateDocument: " + fileInfo.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an existing document.
|
||||||
|
* <p>
|
||||||
|
* Delete methods are user authenticated, so the deletion of site config must be
|
||||||
|
* allowed for the current user.
|
||||||
|
*
|
||||||
|
* @param store the store id
|
||||||
|
* @param path document path
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void deleteDocument(final WebScriptResponse res, final String store, final String path)
|
||||||
|
{
|
||||||
|
final FileInfo fileInfo = resolveFilePath(path);
|
||||||
|
if (fileInfo == null || fileInfo.isFolder())
|
||||||
|
{
|
||||||
|
res.setStatus(Status.STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nodeService.deleteNode(fileInfo.getNodeRef());
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("deleteDocument: " + fileInfo.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists the document paths under a given path.
|
||||||
|
* <p>
|
||||||
|
* The output will be the list of relative document paths found under the path.
|
||||||
|
* Separated by newline characters.
|
||||||
|
*
|
||||||
|
* @param store the store id
|
||||||
|
* @param path document path
|
||||||
|
* @param recurse true to peform a recursive list, false for direct children only.
|
||||||
|
*
|
||||||
|
* @throws IOException if an error occurs listing the documents
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void listDocuments(final WebScriptResponse res, final String store, final String path, final boolean recurse)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
AuthenticationUtil.runAs(new RunAsWork<Object>()
|
||||||
|
{
|
||||||
|
@SuppressWarnings("synthetic-access")
|
||||||
|
public Object doWork() throws Exception
|
||||||
|
{
|
||||||
|
res.setContentType("text/plain;charset=UTF-8");
|
||||||
|
|
||||||
|
final FileInfo fileInfo = resolveNodePath(path, false, true);
|
||||||
|
if (fileInfo == null || !fileInfo.isFolder())
|
||||||
|
{
|
||||||
|
res.setStatus(Status.STATUS_NOT_FOUND);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
final boolean debug = logger.isDebugEnabled();
|
||||||
|
final Writer out = res.getWriter();
|
||||||
|
final NodeRef surfConfigRef = aquireSurfConfigRef(path, false);
|
||||||
|
Map<NodeRef, String> nameCache = new HashMap<NodeRef, String>();
|
||||||
|
List<FileInfo> files = fileFolderService.search(fileInfo.getNodeRef(), "*", true, false, recurse);
|
||||||
|
for (final FileInfo file : files)
|
||||||
|
{
|
||||||
|
// walking up the parent tree manually until the "surf-config" parent is hit
|
||||||
|
// and manually appending the rest of the cm:name path down to the node.
|
||||||
|
StringBuilder displayPath = new StringBuilder(64);
|
||||||
|
NodeRef ref = unprotNodeService.getPrimaryParent(file.getNodeRef()).getParentRef();
|
||||||
|
while (!ref.equals(surfConfigRef))
|
||||||
|
{
|
||||||
|
String name = nameCache.get(ref);
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
name = (String)unprotNodeService.getProperty(ref, ContentModel.PROP_NAME);
|
||||||
|
nameCache.put(ref, name);
|
||||||
|
}
|
||||||
|
displayPath.insert(0, '/');
|
||||||
|
displayPath.insert(0, name);
|
||||||
|
ref = unprotNodeService.getPrimaryParent(ref).getParentRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write("/alfresco/site-data/");
|
||||||
|
out.write(displayPath.toString());
|
||||||
|
out.write(file.getName());
|
||||||
|
out.write('\n');
|
||||||
|
if (debug) logger.debug(" /alfresco/site-data/" + displayPath.toString() + "/" + file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (AccessDeniedException ae)
|
||||||
|
{
|
||||||
|
res.setStatus(Status.STATUS_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
res.getWriter().close();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, AuthenticationUtil.getSystemUserName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists the document paths matching a file pattern under a given path.
|
||||||
|
*
|
||||||
|
* The output will be the list of relative document paths found under the path that
|
||||||
|
* match the given file pattern. Separated by newline characters.
|
||||||
|
*
|
||||||
|
* @param store the store id
|
||||||
|
* @param path document path
|
||||||
|
* @param pattern file pattern to match - allows wildcards e.g. *.xml or site*.xml
|
||||||
|
*
|
||||||
|
* @throws IOException if an error occurs listing the documents
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void listDocuments(final WebScriptResponse res, final String store, final String path, final String pattern)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
AuthenticationUtil.runAs(new RunAsWork<Object>()
|
||||||
|
{
|
||||||
|
@SuppressWarnings("synthetic-access")
|
||||||
|
public Object doWork() throws Exception
|
||||||
|
{
|
||||||
|
res.setContentType("text/plain;charset=UTF-8");
|
||||||
|
|
||||||
|
final FileInfo fileInfo = resolveNodePath(path, false, true);
|
||||||
|
if (fileInfo == null || !fileInfo.isFolder())
|
||||||
|
{
|
||||||
|
res.setStatus(Status.STATUS_NOT_FOUND);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String filePattern = pattern;
|
||||||
|
if (filePattern == null || filePattern.length() == 0)
|
||||||
|
{
|
||||||
|
filePattern = "*";
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean debug = logger.isDebugEnabled();
|
||||||
|
|
||||||
|
//
|
||||||
|
// TODO: remove use of SearchService here! Jan wrote a DB version for AVM - same for DM required.
|
||||||
|
//
|
||||||
|
|
||||||
|
// encode the qname match - and convert back any wildcard characters
|
||||||
|
/*String qname = ISO9075.encode(filePattern);
|
||||||
|
qname = qname.replace(ISO9075_ASTERIX, "*");
|
||||||
|
|
||||||
|
final String encPath = toQNamePath(path);
|
||||||
|
final StringBuilder query = new StringBuilder(128);
|
||||||
|
query.append("+PATH:\"/").append(NamespaceService.APP_MODEL_PREFIX).append(":").append(ADMROOT)
|
||||||
|
.append(encPath != null && encPath.length() != 0 ? encPath : "")
|
||||||
|
.append("//*\" +QNAME:")
|
||||||
|
.append(qname);
|
||||||
|
|
||||||
|
final Writer out = res.getWriter();
|
||||||
|
final int cropPoint = rootPath.length() + 1;
|
||||||
|
StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, store);
|
||||||
|
String sQuery = query.toString();
|
||||||
|
ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, sQuery);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<NodeRef> nodes = resultSet.getNodeRefs();
|
||||||
|
for (final NodeRef nodeRef : nodes)
|
||||||
|
{
|
||||||
|
String name = (String)unprotNodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
|
||||||
|
Path path = unprotNodeService.getPath(nodeRef);
|
||||||
|
// TODO: optimize this section
|
||||||
|
String displayPath = path.toDisplayPath(unprotNodeService, new PermissionServiceNOOPImpl());
|
||||||
|
out.write(displayPath.substring(cropPoint));
|
||||||
|
out.write('/');
|
||||||
|
out.write(name);
|
||||||
|
out.write('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
resultSet.close();
|
||||||
|
}*/
|
||||||
|
// TODO: close writer!
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, AuthenticationUtil.getSystemUserName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param path cm:name based root relative path
|
||||||
|
* example: /alfresco/site-data/pages/customise-user-dashboard.xml
|
||||||
|
*
|
||||||
|
* @return FileInfo representing the file/folder at the specified path location
|
||||||
|
* or null if the supplied path does not exist in the store
|
||||||
|
*/
|
||||||
|
private FileInfo resolveFilePath(final String path)
|
||||||
|
{
|
||||||
|
return resolveNodePath(path, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param path cm:name based root relative path
|
||||||
|
* example: /alfresco/site-data/pages/customise-user-dashboard.xml
|
||||||
|
* /alfresco/site-data/components
|
||||||
|
* @param create if true create the config and folder dirs for the given path returning
|
||||||
|
* the FileInfo for the last parent in the path, if false only attempt to
|
||||||
|
* resolve the folder path if it exists returning the last element.
|
||||||
|
* @param isFolder True if the path is for a folder, false if it ends in a filename
|
||||||
|
*
|
||||||
|
* @return FileInfo representing the file/folder at the specified path location (see create
|
||||||
|
* parameter above) or null if the supplied path does not exist in the store.
|
||||||
|
*/
|
||||||
|
private FileInfo resolveNodePath(final String path, final boolean create, final boolean isFolder)
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("Resolving path: " + path);
|
||||||
|
FileInfo result = null;
|
||||||
|
if (path != null)
|
||||||
|
{
|
||||||
|
// break down the path into its component elements
|
||||||
|
List<String> pathElements = new ArrayList<String>(4);
|
||||||
|
final StringTokenizer t = new StringTokenizer(encodePath(path), "/");
|
||||||
|
// we require paths of the form /alfresco/site-data/<objecttype>[/<folder>]/<file>.xml
|
||||||
|
if (t.countTokens() >= 3)
|
||||||
|
{
|
||||||
|
t.nextToken(); // skip /alfresco
|
||||||
|
t.nextToken(); // skip /site-data
|
||||||
|
// collect remaining folder path (and file)
|
||||||
|
while (t.hasMoreTokens())
|
||||||
|
{
|
||||||
|
pathElements.add(t.nextToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeRef surfConfigRef = aquireSurfConfigRef(path, create);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (create)
|
||||||
|
{
|
||||||
|
// ensure folders exist down to the specified parent
|
||||||
|
result = FileFolderUtil.makeFolders(
|
||||||
|
this.fileFolderService,
|
||||||
|
surfConfigRef,
|
||||||
|
isFolder ? pathElements : pathElements.subList(0, pathElements.size() - 1),
|
||||||
|
ContentModel.TYPE_FOLDER);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// perform the cm:name path lookup against our config root node
|
||||||
|
if (surfConfigRef != null)
|
||||||
|
{
|
||||||
|
result = this.fileFolderService.resolveNamePath(surfConfigRef, pathElements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException fnfErr)
|
||||||
|
{
|
||||||
|
// this is a valid condition - we return null to indicate failed lookup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aquire (optionally create) the NodeRef to the "surf-config" folder as appropriate
|
||||||
|
* for the given path.
|
||||||
|
* <p>
|
||||||
|
* Disassmbles the path to correct match either user, site or generic folder path.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* @param create
|
||||||
|
*
|
||||||
|
* @return NodeRef to the "surf-config" folder, or null if it does not exist yet.
|
||||||
|
*/
|
||||||
|
private NodeRef aquireSurfConfigRef(final String path, final boolean create)
|
||||||
|
{
|
||||||
|
// remap the path into the appropriate Sites or site relative folder location
|
||||||
|
// by first matching the path to appropriate user or site regex
|
||||||
|
String userId = null;
|
||||||
|
String siteName = null;
|
||||||
|
Matcher matcher;
|
||||||
|
if ((matcher = USER_PATTERN_1.matcher(path)).matches())
|
||||||
|
{
|
||||||
|
userId = matcher.group(1);
|
||||||
|
}
|
||||||
|
else if ((matcher = USER_PATTERN_2.matcher(path)).matches())
|
||||||
|
{
|
||||||
|
userId = matcher.group(1);
|
||||||
|
}
|
||||||
|
else if ((matcher = SITE_PATTERN_1.matcher(path)).matches())
|
||||||
|
{
|
||||||
|
siteName = matcher.group(1);
|
||||||
|
}
|
||||||
|
else if ((matcher = SITE_PATTERN_2.matcher(path)).matches())
|
||||||
|
{
|
||||||
|
siteName = matcher.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeRef surfConfigRef;
|
||||||
|
if (userId != null)
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("...resolved user path id: " + userId);
|
||||||
|
surfConfigRef = getSurfConfigNodeRef(getRootNodeRef(), create);
|
||||||
|
}
|
||||||
|
else if (siteName != null)
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("...resolved site path id: " + siteName);
|
||||||
|
surfConfigRef = getSurfConfigNodeRef(getSiteNodeRef(siteName), create);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("...resolved to generic path.");
|
||||||
|
surfConfigRef = getSurfConfigNodeRef(getRootNodeRef(), create);
|
||||||
|
}
|
||||||
|
return surfConfigRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the "surf-config" noderef under the given root. No attempt will be made
|
||||||
|
* to create the node if it does not exist yet.
|
||||||
|
*
|
||||||
|
* @param rootRef Root node reference where the "surf-config" folder should live
|
||||||
|
*
|
||||||
|
* @return surf-config folder ref if found, null otherwise
|
||||||
|
*/
|
||||||
|
private NodeRef getSurfConfigNodeRef(final NodeRef rootRef)
|
||||||
|
{
|
||||||
|
return getSurfConfigNodeRef(rootRef, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the "surf-config" noderef under the given root. Optionally create the
|
||||||
|
* folder if it does not exist yet. NOTE: must only be set to create if within a
|
||||||
|
* WRITE transaction context.
|
||||||
|
*
|
||||||
|
* @param rootRef Root node reference where the "surf-config" folder should live
|
||||||
|
* @param create True to create the folder if missing, false otherwise
|
||||||
|
*
|
||||||
|
* @return surf-config folder ref if found, null otherwise if not creating
|
||||||
|
*/
|
||||||
|
private NodeRef getSurfConfigNodeRef(final NodeRef rootRef, final boolean create)
|
||||||
|
{
|
||||||
|
NodeRef surfConfigRef = this.unprotNodeService.getChildByName(
|
||||||
|
rootRef, ContentModel.ASSOC_CONTAINS, SURF_CONFIG);
|
||||||
|
//
|
||||||
|
// TODO: protect with RRW - keyed from rootRef?
|
||||||
|
//
|
||||||
|
if (surfConfigRef == null && create)
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("'surf-config' system folder not found under path, creating...");
|
||||||
|
QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, SURF_CONFIG);
|
||||||
|
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(1, 1.0f);
|
||||||
|
properties.put(ContentModel.PROP_NAME, (Serializable) SURF_CONFIG);
|
||||||
|
//
|
||||||
|
// TODO: change this to TYPE_SYSTEMFOLDER before commit? that won't stop config files getting found...
|
||||||
|
// suggest new model type that is not cm:content - and is NOT fts enabled...
|
||||||
|
//
|
||||||
|
ChildAssociationRef ref = this.unprotNodeService.createNode(
|
||||||
|
rootRef, ContentModel.ASSOC_CONTAINS, assocQName, ContentModel.TYPE_FOLDER, properties);
|
||||||
|
surfConfigRef = ref.getChildRef();
|
||||||
|
}
|
||||||
|
return surfConfigRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the Sites folder root node reference
|
||||||
|
*/
|
||||||
|
private NodeRef getRootNodeRef()
|
||||||
|
{
|
||||||
|
return this.siteService.getSiteRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param shortName Site shortname
|
||||||
|
*
|
||||||
|
* @return the given Site folder node reference
|
||||||
|
*/
|
||||||
|
private NodeRef getSiteNodeRef(String shortName)
|
||||||
|
{
|
||||||
|
SiteInfo siteInfo = this.siteService.getSite(shortName);
|
||||||
|
return siteInfo != null ? siteInfo.getNodeRef() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverse a Node and recursively output the file paths it contains.
|
||||||
|
*
|
||||||
|
* @param out Writer for output - relative paths separated by newline characters
|
||||||
|
* @param node The FileInfo node to traverse
|
||||||
|
* @param pattern Optional pattern to match filenames against
|
||||||
|
* @param recurse True to recurse sub-directories
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private void traverseNode(Writer out, FileInfo node, String pattern, boolean recurse)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
final int cropPoint = 0;//TODO:rootPath.length() + 1;
|
||||||
|
|
||||||
|
// TODO: this could be optimized a lot:
|
||||||
|
// 1. Perform the traverse and search by hand (is this faster?) using small nodeService
|
||||||
|
// 2. Keep track of parent path cm:name values so Path is not required
|
||||||
|
List<FileInfo> files = this.fileFolderService.search(getRootNodeRef(), pattern, true, false, recurse);
|
||||||
|
for (final FileInfo file : files)
|
||||||
|
{
|
||||||
|
Path path = this.unprotNodeService.getPath(file.getNodeRef());
|
||||||
|
String displayPath = path.toDisplayPath(this.unprotNodeService, new PermissionServiceNOOPImpl());
|
||||||
|
out.write(displayPath.substring(cropPoint));
|
||||||
|
out.write("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -73,9 +73,6 @@ public class AVMRemoteStore extends BaseRemoteStore
|
|||||||
private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
|
private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
|
||||||
private static ThreadLocal<Transformer> transformer = new ThreadLocal<Transformer>()
|
private static ThreadLocal<Transformer> transformer = new ThreadLocal<Transformer>()
|
||||||
{
|
{
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see java.lang.ThreadLocal#initialValue()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected Transformer initialValue()
|
protected Transformer initialValue()
|
||||||
{
|
{
|
||||||
@@ -585,103 +582,4 @@ public class AVMRemoteStore extends BaseRemoteStore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static String encodePath(final String s)
|
|
||||||
{
|
|
||||||
StringBuilder sb = null; //create on demand
|
|
||||||
char ch;
|
|
||||||
final int len = s.length();
|
|
||||||
for (int i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
ch = s.charAt(i);
|
|
||||||
|
|
||||||
if (('A' <= ch && ch <= 'Z') || // 'A'..'Z'
|
|
||||||
('a' <= ch && ch <= 'z') || // 'a'..'z'
|
|
||||||
('0' <= ch && ch <= '9') || // '0'..'9'
|
|
||||||
ch == '/' ||
|
|
||||||
ch == '\'' || ch == ' ' ||
|
|
||||||
ch == '.' || ch == '~' ||
|
|
||||||
ch == '-' || ch == '_' ||
|
|
||||||
ch == '@' || ch == '!' ||
|
|
||||||
ch == '(' || ch == ')' ||
|
|
||||||
ch == ';' || ch == ',' ||
|
|
||||||
ch == '+' || ch == '$')
|
|
||||||
{
|
|
||||||
if (sb != null)
|
|
||||||
{
|
|
||||||
sb.append(ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ((int)ch <= 0x007f) // other ASCII
|
|
||||||
{
|
|
||||||
if (sb == null)
|
|
||||||
{
|
|
||||||
final String soFar = s.substring(0, i);
|
|
||||||
sb = new StringBuilder(len + 16);
|
|
||||||
sb.append(soFar);
|
|
||||||
}
|
|
||||||
sb.append(hex[ch]);
|
|
||||||
}
|
|
||||||
else if ((int)ch <= 0x07FF) // non-ASCII <= 0x7FF
|
|
||||||
{
|
|
||||||
if (sb == null)
|
|
||||||
{
|
|
||||||
final String soFar = s.substring(0, i);
|
|
||||||
sb = new StringBuilder(len + 16);
|
|
||||||
sb.append(soFar);
|
|
||||||
}
|
|
||||||
sb.append(hex[0xc0 | (ch >> 6)]);
|
|
||||||
sb.append(hex[0x80 | (ch & 0x3F)]);
|
|
||||||
}
|
|
||||||
else // 0x7FF < ch <= 0xFFFF
|
|
||||||
{
|
|
||||||
if (sb == null)
|
|
||||||
{
|
|
||||||
final String soFar = s.substring(0, i);
|
|
||||||
sb = new StringBuilder(len + 16);
|
|
||||||
sb.append(soFar);
|
|
||||||
}
|
|
||||||
sb.append(hex[0xe0 | (ch >> 12)]);
|
|
||||||
sb.append(hex[0x80 | ((ch >> 6) & 0x3F)]);
|
|
||||||
sb.append(hex[0x80 | (ch & 0x3F)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (sb != null ? sb.toString() : s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static String[] hex = {
|
|
||||||
"%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
|
|
||||||
"%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
|
|
||||||
"%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
|
|
||||||
"%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
|
|
||||||
"%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
|
|
||||||
"%28", "%29", "%2a", "%2b", "%2c", "%2d", "%2e", "%2f",
|
|
||||||
"%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37",
|
|
||||||
"%38", "%39", "%3a", "%3b", "%3c", "%3d", "%3e", "%3f",
|
|
||||||
"%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47",
|
|
||||||
"%48", "%49", "%4a", "%4b", "%4c", "%4d", "%4e", "%4f",
|
|
||||||
"%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57",
|
|
||||||
"%58", "%59", "%5a", "%5b", "%5c", "%5d", "%5e", "%5f",
|
|
||||||
"%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67",
|
|
||||||
"%68", "%69", "%6a", "%6b", "%6c", "%6d", "%6e", "%6f",
|
|
||||||
"%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77",
|
|
||||||
"%78", "%79", "%7a", "%7b", "%7c", "%7d", "%7e", "%7f",
|
|
||||||
"%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
|
|
||||||
"%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
|
|
||||||
"%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
|
|
||||||
"%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
|
|
||||||
"%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
|
|
||||||
"%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
|
|
||||||
"%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
|
|
||||||
"%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
|
|
||||||
"%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
|
|
||||||
"%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
|
|
||||||
"%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
|
|
||||||
"%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
|
|
||||||
"%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
|
|
||||||
"%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
|
|
||||||
"%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
|
|
||||||
"%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -38,47 +38,54 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remote Store service.
|
* Remote Store service.
|
||||||
*
|
* <p>
|
||||||
* Responsible for providing remote HTTP based access to a store. Designed to be accessed
|
* 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.
|
* from a web-tier application to remotely mirror a WebScript Store instance.
|
||||||
*
|
* <p>
|
||||||
* Request format:
|
* Request format:
|
||||||
|
* <pre>
|
||||||
* <servicepath>/<method>/<path>[?<args>]
|
* <servicepath>/<method>/<path>[?<args>]
|
||||||
* <servicepath>/<method>/s/<store>/<path>[?<args>]
|
* <servicepath>/<method>/s/<store>/<path>[?<args>]
|
||||||
* <servicepath>/<method>/s/<store>/w/<webapp>/<path>[?<args>]
|
* <servicepath>/<method>/s/<store>/w/<webapp>/<path>[?<args>]
|
||||||
*
|
* </pre><p>
|
||||||
* Example:
|
* Example:
|
||||||
|
* <pre>
|
||||||
* /service/remotestore/lastmodified/sites/xyz/pages/page.xml
|
* /service/remotestore/lastmodified/sites/xyz/pages/page.xml
|
||||||
*
|
* </pre><p>
|
||||||
* where:
|
* where:
|
||||||
|
* <pre>
|
||||||
* /service/remotestore -> service path
|
* /service/remotestore -> service path
|
||||||
* /lastmodified -> method name
|
* /lastmodified -> method name
|
||||||
* /sites/../page.xml -> document path
|
* /sites/../page.xml -> document path
|
||||||
*
|
* </pre><p>
|
||||||
* optional request parameters:
|
* optional request parameters:
|
||||||
*
|
* <pre>
|
||||||
* s -> the avm store id
|
* s -> the avm store id
|
||||||
* w -> the wcm web application id
|
* w -> the wcm web application id
|
||||||
*
|
* </pre><p>
|
||||||
* Note: path is relative to the root path as configured for this webscript bean
|
* Note: path is relative to the root path as configured for this webscript bean
|
||||||
*
|
* <p>
|
||||||
* Further URL arguments may be provided if required by specific API methods.
|
* Further URL arguments may be provided if required by specific API methods.
|
||||||
*
|
* <p>
|
||||||
* For content create and update the request should be POSTed and the content sent as the
|
* For content create and update the request should be POSTed and the content sent as the
|
||||||
* payload of the request content.
|
* payload of the request content.
|
||||||
*
|
* <p>
|
||||||
* Supported API methods:
|
* Supported API methods:
|
||||||
* GET lastmodified -> return long timestamp of a document
|
* <pre>
|
||||||
* GET has -> return true/false of existence for a document
|
* GET lastmodified -> return timestamp of a document in ms since 1970 as a long string value
|
||||||
* GET get -> return document content - in addition the usual HTTP headers for the
|
* GET has -> return true or false string as existence for a document
|
||||||
* character encoding, content type, length and modified date will be supplied
|
* GET get -> return raw document content - in addition the appropriate HTTP headers for the
|
||||||
* GET list -> return the list of available document paths under a path
|
* character encoding, content type, length and modified date will be set
|
||||||
|
* GET list -> return the list of available document paths under a path - UTF-8 response text
|
||||||
* GET listall -> return the list of available document paths (recursively) under a given path
|
* GET listall -> return the list of available document paths (recursively) under a given path
|
||||||
|
* - UTF-8 response text
|
||||||
* GET listpattern -> return the list of document paths matching a file pattern under a given path
|
* GET listpattern -> return the list of document paths matching a file pattern under a given path
|
||||||
|
* - UTF-8 response text
|
||||||
* POST create -> create a new document with request content payload
|
* POST create -> create a new document with request content payload
|
||||||
|
* POST createmulti -> create multiple new documents with request content payload
|
||||||
* POST update -> update an existing document with request content payload
|
* POST update -> update an existing document with request content payload
|
||||||
* DELETE delete -> delete an existing document
|
* DELETE delete -> delete an existing document
|
||||||
*
|
* </pre>
|
||||||
* @author Kevin Roast
|
* @author Kevin Roast
|
||||||
*/
|
*/
|
||||||
public abstract class BaseRemoteStore extends AbstractWebScript
|
public abstract class BaseRemoteStore extends AbstractWebScript
|
||||||
@@ -92,7 +99,6 @@ public abstract class BaseRemoteStore extends AbstractWebScript
|
|||||||
private static final Log logger = LogFactory.getLog(BaseRemoteStore.class);
|
private static final Log logger = LogFactory.getLog(BaseRemoteStore.class);
|
||||||
|
|
||||||
protected String defaultStore;
|
protected String defaultStore;
|
||||||
protected ContentService contentService;
|
|
||||||
protected MimetypeService mimetypeService;
|
protected MimetypeService mimetypeService;
|
||||||
|
|
||||||
|
|
||||||
@@ -104,14 +110,6 @@ public abstract class BaseRemoteStore extends AbstractWebScript
|
|||||||
this.defaultStore = defaultStore;
|
this.defaultStore = defaultStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param contentService the ContentService to set
|
|
||||||
*/
|
|
||||||
public void setContentService(ContentService contentService)
|
|
||||||
{
|
|
||||||
this.contentService = contentService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mimetypeService the MimetypeService to set
|
* @param mimetypeService the MimetypeService to set
|
||||||
*/
|
*/
|
||||||
@@ -271,7 +269,7 @@ public abstract class BaseRemoteStore extends AbstractWebScript
|
|||||||
case UPDATE:
|
case UPDATE:
|
||||||
validatePath(path);
|
validatePath(path);
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
logger.debug("CREATE: content length=" + httpReq.getContentLength());
|
logger.debug("UPDATE: content length=" + httpReq.getContentLength());
|
||||||
updateDocument(res, store, path, httpReq.getInputStream());
|
updateDocument(res, store, path, httpReq.getInputStream());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -447,4 +445,103 @@ public abstract class BaseRemoteStore extends AbstractWebScript
|
|||||||
UPDATE,
|
UPDATE,
|
||||||
DELETE
|
DELETE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
protected static String encodePath(final String s)
|
||||||
|
{
|
||||||
|
StringBuilder sb = null; //create on demand
|
||||||
|
char ch;
|
||||||
|
final int len = s.length();
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
ch = s.charAt(i);
|
||||||
|
|
||||||
|
if (('A' <= ch && ch <= 'Z') || // 'A'..'Z'
|
||||||
|
('a' <= ch && ch <= 'z') || // 'a'..'z'
|
||||||
|
('0' <= ch && ch <= '9') || // '0'..'9'
|
||||||
|
ch == '/' ||
|
||||||
|
ch == '\'' || ch == ' ' ||
|
||||||
|
ch == '.' || ch == '~' ||
|
||||||
|
ch == '-' || ch == '_' ||
|
||||||
|
ch == '@' || ch == '!' ||
|
||||||
|
ch == '(' || ch == ')' ||
|
||||||
|
ch == ';' || ch == ',' ||
|
||||||
|
ch == '+' || ch == '$')
|
||||||
|
{
|
||||||
|
if (sb != null)
|
||||||
|
{
|
||||||
|
sb.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((int)ch <= 0x007f) // other ASCII
|
||||||
|
{
|
||||||
|
if (sb == null)
|
||||||
|
{
|
||||||
|
final String soFar = s.substring(0, i);
|
||||||
|
sb = new StringBuilder(len + 16);
|
||||||
|
sb.append(soFar);
|
||||||
|
}
|
||||||
|
sb.append(hex[ch]);
|
||||||
|
}
|
||||||
|
else if ((int)ch <= 0x07FF) // non-ASCII <= 0x7FF
|
||||||
|
{
|
||||||
|
if (sb == null)
|
||||||
|
{
|
||||||
|
final String soFar = s.substring(0, i);
|
||||||
|
sb = new StringBuilder(len + 16);
|
||||||
|
sb.append(soFar);
|
||||||
|
}
|
||||||
|
sb.append(hex[0xc0 | (ch >> 6)]);
|
||||||
|
sb.append(hex[0x80 | (ch & 0x3F)]);
|
||||||
|
}
|
||||||
|
else // 0x7FF < ch <= 0xFFFF
|
||||||
|
{
|
||||||
|
if (sb == null)
|
||||||
|
{
|
||||||
|
final String soFar = s.substring(0, i);
|
||||||
|
sb = new StringBuilder(len + 16);
|
||||||
|
sb.append(soFar);
|
||||||
|
}
|
||||||
|
sb.append(hex[0xe0 | (ch >> 12)]);
|
||||||
|
sb.append(hex[0x80 | ((ch >> 6) & 0x3F)]);
|
||||||
|
sb.append(hex[0x80 | (ch & 0x3F)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (sb != null ? sb.toString() : s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static String[] hex = {
|
||||||
|
"%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
|
||||||
|
"%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
|
||||||
|
"%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
|
||||||
|
"%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
|
||||||
|
"%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
|
||||||
|
"%28", "%29", "%2a", "%2b", "%2c", "%2d", "%2e", "%2f",
|
||||||
|
"%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37",
|
||||||
|
"%38", "%39", "%3a", "%3b", "%3c", "%3d", "%3e", "%3f",
|
||||||
|
"%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47",
|
||||||
|
"%48", "%49", "%4a", "%4b", "%4c", "%4d", "%4e", "%4f",
|
||||||
|
"%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57",
|
||||||
|
"%58", "%59", "%5a", "%5b", "%5c", "%5d", "%5e", "%5f",
|
||||||
|
"%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67",
|
||||||
|
"%68", "%69", "%6a", "%6b", "%6c", "%6d", "%6e", "%6f",
|
||||||
|
"%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77",
|
||||||
|
"%78", "%79", "%7a", "%7b", "%7c", "%7d", "%7e", "%7f",
|
||||||
|
"%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
|
||||||
|
"%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
|
||||||
|
"%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
|
||||||
|
"%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
|
||||||
|
"%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
|
||||||
|
"%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
|
||||||
|
"%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
|
||||||
|
"%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
|
||||||
|
"%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
|
||||||
|
"%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
|
||||||
|
"%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
|
||||||
|
"%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
|
||||||
|
"%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
|
||||||
|
"%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
|
||||||
|
"%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
|
||||||
|
"%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user