Web Script Framework enhancements: ReadOnly transactions, Buffered Responses

- additional 'allow' attribute on <transaction> element in web script descriptor
   - values: readonly, readwrite (default)
   - readonly means that the whole web script executes in read transaction
   - readonly lighter weight; no flushing, no cache checks/updates
- transaction aware web script response buffers
   - only commits to response when trx is committed
   - fixes ALFCOM-2497 - CMIS: createFolder & immediately add document can fail
   - also means errors half-way thru response result in clean response with error contents only
   - readonly transactions are not buffered
- WebScript RepoStore now uses ReadOnly transaction for gets
- CMIS getter Web Scripts set to ReadOnly transaction
- Fix up Web Script pattern that checks for WebScriptServletResponse using instanceof
   - no longer the case, as it may be wrapped in BufferedResponse
   - use getRuntime() instanceof WebScriptServletRuntime and/or
   - WebScriptServletRuntime.getHttpServletResponse/Request(WebScriptReponse r) - returns null, if none

Tests:
- Run CMIS Tests
- Run CMIS BulkCreateSystemTest (now working)
- Run Share

Suggestion:
- Update your 'read' web script descriptors to include <transaction allow="readonly">. This will improve repo performance significantly.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@14670 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
David Caruana
2009-06-11 18:25:59 +00:00
parent 63bc21f5c5
commit 565c57d893
23 changed files with 291 additions and 53 deletions

View File

@@ -31,6 +31,7 @@ It is recommended that “includeAllowableActions” be used with query statemen
</description> </description>
<url>/api/query</url> <url>/api/query</url>
<authentication>user</authentication> <authentication>user</authentication>
<transaction allow="readonly"/>
<format default="atomfeed"/> <format default="atomfeed"/>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -4,6 +4,7 @@
<url>/api/node/{store_type}/{store_id}/{id}/permissions</url> <url>/api/node/{store_type}/{store_id}/{id}/permissions</url>
<url>/api/path/{store_type}/{store_id}/{id}/permissions</url> <url>/api/path/{store_type}/{store_id}/{id}/permissions</url>
<authentication>guest</authentication> <authentication>guest</authentication>
<transaction allow="readonly"/>
<format default="cmisallowableactions">argument</format> <format default="cmisallowableactions">argument</format>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -30,6 +30,7 @@ If no “maxItems” value is provided, then the Repository will determine an ap
</description> </description>
<url>/api/checkedout?folderId={folderId?}&amp;includeDescendants={includeDescendants?}&amp;filter={filter?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/checkedout?folderId={folderId?}&amp;includeDescendants={includeDescendants?}&amp;filter={filter?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<authentication>user</authentication> <authentication>user</authentication>
<transaction allow="readonly"/>
<format default="atomfeed"/> <format default="atomfeed"/>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -37,6 +37,7 @@ If no “maxItems” value is provided, then the Repository will determine an ap
<url>/api/node/{store_type}/{store_id}/{id}/children?types={types}&amp;filter={filter?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/node/{store_type}/{store_id}/{id}/children?types={types}&amp;filter={filter?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<url>/api/path/{store_type}/{store_id}/{id}/children?types={types}&amp;filter={filter?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/path/{store_type}/{store_id}/{id}/children?types={types}&amp;filter={filter?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<authentication>guest</authentication> <authentication>guest</authentication>
<transaction allow="readonly"/>
<format default="atomfeed">argument</format> <format default="atomfeed">argument</format>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -26,6 +26,7 @@ Each CMIS protocol binding will provide a way for fetching a sub-range within a
<url>/api/node/{store_type}/{store_id}/{id}/content{property}?a={attach?}</url> <url>/api/node/{store_type}/{store_id}/{id}/content{property}?a={attach?}</url>
<url>/api/path/{store_type}/{store_id}/{id}/content{property}?a={attach?}</url> <url>/api/path/{store_type}/{store_id}/{id}/content{property}?a={attach?}</url>
<authentication>guest</authentication> <authentication>guest</authentication>
<transaction allow="readonly"/>
<format default="">argument</format> <format default="">argument</format>
<family>CMIS</family> <family>CMIS</family>
<lifecycle>public_api</lifecycle> <lifecycle>public_api</lifecycle>

View File

@@ -35,6 +35,7 @@ If “includeAllowableActions” is TRUE, the repository will return the allowab
<url>/api/node/{store_type}/{store_id}/{id}/descendants?types={types}&amp;filter={filter?}&amp;depth={depth?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/node/{store_type}/{store_id}/{id}/descendants?types={types}&amp;filter={filter?}&amp;depth={depth?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<url>/api/path/{store_type}/{store_id}/{id}/descendants?types={types}&amp;filter={filter?}&amp;depth={depth?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/path/{store_type}/{store_id}/{id}/descendants?types={types}&amp;filter={filter?}&amp;depth={depth?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<authentication>guest</authentication> <authentication>guest</authentication>
<transaction allow="readonly"/>
<format default="atomfeed">argument</format> <format default="atomfeed">argument</format>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -30,6 +30,7 @@ PropertyCollection includes changeToken (if applicable to repository)<br>
<url>/api/path/{store_type}/{store_id}/{id}?filter={filter?}&amp;returnVersion={returnVersion?}&amp;filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/path/{store_type}/{store_id}/{id}?filter={filter?}&amp;returnVersion={returnVersion?}&amp;filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<!-- TODO: consider /api/node/{store_type}/{store_id}/{id}/{child_node} for atom relative paths --> <!-- TODO: consider /api/node/{store_type}/{store_id}/{id}/{child_node} for atom relative paths -->
<authentication>guest</authentication> <authentication>guest</authentication>
<transaction allow="readonly"/>
<format default="atomentry">argument</format> <format default="atomentry">argument</format>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -29,6 +29,7 @@ If “includeAllowableActions” is TRUE, the repository will return the allowab
<url>/api/node/{store_type}/{store_id}/{id}/parent?returnToRoot={returnToRoot}&amp;filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/node/{store_type}/{store_id}/{id}/parent?returnToRoot={returnToRoot}&amp;filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<url>/api/path/{store_type}/{store_id}/{id}/parent?returnToRoot={returnToRoot}&amp;filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/path/{store_type}/{store_id}/{id}/parent?returnToRoot={returnToRoot}&amp;filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<authentication>guest</authentication> <authentication>guest</authentication>
<transaction allow="readonly"/>
<format default="atomfeed">argument</format> <format default="atomfeed">argument</format>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -27,6 +27,7 @@ If “includeAllowableActions” is TRUE, the repository will return the allowab
<url>/api/node/{store_type}/{store_id}/{id}/parents?filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/node/{store_type}/{store_id}/{id}/parents?filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<url>/api/path/{store_type}/{store_id}/{id}/parents?filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/path/{store_type}/{store_id}/{id}/parents?filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<authentication>guest</authentication> <authentication>guest</authentication>
<transaction allow="readonly"/>
<format default="atomfeed">argument</format> <format default="atomfeed">argument</format>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -3,6 +3,7 @@
<description>Retrieves the properties of a private working copy</description> <description>Retrieves the properties of a private working copy</description>
<url>/api/pwc/{store_type}/{store_id}/{id}?filter={filter?}</url> <url>/api/pwc/{store_type}/{store_id}/{id}?filter={filter?}</url>
<authentication>user</authentication> <authentication>user</authentication>
<transaction allow="readonly"/>
<format default="atomentry">argument</format> <format default="atomentry">argument</format>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -4,6 +4,7 @@
</description> </description>
<url>/api/rel/{store_type}/{store_id}/{id}/type/{rel_type}/target/{target_store_type}/{target_store_id}/{target_id}?filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url> <url>/api/rel/{store_type}/{store_id}/{id}/type/{rel_type}/target/{target_store_type}/{target_store_id}/{target_id}?filter={filter?}&amp;includeAllowableActions={includeAllowableActions?}</url>
<authentication>guest</authentication> <authentication>guest</authentication>
<transaction allow="readonly"/>
<format default="atomentry">argument</format> <format default="atomentry">argument</format>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -21,6 +21,7 @@ When includeInheritedProperties is true, the repository SHOULD return all proper
</description> </description>
<url>/api/type/{typeId}?includeInheritedProperties={includeInheritedProperties?}</url> <url>/api/type/{typeId}?includeInheritedProperties={includeInheritedProperties?}</url>
<authentication>user</authentication> <authentication>user</authentication>
<transaction allow="readonly"/>
<format default="atomentry"/> <format default="atomentry"/>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -3,6 +3,7 @@
<description>Retrieve list of all child Types</description> <description>Retrieve list of all child Types</description>
<url>/api/type/{typeId}/children?includePropertyDefinitions={includePropertyDefinitions?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}</url> <url>/api/type/{typeId}/children?includePropertyDefinitions={includePropertyDefinitions?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}</url>
<authentication>user</authentication> <authentication>user</authentication>
<transaction allow="readonly"/>
<format default="atomfeed"/> <format default="atomfeed"/>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -3,6 +3,7 @@
<description>Retrieve list of all descendant Types</description> <description>Retrieve list of all descendant Types</description>
<url>/api/type/{typeId}/descendants?includePropertyDefinitions={includePropertyDefinitions?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}</url> <url>/api/type/{typeId}/descendants?includePropertyDefinitions={includePropertyDefinitions?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}</url>
<authentication>user</authentication> <authentication>user</authentication>
<transaction allow="readonly"/>
<format default="atomfeed"/> <format default="atomfeed"/>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -28,6 +28,7 @@ If “returnPropertyDefinitions” is False, then the Repository will return onl
</description> </description>
<url>/api/types?type={type?}&amp;includePropertyDefinitions={includePropertyDefinitions?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}</url> <url>/api/types?type={type?}&amp;includePropertyDefinitions={includePropertyDefinitions?}&amp;skipCount={skipCount?}&amp;maxItems={maxItems?}</url>
<authentication>user</authentication> <authentication>user</authentication>
<transaction allow="readonly"/>
<format default="atomfeed"/> <format default="atomfeed"/>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -3,6 +3,7 @@
<description>Retrieve list of documents that are not in any folder</description> <description>Retrieve list of documents that are not in any folder</description>
<url>/api/unfiled</url> <url>/api/unfiled</url>
<authentication>guest</authentication> <authentication>guest</authentication>
<transaction allow="readonly"/>
<format default="atomfeed"/> <format default="atomfeed"/>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -21,6 +21,7 @@ Returns all versions the user can access including checked-out version and priva
<url>/api/node/{store_type}/{store_id}/{id}/versions?filter={filter?}</url> <url>/api/node/{store_type}/{store_id}/{id}/versions?filter={filter?}</url>
<url>/api/path/{store_type}/{store_id}/{id}/versions?filter={filter?}</url> <url>/api/path/{store_type}/{store_id}/{id}/versions?filter={filter?}</url>
<authentication>user</authentication> <authentication>user</authentication>
<transaction allow="readonly"/>
<format default="atomfeed">argument</format> <format default="atomfeed">argument</format>
<family>CMIS</family> <family>CMIS</family>
</webscript> </webscript>

View File

@@ -57,13 +57,12 @@ public class BulkCreateSystemTest
abdera.getFactory().registerExtension(new CMISExtensionFactory()); abdera.getFactory().registerExtension(new CMISExtensionFactory());
AbderaClient client = new AbderaClient(abdera); AbderaClient client = new AbderaClient(abdera);
client.setMaxConnectionsTotal(1);
client.usePreemptiveAuthentication(true); client.usePreemptiveAuthentication(true);
client.addCredentials("http://localhost:8080", null, "basic", new UsernamePasswordCredentials("admin", "admin")); client.addCredentials("http://localhost:8080", null, "basic", new UsernamePasswordCredentials("admin", "admin"));
String root = createFolder(client, String root = createFolder(client,
"http://localhost:8080/alfresco/service/api/path/workspace/SpacesStore/Company%20Home/children", "http://localhost:8080/alfresco/service/api/path/workspace/SpacesStore/Company%20Home/children",
"testfolder7"); "testfolder14");
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
{ {

View File

@@ -243,7 +243,7 @@ public class RepoStore implements Store, TenantDeployer
"Web Script Store " + repoStore.toString() + repoPath + " must exist; it was not found"); "Web Script Store " + repoStore.toString() + repoPath + " must exist; it was not found");
} }
} }
}); }, true, false);
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
@@ -364,7 +364,7 @@ public class RepoStore implements Store, TenantDeployer
return documentPaths != null ? documentPaths.toArray(new String[documentPaths.size()]) : new String[0]; return documentPaths != null ? documentPaths.toArray(new String[documentPaths.size()]) : new String[0];
} }
}); }, true, false);
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }
@@ -423,7 +423,7 @@ public class RepoStore implements Store, TenantDeployer
} }
return documentPaths.toArray(new String[documentPaths.size()]); return documentPaths.toArray(new String[documentPaths.size()]);
} }
}); }, true, false);
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }
@@ -499,7 +499,7 @@ public class RepoStore implements Store, TenantDeployer
return documentPaths.toArray(new String[documentPaths.size()]); return documentPaths.toArray(new String[documentPaths.size()]);
} }
}); }, true, false);
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }
@@ -521,7 +521,7 @@ public class RepoStore implements Store, TenantDeployer
findNodeRef(documentPath), ContentModel.PROP_CONTENT); findNodeRef(documentPath), ContentModel.PROP_CONTENT);
return reader.getLastModified(); return reader.getLastModified();
} }
}); }, true, false);
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }
@@ -542,7 +542,7 @@ public class RepoStore implements Store, TenantDeployer
NodeRef nodeRef = findNodeRef(documentPath); NodeRef nodeRef = findNodeRef(documentPath);
return (nodeRef != null); return (nodeRef != null);
} }
}); }, true, false);
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }
@@ -573,7 +573,7 @@ public class RepoStore implements Store, TenantDeployer
} }
return reader.getContentInputStream(); return reader.getContentInputStream();
} }
}); }, true, false);
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }
@@ -867,7 +867,7 @@ public class RepoStore implements Store, TenantDeployer
} }
return location; return location;
} }
}); }, true, false);
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }

View File

@@ -24,7 +24,12 @@
*/ */
package org.alfresco.repo.web.scripts; package org.alfresco.repo.web.scripts;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -32,6 +37,7 @@ import javax.servlet.http.HttpServletResponse;
import javax.transaction.Status; import javax.transaction.Status;
import javax.transaction.UserTransaction; import javax.transaction.UserTransaction;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.model.Repository; import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
@@ -40,6 +46,7 @@ import org.alfresco.repo.tenant.TenantAdminService;
import org.alfresco.repo.tenant.TenantDeployer; import org.alfresco.repo.tenant.TenantDeployer;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.TransactionListener;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.repository.TemplateService;
@@ -47,15 +54,19 @@ import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.web.scripts.AbstractRuntimeContainer; import org.alfresco.web.scripts.AbstractRuntimeContainer;
import org.alfresco.web.scripts.Authenticator; import org.alfresco.web.scripts.Authenticator;
import org.alfresco.web.scripts.Cache;
import org.alfresco.web.scripts.Description; import org.alfresco.web.scripts.Description;
import org.alfresco.web.scripts.Registry; import org.alfresco.web.scripts.Registry;
import org.alfresco.web.scripts.Runtime;
import org.alfresco.web.scripts.ServerModel; import org.alfresco.web.scripts.ServerModel;
import org.alfresco.web.scripts.WebScript; import org.alfresco.web.scripts.WebScript;
import org.alfresco.web.scripts.WebScriptException; import org.alfresco.web.scripts.WebScriptException;
import org.alfresco.web.scripts.WebScriptRequest; import org.alfresco.web.scripts.WebScriptRequest;
import org.alfresco.web.scripts.WebScriptResponse; import org.alfresco.web.scripts.WebScriptResponse;
import org.alfresco.web.scripts.WrappingWebScriptResponse;
import org.alfresco.web.scripts.Description.RequiredAuthentication; import org.alfresco.web.scripts.Description.RequiredAuthentication;
import org.alfresco.web.scripts.Description.RequiredTransaction; import org.alfresco.web.scripts.Description.RequiredTransaction;
import org.alfresco.web.scripts.Description.TransactionCapability;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
@@ -71,6 +82,9 @@ public class RepositoryContainer extends AbstractRuntimeContainer implements Ten
// Logger // Logger
protected static final Log logger = LogFactory.getLog(RepositoryContainer.class); protected static final Log logger = LogFactory.getLog(RepositoryContainer.class);
// Transaction key for buffered response
private static String BUFFERED_RESPONSE_KEY = RepositoryContainer.class.getName() + ".bufferedresponse";
/** Component Dependencies */ /** Component Dependencies */
private Repository repository; private Repository repository;
private RepositoryImageResolver imageResolver; private RepositoryImageResolver imageResolver;
@@ -92,7 +106,8 @@ public class RepositoryContainer extends AbstractRuntimeContainer implements Ten
/** /**
* @param registryFactory * @param registryFactory
*/ */
public void setRegistryFactory(ObjectFactory registryFactory) { public void setRegistryFactory(ObjectFactory registryFactory)
{
this.registryFactory = registryFactory; this.registryFactory = registryFactory;
} }
@@ -314,9 +329,22 @@ public class RepositoryContainer extends AbstractRuntimeContainer implements Ten
try try
{ {
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("Begin retry transaction block: " + description.getRequiredTransaction()); logger.debug("Begin retry transaction block: " + description.getRequiredTransaction() + "," + description.getTransactionCapability());
script.execute(scriptReq, scriptRes); WebScriptResponse redirectedRes = scriptRes;
if (description.getTransactionCapability() == TransactionCapability.readwrite)
{
if (logger.isDebugEnabled())
logger.debug("Creating Transactional Response for ReadWrite transaction");
// create buffered response that's sensitive transaction boundary
BufferedResponse bufferedRes = new BufferedResponse(scriptRes);
AlfrescoTransactionSupport.bindResource(BUFFERED_RESPONSE_KEY, bufferedRes);
AlfrescoTransactionSupport.bindListener(bufferedRes);
redirectedRes = bufferedRes;
}
script.execute(scriptReq, redirectedRes);
} }
catch(Exception e) catch(Exception e)
{ {
@@ -356,21 +384,16 @@ public class RepositoryContainer extends AbstractRuntimeContainer implements Ten
finally finally
{ {
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("End retry transaction block: " + description.getRequiredTransaction()); logger.debug("End retry transaction block: " + description.getRequiredTransaction() + "," + description.getTransactionCapability());
} }
return null; return null;
} }
}; };
if (description.getRequiredTransaction() == RequiredTransaction.required) boolean readonly = description.getTransactionCapability() == TransactionCapability.readonly;
{ boolean requiresNew = description.getRequiredTransaction() == RequiredTransaction.requiresnew;
retryingTransactionHelper.doInTransaction(work); retryingTransactionHelper.doInTransaction(work, readonly, requiresNew);
}
else
{
retryingTransactionHelper.doInTransaction(work, false, true);
}
} }
} }
@@ -465,4 +488,213 @@ public class RepositoryContainer extends AbstractRuntimeContainer implements Ten
{ {
webScriptsRegistryCache.remove(tenantAdminService.getCurrentUserDomain()); webScriptsRegistryCache.remove(tenantAdminService.getCurrentUserDomain());
} }
/**
* Transactional Buffered Response
*/
private static class BufferedResponse implements TransactionListener, WrappingWebScriptResponse
{
private WebScriptResponse res;
private ByteArrayOutputStream outputStream;
private OutputStreamWriter outputWriter;
/**
* Construct
*
* @param res
*/
public BufferedResponse(WebScriptResponse res)
{
this.res = res;
this.outputStream = new ByteArrayOutputStream(4096);
try
{
this.outputWriter = new OutputStreamWriter(outputStream, "UTF-8");
}
catch (UnsupportedEncodingException e)
{
throw new AlfrescoRuntimeException("Failed to create buffered response", e);
};
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WrappingWebScriptResponse#getNext()
*/
public WebScriptResponse getNext()
{
return res;
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#addHeader(java.lang.String, java.lang.String)
*/
public void addHeader(String name, String value)
{
res.addHeader(name, value);
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#encodeScriptUrl(java.lang.String)
*/
public String encodeScriptUrl(String url)
{
return res.encodeScriptUrl(url);
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#getEncodeScriptUrlFunction(java.lang.String)
*/
public String getEncodeScriptUrlFunction(String name)
{
return res.getEncodeScriptUrlFunction(name);
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream()
*/
public OutputStream getOutputStream() throws IOException
{
return outputStream;
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#getRuntime()
*/
public Runtime getRuntime()
{
return res.getRuntime();
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#getWriter()
*/
public Writer getWriter() throws IOException
{
return outputWriter;
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#reset()
*/
public void reset()
{
outputStream.reset();
res.reset();
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#setCache(org.alfresco.web.scripts.Cache)
*/
public void setCache(Cache cache)
{
res.setCache(cache);
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#setContentType(java.lang.String)
*/
public void setContentType(String contentType)
{
res.setContentType(contentType);
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#setContentEncoding(java.lang.String)
*/
public void setContentEncoding(String contentEncoding)
{
res.setContentEncoding(contentEncoding);
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#setHeader(java.lang.String, java.lang.String)
*/
public void setHeader(String name, String value)
{
res.setHeader(name, value);
}
/*
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#setStatus(int)
*/
public void setStatus(int status)
{
res.setStatus(status);
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.transaction.TransactionListener#afterCommit()
*/
public void afterCommit()
{
writeResponse();
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.transaction.TransactionListener#afterRollback()
*/
public void afterRollback()
{
writeResponse();
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.transaction.TransactionListener#beforeCommit(boolean)
*/
public void beforeCommit(boolean readOnly)
{
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.transaction.TransactionListener#beforeCompletion()
*/
public void beforeCompletion()
{
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.transaction.TransactionListener#flush()
*/
public void flush()
{
}
/**
* Write buffered response to underlying response
*/
private void writeResponse()
{
try
{
if (logger.isDebugEnabled())
logger.debug("Writing Transactional response: size=" + outputStream.size());
outputStream.flush();
outputStream.writeTo(res.getOutputStream());
}
catch (IOException e)
{
throw new AlfrescoRuntimeException("Failed to commit buffered response", e);
}
}
}
} }

View File

@@ -46,7 +46,6 @@ import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.web.scripts.Status; import org.alfresco.web.scripts.Status;
import org.alfresco.web.scripts.WebScriptException; import org.alfresco.web.scripts.WebScriptException;
import org.alfresco.web.scripts.WebScriptResponse; import org.alfresco.web.scripts.WebScriptResponse;
import org.alfresco.web.scripts.servlet.WebScriptServletResponse;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@@ -167,11 +166,10 @@ public class AVMRemoteStore extends BaseRemoteStore
} }
// set mimetype for the content and the character encoding + length for the stream // set mimetype for the content and the character encoding + length for the stream
WebScriptServletResponse httpRes = (WebScriptServletResponse)res; res.setContentType(mimetype);
httpRes.setContentType(mimetype); res.setContentEncoding(reader.getEncoding());
httpRes.getHttpServletResponse().setCharacterEncoding(reader.getEncoding()); res.setHeader("Last-Modified", Long.toString(desc.getModDate()));
httpRes.getHttpServletResponse().setDateHeader("Last-Modified", desc.getModDate()); res.setHeader("Content-Length", Long.toString(reader.getSize()));
httpRes.setHeader("Content-Length", Long.toString(reader.getSize()));
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("AVMRemoteStore.getDocument() " + mimetype + " of size: " + reader.getSize()); logger.debug("AVMRemoteStore.getDocument() " + mimetype + " of size: " + reader.getSize());

View File

@@ -47,7 +47,7 @@ import org.alfresco.web.scripts.WebScriptException;
import org.alfresco.web.scripts.WebScriptRequest; import org.alfresco.web.scripts.WebScriptRequest;
import org.alfresco.web.scripts.WebScriptResponse; import org.alfresco.web.scripts.WebScriptResponse;
import org.alfresco.web.scripts.servlet.HTTPProxy; import org.alfresco.web.scripts.servlet.HTTPProxy;
import org.alfresco.web.scripts.servlet.WebScriptServletResponse; import org.alfresco.web.scripts.servlet.WebScriptServletRuntime;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.dom4j.Attribute; import org.dom4j.Attribute;
@@ -166,11 +166,12 @@ public class SearchProxy extends AbstractWebScript implements InitializingBean
// issue request against search engine // issue request against search engine
// NOTE: This web script must be executed in a HTTP servlet environment // NOTE: This web script must be executed in a HTTP servlet environment
if (!(res instanceof WebScriptServletResponse)) if (!(res.getRuntime() instanceof WebScriptServletRuntime))
{ {
throw new WebScriptException("Search Proxy must be executed in HTTP Servlet environment"); throw new WebScriptException("Search Proxy must be executed in HTTP Servlet environment");
} }
HttpServletResponse servletRes = ((WebScriptServletResponse)res).getHttpServletResponse();
HttpServletResponse servletRes = WebScriptServletRuntime.getHttpServletResponse(res);
SearchEngineHttpProxy proxy = new SearchEngineHttpProxy(req.getServicePath() + "/" + req.getContextPath(), engine, engineUrl, servletRes); SearchEngineHttpProxy proxy = new SearchEngineHttpProxy(req.getServicePath() + "/" + req.getContextPath(), engine, engineUrl, servletRes);
proxy.service(); proxy.service();
} }

View File

@@ -35,7 +35,6 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
@@ -58,8 +57,6 @@ import org.alfresco.web.scripts.WebScriptException;
import org.alfresco.web.scripts.WebScriptRequest; import org.alfresco.web.scripts.WebScriptRequest;
import org.alfresco.web.scripts.WebScriptResponse; import org.alfresco.web.scripts.WebScriptResponse;
import org.alfresco.web.scripts.WebScriptStatus; import org.alfresco.web.scripts.WebScriptStatus;
import org.alfresco.web.scripts.servlet.WebScriptServletRequest;
import org.alfresco.web.scripts.servlet.WebScriptServletResponse;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
@@ -298,14 +295,6 @@ public class StreamContent extends AbstractWebScript
protected void streamContent(WebScriptRequest req, WebScriptResponse res, NodeRef nodeRef, QName propertyQName, boolean attach) protected void streamContent(WebScriptRequest req, WebScriptResponse res, NodeRef nodeRef, QName propertyQName, boolean attach)
throws IOException throws IOException
{ {
// NOTE: This web script must be executed in a HTTP Servlet environment
if (!(req instanceof WebScriptServletRequest))
{
throw new WebScriptException("Content retrieval must be executed in HTTP Servlet environment");
}
HttpServletRequest httpReq = ((WebScriptServletRequest)req).getHttpServletRequest();
HttpServletResponse httpRes = ((WebScriptServletResponse)res).getHttpServletResponse();
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("Retrieving content from node ref " + nodeRef.toString() + " (property: " + propertyQName.toString() + ") (attach: " + attach + ")"); logger.debug("Retrieving content from node ref " + nodeRef.toString() + " (property: " + propertyQName.toString() + ") (attach: " + attach + ")");
@@ -317,14 +306,19 @@ public class StreamContent extends AbstractWebScript
// check If-Modified-Since header and set Last-Modified header as appropriate // check If-Modified-Since header and set Last-Modified header as appropriate
Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED);
long modifiedSince = httpReq.getDateHeader("If-Modified-Since"); String modifiedSinceStr = req.getHeader("If-Modified-Since");
if (modifiedSinceStr == null)
{
modifiedSinceStr = "-1";
}
long modifiedSince = new Long(modifiedSinceStr);
if (modifiedSince > 0L) if (modifiedSince > 0L)
{ {
// round the date to the ignore millisecond value which is not supplied by header // round the date to the ignore millisecond value which is not supplied by header
long modDate = (modified.getTime() / 1000L) * 1000L; long modDate = (modified.getTime() / 1000L) * 1000L;
if (modDate <= modifiedSince) if (modDate <= modifiedSince)
{ {
httpRes.setStatus(HttpServletResponse.SC_NOT_MODIFIED); res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return; return;
} }
} }
@@ -398,15 +392,12 @@ public class StreamContent extends AbstractWebScript
protected void streamContentImpl(WebScriptRequest req, WebScriptResponse res, ContentReader reader, boolean attach, Date modified, String eTag) protected void streamContentImpl(WebScriptRequest req, WebScriptResponse res, ContentReader reader, boolean attach, Date modified, String eTag)
throws IOException throws IOException
{ {
HttpServletRequest httpReq = ((WebScriptServletRequest)req).getHttpServletRequest();
HttpServletResponse httpRes = ((WebScriptServletResponse)res).getHttpServletResponse();
// handle attachment // handle attachment
if (attach == true) if (attach == true)
{ {
// set header based on filename - will force a Save As from the browse if it doesn't recognize it // set header based on filename - will force a Save As from the browse if it doesn't recognize it
// this is better than the default response of the browser trying to display the contents // this is better than the default response of the browser trying to display the contents
httpRes.setHeader("Content-Disposition", "attachment"); res.setHeader("Content-Disposition", "attachment");
} }
// establish mimetype // establish mimetype
@@ -428,9 +419,9 @@ public class StreamContent extends AbstractWebScript
} }
// set mimetype for the content and the character encoding + length for the stream // set mimetype for the content and the character encoding + length for the stream
httpRes.setContentType(mimetype); res.setContentType(mimetype);
httpRes.setCharacterEncoding(reader.getEncoding()); res.setContentEncoding(reader.getEncoding());
httpRes.setHeader("Content-Length", Long.toString(reader.getSize())); res.setHeader("Content-Length", Long.toString(reader.getSize()));
// set caching // set caching
Cache cache = new Cache(); Cache cache = new Cache();