Turned WebScripts registry into an asynchronously-refreshed component

- The size of the object and the nature in which it is read means that it does not really
   fit into the clustered cache model.  We just notify the cluster if the cache entry it needs to be
   reloaded, serving stale data for a short while
 - ALF-19982: BM-0013: Reindex: WebScripts registry is repeatedly reset (DeclarativeRegistry)
 - ALF-19983 BM-0013: Reindex: Solr reindex works very slowly 


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@55415 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2013-09-17 14:39:25 +00:00
parent 17d6ce3730
commit 403fd26ace
6 changed files with 1242 additions and 1180 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -206,11 +206,7 @@
</entry> </entry>
</map> </map>
</property> </property>
<property name="registryFactory"> <property name="webScriptsRegistryCache" ref="webScriptsRegistryCache"/>
<bean class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName"><idref local="webscripts.registry.prototype"/></property>
</bean>
</property>
<!-- Use the time-limited transaction helper to keep request times to an acceptable duration --> <!-- Use the time-limited transaction helper to keep request times to an acceptable duration -->
<property name="transactionService" ref="transactionService" /> <property name="transactionService" ref="transactionService" />
<!-- The transaction helper used to generate error responses must be unlimited --> <!-- The transaction helper used to generate error responses must be unlimited -->
@@ -222,7 +218,6 @@
<property name="scriptProcessorRegistry" ref="webscripts.repo.registry.scriptprocessor" /> <property name="scriptProcessorRegistry" ref="webscripts.repo.registry.scriptprocessor" />
<property name="descriptorService" ref="DescriptorService" /> <property name="descriptorService" ref="DescriptorService" />
<property name="tenantAdminService" ref="tenantAdminService" /> <property name="tenantAdminService" ref="tenantAdminService" />
<property name="webScriptsRegistryCache" ref="webScriptsRegistryCache"/>
<property name="encryptTempFiles" value="${webscripts.encryptTempFiles}"/> <property name="encryptTempFiles" value="${webscripts.encryptTempFiles}"/>
<property name="tempDirectoryName" value="${webscripts.tempDirectoryName}"/> <property name="tempDirectoryName" value="${webscripts.tempDirectoryName}"/>
<property name="memoryThreshold" value="${webscripts.memoryThreshold}"/> <property name="memoryThreshold" value="${webscripts.memoryThreshold}"/>
@@ -235,6 +230,15 @@
<property name="uriIndex" ref="webscripts.index.prototype" /> <property name="uriIndex" ref="webscripts.index.prototype" />
</bean> </bean>
<bean name="webScriptsRegistryCache" class="org.alfresco.repo.web.scripts.RegistryAsynchronouslyRefreshedCache" parent="abstractAsynchronouslyRefreshedCache">
<property name="registryFactory">
<bean class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName"><idref local="webscripts.registry.prototype"/></property>
</bean>
</property>
<property name="retryingTransactionHelper" ref="retryingTransactionHelper" />
</bean>
<bean id="webscripts.js.paging" class="org.alfresco.repo.web.util.paging.Paging" /> <bean id="webscripts.js.paging" class="org.alfresco.repo.web.util.paging.Paging" />
<!-- --> <!-- -->

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.web.scripts;
import org.alfresco.repo.cache.AbstractAsynchronouslyRefreshedCache;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.extensions.webscripts.Registry;
/**
* Asynchronously refreshed cache for repository webscripts.
* <p/>
* This does not stop gratuitous calls to <i>refresh</i> but will ensure that, once an instance has been created,
* a version of the registry is returned even if it is slighly out of date. This can be changed so that it waits
* for reset but is probably not required.
*
* @author Derek Hulley
* @since 4.2.0
*/
public class RegistryAsynchronouslyRefreshedCache extends AbstractAsynchronouslyRefreshedCache<Registry> implements InitializingBean
{
private static Log logger = LogFactory.getLog(RegistryAsynchronouslyRefreshedCache.class);
private ObjectFactory<Registry> registryFactory;
private RetryingTransactionHelper retryingTransactionHelper;
/**
* @param registryFactory factory for web script registries
*/
public void setRegistryFactory(ObjectFactory<Registry> registryFactory)
{
this.registryFactory = registryFactory;
}
/**
* @param retryingTransactionHelper the retryingTransactionHelper to set
*/
public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper)
{
this.retryingTransactionHelper = retryingTransactionHelper;
}
@Override
protected Registry buildCache(final String tenantId)
{
return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Registry>()
{
@Override
public Registry execute() throws Throwable
{
return doBuildCache(tenantId);
}
}, true, false);
}
/**
* This method is thread safe as per contract of {@link #buildCache(String)}.
*/
private Registry doBuildCache(String tenantId)
{
Registry registry = registryFactory.getObject();
registry.reset();
logger.info("Fetching web script registry for tenant " + tenantId);
return registry;
}
@Override
public void afterPropertiesSet() throws Exception
{
PropertyCheck.mandatory(this, "registryFactory", registryFactory);
PropertyCheck.mandatory(this, "retryingTransactionHelper", retryingTransactionHelper);
super.afterPropertiesSet();
}
}

View File

@@ -1,159 +1,123 @@
/* /*
* Copyright (C) 2005-2013 Alfresco Software Limited. * Copyright (C) 2005-2013 Alfresco Software Limited.
* *
* This file is part of Alfresco * This file is part of Alfresco
* *
* Alfresco is free software: you can redistribute it and/or modify * Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by * it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.alfresco.repo.web.scripts; package org.alfresco.repo.web.scripts;
import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.cache.AsynchronouslyRefreshedCache;
import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantAdminService;
import org.alfresco.repo.tenant.TenantDeployer; import org.alfresco.repo.tenant.TenantDeployer;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
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.extensions.webscripts.Registry;
import org.springframework.extensions.webscripts.Registry;
/**
* Tenant-aware Repository (server-tier) container for Web Scripts
/** *
* Tenant-aware Repository (server-tier) container for Web Scripts * @author davidc
* */
* @author davidc public class TenantRepositoryContainer extends RepositoryContainer implements TenantDeployer
*/ {
public class TenantRepositoryContainer extends RepositoryContainer implements TenantDeployer // Logger
{ protected static final Log logger = LogFactory.getLog(TenantRepositoryContainer.class);
// Logger
protected static final Log logger = LogFactory.getLog(TenantRepositoryContainer.class); /* Component Dependencies */
protected TenantAdminService tenantAdminService;
/** Component Dependencies */ protected TransactionService transactionService;
protected TenantAdminService tenantAdminService; private AsynchronouslyRefreshedCache<Registry> registryCache;
protected TransactionService transactionService;
protected ObjectFactory registryFactory; /**
protected SimpleCache<String, Registry> webScriptsRegistryCache; * @param registryCache asynchronously maintained cache for script registries
protected boolean initialized; */
public void setWebScriptsRegistryCache(AsynchronouslyRefreshedCache<Registry> registryCache)
/** {
* @param webScriptsRegistryCache this.registryCache = registryCache;
*/ }
public void setWebScriptsRegistryCache(SimpleCache<String, Registry> webScriptsRegistryCache)
{ /**
this.webScriptsRegistryCache = webScriptsRegistryCache; * @param tenantAdminService service to sort out tenant context
} */
public void setTenantAdminService(TenantAdminService tenantAdminService)
/** {
* @param registryFactory this.tenantAdminService = tenantAdminService;
*/ }
public void setRegistryFactory(ObjectFactory registryFactory)
{ /**
this.registryFactory = registryFactory; * @param transactionService service to give transactions when reading from the container
} */
public void setTransactionService(TransactionService transactionService)
/** {
* @param tenantAdminService super.setTransactionService(transactionService);
*/ this.transactionService = transactionService;
public void setTenantAdminService(TenantAdminService tenantAdminService) }
{
this.tenantAdminService = tenantAdminService; @Override
} public Registry getRegistry()
{
/** Registry registry = registryCache.get();
* @param transactionService the transactionService to set boolean isUpToDate = registryCache.isUpToDate();
*/ if (!isUpToDate && logger.isDebugEnabled())
public void setTransactionService(TransactionService transactionService) {
{ logger.debug("Retrieved out of date web script registry for tenant " + tenantAdminService.getCurrentUserDomain());
super.setTransactionService(transactionService); }
this.transactionService = transactionService; return registry;
} }
@Override
/* (non-Javadoc) public void onEnableTenant()
* @see org.alfresco.web.scripts.AbstractRuntimeContainer#getRegistry() {
*/ init();
@Override }
public Registry getRegistry()
{ @Override
String tenantDomain = tenantAdminService.getCurrentUserDomain(); public void onDisableTenant()
Registry registry = webScriptsRegistryCache.get(tenantDomain); {
if (registry == null) destroy();
{ }
registry = (Registry)registryFactory.getObject();
// We only need to reset the registry if the superclass thinks its already initialized @Override
if (initialized) public void init()
{ {
registry.reset(); tenantAdminService.register(this);
} registryCache.refresh();
webScriptsRegistryCache.put(tenantDomain, registry);
} super.reset();
return registry; }
}
@Override
/* (non-Javadoc) public void destroy()
* @see org.alfresco.repo.tenant.TenantDeployer#onEnableTenant() {
*/ registryCache.refresh();
public void onEnableTenant() }
{
init(); @Override
} public void reset()
{
/* (non-Javadoc) transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>()
* @see org.alfresco.repo.tenant.TenantDeployer#onDisableTenant() {
*/ public Object execute() throws Exception
public void onDisableTenant() {
{ destroy();
destroy(); init();
}
return null;
/* (non-Javadoc) }
* @see org.alfresco.repo.tenant.TenantDeployer#init() }, true, false);
*/ }
public void init() }
{
tenantAdminService.register(this);
super.reset();
initialized = true;
}
/* (non-Javadoc)
* @see org.alfresco.repo.tenant.TenantDeployer#destroy()
*/
public void destroy()
{
webScriptsRegistryCache.remove(tenantAdminService.getCurrentUserDomain());
initialized = false;
}
/* (non-Javadoc)
* @see org.alfresco.web.scripts.AbstractRuntimeContainer#reset()
*/
@Override
public void reset()
{
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>()
{
public Object execute() throws Exception
{
destroy();
init();
return null;
}
}, true, false);
}
}

View File

@@ -27,31 +27,25 @@ public class PublicApiRepositoryContainer extends TenantRepositoryContainer
{ {
protected static final Log logger = LogFactory.getLog(PublicApiRepositoryContainer.class); protected static final Log logger = LogFactory.getLog(PublicApiRepositoryContainer.class);
/** /**
* Execute script within required level of transaction * Execute script within required level of transaction
*
* @param scriptReq
* @param scriptRes
* @throws IOException
*/ */
@Override @Override
protected void transactionedExecute(final WebScript script, final WebScriptRequest scriptReq, final WebScriptResponse scriptRes) protected void transactionedExecute(final WebScript script, final WebScriptRequest scriptReq, final WebScriptResponse scriptRes)
throws IOException throws IOException
{ {
final HttpServletRequest httpServletRequest = WebScriptServletRuntime.getHttpServletRequest(scriptReq); final HttpServletRequest httpServletRequest = WebScriptServletRuntime.getHttpServletRequest(scriptReq);
if(httpServletRequest instanceof PublicApiHttpServletRequest) if(httpServletRequest instanceof PublicApiHttpServletRequest)
{ {
// reset the request input stream if it has been read e.g. by getParameter // reset the request input stream if it has been read e.g. by getParameter
PublicApiHttpServletRequest publicApiRequest = (PublicApiHttpServletRequest)httpServletRequest; PublicApiHttpServletRequest publicApiRequest = (PublicApiHttpServletRequest)httpServletRequest;
publicApiRequest.resetInputStream(); publicApiRequest.resetInputStream();
} }
super.transactionedExecute(script, scriptReq, scriptRes); super.transactionedExecute(script, scriptReq, scriptRes);
} }
/* (non-Javadoc) @Override
* @see org.alfresco.web.scripts.RuntimeContainer#executeScript(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse, org.alfresco.web.scripts.Authenticator)
*/
public void executeScript(final WebScriptRequest scriptReq, final WebScriptResponse scriptRes, final Authenticator auth) public void executeScript(final WebScriptRequest scriptReq, final WebScriptResponse scriptRes, final Authenticator auth)
throws IOException throws IOException
{ {
@@ -84,7 +78,7 @@ public class PublicApiRepositoryContainer extends TenantRepositoryContainer
{ {
public Object doWork() throws Exception public Object doWork() throws Exception
{ {
PublicApiRepositoryContainer.super.executeScript(scriptReq, scriptRes, auth); PublicApiRepositoryContainer.super.executeScript(scriptReq, scriptRes, auth);
return null; return null;
} }
@@ -94,7 +88,7 @@ public class PublicApiRepositoryContainer extends TenantRepositoryContainer
{ {
if (tenant.equalsIgnoreCase(TenantUtil.DEFAULT_TENANT)) if (tenant.equalsIgnoreCase(TenantUtil.DEFAULT_TENANT))
{ {
tenant = tenantAdminService.getUserDomain(user); tenant = tenantAdminService.getUserDomain(user);
} }
// run as explicit tenant // run as explicit tenant
@@ -102,7 +96,7 @@ public class PublicApiRepositoryContainer extends TenantRepositoryContainer
{ {
public Object doWork() throws Exception public Object doWork() throws Exception
{ {
PublicApiRepositoryContainer.super.executeScript(scriptReq, scriptRes, auth); PublicApiRepositoryContainer.super.executeScript(scriptReq, scriptRes, auth);
return null; return null;
} }
}, tenant); }, tenant);

View File

@@ -86,24 +86,25 @@ public class RepositoryContainerTest extends BaseWebScriptTest
/** /**
* Person should be current user irrespective of runas user. * Person should be current user irrespective of runas user.
*/ */
public void testRunAsAdmin() throws Exception { public void testRunAsAdmin() throws Exception
authenticationComponent.setCurrentUser(USER_ONE); {
authenticationComponent.setCurrentUser(USER_ONE);
// No runas specified within our webscript descriptor
Response response = sendRequest(new GetRequest("/test/runas"), STATUS_OK); // No runas specified within our webscript descriptor
assertEquals(USER_ONE, response.getContentAsString()); Response response = sendRequest(new GetRequest("/test/runas"), STATUS_OK);
assertEquals(USER_ONE, response.getContentAsString());
authenticationComponent.setCurrentUser(USER_TWO); authenticationComponent.setCurrentUser(USER_TWO);
// runas "Admin" specified within our webscript descriptor // runas "Admin" specified within our webscript descriptor
response = sendRequest(new GetRequest("/test/runasadmin"), STATUS_OK); response = sendRequest(new GetRequest("/test/runasadmin"), STATUS_OK);
assertEquals(USER_TWO, response.getContentAsString()); assertEquals(USER_TWO, response.getContentAsString());
authenticationComponent.setSystemUserAsCurrentUser(); authenticationComponent.setSystemUserAsCurrentUser();
} }
public void testReset() throws Exception public void testReset() throws Exception
{ {
RepositoryContainer repoContainer = (RepositoryContainer) getServer().getApplicationContext().getBean("webscripts.container"); RepositoryContainer repoContainer = (RepositoryContainer) getServer().getApplicationContext().getBean("webscripts.container");
repoContainer.reset(); repoContainer.reset();