diff --git a/config/alfresco-global.properties.sample b/config/alfresco-global.properties.sample index d35d089227..dbabf5079c 100644 --- a/config/alfresco-global.properties.sample +++ b/config/alfresco-global.properties.sample @@ -31,7 +31,7 @@ # alfresco.rmi.services.port=50500 # # RMI service ports for the individual services. -# These six services are available remotely. +# These seven services are available remotely. # # Assign individual ports for each service for best performance # or run several services on the same port. You can even run everything on 50500 if needed. @@ -44,3 +44,4 @@ #authentication.rmi.service.port=50504 #repo.rmi.service.port=50505 #action.rmi.service.port=50506 +#wcm-deployment-receiver.rmi.service.port=50507 \ No newline at end of file diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml index 44deadc447..3d320cd7ca 100644 --- a/config/alfresco/authentication-services-context.xml +++ b/config/alfresco/authentication-services-context.xml @@ -142,8 +142,8 @@ - - + + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 261a80f8dc..e90acf725e 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -387,6 +387,9 @@ + + + diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index f243c7b94c..2f818357dd 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -498,39 +498,6 @@ - - - - - - - - - - - - org.alfresco.cache.sysAdminCache - - - - - - - - - - - - - - org.alfresco.sysAdminTransactionalCache - - - 10 - - - - diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 5253fb490c..323ce9b7c9 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -214,8 +214,6 @@ - ${server.maxusers} - ${server.singleuseronly.name} @@ -316,8 +314,11 @@ - - + + + + + ${server.transaction.allow-writes} @@ -1396,5 +1397,17 @@ 10 20 - + + + + + + + + + org.alfresco.repo.admin.SysAdminParams + + + + diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 88a1086708..57b4a3872f 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -317,12 +317,6 @@ eternal="true" overflowToDisk="true" /> - - - - diff --git a/config/alfresco/subsystems/Authentication/alfresco/alfresco-authentication-context.xml b/config/alfresco/subsystems/Authentication/alfresco/alfresco-authentication-context.xml index 4059525b1e..fffc263a3a 100644 --- a/config/alfresco/subsystems/Authentication/alfresco/alfresco-authentication-context.xml +++ b/config/alfresco/subsystems/Authentication/alfresco/alfresco-authentication-context.xml @@ -114,8 +114,8 @@ - - + + diff --git a/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml b/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml index 65c1708705..7e37f55708 100644 --- a/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml +++ b/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml @@ -82,8 +82,8 @@ - - + + diff --git a/config/alfresco/subsystems/Authentication/common-ldap-context.xml b/config/alfresco/subsystems/Authentication/common-ldap-context.xml index 0075fd01b0..a38df65dfa 100644 --- a/config/alfresco/subsystems/Authentication/common-ldap-context.xml +++ b/config/alfresco/subsystems/Authentication/common-ldap-context.xml @@ -98,8 +98,8 @@ - - + + diff --git a/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication-context.xml b/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication-context.xml index 66bbc2b2fe..77ccf8bd9d 100644 --- a/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication-context.xml +++ b/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication-context.xml @@ -68,8 +68,8 @@ - - + + diff --git a/config/alfresco/subsystems/Authentication/passthru/passthru-authentication-context.xml b/config/alfresco/subsystems/Authentication/passthru/passthru-authentication-context.xml index 12267243ca..b0417da58e 100644 --- a/config/alfresco/subsystems/Authentication/passthru/passthru-authentication-context.xml +++ b/config/alfresco/subsystems/Authentication/passthru/passthru-authentication-context.xml @@ -103,8 +103,8 @@ - - + + diff --git a/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml b/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml index f67dffe3d3..22bdaba0a0 100644 --- a/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml +++ b/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml @@ -1,9 +1,12 @@ - - - + + + @@ -13,7 +16,7 @@ - + ${synchronization.synchronizeChangesOnly} @@ -23,22 +26,22 @@ - ${synchronization.import.cron} - + ${synchronization.import.cron} + - + - + ${synchronization.syncWhenMissingPeopleLogIn} - + ${synchronization.syncOnStartup} - + ${synchronization.autoCreatePeopleOnLogin} @@ -54,7 +57,10 @@ - + + + + userRegistry diff --git a/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml new file mode 100644 index 0000000000..1aef144a3d --- /dev/null +++ b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml @@ -0,0 +1,18 @@ + + + + + + + + ${server.maxusers} + + + ${server.allowedusers} + + + ${server.transaction.allow-writes} + + + + diff --git a/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties new file mode 100644 index 0000000000..c1a3b01789 --- /dev/null +++ b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties @@ -0,0 +1,3 @@ +server.maxusers=-1 +server.allowedusers= +server.transaction.allow-writes=true \ No newline at end of file diff --git a/source/java/org/alfresco/linkvalidation/LinkValidationService.java b/source/java/org/alfresco/linkvalidation/LinkValidationService.java index 44bb79393a..bafc440d78 100644 --- a/source/java/org/alfresco/linkvalidation/LinkValidationService.java +++ b/source/java/org/alfresco/linkvalidation/LinkValidationService.java @@ -190,7 +190,5 @@ public interface LinkValidationService //------------------------------------------------------------------------- public List getHrefsDependentUponFile(String path); - public void setLinkValidationDisabled(boolean disabled); - public boolean isLinkValidationDisabled(); } diff --git a/source/java/org/alfresco/repo/admin/RepoServerMgmt.java b/source/java/org/alfresco/repo/admin/RepoServerMgmt.java index 3e8aee2a94..28b4b0689b 100644 --- a/source/java/org/alfresco/repo/admin/RepoServerMgmt.java +++ b/source/java/org/alfresco/repo/admin/RepoServerMgmt.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 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 @@ -15,17 +15,15 @@ * 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: + * 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 received 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.admin; -import java.util.ArrayList; -import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @@ -34,29 +32,19 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.linkvalidation.LinkValidationService; import org.alfresco.repo.security.authentication.AbstractAuthenticationService; import org.alfresco.repo.transaction.TransactionServiceImpl; -import org.alfresco.service.license.LicenseService; -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.NoSuchBeanDefinitionException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAware, InitializingBean +public class RepoServerMgmt implements RepoServerMgmtMBean { private static final Log log = LogFactory.getLog(RepoServerMgmt.class); - private ApplicationContext ctx; // to get license component, if installed - private TransactionServiceImpl transactionService; private AbstractAuthenticationService authenticationService; private LinkValidationService linkValidationService; - private boolean initialised = false; - public void setTransactionService(TransactionServiceImpl transactionService) { this.transactionService = transactionService; @@ -73,50 +61,6 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw this.linkValidationService = linkValidationService; } - public void setApplicationContext(ApplicationContext ctx) - { - this.ctx = ctx; - } - - public void setReadOnly(boolean readOnly) - { - if (readOnly && isReadOnly()) - { - log.warn("Alfresco is already read-only"); - return; - } - - if (!readOnly && !isReadOnly()) - { - log.warn("Alfresco is already read-write"); - return; - } - - if (!readOnly) - { - LicenseService licenseService = null; - try - { - licenseService = (LicenseService) ctx.getBean("org.alfresco.enterprise.license.LicenseComponent"); - readOnly = !licenseService.isLicenseValid(); - } - catch (NoSuchBeanDefinitionException e) - { - // ignore - } - } - transactionService.setAllowWrite(!readOnly); - - if (readOnly) - { - log.warn("Alfresco set to be read-only"); - } - else - { - log.info("Alfresco set to be read-write"); - } - } - public boolean isReadOnly() { return transactionService.isReadOnly(); @@ -182,130 +126,11 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw log.info("User invalidated: " + username); } - public void setSingleUserOnly(String allowedUsername) - { - - List allowedUsers = null; - if (PropertyCheck.isValidPropertyString(allowedUsername)) - { - allowedUsers = new ArrayList(0); - allowedUsers.add(allowedUsername); - - if (initialised) - { - int maxUsers = getMaxUsers(); - invalidateTicketsAll(); - - if (maxUsers != 0) - { - log.warn("Alfresco set to allow single-user (" + allowedUsername + ") logins only"); - } - else - { - log.warn("Alfresco set to allow single-user (" + allowedUsername + ") logins - although further logins are currently prevented (limit = 0)"); - } - } - } - else - { - if (initialised) - { - int maxUsers = getMaxUsers(); - if (maxUsers == -1) - { - log.info("Alfresco set to allow logins (no limit set)"); - } - else if (maxUsers == 0) - { - log.warn("Alfresco set to allow logins - although further logins are currently prevented (limit = 0)"); - } - else if (maxUsers != 0) - { - log.info("Alfresco set to allow logins (limit = " + maxUsers + ")"); - } - } - } - - authenticationService.setAllowedUsers(allowedUsers); - } - - public String getSingleUserOnly() - { - List allowedUsers = authenticationService.getAllowedUsers(); - if (allowedUsers != null) - { - if (allowedUsers.size() > 1) - { - throw new AlfrescoRuntimeException("Unexpected: more than one user allowed"); - } - if (allowedUsers.size() == 1) - { - return allowedUsers.get(0); - } - } - return null; - } - - public void setMaxUsers(int maxUsers) - { - authenticationService.setMaxUsers(maxUsers); - - if (initialised) - { - String singleUserOnlyName = getSingleUserOnly(); - if (maxUsers == -1) - { - if ((singleUserOnlyName != null) && (!singleUserOnlyName.equals(""))) - { - log.info("Alfresco set to allow logins (no limit set) - although currently restricted to single-user (" + singleUserOnlyName + ")"); - } - else - { - log.info("Alfresco set to allow logins (no limit set)"); - } - } - else if (maxUsers == 0) - { - log.warn("Alfresco set to prevent further logins (limit = 0)"); - } - else - { - if ((singleUserOnlyName != null) && (!singleUserOnlyName.equals(""))) - { - log.info("Alfresco set to allow logins (limit = " + maxUsers + ") - although currently restricted to single-user (" + singleUserOnlyName + ")"); - } - else - { - log.info("Alfresco set to allow logins (limit = " + maxUsers + ")"); - } - } - } - } - public int getMaxUsers() { return authenticationService.getMaxUsers(); } - public void setLinkValidationDisabled(boolean disable) - { - if (linkValidationService == null) - { - log.error("LinkValidationService not registered"); - throw new AlfrescoRuntimeException("LinkValidationService not registered"); - } - - linkValidationService.setLinkValidationDisabled(disable); - if (disable) - { - log.warn("Link validation disabled"); - } - else - { - log.info("Link validation enabled"); - } - } - public boolean isLinkValidationDisabled() { if (linkValidationService == null) @@ -316,9 +141,4 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw return linkValidationService.isLinkValidationDisabled(); } - - public void afterPropertiesSet() throws Exception - { - initialised = true; - } } diff --git a/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java b/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java index 7bb3dbb434..c8d378af9d 100644 --- a/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java +++ b/source/java/org/alfresco/repo/admin/RepoServerMgmtMBean.java @@ -32,13 +32,6 @@ package org.alfresco.repo.admin; */ public interface RepoServerMgmtMBean { - /** - * Set whether Repository allows writes or not - * - * @param readOnly true is READONLY, false is WRITEABLE - */ - public void setReadOnly(boolean readOnly); - /** * Does the Repository allows writes or not ? * @@ -117,42 +110,6 @@ public interface RepoServerMgmtMBean */ public void invalidateUser(String username); - /** - * Set whether Repository allows single user mode or not - * - * If single user mode is set then all tickets will be invalidated first before allowing the - * named user to login (with one or more sessions) assuming maxUsers is not set to 0 - * - * Note: This can also be configured at startup. Refer to repository property (server.singleuseronly.name). - * - * @param String allowed username (eg. 'admin') or null to unset (ie. allow all users) - */ - public void setSingleUserOnly(String allowedUsername); - - /** - * If Repository is in single user mode then return the name of the allowed user else return null - * - * @param String allowed username (eg. 'admin') or null (ie. allow all users) - */ - public String getSingleUserOnly(); - - /** - * Set limit for max users and/or prevent further logins - * - * If number of non-expired logins is greater or equal to the limit then further logins will be prevented - * otherwise valid login attempt will be permitted, unless the system is in single-user mode. - * - * Note: - * - * Max users = 0 prevents further logins (will also prevent single-user mode login) - * Max users = -1 allow logins (without a max limit) - * - * Note: This can also be configured at startup. Refer to repository property (server.maxusers). - * - * @param maxUsers - */ - public void setMaxUsers(int maxUsers); - /** * Get limit for max users * @@ -166,13 +123,7 @@ public interface RepoServerMgmtMBean */ public int getMaxUsers(); - /** - * Disable or re-enable link validation - * - * @param disable true = disable, false = re-enable - */ - public void setLinkValidationDisabled(boolean disable); - + /** * Is link validation disabled ? * diff --git a/source/java/org/alfresco/repo/admin/SysAdminParams.java b/source/java/org/alfresco/repo/admin/SysAdminParams.java new file mode 100644 index 0000000000..722556d684 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/SysAdminParams.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2009 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 received 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.admin; + +import java.util.List; + +/** + * An interface for retrieving configurable system parameters. + * + * @author dward + */ +public interface SysAdminParams +{ + + /** + * Do we allow write operations by non-system users on the repository?. + * + * @return true if we allow write operations by non-system users on the repository + */ + public boolean getAllowWrite(); + + /** + * Gets the list of users who are allowed to log in. + * + * @return the allowed user list or null if all users are allowed to log in + */ + public List getAllowedUserList(); + + /** + * Gets the maximum number of users who are allowed to log in. + * + * @return the the maximum number of users who are allowed to log in + */ + public int getMaxUsers(); +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/admin/SysAdminParamsImpl.java b/source/java/org/alfresco/repo/admin/SysAdminParamsImpl.java new file mode 100644 index 0000000000..77d4d8a5ff --- /dev/null +++ b/source/java/org/alfresco/repo/admin/SysAdminParamsImpl.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2005-2009 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 received 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.admin; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.alfresco.service.license.LicenseService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * Configurable system parameters. + */ +public class SysAdminParamsImpl implements SysAdminParams, ApplicationContextAware, InitializingBean +{ + /** The application context, to get license component, if installed. */ + private ApplicationContext ctx; + + /** The max users. */ + private Integer maxUsers; + + /** The allowed users. */ + private List allowedUsers; + + /** The allow write. */ + private boolean allowWrite = true; + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context. + * ApplicationContext) + */ + public void setApplicationContext(ApplicationContext ctx) + { + this.ctx = ctx; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + if (this.allowWrite) + { + LicenseService licenseService = null; + try + { + licenseService = (LicenseService) this.ctx.getBean("licenseService"); + this.allowWrite = licenseService.isLicenseValid(); + } + catch (NoSuchBeanDefinitionException e) + { + // ignore + } + } + } + + /** + * Sets the list of users who are allowed to log in. + * + * @param allowedUsers + * a comma-separated list of users who are allowed to log in or null if all users are + * allowed to log in + */ + public void setAllowedUsers(String allowedUsers) + { + StringTokenizer tkn = new StringTokenizer(allowedUsers, ","); + int length = tkn.countTokens(); + if (length > 0) + { + this.allowedUsers = new ArrayList(length); + while (tkn.hasMoreTokens()) + { + this.allowedUsers.add(tkn.nextToken().trim()); + } + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.admin.SysAdminParams#getAllowedUserList() + */ + public List getAllowedUserList() + { + return this.allowedUsers; + } + + /** + * Sets the maximum number of users who are allowed to log in. + * + * @param maxUsers + * the maximum number of users who are allowed to log in + */ + public void setMaxUsers(int maxUsers) + { + this.maxUsers = new Integer(maxUsers); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.admin.SysAdminParams#getMaxUsers() + */ + public int getMaxUsers() + { + return this.maxUsers; + } + + /** + * Controls where we allow write operations by non-system users on the repository. + * + * @param allowWrite + * true if we allow write operations by non-system users on the repository + */ + public void setAllowWrite(boolean allowWrite) + { + this.allowWrite = allowWrite; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.admin.SysAdminParams#getAllowWrite() + */ + public boolean getAllowWrite() + { + return this.allowWrite; + } + +} diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java index 62be916e2e..448a629d3d 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java @@ -165,21 +165,31 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc @Override protected void onBootstrap(ApplicationEvent event) { - // Initialize the repository descriptor - // note: this requires that the repository schema has already been initialized - final RetryingTransactionCallback createDescriptorWork = new RetryingTransactionCallback() + AuthenticationUtil.runAs(new RunAsWork() { - public Descriptor execute() throws ClassNotFoundException + public Object doWork() throws Exception { - boolean initialiseHeartBeat = false; + final boolean initialiseHeartBeat; // Initialize license service (if installed) - DescriptorServiceImpl.this.licenseService = (LicenseService) constructSpecialService("org.alfresco.enterprise.license.LicenseComponent"); + DescriptorServiceImpl.this.licenseService = DescriptorServiceImpl.this.transactionService + .getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() + { + public LicenseService execute() + { + return (LicenseService) constructSpecialService("org.alfresco.enterprise.license.LicenseComponent"); + } + }, DescriptorServiceImpl.this.transactionService.isReadOnly(), false); if (DescriptorServiceImpl.this.licenseService == null) { DescriptorServiceImpl.this.licenseService = new NOOPLicenseService(); initialiseHeartBeat = true; } + else + { + initialiseHeartBeat = false; + } // Make the license service available through the application context as a singleton for other beans // that need it (e.g. the HeartBeat). @@ -190,39 +200,41 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc "licenseService", DescriptorServiceImpl.this.licenseService); } - // verify license, but only if license component is installed - try - { - DescriptorServiceImpl.this.licenseService.verifyLicense(); - LicenseDescriptor l = DescriptorServiceImpl.this.licenseService.getLicense(); - // Initialize the heartbeat unless it is disabled by the license - if (initialiseHeartBeat || l == null || !l.isHeartBeatDisabled()) - { - DescriptorServiceImpl.this.heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat"); - } - } - catch (LicenseException e) - { - // Initialize heart beat anyway - DescriptorServiceImpl.this.heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat"); - throw e; - } + DescriptorServiceImpl.this.installedRepoDescriptor = DescriptorServiceImpl.this.transactionService + .getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Descriptor execute() throws ClassNotFoundException + { - // persist the server descriptor values - DescriptorServiceImpl.this.currentRepoDescriptor = DescriptorServiceImpl.this.currentRepoDescriptorDAO - .updateDescriptor(DescriptorServiceImpl.this.serverDescriptor); + // verify license, but only if license component is installed + try + { + DescriptorServiceImpl.this.licenseService.verifyLicense(); + LicenseDescriptor l = DescriptorServiceImpl.this.licenseService.getLicense(); + // Initialize the heartbeat unless it is disabled by the license + if (initialiseHeartBeat || l == null || !l.isHeartBeatDisabled()) + { + DescriptorServiceImpl.this.heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat"); + } + } + catch (LicenseException e) + { + // Initialize heart beat anyway + DescriptorServiceImpl.this.heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat"); + throw e; + } - // create the installed descriptor - Descriptor installed = DescriptorServiceImpl.this.installedRepoDescriptorDAO.getDescriptor(); - return installed == null ? new UnknownDescriptor() : installed; - } - }; - this.installedRepoDescriptor = AuthenticationUtil.runAs(new RunAsWork() - { - public Descriptor doWork() throws Exception - { - return DescriptorServiceImpl.this.transactionService.getRetryingTransactionHelper().doInTransaction( - createDescriptorWork, DescriptorServiceImpl.this.transactionService.isReadOnly(), false); + // persist the server descriptor values + DescriptorServiceImpl.this.currentRepoDescriptor = DescriptorServiceImpl.this.currentRepoDescriptorDAO + .updateDescriptor(DescriptorServiceImpl.this.serverDescriptor); + + // create the installed descriptor + Descriptor installed = DescriptorServiceImpl.this.installedRepoDescriptorDAO + .getDescriptor(); + return installed == null ? new UnknownDescriptor() : installed; + } + }, DescriptorServiceImpl.this.transactionService.isReadOnly(), false); + return null; } }, AuthenticationUtil.getSystemUserName()); diff --git a/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java b/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java index 1e968a61e3..da8cee3dc0 100644 --- a/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java +++ b/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java @@ -24,10 +24,13 @@ */ package org.alfresco.repo.management.subsystems; +import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Properties; +import java.util.Set; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanNameAware; @@ -43,7 +46,10 @@ import org.springframework.context.event.ContextRefreshedEvent; /** * A base class for {@link PropertyBackedBean}s. Gets its category from its Spring bean name and automatically * propagates and resolves property defaults on initialization. Automatically destroys itself on server shutdown. - * Communicates its creation and destruction to a {@link PropertyBackedBeanRegistry}. + * Communicates its creation and destruction and start and stop events to a {@link PropertyBackedBeanRegistry}. Listens + * for start and stop events from remote nodes in order to keep the bean in sync with edits made on a remote node. On + * receiving a start event from a remote node, the bean is completely reinitialized, allowing it to be resynchronized + * with any persisted changes. * * @author dward */ @@ -51,12 +57,8 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, ApplicationListener, InitializingBean, DisposableBean, BeanNameAware { - /** The root component of the default ID. */ - protected static final String DEFAULT_ID_ROOT = "default"; - - /** The default ID (when we do not expect there to be more than one instance within a category). */ - protected static final List DEFAULT_ID = Collections - .singletonList(AbstractPropertyBackedBean.DEFAULT_ID_ROOT); + /** The default final part of an ID. */ + protected static final String DEFAULT_INSTANCE_NAME = "default"; /** The parent application context. */ private ApplicationContext parent; @@ -64,12 +66,15 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, /** The registry of all property backed beans. */ private PropertyBackedBeanRegistry registry; - /** The hierarchical id. Must be unique within the category. */ - private List id = AbstractPropertyBackedBean.DEFAULT_ID; - - /** The category. */ + /** The category (first part of the ID). */ private String category; + /** The hierarchical instance path within the category (second part of the ID). */ + private List instancePath = Collections.singletonList(AbstractPropertyBackedBean.DEFAULT_INSTANCE_NAME); + + /** The combined unique id. */ + private List id; + /** Should the application context be started on startup of the parent application?. */ private boolean autoStart; @@ -79,6 +84,12 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, /** Resolves placeholders in the property defaults. */ private DefaultResolver defaultResolver = new DefaultResolver(); + /** Has the state been started yet?. */ + private boolean isStarted; + + /** The state. */ + private PropertyBackedBeanState state; + /* * (non-Javadoc) * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context. @@ -120,14 +131,14 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, } /** - * Sets the id. + * Sets the hierarchical instance path within the category (second part of the ID).. * - * @param id - * the id to set + * @param instancePath + * the instance path */ - public void setId(List id) + public void setInstancePath(List instancePath) { - this.id = id; + this.instancePath = instancePath; } /** @@ -189,44 +200,126 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, return this.parent; } + /** + * Gets the state. + * + * @param start + * are we making use of the state? I.e. should we start it if it has not been already? + * @return the state + */ + protected synchronized PropertyBackedBeanState getState(boolean start) + { + if (start) + { + start(); + } + return this.state; + } + /* * (non-Javadoc) * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ public void afterPropertiesSet() throws Exception { - // Override default settings using corresponding global defaults (this allows installer settings - // to propagate through) - for (String property : getPropertyNames()) - { - String value = resolveDefault(property); - if (value != null) - { - setProperty(property, value); - } - } + // Derive the unique ID from the category and instance path + List path = getInstancePath(); + this.id = new ArrayList(path.size() + 1); + this.id.add(getCategory()); + this.id.addAll(getInstancePath()); - this.registry.register(this); + init(); + } + + /** + * Initializes or resets the bean and its state. + */ + public void init() + { + if (this.state == null) + { + try + { + this.state = createInitialState(); + applyDefaultOverrides(this.state); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + this.registry.register(this); + } } /* * (non-Javadoc) - * @see org.alfresco.repo.management.SelfDescribingBean#getId() + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#revert() + */ + public synchronized void revert() + { + stop(); + destroy(true); + init(); + } + + /** + * Creates the initial state. + * + * @return the property backed bean state + * @throws IOException + * Signals that an I/O exception has occurred. + */ + protected abstract PropertyBackedBeanState createInitialState() throws IOException; + + /** + * Applies default overrides to the initial state. + * + * @param state + * the state + * @throws IOException + * Signals that an I/O exception has occurred. + */ + protected void applyDefaultOverrides(PropertyBackedBeanState state) throws IOException + { + for (String name : state.getPropertyNames()) + { + String override = resolveDefault(name); + if (override != null) + { + state.setProperty(name, override); + } + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getId() */ public List getId() { return this.id; } - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getCategory() + /** + * Gets the category. + * + * @return the category */ - public String getCategory() + protected String getCategory() { return this.category; } + /** + * Gets the hierarchical instance path within the category (second part of the ID). + * + * @return the instance path + */ + protected List getInstancePath() + { + return this.instancePath; + } + /* * (non-Javadoc) * @see org.springframework.beans.factory.DisposableBean#destroy() @@ -236,14 +329,22 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, destroy(false); } - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#destroy(boolean) + /** + * Releases any resources held by this component. + * + * @param isPermanent + * is the component being destroyed forever, i.e. should persisted values be removed? On server shutdown, + * this value would be false, whereas on the removal of a dynamically created instance, this + * value would be true. */ - public void destroy(boolean isPermanent) + protected synchronized void destroy(boolean isPermanent) { - stop(); - this.registry.deregister(this, isPermanent); + if (this.state != null) + { + stop(false); + this.registry.deregister(this, isPermanent); + this.state = null; + } } /* @@ -273,7 +374,113 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, { if (this.autoStart && event instanceof ContextRefreshedEvent && event.getSource() == this.parent) { - start(); + start(false); + } + else if (event instanceof PropertyBackedBeanStartedEvent) + { + synchronized (this) + { + if (!this.isStarted) + { + // Reinitialize so that we pick up state changes from the database + destroy(false); + start(false); + } + } + } + else if (event instanceof PropertyBackedBeanStoppedEvent) + { + // Completely destroy the state so that it will have to be reinitialized should the bean be put back in to + // use by this node + destroy(false); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBeanState#getProperty(java.lang.String) + */ + public synchronized String getProperty(String name) + { + init(); + return this.state.getProperty(name); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBeanState#getPropertyNames() + */ + public synchronized Set getPropertyNames() + { + init(); + return this.state.getPropertyNames(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBeanState#setProperty(java.lang.String, + * java.lang.String) + */ + public synchronized void setProperty(String name, String value) + { + init(); + this.state.setProperty(name, value); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBeanState#start() + */ + public synchronized void start() + { + start(true); + } + + /** + * Starts the bean, optionally broadcasting the event to remote nodes. + * + * @param broadcast + * Should the event be broadcast? + */ + protected synchronized void start(boolean broadcast) + { + if (!this.isStarted) + { + init(); + if (broadcast) + { + this.registry.broadcastStart(this); + } + this.state.start(); + this.isStarted = true; + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBeanState#stop() + */ + public void stop() + { + stop(true); + } + + /** + * Stops the bean, optionally broadcasting the event to remote nodes. + * + * @param broadcast + * Should the event be broadcast? + */ + protected synchronized void stop(boolean broadcast) + { + if (this.isStarted) + { + if (broadcast) + { + this.registry.broadcastStop(this); + } + this.state.stop(); + this.isStarted = false; } } @@ -293,7 +500,7 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, } /** - * Expands the given value, resolving any ${} placeholders using the property defaults + * Expands the given value, resolving any ${} placeholders using the property defaults. * * @param val * the value to expand diff --git a/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java b/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java index 371ea72874..33f0292b31 100644 --- a/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java +++ b/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java @@ -113,10 +113,10 @@ import org.springframework.core.io.support.ResourcePatternResolver; * *
  * <bean id="imap.server.mountPoints" class="org.springframework.beans.factory.config.ListFactoryBean">
- *    <property name="sourceList">
- *       <!-- Whatever you declare in here will get replaced by the property value list -->
- *       <!-- This property is not actually required at all -->
- *    </property>
+ * <property name="sourceList">
+ * <!-- Whatever you declare in here will get replaced by the property value list -->
+ * <!-- This property is not actually required at all -->
+ * </property>
  * </bean>
  * 
* @@ -148,21 +148,12 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i /** The logger. */ private static Log logger = LogFactory.getLog(ChildApplicationContextFactory.class); - /** The properties to be used in placeholder expansion. */ - private Properties properties; - - /** The child application context. */ - private ClassPathXmlApplicationContext applicationContext; - /** The type name. */ private String typeName; - /** The registered composite propertes and their types. */ + /** The registered composite properties and their types. */ private Map> compositePropertyTypes = Collections.emptyMap(); - /** The composite property values. */ - private Map> compositeProperties = new TreeMap>(); - /** * Default constructor for container construction. */ @@ -183,20 +174,21 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i * the category * @param typeName * the type name - * @param id - * the instance id + * @param instancePath + * the instance path within the category * @throws IOException * Signals that an I/O exception has occurred. */ public ChildApplicationContextFactory(ApplicationContext parent, PropertyBackedBeanRegistry registry, - Properties propertyDefaults, String category, String typeName, List id) throws IOException + Properties propertyDefaults, String category, String typeName, List instancePath) + throws IOException { setApplicationContext(parent); setRegistry(registry); setPropertyDefaults(propertyDefaults); setBeanName(category); setTypeName(typeName); - setId(id); + setInstancePath(instancePath); try { @@ -253,26 +245,42 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i @Override public void afterPropertiesSet() throws Exception { - List idList = getId(); + List idList = getInstancePath(); if (idList.isEmpty()) { - throw new IllegalStateException("Invalid ID"); + throw new IllegalStateException("Invalid instance path"); } if (getTypeName() == null) { setTypeName(idList.get(0)); } - // Load the property defaults - PropertiesFactoryBean factory = new PropertiesFactoryBean(); - factory.setLocations(getParent().getResources( - ChildApplicationContextFactory.CLASSPATH_PREFIX + getCategory() + '/' + getTypeName() - + ChildApplicationContextFactory.PROPERTIES_SUFFIX)); - factory.afterPropertiesSet(); - this.properties = (Properties) factory.getObject(); - - // Now let the superclass propagate default settings from the global properties and register us super.afterPropertiesSet(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#createInitialState() + */ + @Override + protected PropertyBackedBeanState createInitialState() throws IOException + { + return new ApplicationContextState(); + } + + /* + * (non-Javadoc) + * @see + * org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#applyDefaultOverrides(org.alfresco.repo.management + * .subsystems.PropertyBackedBeanState) + */ + @Override + protected void applyDefaultOverrides(PropertyBackedBeanState state) throws IOException + { + // Let the superclass propagate default settings from the global properties and register us + super.applyDefaultOverrides(state); + + List idList = getId(); // Apply any property overrides from the extension classpath and also allow system properties and JNDI to // override. We use the type name and last component of the ID in the path @@ -281,146 +289,9 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i overrideFactory.setLocations(getParent().getResources( ChildApplicationContextFactory.EXTENSION_CLASSPATH_PREFIX + getCategory() + '/' + getTypeName() + '/' + idList.get(idList.size() - 1) + '/' + ChildApplicationContextFactory.PROPERTIES_SUFFIX)); - overrideFactory.setProperties(this.properties); + overrideFactory.setProperties(((ApplicationContextState) state).properties); overrideFactory.afterPropertiesSet(); - this.properties = (Properties) overrideFactory.getObject(); - - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getPropertyNames() - */ - @SuppressWarnings("unchecked") - public synchronized Set getPropertyNames() - { - Set result = new TreeSet(((Map) this.properties).keySet()); - result.add(ChildApplicationContextFactory.TYPE_NAME_PROPERTY); - result.addAll(this.compositePropertyTypes.keySet()); - return result; - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getProperty(java.lang.String) - */ - public synchronized String getProperty(String name) - { - if (name.equals(ChildApplicationContextFactory.TYPE_NAME_PROPERTY)) - { - return getTypeName(); - } - else if (this.compositePropertyTypes.containsKey(name)) - { - Map beans = this.compositeProperties.get(name); - if (beans != null) - { - StringBuilder list = new StringBuilder(100); - for (String id : beans.keySet()) - { - if (list.length() > 0) - { - list.append(','); - } - list.append(id); - } - return list.toString(); - } - return ""; - } - else - { - return this.properties.getProperty(name); - } - - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, java.lang.String) - */ - public void setProperty(String name, String value) - { - if (name.equals(ChildApplicationContextFactory.TYPE_NAME_PROPERTY)) - { - throw new IllegalStateException("Illegal write to property \"" - + ChildApplicationContextFactory.TYPE_NAME_PROPERTY + "\""); - } - Class type = this.compositePropertyTypes.get(name); - if (type != null) - { - updateCompositeProperty(name, value, type); - } - else - { - this.properties.setProperty(name, value); - } - } - - /** - * Updates a composite property with a new list of instance names. Properties of those instances that existed - * previously will be preserved. Instances that no longer exist will be destroyed. New instances will be brought - * into life with default values, as described in the class description. - * - * @param name - * the composite property name - * @param value - * a list of bean instance IDs - * @param type - * the bean class - */ - private void updateCompositeProperty(String name, String value, Class type) - { - // Retrieve the map of existing values of this property - Map propertyValues = this.compositeProperties.get(name); - if (propertyValues == null) - { - propertyValues = Collections.emptyMap(); - } - - try - { - Map newPropertyValues = new LinkedHashMap(11); - StringTokenizer tkn = new StringTokenizer(value, ", \t\n\r\f"); - while (tkn.hasMoreTokens()) - { - String id = tkn.nextToken(); - - // Generate a unique ID within the category - List childId = new ArrayList(3); - childId.addAll(getId()); - childId.add(name); - childId.add(id); - - // Look out for new or updated children - CompositeDataBean child = propertyValues.get(id); - - if (child == null) - { - child = new CompositeDataBean(getParent(), this, getRegistry(), getPropertyDefaults(), - getCategory(), type, childId); - } - newPropertyValues.put(id, child); - } - - // Destroy any children that have been removed - Set idsToRemove = new TreeSet(propertyValues.keySet()); - idsToRemove.removeAll(newPropertyValues.keySet()); - for (String id : idsToRemove) - { - CompositeDataBean child = propertyValues.get(id); - child.destroy(true); - } - this.compositeProperties.put(name, newPropertyValues); - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - throw new RuntimeException(e); - } + ((ApplicationContextState) state).properties = (Properties) overrideFactory.getObject(); } /* @@ -446,63 +317,22 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i .getDescription(name); } - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#start() - */ - public synchronized void start() - { - // This is where we actually create and start a child application context based on the configured properties. - if (this.applicationContext == null) - { - ChildApplicationContextFactory.logger.info("Starting '" + getCategory() + "' subsystem, ID: " + getId()); - this.applicationContext = new ChildApplicationContext(); - this.applicationContext.refresh(); - ChildApplicationContextFactory.logger.info("Startup of '" + getCategory() + "' subsystem, ID: " + getId() - + " complete"); - } - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#stop() - */ - public void stop() - { - if (this.applicationContext != null) - { - ChildApplicationContextFactory.logger.info("Stopping '" + getCategory() + "' subsystem, ID: " + getId()); - try - { - this.applicationContext.close(); - } - catch (Exception e) - { - ChildApplicationContextFactory.logger.error(e); - // Continue anyway. Perhaps it didn't start properly - } - this.applicationContext = null; - ChildApplicationContextFactory.logger.info("Stopped '" + getCategory() + "' subsystem, ID: " + getId()); - } - } - /* * (non-Javadoc) * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#destroy(boolean) */ @Override - public void destroy(boolean permanent) + protected synchronized void destroy(boolean permanent) { - super.destroy(permanent); + ApplicationContextState state = (ApplicationContextState) getState(false); // Cascade the destroy / shutdown - for (Map beans : this.compositeProperties.values()) + if (state != null) { - for (CompositeDataBean bean : beans.values()) - { - bean.destroy(permanent); - } + state.destroy(permanent); } + + super.destroy(permanent); } /* @@ -511,8 +341,7 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i */ public synchronized ApplicationContext getApplicationContext() { - start(); - return this.applicationContext; + return ((ApplicationContextState) getState(true)).getApplicationContext(); } /** @@ -524,13 +353,21 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i private class ChildApplicationContext extends ClassPathXmlApplicationContext { + /** The composite property values. */ + private Map> compositeProperties; + /** * The Constructor. * + * @param properties + * the properties + * @param compositeProperties + * the composite properties * @throws BeansException * the beans exception */ - private ChildApplicationContext() throws BeansException + private ChildApplicationContext(Properties properties, + Map> compositeProperties) throws BeansException { super(new String[] { @@ -540,9 +377,11 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i + getId().get(getId().size() - 1) + '/' + ChildApplicationContextFactory.CONTEXT_SUFFIX }, false, ChildApplicationContextFactory.this.getParent()); + this.compositeProperties = compositeProperties; + // Add a property placeholder configurer, with the subsystem-scoped default properties PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer(); - configurer.setProperties(ChildApplicationContextFactory.this.properties); + configurer.setProperties(properties); configurer.setIgnoreUnresolvablePlaceholders(true); addBeanFactoryPostProcessor(configurer); @@ -589,7 +428,7 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i if (bean instanceof ListFactoryBean && ChildApplicationContextFactory.this.compositePropertyTypes.containsKey(beanName)) { - Map beans = ChildApplicationContextFactory.this.compositeProperties + Map beans = ChildApplicationContext.this.compositeProperties .get(beanName); List beanList; if (beans != null) @@ -611,4 +450,249 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i }); } } + + /** + * The Class ApplicationContextState. + */ + protected class ApplicationContextState implements PropertyBackedBeanState + { + + /** The properties to be used in placeholder expansion. */ + private Properties properties; + + /** The composite property values. */ + private Map> compositeProperties = new TreeMap>(); + + /** The child application context. */ + private ClassPathXmlApplicationContext applicationContext; + + /** + * Instantiates a new application context state. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + protected ApplicationContextState() throws IOException + { + // Load the property defaults + PropertiesFactoryBean factory = new PropertiesFactoryBean(); + factory.setLocations(getParent().getResources( + ChildApplicationContextFactory.CLASSPATH_PREFIX + getCategory() + '/' + getTypeName() + + ChildApplicationContextFactory.PROPERTIES_SUFFIX)); + factory.afterPropertiesSet(); + this.properties = (Properties) factory.getObject(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getPropertyNames() + */ + @SuppressWarnings("unchecked") + public synchronized Set getPropertyNames() + { + Set result = new TreeSet(((Map) this.properties).keySet()); + result.add(ChildApplicationContextFactory.TYPE_NAME_PROPERTY); + result.addAll(ChildApplicationContextFactory.this.compositePropertyTypes.keySet()); + return result; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getProperty(java.lang.String) + */ + public synchronized String getProperty(String name) + { + if (name.equals(ChildApplicationContextFactory.TYPE_NAME_PROPERTY)) + { + return getTypeName(); + } + else if (ChildApplicationContextFactory.this.compositePropertyTypes.containsKey(name)) + { + Map beans = this.compositeProperties.get(name); + if (beans != null) + { + StringBuilder list = new StringBuilder(100); + for (String id : beans.keySet()) + { + if (list.length() > 0) + { + list.append(','); + } + list.append(id); + } + return list.toString(); + } + return ""; + } + else + { + return this.properties.getProperty(name); + } + + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, + * java.lang.String) + */ + public void setProperty(String name, String value) + { + if (name.equals(ChildApplicationContextFactory.TYPE_NAME_PROPERTY)) + { + throw new IllegalStateException("Illegal write to property \"" + + ChildApplicationContextFactory.TYPE_NAME_PROPERTY + "\""); + } + Class type = ChildApplicationContextFactory.this.compositePropertyTypes.get(name); + if (type != null) + { + updateCompositeProperty(name, value, type); + } + else + { + this.properties.setProperty(name, value); + } + } + + /** + * Updates a composite property with a new list of instance names. Properties of those instances that existed + * previously will be preserved. Instances that no longer exist will be destroyed. New instances will be brought + * into life with default values, as described in the class description. + * + * @param name + * the composite property name + * @param value + * a list of bean instance IDs + * @param type + * the bean class + */ + private void updateCompositeProperty(String name, String value, Class type) + { + // Retrieve the map of existing values of this property + Map propertyValues = this.compositeProperties.get(name); + if (propertyValues == null) + { + propertyValues = Collections.emptyMap(); + } + + try + { + Map newPropertyValues = new LinkedHashMap(11); + StringTokenizer tkn = new StringTokenizer(value, ", \t\n\r\f"); + while (tkn.hasMoreTokens()) + { + String id = tkn.nextToken(); + + // Generate a unique ID within the category + List childPath = new ArrayList(4); + childPath.addAll(getInstancePath()); + childPath.add(name); + childPath.add(id); + + // Look out for new or updated children + CompositeDataBean child = propertyValues.get(id); + + if (child == null) + { + child = new CompositeDataBean(getParent(), ChildApplicationContextFactory.this, getRegistry(), + getPropertyDefaults(), getCategory(), type, childPath); + } + newPropertyValues.put(id, child); + } + + // Destroy any children that have been removed + Set idsToRemove = new TreeSet(propertyValues.keySet()); + idsToRemove.removeAll(newPropertyValues.keySet()); + for (String id : idsToRemove) + { + CompositeDataBean child = propertyValues.get(id); + child.destroy(true); + } + this.compositeProperties.put(name, newPropertyValues); + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#start() + */ + public synchronized void start() + { + // This is where we actually create and start a child application context based on the configured + // properties. + if (this.applicationContext == null) + { + ChildApplicationContextFactory.logger + .info("Starting '" + getCategory() + "' subsystem, ID: " + getId()); + this.applicationContext = ChildApplicationContextFactory.this.new ChildApplicationContext( + this.properties, this.compositeProperties); + this.applicationContext.refresh(); + ChildApplicationContextFactory.logger.info("Startup of '" + getCategory() + "' subsystem, ID: " + + getId() + " complete"); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#stop() + */ + public void stop() + { + if (this.applicationContext != null) + { + ChildApplicationContextFactory.logger + .info("Stopping '" + getCategory() + "' subsystem, ID: " + getId()); + try + { + this.applicationContext.close(); + } + catch (Exception e) + { + ChildApplicationContextFactory.logger.error(e); + // Continue anyway. Perhaps it didn't start properly + } + this.applicationContext = null; + ChildApplicationContextFactory.logger.info("Stopped '" + getCategory() + "' subsystem, ID: " + getId()); + } + } + + /** + * Releases any resources held by this state. + * + * @param permanent + * is the state being destroyed forever, i.e. should persisted values be removed? On server shutdown, + * this value would be false, whereas on the removal of a dynamically created instance, + * this value would be true. + */ + public void destroy(boolean permanent) + { + // Cascade the destroy / shutdown + for (Map beans : this.compositeProperties.values()) + { + for (CompositeDataBean bean : beans.values()) + { + bean.destroy(permanent); + } + } + } + + /** + * Gets the application context. + * + * @return the application context + */ + public synchronized ApplicationContext getApplicationContext() + { + start(); + return this.applicationContext; + } + } } diff --git a/source/java/org/alfresco/repo/management/subsystems/CompositeDataBean.java b/source/java/org/alfresco/repo/management/subsystems/CompositeDataBean.java index 4d7d8ed2a7..b1f1c498b3 100644 --- a/source/java/org/alfresco/repo/management/subsystems/CompositeDataBean.java +++ b/source/java/org/alfresco/repo/management/subsystems/CompositeDataBean.java @@ -32,6 +32,7 @@ import java.util.Properties; import java.util.Set; import java.util.TreeSet; +import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.factory.BeanNameAware; @@ -47,14 +48,12 @@ import org.springframework.context.ApplicationContext; */ public class CompositeDataBean extends AbstractPropertyBackedBean { - /** The owning bean */ + + /** The owning bean. */ private final PropertyBackedBean owner; - /** The Java bean instance. */ - private final Object bean; - - /** A Spring wrapper around the Java bean, allowing easy configuration of properties. */ - private final BeanWrapper wrappedBean; + /** The bean type. */ + private final Class type; /** The property names. */ private final Set propertyNames; @@ -62,10 +61,10 @@ public class CompositeDataBean extends AbstractPropertyBackedBean /** The writeable properties. */ private final Set writeableProperties; - /** The prefix used to look up default values for this bean's properties */ + /** The prefix used to look up default values for this bean's properties. */ private String defaultKeyPrefix; - /** The prefix used to look up instance-specific default values for this bean's properties */ + /** The prefix used to look up instance-specific default values for this bean's properties. */ private String instanceKeyPrefix; /** @@ -79,8 +78,8 @@ public class CompositeDataBean extends AbstractPropertyBackedBean * property defaults provided by the installer or System properties * @param category * the category - * @param id - * the instance id + * @param instancePath + * the instance path within the category * @param owner * the owning bean * @param type @@ -89,25 +88,19 @@ public class CompositeDataBean extends AbstractPropertyBackedBean * Signals that an I/O exception has occurred. */ public CompositeDataBean(ApplicationContext parent, PropertyBackedBean owner, PropertyBackedBeanRegistry registry, - Properties propertyDefaults, String category, Class type, List id) throws IOException + Properties propertyDefaults, String category, Class type, List instancePath) throws IOException { setApplicationContext(parent); setRegistry(registry); setPropertyDefaults(propertyDefaults); setBeanName(category); - setId(id); + setInstancePath(instancePath); this.owner = owner; + this.type = type; try { - this.bean = type.newInstance(); - // Tell the bean its name if it cares - if (this.bean instanceof BeanNameAware) - { - ((BeanNameAware) this.bean).setBeanName(id.get(id.size() - 1)); - } - this.wrappedBean = new BeanWrapperImpl(this.bean); - PropertyDescriptor[] descriptors = this.wrappedBean.getPropertyDescriptors(); + PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(type); this.propertyNames = new TreeSet(); this.writeableProperties = new TreeSet(); for (PropertyDescriptor descriptor : descriptors) @@ -149,7 +142,7 @@ public class CompositeDataBean extends AbstractPropertyBackedBean // Derive a default and instance key prefix of the form ".default." and ".value.." StringBuilder defaultKeyPrefixBuff = new StringBuilder(200); StringBuilder instanceKeyPrefixBuff = new StringBuilder(200); - List id = getId(); + List id = getInstancePath(); int size = id.size(); if (size > 1) { @@ -181,30 +174,12 @@ public class CompositeDataBean extends AbstractPropertyBackedBean /* * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getProperty(java.lang.String) + * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#createInitialState() */ - public String getProperty(String name) + @Override + protected PropertyBackedBeanState createInitialState() throws IOException { - Object value = this.wrappedBean.getPropertyValue(name); - return value == null ? null : value.toString(); - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getPropertyNames() - */ - public Set getPropertyNames() - { - return this.propertyNames; - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, java.lang.String) - */ - public void setProperty(String name, String value) - { - this.wrappedBean.setPropertyValue(name, value); + return new CompositeDataBeanState(); } /* @@ -217,24 +192,6 @@ public class CompositeDataBean extends AbstractPropertyBackedBean return this.writeableProperties.contains(name); } - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#start() - */ - public void start() - { - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#stop() - */ - public void stop() - { - // Ensure any edits to child composites cause the parent to be shut down and subsequently re-initialized - this.owner.stop(); - } - /** * Gets the wrapped Java bean. * @@ -242,6 +199,115 @@ public class CompositeDataBean extends AbstractPropertyBackedBean */ protected Object getBean() { - return this.bean; + return ((CompositeDataBeanState) getState(true)).getBean(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#stop(boolean) + */ + @Override + protected synchronized void stop(boolean broadcast) + { + super.stop(broadcast); + + // Ensure any edits to child composites cause the parent to be shut down and subsequently re-initialized + if (broadcast) + { + this.owner.stop(); + } + } + + /** + * The Class CompositeDataBeanState. + */ + protected class CompositeDataBeanState implements PropertyBackedBeanState + { + + /** The Java bean instance. */ + private final Object bean; + + /** A Spring wrapper around the Java bean, allowing easy configuration of properties. */ + private final BeanWrapper wrappedBean; + + /** + * Instantiates a new composite data bean state. + */ + protected CompositeDataBeanState() + { + try + { + this.bean = CompositeDataBean.this.type.newInstance(); + // Tell the bean its name if it cares + if (this.bean instanceof BeanNameAware) + { + ((BeanNameAware) this.bean).setBeanName(getId().get(getId().size() - 1)); + } + this.wrappedBean = new BeanWrapperImpl(this.bean); + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getProperty(java.lang.String) + */ + public String getProperty(String name) + { + Object value = this.wrappedBean.getPropertyValue(name); + return value == null ? null : value.toString(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getPropertyNames() + */ + public Set getPropertyNames() + { + return CompositeDataBean.this.propertyNames; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, + * java.lang.String) + */ + public void setProperty(String name, String value) + { + this.wrappedBean.setPropertyValue(name, value); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#start() + */ + public void start() + { + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#stop() + */ + public void stop() + { + } + + /** + * Gets the wrapped Java bean. + * + * @return the Java bean + */ + protected Object getBean() + { + return this.bean; + } } } diff --git a/source/java/org/alfresco/repo/management/subsystems/DefaultChildApplicationContextManager.java b/source/java/org/alfresco/repo/management/subsystems/DefaultChildApplicationContextManager.java index b6cc366a46..8cb0fee312 100644 --- a/source/java/org/alfresco/repo/management/subsystems/DefaultChildApplicationContextManager.java +++ b/source/java/org/alfresco/repo/management/subsystems/DefaultChildApplicationContextManager.java @@ -24,6 +24,7 @@ */ package org.alfresco.repo.management.subsystems; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -66,18 +67,12 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke /** The default chain. */ private String defaultChain; - /** The instance ids. */ - private List instanceIds = new ArrayList(10); - - /** The child application contexts. */ - private Map childApplicationContexts = new TreeMap(); - /** * Instantiates a new default child application context manager. */ public DefaultChildApplicationContextManager() { - setId(Collections.singletonList("manager")); + setInstancePath(Collections.singletonList("manager")); } /** @@ -106,94 +101,6 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke this.defaultChain = defaultChain; } - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() throws Exception - { - if (this.defaultChain != null && this.defaultChain.length() > 0) - { - // Use the first type as the default, unless one is specified explicitly - if (this.defaultTypeName == null) - { - updateOrder(this.defaultChain, AbstractPropertyBackedBean.DEFAULT_ID_ROOT); - this.defaultTypeName = this.childApplicationContexts.get(this.instanceIds.get(0)).getTypeName(); - } - else - { - updateOrder(this.defaultChain, this.defaultTypeName); - } - } - else if (this.defaultTypeName == null) - { - setDefaultTypeName(AbstractPropertyBackedBean.DEFAULT_ID_ROOT); - } - - super.afterPropertiesSet(); - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#start() - */ - public void start() - { - for (String instance : getInstanceIds()) - { - getApplicationContext(instance); - } - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#stop() - */ - public void stop() - { - // Nothing to do - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#destroy(boolean) - */ - @Override - public void destroy(boolean permanent) - { - super.destroy(permanent); - - // Cascade the destroy / shutdown - for (String id : this.instanceIds) - { - ChildApplicationContextFactory factory = this.childApplicationContexts.get(id); - factory.destroy(permanent); - } - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getProperty(java.lang.String) - */ - public synchronized String getProperty(String name) - { - if (!name.equals(DefaultChildApplicationContextManager.ORDER_PROPERTY)) - { - return null; - } - return getOrderString(); - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getPropertyNames() - */ - public Set getPropertyNames() - { - return Collections.singleton(DefaultChildApplicationContextManager.ORDER_PROPERTY); - } - /* * (non-Javadoc) * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#getDescription(java.lang.String) @@ -206,115 +113,281 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke /* * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, java.lang.String) + * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#destroy(boolean) */ - public synchronized void setProperty(String name, String value) + @Override + public void destroy(boolean permanent) { - if (!name.equals(DefaultChildApplicationContextManager.ORDER_PROPERTY)) + ApplicationContextManagerState state = (ApplicationContextManagerState) getState(false); + + if (state != null) { - throw new IllegalStateException("Illegal attempt to write to property \"" + name + "\""); + // Cascade the destroy / shutdown + for (String id : state.getInstanceIds()) + { + ChildApplicationContextFactory factory = state.getApplicationContextFactory(id); + factory.destroy(permanent); + } } - updateOrder(value, this.defaultTypeName); + + super.destroy(permanent); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#createInitialState() + */ + @Override + protected PropertyBackedBeanState createInitialState() throws IOException + { + return new ApplicationContextManagerState(this.defaultChain, this.defaultTypeName); } /* * (non-Javadoc) * @see org.alfresco.repo.management.ChildApplicationContextManager#getInstanceIds() */ - public synchronized Collection getInstanceIds() + public Collection getInstanceIds() { - return Collections.unmodifiableList(this.instanceIds); + return ((ApplicationContextManagerState) getState(true)).getInstanceIds(); } /* * (non-Javadoc) * @see org.alfresco.repo.management.ChildApplicationContextManager#getApplicationContext(java.lang.String) */ - public synchronized ApplicationContext getApplicationContext(String id) + public ApplicationContext getApplicationContext(String id) { - ChildApplicationContextFactory child = this.childApplicationContexts.get(id); - return child == null ? null : child.getApplicationContext(); + return ((ApplicationContextManagerState) getState(true)).getApplicationContext(id); } /** - * Gets the order string. - * - * @return the order string + * The Class ApplicationContextManagerState. */ - private String getOrderString() + protected class ApplicationContextManagerState implements PropertyBackedBeanState { - StringBuilder orderString = new StringBuilder(100); - for (String id : this.instanceIds) + + /** The instance ids. */ + private List instanceIds = new ArrayList(10); + + /** The child application contexts. */ + private Map childApplicationContexts = new TreeMap(); + + /** The default type name. */ + private String defaultTypeName; + + /** + * Instantiates a new application context manager state. + * + * @param defaultChain + * the default chain + * @param defaultTypeName + * the default type name + */ + protected ApplicationContextManagerState(String defaultChain, String defaultTypeName) { - if (orderString.length() > 0) + // Work out what the default type name should be; either specified explicitly or implied by the first member + // of the default chain + if (defaultChain != null && defaultChain.length() > 0) { - orderString.append(","); - } - orderString.append(id).append(':').append(this.childApplicationContexts.get(id).getTypeName()); - } - return orderString.toString(); - } - - /** - * Updates the order from a comma or whitespace separated string. - * - * @param orderString - * the order as a comma or whitespace separated string - * @param defaultTypeName - * the default type name - */ - private void updateOrder(String orderString, String defaultTypeName) - { - try - { - StringTokenizer tkn = new StringTokenizer(orderString, ", \t\n\r\f"); - List newInstanceIds = new ArrayList(tkn.countTokens()); - while (tkn.hasMoreTokens()) - { - String instance = tkn.nextToken(); - int sepIndex = instance.indexOf(':'); - String id = sepIndex == -1 ? instance : instance.substring(0, sepIndex); - String typeName = sepIndex == -1 || sepIndex + 1 >= instance.length() ? defaultTypeName : instance - .substring(sepIndex + 1); - newInstanceIds.add(id); - - // Look out for new or updated children - ChildApplicationContextFactory factory = this.childApplicationContexts.get(id); - - // If we have the same instance ID but a different type, treat that as a destroy and remove - if (factory != null && !factory.getTypeName().equals(typeName)) + // Use the first type as the default, unless one is specified explicitly + if (defaultTypeName == null) { + updateOrder(defaultChain, AbstractPropertyBackedBean.DEFAULT_INSTANCE_NAME); + this.defaultTypeName = this.childApplicationContexts.get(this.instanceIds.get(0)).getTypeName(); + } + else + { + this.defaultTypeName = defaultTypeName; + updateOrder(defaultChain, defaultTypeName); + } + } + else if (defaultTypeName == null) + { + this.defaultTypeName = AbstractPropertyBackedBean.DEFAULT_INSTANCE_NAME; + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getProperty(java.lang.String) + */ + public synchronized String getProperty(String name) + { + if (!name.equals(DefaultChildApplicationContextManager.ORDER_PROPERTY)) + { + return null; + } + return getOrderString(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getPropertyNames() + */ + public Set getPropertyNames() + { + return Collections.singleton(DefaultChildApplicationContextManager.ORDER_PROPERTY); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, + * java.lang.String) + */ + public synchronized void setProperty(String name, String value) + { + if (!name.equals(DefaultChildApplicationContextManager.ORDER_PROPERTY)) + { + throw new IllegalStateException("Illegal attempt to write to property \"" + name + "\""); + } + updateOrder(value, this.defaultTypeName); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#start() + */ + public void start() + { + for (String instance : getInstanceIds()) + { + getApplicationContext(instance); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#stop() + */ + public void stop() + { + // Nothing to do + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.ChildApplicationContextManager#getInstanceIds() + */ + /** + * Gets the instance ids. + * + * @return the instance ids + */ + public synchronized Collection getInstanceIds() + { + return Collections.unmodifiableList(this.instanceIds); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.ChildApplicationContextManager#getApplicationContext(java.lang.String) + */ + /** + * Gets the application context. + * + * @param id + * the id + * @return the application context + */ + public synchronized ApplicationContext getApplicationContext(String id) + { + ChildApplicationContextFactory child = this.childApplicationContexts.get(id); + return child == null ? null : child.getApplicationContext(); + } + + /** + * Gets the application context factory. + * + * @param id + * the id + * @return the application context factory + */ + protected ChildApplicationContextFactory getApplicationContextFactory(String id) + { + return this.childApplicationContexts.get(id); + } + + /** + * Gets the order string. + * + * @return the order string + */ + private String getOrderString() + { + StringBuilder orderString = new StringBuilder(100); + for (String id : this.instanceIds) + { + if (orderString.length() > 0) + { + orderString.append(","); + } + orderString.append(id).append(':').append(this.childApplicationContexts.get(id).getTypeName()); + } + return orderString.toString(); + } + + /** + * Updates the order from a comma or whitespace separated string. + * + * @param orderString + * the order as a comma or whitespace separated string + * @param defaultTypeName + * the default type name + */ + private void updateOrder(String orderString, String defaultTypeName) + { + try + { + StringTokenizer tkn = new StringTokenizer(orderString, ", \t\n\r\f"); + List newInstanceIds = new ArrayList(tkn.countTokens()); + while (tkn.hasMoreTokens()) + { + String instance = tkn.nextToken(); + int sepIndex = instance.indexOf(':'); + String id = sepIndex == -1 ? instance : instance.substring(0, sepIndex); + String typeName = sepIndex == -1 || sepIndex + 1 >= instance.length() ? defaultTypeName : instance + .substring(sepIndex + 1); + newInstanceIds.add(id); + + // Look out for new or updated children + ChildApplicationContextFactory factory = this.childApplicationContexts.get(id); + + // If we have the same instance ID but a different type, treat that as a destroy and remove + if (factory != null && !factory.getTypeName().equals(typeName)) + { + factory.destroy(true); + factory = null; + } + if (factory == null) + { + // Generate a unique ID within the category + List childId = new ArrayList(2); + childId.add("managed"); + childId.add(id); + this.childApplicationContexts.put(id, new ChildApplicationContextFactory(getParent(), + getRegistry(), getPropertyDefaults(), getCategory(), typeName, childId)); + } + } + + // Destroy any children that have been removed + Set idsToRemove = new TreeSet(this.childApplicationContexts.keySet()); + idsToRemove.removeAll(newInstanceIds); + for (String id : idsToRemove) + { + ChildApplicationContextFactory factory = this.childApplicationContexts.remove(id); factory.destroy(true); - factory = null; - } - if (factory == null) - { - // Generate a unique ID within the category - List childId = new ArrayList(2); - childId.add("managed"); - childId.add(id); - this.childApplicationContexts.put(id, new ChildApplicationContextFactory(getParent(), - getRegistry(), getPropertyDefaults(), getCategory(), typeName, childId)); } + this.instanceIds = newInstanceIds; } - - // Destroy any children that have been removed - Set idsToRemove = new TreeSet(this.childApplicationContexts.keySet()); - idsToRemove.removeAll(newInstanceIds); - for (String id : idsToRemove) + catch (RuntimeException e) { - ChildApplicationContextFactory factory = this.childApplicationContexts.remove(id); - factory.destroy(true); + throw e; + } + catch (Exception e) + { + throw new RuntimeException(e); } - this.instanceIds = newInstanceIds; - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - throw new RuntimeException(e); } + } } diff --git a/source/java/org/alfresco/repo/management/subsystems/DefaultPropertyBackedBeanRegistry.java b/source/java/org/alfresco/repo/management/subsystems/DefaultPropertyBackedBeanRegistry.java index 008f512281..981ed691c6 100644 --- a/source/java/org/alfresco/repo/management/subsystems/DefaultPropertyBackedBeanRegistry.java +++ b/source/java/org/alfresco/repo/management/subsystems/DefaultPropertyBackedBeanRegistry.java @@ -82,6 +82,28 @@ public class DefaultPropertyBackedBeanRegistry implements PropertyBackedBeanRegi broadcastEvent(new PropertyBackedBeanUnregisteredEvent(bean, isPermanent)); } + /* + * (non-Javadoc) + * @see + * org.alfresco.repo.management.subsystems.PropertyBackedBeanRegistry#broadcastStart(org.alfresco.repo.management + * .subsystems.PropertyBackedBean) + */ + public void broadcastStart(PropertyBackedBean bean) + { + broadcastEvent(new PropertyBackedBeanStartedEvent(bean)); + } + + /* + * (non-Javadoc) + * @see + * org.alfresco.repo.management.subsystems.PropertyBackedBeanRegistry#broadcastStop(org.alfresco.repo.management + * .subsystems.PropertyBackedBean) + */ + public void broadcastStop(PropertyBackedBean bean) + { + broadcastEvent(new PropertyBackedBeanStoppedEvent(bean)); + } + /** * Broadcast event. * diff --git a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBean.java b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBean.java index f384bad950..c2f1730a82 100644 --- a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBean.java +++ b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBean.java @@ -25,64 +25,25 @@ package org.alfresco.repo.management.subsystems; import java.util.List; -import java.util.Set; /** * A PropertyBackedBean is a reconfigurable sub-component or subsystem in the Alfresco server. It exposes - * configurable properties, along with {@link #stop()}, {@link #start()} and {@link #destroy(boolean)} methods. To - * reconfigure a bean, first ensure it is stopped by calling {@link #stop()}. Then set one or more properties. Then test - * out the changes with {@link #start()}. To bring the bean instance out of play (e.g. on server shutdown) call - * {@link #destroy(boolean)}. In the Alfresco enterprise edition PropertyBackedBeans are exposed as - * persistent MBeans and can be reconfigured at runtime via JMX. + * its state through the {@link PropertyBackedBeanState} interface, along with fixed attributes and a method for + * reverting the bean to its default initial state. In the Alfresco enterprise edition PropertyBackedBeans + * are exposed as persistent MBeans and can be reconfigured across a cluster at runtime via JMX. * * @author dward */ -public interface PropertyBackedBean +public interface PropertyBackedBean extends PropertyBackedBeanState { - /** - * Gets a human readable categorization of this bean, explaining its purpose. This category may be used e.g. in - * administration UIs and JMX object names. - * - * @return the category - */ - public String getCategory(); - - /** - * Gets an identifier for the bean. Must be unique within the category. The ID is a List to encourage hierarchical - * structuring of IDs, e.g. to aid construction of JMX Object names and presentation in JConsole. + * Gets a unique identifier for the bean. The ID is a List to encourage hierarchical structuring of IDs, e.g. to aid + * construction of JMX Object names and presentation in JConsole. * * @return the id */ public List getId(); - /** - * Gets the names of all properties. - * - * @return the property names - */ - public Set getPropertyNames(); - - /** - * Gets a property value. - * - * @param name - * the name - * @return the property value - */ - public String getProperty(String name); - - /** - * Sets the value of a property. This may only be called after {@link #stop()} and should only be called for - * property names for which the {@link #isUpdateable(String)} method returns true. - * - * @param name - * the property name - * @param value - * the property value - */ - public void setProperty(String name, String value); - /** * Checks if a property is updateable. * @@ -102,22 +63,7 @@ public interface PropertyBackedBean public String getDescription(String name); /** - * Starts up the component, using its new property values. + * Reverts this component to its original default start state, removing any previously persisted state changes. */ - public void start(); - - /** - * Stops the component, so that its property values can be changed. - */ - public void stop(); - - /** - * Releases any resources held by this component. - * - * @param isPermanent - * is the component being destroyed forever, i.e. should persisted values be removed? On server shutdown, - * this value would be false, whereas on the removal of a dynamically created instance, this - * value would be true. - */ - public void destroy(boolean isPermanent); + public void revert(); } diff --git a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanEvent.java b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanEvent.java index a723ec80ef..976ba995bc 100644 --- a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanEvent.java +++ b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanEvent.java @@ -24,6 +24,8 @@ */ package org.alfresco.repo.management.subsystems; +import java.util.List; + import org.springframework.context.ApplicationEvent; /** @@ -33,8 +35,10 @@ import org.springframework.context.ApplicationEvent; */ public abstract class PropertyBackedBeanEvent extends ApplicationEvent { + private static final long serialVersionUID = -5414152423990988923L; - private static final long serialVersionUID = 1848914557290327762L; + /** The ID of the bean that emitted the event. */ + private List sourceId; /** * The Constructor. @@ -45,16 +49,16 @@ public abstract class PropertyBackedBeanEvent extends ApplicationEvent public PropertyBackedBeanEvent(PropertyBackedBean source) { super(source); + this.sourceId = source.getId(); } /** - * Gets the bean that emitted the event. + * Gets the ID of the bean that emitted the event. * - * @return the bean + * @return the ID */ - public PropertyBackedBean getBean() + public List getSourceId() { - return (PropertyBackedBean) getSource(); + return this.sourceId; } - } diff --git a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanRegisteredEvent.java b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanRegisteredEvent.java index af452b5501..65133c36dd 100644 --- a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanRegisteredEvent.java +++ b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanRegisteredEvent.java @@ -31,7 +31,7 @@ package org.alfresco.repo.management.subsystems; */ public class PropertyBackedBeanRegisteredEvent extends PropertyBackedBeanEvent { - private static final long serialVersionUID = -5922059120018335685L; + private static final long serialVersionUID = -2860105961131524745L; /** * The Constructor. @@ -42,6 +42,5 @@ public class PropertyBackedBeanRegisteredEvent extends PropertyBackedBeanEvent public PropertyBackedBeanRegisteredEvent(PropertyBackedBean source) { super(source); - } - + } } diff --git a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanRegistry.java b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanRegistry.java index bd1b7fbbf6..2946687d9d 100644 --- a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanRegistry.java +++ b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanRegistry.java @@ -64,4 +64,21 @@ public interface PropertyBackedBeanRegistry * value would be true. */ public void deregister(PropertyBackedBean bean, boolean isPermanent); + + /** + * Signals that a {@link PropertyBackedBean} has been started. + * + * @param bean + * the bean + */ + public void broadcastStart(PropertyBackedBean bean); + + + /** + * Signals that a {@link PropertyBackedBean} has been stopped. + * + * @param bean + * the bean + */ + public void broadcastStop(PropertyBackedBean bean); } diff --git a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanStartedEvent.java b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanStartedEvent.java new file mode 100644 index 0000000000..e0cd5e37f1 --- /dev/null +++ b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanStartedEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2009 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 received 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.management.subsystems; + +import java.util.List; + + +/** + * An event emitted after a {@link PropertyBackedBean} is started. + * + * @author dward + */ +public class PropertyBackedBeanStartedEvent extends PropertyBackedBeanEvent +{ + private static final long serialVersionUID = 6019157155489029474L; + + /** + * The Constructor. + * + * @param source + * the source of the event + */ + public PropertyBackedBeanStartedEvent(PropertyBackedBean source) + { + super(source); + } +} diff --git a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanState.java b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanState.java new file mode 100644 index 0000000000..826cf680a4 --- /dev/null +++ b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanState.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2005-2009 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 received 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.management.subsystems; + +import java.util.Set; + +/** + * A PropertyBackedBeanState represents the state of a configurable sub-component or subsystem in the + * Alfresco server. It exposes configurable properties, along with {@link #stop()} and {@link #start()} methods. To + * modify the state, first ensure its associated component is stopped by calling {@link #stop()}. Then set one or more + * properties. Then test out the changes with {@link #start()}. In the Alfresco enterprise edition + * PropertyBackedBeanStates are exposed as persistent MBeans and can be reconfigured at runtime across a + * cluster via JMX. + * + * @author dward + */ +public interface PropertyBackedBeanState +{ + /** + * Gets the names of all properties. + * + * @return the property names + */ + public Set getPropertyNames(); + + /** + * Gets a property value. + * + * @param name + * the name + * @return the property value + */ + public String getProperty(String name); + + /** + * Sets the value of a property. This may only be called after {@link #stop()} and should only be called for + * property names for which the {@link #isUpdateable(String)} method returns true. + * + * @param name + * the property name + * @param value + * the property value + */ + public void setProperty(String name, String value); + + /** + * Starts up the component, using its new property values. + */ + public void start(); + + /** + * Stops the component, so that its property values can be changed. + */ + public void stop(); +} diff --git a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanStoppedEvent.java b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanStoppedEvent.java new file mode 100644 index 0000000000..926ed80596 --- /dev/null +++ b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanStoppedEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2009 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 received 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.management.subsystems; + +import java.util.List; + + +/** + * An event emitted before a {@link PropertyBackedBean} is stopped. + * + * @author dward + */ +public class PropertyBackedBeanStoppedEvent extends PropertyBackedBeanEvent +{ + private static final long serialVersionUID = -8096989839647678810L; + + /** + * The Constructor. + * + * @param source + * the source of the event + */ + public PropertyBackedBeanStoppedEvent(PropertyBackedBean source) + { + super(source); + } +} diff --git a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanUnregisteredEvent.java b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanUnregisteredEvent.java index c9673e311c..2c56aa8784 100644 --- a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanUnregisteredEvent.java +++ b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBeanUnregisteredEvent.java @@ -25,13 +25,13 @@ package org.alfresco.repo.management.subsystems; /** - * An event emitted after {@link PropertyBackedBean#destroy(boolean)} is called on a bean. + * An event emitted a {@link PropertyBackedBean} is destroyed. * * @author dward */ public class PropertyBackedBeanUnregisteredEvent extends PropertyBackedBeanEvent { - private static final long serialVersionUID = -7878510109531750057L; + private static final long serialVersionUID = 4154847737689541132L; private final boolean isPermanent; diff --git a/source/java/org/alfresco/repo/management/subsystems/SwitchableApplicationContextFactory.java b/source/java/org/alfresco/repo/management/subsystems/SwitchableApplicationContextFactory.java index 1102338762..e517c7f71a 100644 --- a/source/java/org/alfresco/repo/management/subsystems/SwitchableApplicationContextFactory.java +++ b/source/java/org/alfresco/repo/management/subsystems/SwitchableApplicationContextFactory.java @@ -24,6 +24,7 @@ */ package org.alfresco.repo.management.subsystems; +import java.io.IOException; import java.util.Collections; import java.util.Set; @@ -37,121 +38,162 @@ import org.springframework.context.ApplicationContext; public class SwitchableApplicationContextFactory extends AbstractPropertyBackedBean implements ApplicationContextFactory { - /** - * - */ + + /** The name of the property holding the bean name of the source {@link ApplicationContextFactory}. */ private static final String SOURCE_BEAN_PROPERTY = "sourceBeanName"; - /** The bean name of the source {@link ApplicationContextFactory}. */ + /** The default bean name of the source {@link ApplicationContextFactory}. */ private String sourceBeanName; - /** The current source application context factory. */ - private ApplicationContextFactory sourceApplicationContextFactory; - /** - * Sets the bean name of the source {@link ApplicationContextFactory}. + * Sets the default bean name of the source {@link ApplicationContextFactory}. * * @param sourceBeanName * the bean name - * @throws Exception - * on error */ - public synchronized void setSourceBeanName(String sourceBeanName) + public void setSourceBeanName(String sourceBeanName) { - if (this.sourceApplicationContextFactory != null) - { - stop(); - this.sourceBeanName = sourceBeanName; - start(); - } - else + this.sourceBeanName = sourceBeanName; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.ApplicationContextFactory#getApplicationContext() + */ + public ApplicationContext getApplicationContext() + { + return ((SwitchableState) getState(true)).getApplicationContext(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#createInitialState() + */ + @Override + protected PropertyBackedBeanState createInitialState() throws IOException + { + return new SwitchableState(this.sourceBeanName); + } + + /** + * Represents the state of a {@link SwitchableApplicationContextFactory}. + */ + protected class SwitchableState implements PropertyBackedBeanState + { + + /** The current source application context factory. */ + private ApplicationContextFactory sourceApplicationContextFactory; + + /** The bean name of the source {@link ApplicationContextFactory}. */ + private String sourceBeanName; + + /** + * Instantiates a new switchable state. + * + * @param sourceBeanName + * the source bean name + */ + protected SwitchableState(String sourceBeanName) { this.sourceBeanName = sourceBeanName; } - } - /* - * (non-Javadoc) - * @see org.alfresco.enterprise.repo.management.ConfigurableBean#onStart() - */ - public synchronized void start() - { - if (this.sourceApplicationContextFactory == null) + /* + * (non-Javadoc) + * @see org.alfresco.enterprise.repo.management.ConfigurableBean#onStart() + */ + public synchronized void start() { - this.sourceApplicationContextFactory = (ApplicationContextFactory) getParent().getBean(this.sourceBeanName); - this.sourceApplicationContextFactory.start(); - } - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.SelfDescribingBean#onStop() - */ - public void stop() - { - if (this.sourceApplicationContextFactory != null) - { - try + if (this.sourceApplicationContextFactory == null) { - this.sourceApplicationContextFactory.stop(); - } - catch (Exception e) - { - throw new RuntimeException(e); + this.sourceApplicationContextFactory = (ApplicationContextFactory) getParent().getBean( + this.sourceBeanName); + this.sourceApplicationContextFactory.start(); } } - this.sourceApplicationContextFactory = null; - } - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.ManagedApplicationContextFactory#getApplicationContext() - */ - public synchronized ApplicationContext getApplicationContext() - { - if (this.sourceApplicationContextFactory == null) + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.SelfDescribingBean#onStop() + */ + public void stop() { - start(); + if (this.sourceApplicationContextFactory != null) + { + try + { + this.sourceApplicationContextFactory.stop(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + this.sourceApplicationContextFactory = null; } - return this.sourceApplicationContextFactory.getApplicationContext(); - } - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getProperty(java.lang.String) - */ - public synchronized String getProperty(String name) - { - if (!name.equals(SOURCE_BEAN_PROPERTY)) + /** + * Gets the application context. + * + * @return the application context + */ + public synchronized ApplicationContext getApplicationContext() { - return null; + if (this.sourceApplicationContextFactory == null) + { + start(); + } + return this.sourceApplicationContextFactory.getApplicationContext(); } - return this.sourceBeanName; - } - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getPropertyNames() - */ - public Set getPropertyNames() - { - return Collections.singleton(SOURCE_BEAN_PROPERTY); - } + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getProperty(java.lang.String) + */ + public synchronized String getProperty(String name) + { + if (!name.equals(SwitchableApplicationContextFactory.SOURCE_BEAN_PROPERTY)) + { + return null; + } + return this.sourceBeanName; + } - /* - * (non-Javadoc) - * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, java.lang.String) - */ - public synchronized void setProperty(String name, String value) - { - if (!name.equals(SOURCE_BEAN_PROPERTY)) + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getPropertyNames() + */ + public Set getPropertyNames() { - throw new IllegalStateException("Illegal attempt to write to property \"" + name + "\""); + return Collections.singleton(SwitchableApplicationContextFactory.SOURCE_BEAN_PROPERTY); } - if (!getParent().containsBean(value)) + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, + * java.lang.String) + */ + public synchronized void setProperty(String name, String value) { - throw new IllegalStateException("\"" + value + "\" is not a valid bean name"); + if (!name.equals(SwitchableApplicationContextFactory.SOURCE_BEAN_PROPERTY)) + { + throw new IllegalStateException("Illegal attempt to write to property \"" + name + "\""); + } + if (!getParent().containsBean(value)) + { + throw new IllegalStateException("\"" + value + "\" is not a valid bean name"); + } + if (this.sourceApplicationContextFactory != null) + { + stop(); + this.sourceBeanName = value; + start(); + } + else + { + this.sourceBeanName = value; + } } - setSourceBeanName(value); + } } diff --git a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationService.java b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationService.java index 1011aa9b00..5eddc4a099 100644 --- a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationService.java +++ b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationService.java @@ -27,48 +27,35 @@ package org.alfresco.repo.security.authentication; import java.util.List; import java.util.Set; -import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.service.cmr.security.AuthenticationService; -import org.springframework.beans.factory.InitializingBean; /** * Common code for authentication services * * @author andyh */ -public abstract class AbstractAuthenticationService implements AuthenticationService, InitializingBean +public abstract class AbstractAuthenticationService implements AuthenticationService { + private SysAdminParams sysAdminParams; - private SimpleCache sysAdminCache; - - private static final String KEY_SYSADMIN_ALLOWED_USERS = "sysAdminCache.authAllowedUsers"; - - private static final String KEY_SYSADMIN_MAX_USERS = "sysAdminCache.authMaxUsers"; - - private boolean initialised = false; - - private Integer initialMaxUsers = null; - - private List initialAllowedUsers = null; - - public void setSysAdminCache(SimpleCache sysAdminCache) + public void setSysAdminParams(SysAdminParams sysAdminParams) { - this.sysAdminCache = sysAdminCache; + this.sysAdminParams = sysAdminParams; } - @SuppressWarnings("unchecked") public void preAuthenticationCheck(String userName) throws AuthenticationException { - if (sysAdminCache != null) + if (sysAdminParams != null) { - List allowedUsers = (List) sysAdminCache.get(KEY_SYSADMIN_ALLOWED_USERS); + List allowedUsers = sysAdminParams.getAllowedUserList(); if ((allowedUsers != null) && (!allowedUsers.contains(userName))) { throw new AuthenticationDisallowedException("Username not allowed: " + userName); } - Integer maxUsers = (Integer) sysAdminCache.get(KEY_SYSADMIN_MAX_USERS); + Integer maxUsers = (Integer) sysAdminParams.getMaxUsers(); if ((maxUsers != null) && (maxUsers != -1) && (getUsersWithTickets(true).size() >= maxUsers)) { @@ -77,62 +64,14 @@ public abstract class AbstractAuthenticationService implements AuthenticationSer } } - public void setAllowedUsers(List allowedUsers) - { - if (initialised) - { - if (sysAdminCache != null) - { - sysAdminCache.put(KEY_SYSADMIN_ALLOWED_USERS, allowedUsers); - } - } - else - { - initialAllowedUsers = allowedUsers; - } - - } - - @SuppressWarnings("unchecked") public List getAllowedUsers() { - if (sysAdminCache != null) - { - return (List) sysAdminCache.get(KEY_SYSADMIN_ALLOWED_USERS); - } - else - { - return null; - } + return sysAdminParams.getAllowedUserList(); } - public void setMaxUsers(int maxUsers) - { - if (initialised) - { - if (sysAdminCache != null) - { - sysAdminCache.put(KEY_SYSADMIN_MAX_USERS, new Integer(maxUsers)); - } - } - else - { - initialMaxUsers = new Integer(maxUsers); - } - } - - @SuppressWarnings("unchecked") public int getMaxUsers() { - if (sysAdminCache != null) - { - Integer maxUsers = (Integer) sysAdminCache.get(KEY_SYSADMIN_MAX_USERS); - return (maxUsers == null ? -1 : maxUsers.intValue()); - } - else - { - return -1; - } + return sysAdminParams.getMaxUsers(); } public abstract Set getUsersWithTickets(boolean nonExpiredOnly); @@ -142,15 +81,4 @@ public abstract class AbstractAuthenticationService implements AuthenticationSer public abstract int countTickets(boolean nonExpiredOnly); public abstract Set getTicketComponents(); - - final public void afterPropertiesSet() throws Exception - { - initialised = true; - if (sysAdminCache != null) - { - sysAdminCache.put(KEY_SYSADMIN_MAX_USERS, initialMaxUsers); - sysAdminCache.put(KEY_SYSADMIN_ALLOWED_USERS, initialAllowedUsers); - } - } - } diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java index c0745a8bdc..3c0e8771e0 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java @@ -38,6 +38,8 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.attributes.Attribute; import org.alfresco.repo.attributes.LongAttributeValue; import org.alfresco.repo.attributes.MapAttributeValue; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.management.subsystems.ChildApplicationContextManager; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -50,6 +52,8 @@ import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.alfresco.util.AbstractLifecycleBean; import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; @@ -65,7 +69,8 @@ import org.springframework.context.ApplicationEvent; * the 'chain' of application contexts, managed by a {@link ChildApplicationContextManager}, and compares its * timestamped user and group information with the local users and groups last retrieved from the same source. Any * updates and additions made to those users and groups are applied to the local copies. The ordering of each - * {@link UserRegistry} in the chain determines its precedence when it comes to user and group name collisions. + * {@link UserRegistry} in the chain determines its precedence when it comes to user and group name collisions. The + * {@link JobLockService} is used to ensure that in a cluster, no two nodes actually run a synchronize at the same time. *

* The force argument determines whether a complete or partial set of information is queried from the * {@link UserRegistry}. When true then all users and groups are queried. With this complete set of @@ -84,12 +89,20 @@ import org.springframework.context.ApplicationEvent; */ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean implements UserRegistrySynchronizer { - /** The number of users / groups we add at a time in a transaction **/ + + /** The number of users / groups we add at a time in a transaction *. */ private static final int BATCH_SIZE = 10; /** The logger. */ private static final Log logger = LogFactory.getLog(ChainingUserRegistrySynchronizer.class); + /** The name of the lock used to ensure that a synchronize does not run on more than one node at the same time. */ + private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, + "ChainingUserRegistrySynchronizer"); + + /** The maximum time this lock will be held for (1 day). */ + private static final long LOCK_TTL = 1000 * 60 * 60 * 24; + /** The path in the attribute service below which we persist attributes. */ private static final String ROOT_ATTRIBUTE_PATH = ".ChainingUserRegistrySynchronizer"; @@ -117,13 +130,16 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl /** The retrying transaction helper. */ private RetryingTransactionHelper retryingTransactionHelper; - /** Should we trigger a differential sync when missing people log in? */ + /** The job lock service. */ + private JobLockService jobLockService; + + /** Should we trigger a differential sync when missing people log in?. */ private boolean syncWhenMissingPeopleLogIn = true; - /** Should we trigger a differential sync on startup? */ + /** Should we trigger a differential sync on startup?. */ private boolean syncOnStartup = true; - /** Should we auto create a missing person on log in? */ + /** Should we auto create a missing person on log in?. */ private boolean autoCreatePeopleOnLogin = true; /** @@ -193,7 +209,18 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl } /** - * Controls whether we auto create a missing person on log in + * Sets the job lock service. + * + * @param jobLockService + * the job lock service + */ + public void setJobLockService(JobLockService jobLockService) + { + this.jobLockService = jobLockService; + } + + /** + * Controls whether we auto create a missing person on log in. * * @param autoCreatePeopleOnLogin * true if we should auto create a missing person on log in @@ -204,7 +231,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl } /** - * Controls whether we trigger a differential sync when missing people log in + * Controls whether we trigger a differential sync when missing people log in. * * @param syncWhenMissingPeopleLogIn * if we should trigger a sync when missing people log in @@ -215,7 +242,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl } /** - * Controls whether we trigger a differential sync when the subsystem starts up + * Controls whether we trigger a differential sync when the subsystem starts up. * * @param syncOnStartup * if we should trigger a sync on startup @@ -231,10 +258,36 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl */ public void synchronize(boolean force, boolean splitTxns) { + // First, try to obtain a lock to ensure we are the only node trying to run this job + try + { + if (splitTxns) + { + // If this is an automated sync on startup or scheduled sync, don't even wait around for the lock. + // Assume the sync will be completed on another node. + this.jobLockService.getTransactionalLock(ChainingUserRegistrySynchronizer.LOCK_QNAME, + ChainingUserRegistrySynchronizer.LOCK_TTL, 0, 1); + } + else + { + // If this is a login-triggered sync, give it a few retries before giving up + this.jobLockService.getTransactionalLock(ChainingUserRegistrySynchronizer.LOCK_QNAME, + ChainingUserRegistrySynchronizer.LOCK_TTL, 3000, 10); + } + } + catch (LockAcquisitionException e) + { + // Don't proceed with the sync if it is running on another node + ChainingUserRegistrySynchronizer.logger + .warn("User registry synchronization already running in another thread. Synchronize aborted"); + return; + } + Set visitedZoneIds = new TreeSet(); Collection instanceIds = this.applicationContextManager.getInstanceIds(); - // Work out the set of all zone IDs in the authentication chain so that we can decide which users / groups need + // Work out the set of all zone IDs in the authentication chain so that we can decide which users / groups + // need // 're-zoning' Set allZoneIds = new TreeSet(); for (String id : instanceIds) @@ -894,6 +947,10 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl return zones; } + /* + * (non-Javadoc) + * @see org.alfresco.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.ApplicationEvent) + */ @Override protected void onBootstrap(ApplicationEvent event) { @@ -928,6 +985,10 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl } } + /* + * (non-Javadoc) + * @see org.alfresco.util.AbstractLifecycleBean#onShutdown(org.springframework.context.ApplicationEvent) + */ @Override protected void onShutdown(ApplicationEvent event) { diff --git a/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java b/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java index 12c7a54c1c..9220d6ff94 100644 --- a/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java +++ b/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 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 @@ -18,7 +18,7 @@ * 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 + * FLOSS exception. You should have received a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ @@ -26,7 +26,8 @@ package org.alfresco.repo.transaction; import javax.transaction.UserTransaction; -import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.VmShutdownListener; import org.alfresco.util.transaction.SpringAwareUserTransaction; @@ -45,59 +46,65 @@ public class TransactionServiceImpl implements TransactionService private static VmShutdownListener shutdownListener = new VmShutdownListener("TransactionService"); private PlatformTransactionManager transactionManager; + private AuthenticationContext authenticationContext; private int maxRetries = -1; private int minRetryWaitMs = -1; private int maxRetryWaitMs = -1; private int retryWaitIncrementMs = -1; - - // SysAdmin cache - used to cluster certain JMX operations - private SimpleCache sysAdminCache; - private final static String KEY_SYSADMIN_ALLOW_WRITE = "sysAdminCache.txAllowWrite"; - - + + // SysAdmin cache - used to cluster certain configuration parameters + private SysAdminParams sysAdminParams; + private boolean allowWrite; + /** * Set the transaction manager to use * - * @param transactionManager platform transaction manager + * @param transactionManager + * platform transaction manager */ public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } - - public void setSysAdminCache(SimpleCache sysAdminCache) + + /** + * Sets the authentication context. + * + * @param authenticationContext + * the authentication context + */ + public void setAuthenticationContext(AuthenticationContext authenticationContext) { - this.sysAdminCache = sysAdminCache; + this.authenticationContext = authenticationContext; + } + + public void setSysAdminParams(SysAdminParams sysAdminParams) + { + this.sysAdminParams = sysAdminParams; } /** * Set the read-only mode for all generated transactions. * - * @param allowWrite false if all transactions must be read-only + * @param allowWrite + * false if all transactions must be read-only */ public void setAllowWrite(boolean allowWrite) { - sysAdminCache.put(KEY_SYSADMIN_ALLOW_WRITE, allowWrite); + this.allowWrite = allowWrite; } - + public boolean isReadOnly() { if (shutdownListener.isVmShuttingDown()) { return true; } - try - { - Boolean allowWrite = (Boolean)sysAdminCache.get(KEY_SYSADMIN_ALLOW_WRITE); - return (allowWrite == null ? false : ! allowWrite); - } - catch (IllegalStateException e) - { - // The cache is not working - return true; - } + // Make the repo writable to the system user, so that e.g. the allow write flag can still be edited by JMX + return !this.allowWrite || !this.authenticationContext.isCurrentUserTheSystemUser() + && !this.sysAdminParams.getAllowWrite(); } - + /** * @see RetryingTransactionHelper#setMaxRetries(int) */ @@ -135,25 +142,19 @@ public class TransactionServiceImpl implements TransactionService */ public UserTransaction getUserTransaction() { - SpringAwareUserTransaction txn = new SpringAwareUserTransaction( - transactionManager, - isReadOnly(), - TransactionDefinition.ISOLATION_DEFAULT, - TransactionDefinition.PROPAGATION_REQUIRED, + SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, isReadOnly(), + TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRED, TransactionDefinition.TIMEOUT_DEFAULT); return txn; } - + /** * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED */ public UserTransaction getUserTransaction(boolean readOnly) { - SpringAwareUserTransaction txn = new SpringAwareUserTransaction( - transactionManager, - (readOnly | isReadOnly()), - TransactionDefinition.ISOLATION_DEFAULT, - TransactionDefinition.PROPAGATION_REQUIRED, + SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, (readOnly | isReadOnly()), + TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRED, TransactionDefinition.TIMEOUT_DEFAULT); return txn; } @@ -163,11 +164,8 @@ public class TransactionServiceImpl implements TransactionService */ public UserTransaction getNonPropagatingUserTransaction() { - SpringAwareUserTransaction txn = new SpringAwareUserTransaction( - transactionManager, - isReadOnly(), - TransactionDefinition.ISOLATION_DEFAULT, - TransactionDefinition.PROPAGATION_REQUIRES_NEW, + SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, isReadOnly(), + TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRES_NEW, TransactionDefinition.TIMEOUT_DEFAULT); return txn; } @@ -177,18 +175,15 @@ public class TransactionServiceImpl implements TransactionService */ public UserTransaction getNonPropagatingUserTransaction(boolean readOnly) { - SpringAwareUserTransaction txn = new SpringAwareUserTransaction( - transactionManager, - (readOnly | isReadOnly()), - TransactionDefinition.ISOLATION_DEFAULT, - TransactionDefinition.PROPAGATION_REQUIRES_NEW, + SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, (readOnly | isReadOnly()), + TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRES_NEW, TransactionDefinition.TIMEOUT_DEFAULT); return txn; } /** - * Creates a new helper instance. It can be reused or customized by the client code: - * each instance is new and initialized afresh. + * Creates a new helper instance. It can be reused or customized by the client code: each instance is new and + * initialized afresh. */ public RetryingTransactionHelper getRetryingTransactionHelper() { diff --git a/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java b/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java index e9592e9c93..ff95c2f256 100644 --- a/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java +++ b/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java @@ -29,10 +29,9 @@ import javax.transaction.Status; import javax.transaction.UserTransaction; import junit.framework.TestCase; -import net.sf.ehcache.Cache; -import net.sf.ehcache.CacheManager; -import org.alfresco.repo.cache.EhCacheAdapter; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.NodeService; @@ -59,17 +58,10 @@ public class TransactionServiceImplTest extends TestCase { transactionManager = (PlatformTransactionManager) ctx.getBean("transactionManager"); transactionService = new TransactionServiceImpl(); - transactionService.setTransactionManager(transactionManager); - - CacheManager cacheManager = new CacheManager(); - Cache sysAdminEhCache = new Cache("sysAdminCache", 10, false, true, 0L, 0L); - cacheManager.addCache(sysAdminEhCache); - EhCacheAdapter sysAdminCache = new EhCacheAdapter(); - sysAdminCache.setCache(sysAdminEhCache); - - transactionService.setSysAdminCache(sysAdminCache); - + transactionService.setTransactionManager(transactionManager); transactionService.setAllowWrite(true); + transactionService.setAuthenticationContext((AuthenticationContext) ctx.getBean("authenticationContext")); + transactionService.setSysAdminParams((SysAdminParams) ctx.getBean("sysAdminParams")); nodeService = (NodeService) ctx.getBean("dbNodeService"); } diff --git a/source/test-resources/sync-test-context.xml b/source/test-resources/sync-test-context.xml index 8eef1087c8..6658891f94 100644 --- a/source/test-resources/sync-test-context.xml +++ b/source/test-resources/sync-test-context.xml @@ -16,7 +16,10 @@ - + + + + userRegistry