From f13dc0a41518aecbc2dbc19f1f66a6859f1a8022 Mon Sep 17 00:00:00 2001 From: Alan Davis Date: Tue, 14 Jul 2015 07:03:15 +0000 Subject: [PATCH] Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud) 108205: Merged 5.0.N (5.0.3) to HEAD-BUG-FIX (5.1/Cloud) 108186: Reverse merged 5.0.N (5.0.3) << Better solution found >> 106968: Merged DEV to 5.0.N (5.0.3) 106962: MNT-13706 : Admin console unusable after we set any busy port in Port Number Value for subsystem - Implemented fix for Transformations (JodConverter), FTP, SMTP and IMAP. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@108221 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/bootstrap-context.xml | 9 - .../messages/system-messages.properties | 7 - .../alfresco/email/server/EmailServer.java | 807 +++--- .../config/ServerConfigurationBean.java | 24 +- .../repo/imap/AlfrescoImapServer.java | 24 +- .../AbstractPropertyBackedBean.java | 2375 ++++++++--------- .../subsystems/LoggableErrorEvent.java | 59 - source/java/org/alfresco/util/PortUtil.java | 71 - 8 files changed, 1533 insertions(+), 1843 deletions(-) delete mode 100644 source/java/org/alfresco/repo/management/subsystems/LoggableErrorEvent.java delete mode 100644 source/java/org/alfresco/util/PortUtil.java diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index a27671c194..9221da6fca 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -464,9 +464,6 @@ - - ^.*ServerConfigurationBean.*$ - @@ -513,9 +510,6 @@ - - ^.*AlfrescoImapServer.*$ - @@ -552,9 +546,6 @@ true - - ^.*EmailServer.*$ - diff --git a/config/alfresco/messages/system-messages.properties b/config/alfresco/messages/system-messages.properties index 52e12e5595..60d782f8f9 100644 --- a/config/alfresco/messages/system-messages.properties +++ b/config/alfresco/messages/system-messages.properties @@ -89,13 +89,6 @@ system.usage.err.limit_documents_exceeded=The allowable content limit of {0} has system.usage.err.limit_license_expiring=The Alfresco license will expire in {0} days. system.usage.err.limit_license_expired=The Alfresco license has expired. -# Subsystems error messages -system.loggable_error_event.unspecified_error=Unspecified error. -system.jodconverter.err.failed_start=Unable to start JodConverter library. Cause: {0}. -system.imap.err.port_in_use=The port chosen for IMAP is already in use: {0}. -system.smtp.err.port_in_use=The port chosen for SMTP is already in use: {0}. -system.ftp.err.port_in_use=The port chosen for FTP is already in use: {0}. - # License system.license.msg.unknown=Unknown system.license.msg.unlimited=Unlimited diff --git a/source/java/org/alfresco/email/server/EmailServer.java b/source/java/org/alfresco/email/server/EmailServer.java index 66226985b9..65d2d9cfcd 100644 --- a/source/java/org/alfresco/email/server/EmailServer.java +++ b/source/java/org/alfresco/email/server/EmailServer.java @@ -1,414 +1,405 @@ -/* - * Copyright (C) 2005-2010 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.email.server; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.StringTokenizer; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationException; -import org.alfresco.service.cmr.email.EmailMessageException; -import org.alfresco.service.cmr.email.EmailService; -import org.alfresco.util.PortUtil; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; -import org.alfresco.util.PropertyCheck; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -/** - * Base implementation of an email server. - * @since 2.2 - */ -public abstract class EmailServer extends AbstractLifecycleBean -{ - private static final String ERR_SENDER_BLOCKED = "email.server.err.sender_blocked"; - private static final String SMTP_PORT_OCCUPIED_MESSAGE = "system.smtp.err.port_in_use"; - - private boolean enabled; - private String domain; - private int port; - private int maxConnections; - private Set blockedSenders; - private Set allowedSenders; - private boolean hideTLS = false; - private boolean enableTLS = true; - private boolean requireTLS = false; - private boolean authenticate = false; - +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.email.server; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.service.cmr.email.EmailMessageException; +import org.alfresco.service.cmr.email.EmailService; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.alfresco.util.PropertyCheck; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Base implementation of an email server. + * @since 2.2 + */ +public abstract class EmailServer extends AbstractLifecycleBean +{ + private static final String ERR_SENDER_BLOCKED = "email.server.err.sender_blocked"; + + private boolean enabled; + private String domain; + private int port; + private int maxConnections; + private Set blockedSenders; + private Set allowedSenders; + private boolean hideTLS = false; + private boolean enableTLS = true; + private boolean requireTLS = false; + private boolean authenticate = false; + private EmailService emailService; private AuthenticationComponent authenticationComponent; protected EmailServer() { this.enabled = false; - this.port = 25; - this.domain = null; - this.maxConnections = 3; - this.blockedSenders = new HashSet(23); - this.allowedSenders = new HashSet(23); - } - - /** - * @param enabled Enable/disable server - */ - public void setEnabled(boolean enabled) - { - this.enabled = enabled; - } - - protected String getDomain() - { - return domain; - } - - public void setDomain(String domain) - { - this.domain = domain; - } - - protected int getPort() - { - return port; - } - - /** - * @param port SMTP port (25 is default) - */ - public void setPort(int port) - { - this.port = port; - } - - /** - * Returns the maximum number of connection accepted by the server. - * @return the maximum number of connections - */ - protected int getMaxConnections() - { - return maxConnections; - } - - /** - * Sets the maximum number of connection accepted by the server - * @param maxConnections - */ - public void setMaxConnections(int maxConnections) - { - this.maxConnections = maxConnections; - } - - /** - * Set the blocked senders as a comma separated list. The entries will be trimmed of - * all whitespace. - * - * @param blockedSenders a comman separated list of blocked senders - */ - public void setBlockedSenders(String blockedSenders) - { - StringTokenizer tokenizer = new StringTokenizer(blockedSenders, ",", false); - while (tokenizer.hasMoreTokens()) - { - String sender = tokenizer.nextToken().trim(); - this.blockedSenders.add(sender); - } - } - - /** - * @param blockedSenders a list of senders that are not allowed to email in - */ - public void setBlockedSendersList(List blockedSenders) - { - this.blockedSenders.addAll(blockedSenders); - } - - /** - * Set the allowed senders as a comma separated list. The entries will be trimmed of - * all whitespace. - * - * @param allowedSenders a comman separated list of blocked senders - */ - public void setAllowedSenders(String allowedSenders) - { - StringTokenizer tokenizer = new StringTokenizer(allowedSenders, ",", false); - while (tokenizer.hasMoreTokens()) - { - String sender = tokenizer.nextToken().trim(); - if (sender.length() == 0) - { - // Nothing - continue; - } - this.allowedSenders.add(sender); - } - } - - /** - * @param allowedSenders a list of senders that are allowed to email in - */ - public void setAllowedSendersList(List allowedSenders) - { - this.allowedSenders.addAll(allowedSenders); - } - - /** - * @return the service interface to interact with - */ - protected EmailService getEmailService() - { - return emailService; - } - - /** - * @param emailService the service interface to interact with - */ - public void setEmailService(EmailService emailService) - { - this.emailService = emailService; - } - - /** - * Filter incoming message by its sender e-mail address. - * - * @param sender An e-mail address of sender - * @throws EmailMessageException if the e-mail is rejected accordingly with blocked and allowed lists - */ - protected void filterSender(String sender) - { - // Check if the sender is in the blocked list - for (String blockedSender : blockedSenders) - { - if (sender.matches(blockedSender)) - { - throw new EmailMessageException(ERR_SENDER_BLOCKED, sender); - } - } - - // If there are any restrictions in the allowed list, then a positive match - // is absolutely required - if (!allowedSenders.isEmpty()) - { - boolean matched = false; - for (String allowedSender : allowedSenders) - { - if (sender.matches(allowedSender)) - { - matched = true; - break; - } - } - if (!matched) - { - throw new EmailMessageException(ERR_SENDER_BLOCKED, sender); - } - } - } - - /** - * Method is called when server is starting up. - */ - public abstract void startup(); - - /** - * Method is called when server is shutting down. - */ - public abstract void shutdown(); - - /** - * {@inheritDoc} - */ - @Override - protected void onBootstrap(ApplicationEvent event) - { - if (!enabled) - { - return; - } - // Check properties - PropertyCheck.mandatory(this, "domain", domain); - if (port <= 0 || port > 65535) - { - throw new AlfrescoRuntimeException("Property 'port' is incorrect"); - } - - // Check if port is occupied. - if (!PortUtil.isPortFree(port)) - { - throw new AlfrescoRuntimeException(SMTP_PORT_OCCUPIED_MESSAGE, new String[] { "" + port }); - } - - PropertyCheck.mandatory(this, "emailService", emailService); - // Startup - startup(); - } - - /** - * {@inheritDoc} - */ - @Override - protected void onShutdown(ApplicationEvent event) - { - if (enabled) - { - shutdown(); - } - } - - private static volatile Boolean stop = false; - - public static void main(String[] args) - { - if (args.length == 0) - { - usage(); - return; - } - AbstractApplicationContext context = null; - try - { - context = new ClassPathXmlApplicationContext(args); - } catch (BeansException e) - { - System.err.println("Erro create context: " + e); - usage(); - return; - } - - try - { - if (!context.containsBean("emailServer")) - { - usage(); - return; - } - - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run() - { - stop = true; - synchronized (stop) - { - stop.notifyAll(); - } - } - }); - System.out.println("Use Ctrl-C to shutdown EmailServer"); - - while (!stop) - { - synchronized (stop) - { - stop.wait(); - } - } - } - catch (InterruptedException e) - { - } - finally - { - context.close(); - } - - } - - private static void usage() - { - System.err.println("Use: EmailServer configLocation1, configLocation2, ..."); - System.err.println("\t configLocation - spring xml configs with EmailServer related beans (emailServer, emailServerConfiguration, emailService)"); - } - - /** - * authenticate with a user/password - * @param userName - * @param password - * @return true - authenticated - */ - protected boolean authenticateUserNamePassword(String userName, char[] password) - { - try - { - getAuthenticationComponent().authenticate(userName, password); - return true; - } - catch (AuthenticationException e) - { - return false; - } - } - - /** Hide the TLS (Trusted Login Session) option - * - * @param hideTLS - */ - public void setHideTLS(boolean hideTLS) - { - this.hideTLS = hideTLS; - } - - public boolean isHideTLS() - { - return hideTLS; - } - - public void setEnableTLS(boolean enableTLS) - { - this.enableTLS = enableTLS; - } - - public boolean isEnableTLS() - { - return enableTLS; - } - - public void setRequireTLS(boolean requireTLS) - { - this.requireTLS = requireTLS; - } - - public boolean isRequireTLS() - { - return requireTLS; - } - - public void setAuthenticate(boolean enableAuthentication) - { - this.authenticate = enableAuthentication; - } - - public boolean isAuthenticate() - { - return authenticate; - } - - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - public AuthenticationComponent getAuthenticationComponent() - { - return authenticationComponent; - } - -} + this.port = 25; + this.domain = null; + this.maxConnections = 3; + this.blockedSenders = new HashSet(23); + this.allowedSenders = new HashSet(23); + } + + /** + * @param enabled Enable/disable server + */ + public void setEnabled(boolean enabled) + { + this.enabled = enabled; + } + + protected String getDomain() + { + return domain; + } + + public void setDomain(String domain) + { + this.domain = domain; + } + + protected int getPort() + { + return port; + } + + /** + * @param port SMTP port (25 is default) + */ + public void setPort(int port) + { + this.port = port; + } + + /** + * Returns the maximum number of connection accepted by the server. + * @return the maximum number of connections + */ + protected int getMaxConnections() + { + return maxConnections; + } + + /** + * Sets the maximum number of connection accepted by the server + * @param maxConnections + */ + public void setMaxConnections(int maxConnections) + { + this.maxConnections = maxConnections; + } + + /** + * Set the blocked senders as a comma separated list. The entries will be trimmed of + * all whitespace. + * + * @param blockedSenders a comman separated list of blocked senders + */ + public void setBlockedSenders(String blockedSenders) + { + StringTokenizer tokenizer = new StringTokenizer(blockedSenders, ",", false); + while (tokenizer.hasMoreTokens()) + { + String sender = tokenizer.nextToken().trim(); + this.blockedSenders.add(sender); + } + } + + /** + * @param blockedSenders a list of senders that are not allowed to email in + */ + public void setBlockedSendersList(List blockedSenders) + { + this.blockedSenders.addAll(blockedSenders); + } + + /** + * Set the allowed senders as a comma separated list. The entries will be trimmed of + * all whitespace. + * + * @param allowedSenders a comman separated list of blocked senders + */ + public void setAllowedSenders(String allowedSenders) + { + StringTokenizer tokenizer = new StringTokenizer(allowedSenders, ",", false); + while (tokenizer.hasMoreTokens()) + { + String sender = tokenizer.nextToken().trim(); + if (sender.length() == 0) + { + // Nothing + continue; + } + this.allowedSenders.add(sender); + } + } + + /** + * @param allowedSenders a list of senders that are allowed to email in + */ + public void setAllowedSendersList(List allowedSenders) + { + this.allowedSenders.addAll(allowedSenders); + } + + /** + * @return the service interface to interact with + */ + protected EmailService getEmailService() + { + return emailService; + } + + /** + * @param emailService the service interface to interact with + */ + public void setEmailService(EmailService emailService) + { + this.emailService = emailService; + } + + /** + * Filter incoming message by its sender e-mail address. + * + * @param sender An e-mail address of sender + * @throws EmailMessageException if the e-mail is rejected accordingly with blocked and allowed lists + */ + protected void filterSender(String sender) + { + // Check if the sender is in the blocked list + for (String blockedSender : blockedSenders) + { + if (sender.matches(blockedSender)) + { + throw new EmailMessageException(ERR_SENDER_BLOCKED, sender); + } + } + + // If there are any restrictions in the allowed list, then a positive match + // is absolutely required + if (!allowedSenders.isEmpty()) + { + boolean matched = false; + for (String allowedSender : allowedSenders) + { + if (sender.matches(allowedSender)) + { + matched = true; + break; + } + } + if (!matched) + { + throw new EmailMessageException(ERR_SENDER_BLOCKED, sender); + } + } + } + + /** + * Method is called when server is starting up. + */ + public abstract void startup(); + + /** + * Method is called when server is shutting down. + */ + public abstract void shutdown(); + + /** + * {@inheritDoc} + */ + @Override + protected void onBootstrap(ApplicationEvent event) + { + if (!enabled) + { + return; + } + // Check properties + PropertyCheck.mandatory(this, "domain", domain); + if (port <= 0 || port > 65535) + { + throw new AlfrescoRuntimeException("Property 'port' is incorrect"); + } + PropertyCheck.mandatory(this, "emailService", emailService); + // Startup + startup(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onShutdown(ApplicationEvent event) + { + if (enabled) + { + shutdown(); + } + } + + private static volatile Boolean stop = false; + + public static void main(String[] args) + { + if (args.length == 0) + { + usage(); + return; + } + AbstractApplicationContext context = null; + try + { + context = new ClassPathXmlApplicationContext(args); + } catch (BeansException e) + { + System.err.println("Erro create context: " + e); + usage(); + return; + } + + try + { + if (!context.containsBean("emailServer")) + { + usage(); + return; + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() + { + stop = true; + synchronized (stop) + { + stop.notifyAll(); + } + } + }); + System.out.println("Use Ctrl-C to shutdown EmailServer"); + + while (!stop) + { + synchronized (stop) + { + stop.wait(); + } + } + } + catch (InterruptedException e) + { + } + finally + { + context.close(); + } + + } + + private static void usage() + { + System.err.println("Use: EmailServer configLocation1, configLocation2, ..."); + System.err.println("\t configLocation - spring xml configs with EmailServer related beans (emailServer, emailServerConfiguration, emailService)"); + } + + /** + * authenticate with a user/password + * @param userName + * @param password + * @return true - authenticated + */ + protected boolean authenticateUserNamePassword(String userName, char[] password) + { + try + { + getAuthenticationComponent().authenticate(userName, password); + return true; + } + catch (AuthenticationException e) + { + return false; + } + } + + /** Hide the TLS (Trusted Login Session) option + * + * @param hideTLS + */ + public void setHideTLS(boolean hideTLS) + { + this.hideTLS = hideTLS; + } + + public boolean isHideTLS() + { + return hideTLS; + } + + public void setEnableTLS(boolean enableTLS) + { + this.enableTLS = enableTLS; + } + + public boolean isEnableTLS() + { + return enableTLS; + } + + public void setRequireTLS(boolean requireTLS) + { + this.requireTLS = requireTLS; + } + + public boolean isRequireTLS() + { + return requireTLS; + } + + public void setAuthenticate(boolean enableAuthentication) + { + this.authenticate = enableAuthentication; + } + + public boolean isAuthenticate() + { + return authenticate; + } + + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + public AuthenticationComponent getAuthenticationComponent() + { + return authenticationComponent; + } + +} diff --git a/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java b/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java index 3a321e58f7..882260fe9a 100644 --- a/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java +++ b/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java @@ -83,11 +83,7 @@ import org.alfresco.jlan.util.Platform; import org.alfresco.jlan.util.StringList; import org.alfresco.jlan.util.X64; import org.alfresco.repo.management.subsystems.ActivateableBean; -import org.alfresco.repo.management.subsystems.LoggableErrorEvent; -import org.alfresco.util.PortUtil; import org.springframework.beans.factory.DisposableBean; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.extensions.config.element.GenericConfigElement; @@ -103,10 +99,8 @@ import org.springframework.extensions.config.element.GenericConfigElement; * @author dward * @author mrogers */ -public class ServerConfigurationBean extends AbstractServerConfigurationBean implements DisposableBean, ApplicationEventPublisherAware +public class ServerConfigurationBean extends AbstractServerConfigurationBean implements DisposableBean { - private static final String FTP_PORT_OCCUPIED_MESSAGE = "system.ftp.err.port_in_use"; - private CIFSConfigBean cifsConfigBean; private FTPConfigBean ftpConfigBean; private NFSConfigBean nfsConfigBean; @@ -116,8 +110,7 @@ public class ServerConfigurationBean extends AbstractServerConfigurationBean imp private ThreadRequestPool threadPool; protected ClusterConfigBean clusterConfigBean; - private ApplicationEventPublisher applicationEventPublisher; - + /** * Default constructor */ @@ -1227,13 +1220,6 @@ public class ServerConfigurationBean extends AbstractServerConfigurationBean imp ftpConfig.setFTPPort(port); if (ftpConfig.getFTPPort() <= 0 || ftpConfig.getFTPPort() >= 65535) throw new AlfrescoRuntimeException("FTP server port out of valid range"); - - // Check if port is occupied. - if (!PortUtil.isPortFree(port)) - { - applicationEventPublisher.publishEvent(new LoggableErrorEvent(this, - new AlfrescoRuntimeException(FTP_PORT_OCCUPIED_MESSAGE, new String[] { "" + port }))); - } } else { @@ -2382,10 +2368,4 @@ public class ServerConfigurationBean extends AbstractServerConfigurationBean imp } } - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) - { - this.applicationEventPublisher = applicationEventPublisher; - } - } diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java b/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java index 99c0caaa7e..831e263b6a 100644 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java @@ -37,27 +37,12 @@ import com.icegreen.greenmail.util.ServerSetup; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLServerSocket; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.management.subsystems.LoggableErrorEvent; -import org.alfresco.util.PortUtil; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; /** * @author Mike Shavnev */ -public class AlfrescoImapServer extends AbstractLifecycleBean implements ApplicationEventPublisherAware +public class AlfrescoImapServer extends AbstractLifecycleBean { - private static final String IMAP_PORT_OCCUPIED_MESSAGE = "system.imap.err.port_in_use"; - - private ApplicationEventPublisher applicationEventPublisher; - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) - { - this.applicationEventPublisher = applicationEventPublisher; - } - private class SecureImapServer extends ImapServer { @@ -265,13 +250,6 @@ public class AlfrescoImapServer extends AbstractLifecycleBean implements Applica if(isImapEnabled()) { - // Check if port is occupied. - if (!PortUtil.isPortFree(port)) - { - applicationEventPublisher.publishEvent(new LoggableErrorEvent(this, - new AlfrescoRuntimeException(IMAP_PORT_OCCUPIED_MESSAGE, new String[] { "" + port }))); - } - AtomicReference serverOpeningExceptionRef = new AtomicReference(); serverImpl = new DefaultImapServer(new ServerSetup(port, host, ServerSetup.PROTOCOL_IMAP), imapManagers, serverOpeningExceptionRef); serverImpl.startService(null); diff --git a/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java b/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java index 7b96613f5f..806c7dbb3f 100644 --- a/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java +++ b/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java @@ -1,888 +1,849 @@ -/* - * Copyright (C) 2005-2013 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.management.subsystems; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import org.alfresco.error.AlfrescoRuntimeException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.util.PropertyPlaceholderHelper; - -/** - * 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 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 - */ -public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, ApplicationContextAware, - ApplicationListener, InitializingBean, DisposableBean, BeanNameAware -{ - /** - * When true, calls to setProperties / setProperty are persisted to the MBean if it exists. - */ - private boolean saveSetProperty = false; - - /** The default final part of an ID. */ - protected static final String DEFAULT_INSTANCE_NAME = "default"; - - /** The parent application context. */ - private ApplicationContext parent; - - /** The registry of all property backed beans. */ - private PropertyBackedBeanRegistry registry; - - /** The category (first part of the ID). */ - private String category; - - /** The bean name if we have been initialized by Spring. */ - private String beanName; - - /** 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; - - /** Property defaults provided by the installer or System properties. */ - private Properties propertyDefaults; - - /** Property defaults provided by the JASYPT decryptor. */ - private Properties encryptedPropertyDefaults; - - /** Resolves placeholders in the property defaults. */ - private DefaultResolver defaultResolver = new DefaultResolver(); - private LoggableErrorEvent loggableErrorEvent; - - /** The lifecycle states. */ - protected enum RuntimeState {UNINITIALIZED, STOPPED, PENDING_BROADCAST_START, STARTED}; - - protected RuntimeState runtimeState = RuntimeState.UNINITIALIZED; - - /** The state. */ - private PropertyBackedBeanState state; - - private int timeoutForLoggableErrorEventMonitoringThread = 60000; - private int incrementForLoggableErrorEventMonitoringThread = 100; - - /** Lock for concurrent access. */ - protected ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - - private String loggableErrorEventMatcher = ""; - - public void setLoggableErrorEventMatcher(String loggableErrorEventMatcher) - { - this.loggableErrorEventMatcher = loggableErrorEventMatcher; - } - - public void setTimeoutForLoggableErrorEventMonitoringThread(int timeout) - { - timeoutForLoggableErrorEventMonitoringThread = timeout; - } - - public void setIncrementForLoggableErrorEventMonitoringThread(int increment) - { - incrementForLoggableErrorEventMonitoringThread = increment; - } - - /** - * Used in conjunction with {@link #localSetProperties} to control setting of - * properties from either a JMX client or by code in the local Alfresco - * node calling {@link AbstractPropertyBackedBean#setProperties(Map)} or - * {@link AbstractPropertyBackedBean#setProperty(String, String)}. - * Is true when there is a nested call to either of these - * methods. This is the case when there is an MBean AND one of these method was - * NOT originally called from that MBean (it is a local code). - */ - private ThreadLocal nestedCall = new ThreadLocal() - { - @Override - protected Boolean initialValue() - { - return false; - } - }; - - /** - * Used in conjunction with {@link #nestedCall} to control setting of - * properties from either a JMX client or by code in the local Alfresco - * node calling {@link AbstractPropertyBackedBean#setProperties(Map)} or - * {@link AbstractPropertyBackedBean#setProperty(String, String)}. - * Is set to true when there is a nested call back from - * a JMX bean. - */ - private ThreadLocal localSetProperties = new ThreadLocal() - { - @Override - protected Boolean initialValue() - { - return false; - } - }; - - /** The logger. */ - private static Log logger = LogFactory.getLog(AbstractPropertyBackedBean.class); - - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context. - * ApplicationContext) - */ - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - this.parent = applicationContext; - } - - /** - * Sets the registry of all property backed beans. - * - * @param registry - * the registry of all property backed beans - */ - public void setRegistry(PropertyBackedBeanRegistry registry) - { - this.registry = registry; - } - - /** - * Gets the registry of all property backed beans. - * - * @return the registry of all property backed beans - */ - protected PropertyBackedBeanRegistry getRegistry() - { - return this.registry; - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String) - */ - public void setBeanName(String name) - { - this.beanName = name; - } - - /** - * Sets the category (first part of the ID). - * - * @param category - * the category - */ - public void setCategory(String category) - { - this.category = category; - } - - /** - * Sets the hierarchical instance path within the category (second part of the ID).. - * - * @param instancePath - * the instance path - */ - public void setInstancePath(List instancePath) - { - this.instancePath = instancePath; - } - - /** - * Indicates whether the bean should be started on startup of the parent application context. - * - * @param autoStart - * true if the bean should be started on startup of the parent application context - */ - public void setAutoStart(boolean autoStart) - { - this.autoStart = autoStart; - } - - /** - * Sets the property defaults provided by the installer or System properties. - * - * @param propertyDefaults - * the property defaults - */ - public void setPropertyDefaults(Properties propertyDefaults) - { - this.propertyDefaults = propertyDefaults; - } - - public void setEncryptedPropertyDefaults(Properties propertyDefaults) - { - this.encryptedPropertyDefaults = propertyDefaults; - - } - - /** - * Gets the property defaults provided by the installer or System properties. - * - * @return the property defaults - */ - protected Properties getPropertyDefaults() - { - return this.propertyDefaults; - } - - /** - * Resolves the default value of a property, if there is one, expanding placholders as necessary. - * - * @param name - * the property name - * @return the resolved default value or null if there isn't one - */ - protected String resolveDefault(String name) - { - Properties props = new Properties(); - - if(propertyDefaults != null) - { - for( Object key : propertyDefaults.keySet()) - { - props.setProperty((String)key, propertyDefaults.getProperty((String)key)); - } - } - - if(encryptedPropertyDefaults != null) - { - for( Object key : encryptedPropertyDefaults.keySet()) - { - props.setProperty((String)key, encryptedPropertyDefaults.getProperty((String)key)); - } - } - - String value = props.getProperty(name); - if (value != null) - { - value = this.defaultResolver.resolveValue(value); - } - return value; - } - - /** - * Gets the parent application context. - * - * @return the parent application context - */ - protected ApplicationContext getParent() - { - 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 PropertyBackedBeanState getState(boolean start) - { - if (start) - { - start(true, false); - } - return this.state; - } - - /** - * When set to true, calls to setProperties / setProperty are persisted to the MBean if it exists. - */ - public void setSaveSetProperty(boolean saveSetProperty) - { - this.saveSetProperty = saveSetProperty; - } - - /** - * {@inheritDoc} - */ - public void afterPropertiesSet() throws Exception - { - // Default the category to the bean name - if (this.category == null) - { - if (this.beanName == null) - { - throw new IllegalStateException("Category not provided"); - } - this.category = this.beanName; - } - - // Derive the unique ID from the category and instance path - List path = getInstancePath(); - this.id = new ArrayList(path.size() + 1); - this.id.add(this.category); - this.id.addAll(getInstancePath()); - - init(); - } - - /** - * Initializes or resets the bean and its state. - */ - public final void init() - { - this.lock.writeLock().lock(); - try - { - doInit(); - } - finally - { - this.lock.writeLock().unlock(); - } - } - - /** - * Initializes or resets the bean and its state. - */ - protected void doInit() - { - boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); - if (this.runtimeState == RuntimeState.UNINITIALIZED) - { - if (!hadWriteLock) - { - this.lock.readLock().unlock(); - this.lock.writeLock().lock(); - } - try - { - if (this.runtimeState == RuntimeState.UNINITIALIZED) - { - logger.debug("doInit() createInitialState"); - this.state = createInitialState(); - logger.debug("doInit() applyDefaultOverrides "+state); - applyDefaultOverrides(this.state); - this.runtimeState = RuntimeState.STOPPED; - logger.debug("doInit() register"); - this.registry.register(this); - logger.debug("doInit() done"); - } - } - catch (IOException e) - { - throw new RuntimeException(e); - } - finally - { - logger.debug("doInit() state="+runtimeState); - if (!hadWriteLock) - { - this.lock.readLock().lock(); - this.lock.writeLock().unlock(); - } - } - } - } - - /** - * {@inheritDoc} - */ - public final void revert() - { - this.lock.writeLock().lock(); - try - { - stop(true); - destroy(true); - doInit(); - } - finally - { - this.lock.writeLock().unlock(); - } - } - - /** - * 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); - } - } - } - - /** - * {@inheritDoc} - */ - public List getId() - { - return this.id; - } - - /** - * Gets the category. - * - * @return the category - */ - 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; - } - - /** - * {@inheritDoc} - */ - public final void destroy() - { - this.lock.writeLock().lock(); - try - { - destroy(false); - } - finally - { - this.lock.writeLock().unlock(); - } - } - - /** - * 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. - */ - protected void destroy(boolean isPermanent) - { - if (this.runtimeState != RuntimeState.UNINITIALIZED) - { - boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); - if (!hadWriteLock) - { - this.lock.readLock().unlock(); - this.lock.writeLock().lock(); - } - try - { - if (this.runtimeState != RuntimeState.UNINITIALIZED) - { - logger.debug("destroy() stop state="+runtimeState); - stop(false); - logger.debug("destroy() deregister "+isPermanent); - this.registry.deregister(this, isPermanent); - this.state = null; - this.runtimeState = RuntimeState.UNINITIALIZED; - logger.debug("destroy() done"); - } - } - finally - { - logger.debug("destroy() state="+runtimeState); - if (!hadWriteLock) - { - this.lock.readLock().lock(); - this.lock.writeLock().unlock(); - } - } - } - } - - /** - * {@inheritDoc} - */ - public boolean isUpdateable(String name) - { - return true; - } - - /** - * {@inheritDoc} - */ - public String getDescription(String name) - { - return isUpdateable(name) ? "Editable Property " + name : "Read-only Property " + name; - } - - /** - * {@inheritDoc} - */ - public void onApplicationEvent(ApplicationEvent event) - { - if (this.autoStart && event instanceof ContextRefreshedEvent && event.getSource() == this.parent) - { - this.lock.writeLock().lock(); - try - { - start(false, false); - } - catch (Exception e) - { - // Let's log and swallow auto-start exceptions so that they are non-fatal. This means that the system - // can hopefully be brought up to a level where its configuration can be edited and corrected - logger.error("Error auto-starting subsystem", e); - } - finally - { - this.lock.writeLock().unlock(); - } - } - else if (event instanceof PropertyBackedBeanStartedEvent) - { - this.lock.writeLock().lock(); - try - { - // If we aren't started, reinitialize so that we pick up state changes from the database - switch (this.runtimeState) - { - case PENDING_BROADCAST_START: - case STOPPED: - destroy(false); - // fall through - case UNINITIALIZED: - start(false, false); - } - } - finally - { - this.lock.writeLock().unlock(); - } - } - else if (event instanceof PropertyBackedBeanStoppedEvent) - { - this.lock.writeLock().lock(); - try - { - // 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); - } - finally - { - this.lock.writeLock().unlock(); - } - } - else if (event instanceof LoggableErrorEvent) - { - Object source = event.getSource(); - if ((source != null) && source.getClass().getName().matches(loggableErrorEventMatcher)) - { - this.lock.writeLock().lock(); - - try - { - this.loggableErrorEvent = (LoggableErrorEvent) event; - } - finally - { - this.lock.writeLock().unlock(); - } - } - } - } - - /** - * {@inheritDoc} - */ - public String getProperty(String name) - { - this.lock.readLock().lock(); - try - { - doInit(); - return this.state.getProperty(name); - } - finally - { - this.lock.readLock().unlock(); - } - } - - /** - * {@inheritDoc} - */ - public Set getPropertyNames() - { - this.lock.readLock().lock(); - try - { - doInit(); - return this.state.getPropertyNames(); - } - finally - { - this.lock.readLock().unlock(); - } - } - - private void setPropertyInternal(String name, String value) - { - // Bring down the bean. The caller may have already broadcast this across the cluster - stop(false); - doInit(); - this.state.setProperty(name, value); - } - - private void setPropertiesInternal(Map properties) - { - // Bring down the bean. The caller may have already broadcast this across the cluster - stop(false); - doInit(); - - Map previousValues = new HashMap(properties.size() * 2); - try - { - // Set each of the properties and back up their previous values just in case - for (Map.Entry entry : properties.entrySet()) - { - String property = entry.getKey(); - String previousValue = this.state.getProperty(property); - this.state.setProperty(property, entry.getValue()); - previousValues.put(property, previousValue); - } - - // Attempt to start locally - start(false, true); - - // We still haven't broadcast the start - a persist is required first so this will be done by the caller - } - catch (Exception e) - { - // Oh dear - something went wrong. So restore previous state before rethrowing - for (Map.Entry entry : previousValues.entrySet()) - { - this.state.setProperty(entry.getKey(), entry.getValue()); - } - - // Bring the bean back up across the cluster - start(true, false); - if (e instanceof RuntimeException) - { - throw (RuntimeException) e; - } - throw new IllegalStateException(e); - } - } - - /** - * {@inheritDoc} - * - *

When called from code within the local node the values are saved to the - * database in the Enterprise edition and will be visible in a JMX client.

- * - * @param name - * @param value - */ - public void setProperty(String name, String value) - { - if (logger.isDebugEnabled()) - { - logger.debug("setProperty("+name+','+value+")"); - } - if (!nestedCall.get()) - { - nestedCall.set(true); - this.lock.writeLock().lock(); - try - { - boolean mBeanInfoChange = !getPropertyNames().contains(name); - - // When setting properties locally AND there is an MBean, the following broadcast - // results in a call to the MBean's setAttributes method, which in turn results - // in a nested call back. The call back sets the values in this bean and - // localSetProperties will be set to true. The MBean persists the changes and the - // broadcast method returns. If there is no MBean (community edition) OR when - // initiated from the MBean (say setting a value via JConsole), nothing happens - // as a result of the broadcast. - if (saveSetProperty) - { - logger.debug("setProperty() broadcastSetProperties"); - this.registry.broadcastSetProperty(this, name, value); - } - - if (localSetProperties.get()) - { - if (mBeanInfoChange) - { - // Re register the bean so new properties are visible in JConsole which does - // not check MBeanInfo for changes otherwise. - logger.debug("setProperty() destroy"); -// Commented out to avoid "UserTransaction is not visible from class loader" as it drops the context. -// So we have to just live with the JConsole as it is. -// destroy(false); - - // Attempt to start locally - start(false, true); - } - } - else - { - logger.debug("setProperty() setPropertyInternal"); - setPropertyInternal(name, value); - } - } - finally - { - localSetProperties.set(false); - nestedCall.set(false); - this.lock.writeLock().unlock(); - } - } - else - { - // A nested call indicates there is a MBean and that this method was - // NOT originally called from that MBean. - localSetProperties.set(true); - - logger.debug("setProperty() callback setPropertyInternal"); - setPropertyInternal(name, value); - } - } - - /** - * {@inheritDoc} - * - *

When called from code within the local node the values are saved to the - * database in the Enterprise edition and will be visible in a JMX client.

- * - * @param properties to be saved. - */ - public void setProperties(Map properties) - { - if (logger.isDebugEnabled()) - { - logger.debug("setProperties("+properties+")"); - } - if (!nestedCall.get()) - { - nestedCall.set(true); - - boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); - if (!hadWriteLock) - { - this.lock.writeLock().lock(); - } - try - { - boolean mBeanInfoChange = !getPropertyNames().containsAll(properties.keySet()); - - // When setting properties locally AND there is an MBean, the following broadcast - // results in a call to the MBean's setAttributes method, which in turn results - // in a nested call back. The call back sets the values in this bean and - // localSetProperties will be set to true. The MBean persists the changes and the - // broadcast method returns. If there is no MBean (community edition) OR when - // initiated from the MBean (say setting a value via JConsole), nothing happens - // as a result of the broadcast. - if (saveSetProperty) - { - logger.debug("setProperties() broadcastSetProperties"); - this.registry.broadcastSetProperties(this, properties); - } - - if (localSetProperties.get()) - { - if (mBeanInfoChange) - { - // Re register the bean so new properties are visible in JConsole which does - // not check MBeanInfo for changes otherwise. - logger.debug("setProperties() destroy"); -// Commented out to avoid "UserTransaction is not visible from class loader" as it drops the context. -// So we have to just live with the JConsole as it is. -// destroy(false); - - // Attempt to start locally - start(true, false); - } - } - else - { - logger.debug("setProperties() setPropertiesInternal"); - setPropertiesInternal(properties); - } - } - finally - { - localSetProperties.set(false); - nestedCall.set(false); - if (!hadWriteLock) - { - this.lock.writeLock().unlock(); - } - } - } - else - { - // A nested call indicates there is a MBean and that this method was - // NOT originally called from that MBean. - localSetProperties.set(true); - - logger.debug("setProperties() callback setPropertiesInternal"); - setPropertiesInternal(properties); - } - } - +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.management.subsystems; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.util.PropertyPlaceholderHelper; + +/** + * 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 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 + */ +public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, ApplicationContextAware, + ApplicationListener, InitializingBean, DisposableBean, BeanNameAware +{ + /** + * When true, calls to setProperties / setProperty are persisted to the MBean if it exists. + */ + private boolean saveSetProperty = false; + + /** The default final part of an ID. */ + protected static final String DEFAULT_INSTANCE_NAME = "default"; + + /** The parent application context. */ + private ApplicationContext parent; + + /** The registry of all property backed beans. */ + private PropertyBackedBeanRegistry registry; + + /** The category (first part of the ID). */ + private String category; + + /** The bean name if we have been initialized by Spring. */ + private String beanName; + + /** 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; + + /** Property defaults provided by the installer or System properties. */ + private Properties propertyDefaults; + + /** Property defaults provided by the JASYPT decryptor. */ + private Properties encryptedPropertyDefaults; + + /** Resolves placeholders in the property defaults. */ + private DefaultResolver defaultResolver = new DefaultResolver(); + + /** The lifecycle states. */ + protected enum RuntimeState {UNINITIALIZED, STOPPED, PENDING_BROADCAST_START, STARTED}; + + protected RuntimeState runtimeState = RuntimeState.UNINITIALIZED; + + /** The state. */ + private PropertyBackedBeanState state; + + /** Lock for concurrent access. */ + protected ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + /** + * Used in conjunction with {@link #localSetProperties} to control setting of + * properties from either a JMX client or by code in the local Alfresco + * node calling {@link AbstractPropertyBackedBean#setProperties(Map)} or + * {@link AbstractPropertyBackedBean#setProperty(String, String)}. + * Is true when there is a nested call to either of these + * methods. This is the case when there is an MBean AND one of these method was + * NOT originally called from that MBean (it is a local code). + */ + private ThreadLocal nestedCall = new ThreadLocal() + { + @Override + protected Boolean initialValue() + { + return false; + } + }; + + /** + * Used in conjunction with {@link #nestedCall} to control setting of + * properties from either a JMX client or by code in the local Alfresco + * node calling {@link AbstractPropertyBackedBean#setProperties(Map)} or + * {@link AbstractPropertyBackedBean#setProperty(String, String)}. + * Is set to true when there is a nested call back from + * a JMX bean. + */ + private ThreadLocal localSetProperties = new ThreadLocal() + { + @Override + protected Boolean initialValue() + { + return false; + } + }; + + /** The logger. */ + private static Log logger = LogFactory.getLog(AbstractPropertyBackedBean.class); + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context. + * ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.parent = applicationContext; + } + + /** + * Sets the registry of all property backed beans. + * + * @param registry + * the registry of all property backed beans + */ + public void setRegistry(PropertyBackedBeanRegistry registry) + { + this.registry = registry; + } + + /** + * Gets the registry of all property backed beans. + * + * @return the registry of all property backed beans + */ + protected PropertyBackedBeanRegistry getRegistry() + { + return this.registry; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String) + */ + public void setBeanName(String name) + { + this.beanName = name; + } + + /** + * Sets the category (first part of the ID). + * + * @param category + * the category + */ + public void setCategory(String category) + { + this.category = category; + } + + /** + * Sets the hierarchical instance path within the category (second part of the ID).. + * + * @param instancePath + * the instance path + */ + public void setInstancePath(List instancePath) + { + this.instancePath = instancePath; + } + + /** + * Indicates whether the bean should be started on startup of the parent application context. + * + * @param autoStart + * true if the bean should be started on startup of the parent application context + */ + public void setAutoStart(boolean autoStart) + { + this.autoStart = autoStart; + } + + /** + * Sets the property defaults provided by the installer or System properties. + * + * @param propertyDefaults + * the property defaults + */ + public void setPropertyDefaults(Properties propertyDefaults) + { + this.propertyDefaults = propertyDefaults; + } + + public void setEncryptedPropertyDefaults(Properties propertyDefaults) + { + this.encryptedPropertyDefaults = propertyDefaults; + + } + + /** + * Gets the property defaults provided by the installer or System properties. + * + * @return the property defaults + */ + protected Properties getPropertyDefaults() + { + return this.propertyDefaults; + } + + /** + * Resolves the default value of a property, if there is one, expanding placholders as necessary. + * + * @param name + * the property name + * @return the resolved default value or null if there isn't one + */ + protected String resolveDefault(String name) + { + Properties props = new Properties(); + + if(propertyDefaults != null) + { + for( Object key : propertyDefaults.keySet()) + { + props.setProperty((String)key, propertyDefaults.getProperty((String)key)); + } + } + + if(encryptedPropertyDefaults != null) + { + for( Object key : encryptedPropertyDefaults.keySet()) + { + props.setProperty((String)key, encryptedPropertyDefaults.getProperty((String)key)); + } + } + + String value = props.getProperty(name); + if (value != null) + { + value = this.defaultResolver.resolveValue(value); + } + return value; + } + + /** + * Gets the parent application context. + * + * @return the parent application context + */ + protected ApplicationContext getParent() + { + 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 PropertyBackedBeanState getState(boolean start) + { + if (start) + { + start(true, false); + } + return this.state; + } + + /** + * When set to true, calls to setProperties / setProperty are persisted to the MBean if it exists. + */ + public void setSaveSetProperty(boolean saveSetProperty) + { + this.saveSetProperty = saveSetProperty; + } + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception + { + // Default the category to the bean name + if (this.category == null) + { + if (this.beanName == null) + { + throw new IllegalStateException("Category not provided"); + } + this.category = this.beanName; + } + + // Derive the unique ID from the category and instance path + List path = getInstancePath(); + this.id = new ArrayList(path.size() + 1); + this.id.add(this.category); + this.id.addAll(getInstancePath()); + + init(); + } + + /** + * Initializes or resets the bean and its state. + */ + public final void init() + { + this.lock.writeLock().lock(); + try + { + doInit(); + } + finally + { + this.lock.writeLock().unlock(); + } + } + + /** + * Initializes or resets the bean and its state. + */ + protected void doInit() + { + boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); + if (this.runtimeState == RuntimeState.UNINITIALIZED) + { + if (!hadWriteLock) + { + this.lock.readLock().unlock(); + this.lock.writeLock().lock(); + } + try + { + if (this.runtimeState == RuntimeState.UNINITIALIZED) + { + logger.debug("doInit() createInitialState"); + this.state = createInitialState(); + logger.debug("doInit() applyDefaultOverrides "+state); + applyDefaultOverrides(this.state); + this.runtimeState = RuntimeState.STOPPED; + logger.debug("doInit() register"); + this.registry.register(this); + logger.debug("doInit() done"); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + finally + { + logger.debug("doInit() state="+runtimeState); + if (!hadWriteLock) + { + this.lock.readLock().lock(); + this.lock.writeLock().unlock(); + } + } + } + } + + /** + * {@inheritDoc} + */ + public final void revert() + { + this.lock.writeLock().lock(); + try + { + stop(true); + destroy(true); + doInit(); + } + finally + { + this.lock.writeLock().unlock(); + } + } + + /** + * 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); + } + } + } + + /** + * {@inheritDoc} + */ + public List getId() + { + return this.id; + } + + /** + * Gets the category. + * + * @return the category + */ + 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; + } + + /** + * {@inheritDoc} + */ + public final void destroy() + { + this.lock.writeLock().lock(); + try + { + destroy(false); + } + finally + { + this.lock.writeLock().unlock(); + } + } + + /** + * 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. + */ + protected void destroy(boolean isPermanent) + { + if (this.runtimeState != RuntimeState.UNINITIALIZED) + { + boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); + if (!hadWriteLock) + { + this.lock.readLock().unlock(); + this.lock.writeLock().lock(); + } + try + { + if (this.runtimeState != RuntimeState.UNINITIALIZED) + { + logger.debug("destroy() stop state="+runtimeState); + stop(false); + logger.debug("destroy() deregister "+isPermanent); + this.registry.deregister(this, isPermanent); + this.state = null; + this.runtimeState = RuntimeState.UNINITIALIZED; + logger.debug("destroy() done"); + } + } + finally + { + logger.debug("destroy() state="+runtimeState); + if (!hadWriteLock) + { + this.lock.readLock().lock(); + this.lock.writeLock().unlock(); + } + } + } + } + + /** + * {@inheritDoc} + */ + public boolean isUpdateable(String name) + { + return true; + } + + /** + * {@inheritDoc} + */ + public String getDescription(String name) + { + return isUpdateable(name) ? "Editable Property " + name : "Read-only Property " + name; + } + + /** + * {@inheritDoc} + */ + public void onApplicationEvent(ApplicationEvent event) + { + if (this.autoStart && event instanceof ContextRefreshedEvent && event.getSource() == this.parent) + { + this.lock.writeLock().lock(); + try + { + start(false, false); + } + catch (Exception e) + { + // Let's log and swallow auto-start exceptions so that they are non-fatal. This means that the system + // can hopefully be brought up to a level where its configuration can be edited and corrected + logger.error("Error auto-starting subsystem", e); + } + finally + { + this.lock.writeLock().unlock(); + } + } + else if (event instanceof PropertyBackedBeanStartedEvent) + { + this.lock.writeLock().lock(); + try + { + // If we aren't started, reinitialize so that we pick up state changes from the database + switch (this.runtimeState) + { + case PENDING_BROADCAST_START: + case STOPPED: + destroy(false); + // fall through + case UNINITIALIZED: + start(false, false); + } + } + finally + { + this.lock.writeLock().unlock(); + } + } + else if (event instanceof PropertyBackedBeanStoppedEvent) + { + this.lock.writeLock().lock(); + try + { + // 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); + } + finally + { + this.lock.writeLock().unlock(); + } + } + } + + /** + * {@inheritDoc} + */ + public String getProperty(String name) + { + this.lock.readLock().lock(); + try + { + doInit(); + return this.state.getProperty(name); + } + finally + { + this.lock.readLock().unlock(); + } + } + + /** + * {@inheritDoc} + */ + public Set getPropertyNames() + { + this.lock.readLock().lock(); + try + { + doInit(); + return this.state.getPropertyNames(); + } + finally + { + this.lock.readLock().unlock(); + } + } + + private void setPropertyInternal(String name, String value) + { + // Bring down the bean. The caller may have already broadcast this across the cluster + stop(false); + doInit(); + this.state.setProperty(name, value); + } + + private void setPropertiesInternal(Map properties) + { + // Bring down the bean. The caller may have already broadcast this across the cluster + stop(false); + doInit(); + + Map previousValues = new HashMap(properties.size() * 2); + try + { + // Set each of the properties and back up their previous values just in case + for (Map.Entry entry : properties.entrySet()) + { + String property = entry.getKey(); + String previousValue = this.state.getProperty(property); + this.state.setProperty(property, entry.getValue()); + previousValues.put(property, previousValue); + } + + // Attempt to start locally + start(false, true); + + // We still haven't broadcast the start - a persist is required first so this will be done by the caller + } + catch (Exception e) + { + // Oh dear - something went wrong. So restore previous state before rethrowing + for (Map.Entry entry : previousValues.entrySet()) + { + this.state.setProperty(entry.getKey(), entry.getValue()); + } + + // Bring the bean back up across the cluster + start(true, false); + if (e instanceof RuntimeException) + { + throw (RuntimeException) e; + } + throw new IllegalStateException(e); + } + } + + /** + * {@inheritDoc} + * + *

When called from code within the local node the values are saved to the + * database in the Enterprise edition and will be visible in a JMX client.

+ * + * @param name + * @param value + */ + public void setProperty(String name, String value) + { + if (logger.isDebugEnabled()) + { + logger.debug("setProperty("+name+','+value+")"); + } + if (!nestedCall.get()) + { + nestedCall.set(true); + this.lock.writeLock().lock(); + try + { + boolean mBeanInfoChange = !getPropertyNames().contains(name); + + // When setting properties locally AND there is an MBean, the following broadcast + // results in a call to the MBean's setAttributes method, which in turn results + // in a nested call back. The call back sets the values in this bean and + // localSetProperties will be set to true. The MBean persists the changes and the + // broadcast method returns. If there is no MBean (community edition) OR when + // initiated from the MBean (say setting a value via JConsole), nothing happens + // as a result of the broadcast. + if (saveSetProperty) + { + logger.debug("setProperty() broadcastSetProperties"); + this.registry.broadcastSetProperty(this, name, value); + } + + if (localSetProperties.get()) + { + if (mBeanInfoChange) + { + // Re register the bean so new properties are visible in JConsole which does + // not check MBeanInfo for changes otherwise. + logger.debug("setProperty() destroy"); +// Commented out to avoid "UserTransaction is not visible from class loader" as it drops the context. +// So we have to just live with the JConsole as it is. +// destroy(false); + + // Attempt to start locally + start(false, true); + } + } + else + { + logger.debug("setProperty() setPropertyInternal"); + setPropertyInternal(name, value); + } + } + finally + { + localSetProperties.set(false); + nestedCall.set(false); + this.lock.writeLock().unlock(); + } + } + else + { + // A nested call indicates there is a MBean and that this method was + // NOT originally called from that MBean. + localSetProperties.set(true); + + logger.debug("setProperty() callback setPropertyInternal"); + setPropertyInternal(name, value); + } + } + + /** + * {@inheritDoc} + * + *

When called from code within the local node the values are saved to the + * database in the Enterprise edition and will be visible in a JMX client.

+ * + * @param properties to be saved. + */ + public void setProperties(Map properties) + { + if (logger.isDebugEnabled()) + { + logger.debug("setProperties("+properties+")"); + } + if (!nestedCall.get()) + { + nestedCall.set(true); + + boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); + if (!hadWriteLock) + { + this.lock.writeLock().lock(); + } + try + { + boolean mBeanInfoChange = !getPropertyNames().containsAll(properties.keySet()); + + // When setting properties locally AND there is an MBean, the following broadcast + // results in a call to the MBean's setAttributes method, which in turn results + // in a nested call back. The call back sets the values in this bean and + // localSetProperties will be set to true. The MBean persists the changes and the + // broadcast method returns. If there is no MBean (community edition) OR when + // initiated from the MBean (say setting a value via JConsole), nothing happens + // as a result of the broadcast. + if (saveSetProperty) + { + logger.debug("setProperties() broadcastSetProperties"); + this.registry.broadcastSetProperties(this, properties); + } + + if (localSetProperties.get()) + { + if (mBeanInfoChange) + { + // Re register the bean so new properties are visible in JConsole which does + // not check MBeanInfo for changes otherwise. + logger.debug("setProperties() destroy"); +// Commented out to avoid "UserTransaction is not visible from class loader" as it drops the context. +// So we have to just live with the JConsole as it is. +// destroy(false); + + // Attempt to start locally + start(true, false); + } + } + else + { + logger.debug("setProperties() setPropertiesInternal"); + setPropertiesInternal(properties); + } + } + finally + { + localSetProperties.set(false); + nestedCall.set(false); + if (!hadWriteLock) + { + this.lock.writeLock().unlock(); + } + } + } + else + { + // A nested call indicates there is a MBean and that this method was + // NOT originally called from that MBean. + localSetProperties.set(true); + + logger.debug("setProperties() callback setPropertiesInternal"); + setPropertiesInternal(properties); + } + } + /** * Removes a property added by code within the local node. * @@ -890,9 +851,9 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, */ public void removeProperty(String name) { - removeProperties(Collections.singleton(name)); - } - + removeProperties(Collections.singleton(name)); + } + /** * Removes properties added by code within the local node. * @@ -900,359 +861,285 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, */ public void removeProperties(Collection properties) { - if (logger.isDebugEnabled()) - { - logger.debug("removeProperties("+properties+")"); - } - if (!nestedCall.get()) - { - nestedCall.set(true); - - boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); - if (!hadWriteLock) - { - this.lock.writeLock().lock(); - } - try - { - boolean mBeanInfoChange = !getPropertyNames().containsAll(properties); - - // When setting properties locally AND there is an MBean, the following broadcast - // results in a call to the MBean's setAttributes method, which in turn results - // in a nested call back. The call back sets the values in this bean and - // localSetProperties will be set to true. The MBean persists the changes and the - // broadcast method returns. If there is no MBean (community edition) OR when - // initiated from the MBean (say setting a value via JConsole), nothing happens - // as a result of the broadcast. - if (saveSetProperty) - { - logger.debug("removeProperties() broadcastRemoveProperties"); - this.registry.broadcastRemoveProperties(this, properties); - } - - if (localSetProperties.get()) - { - if (mBeanInfoChange) - { - // Re register the bean so new properties are visible in JConsole which does - // not check MBeanInfo for changes otherwise. - logger.debug("removeProperties() destroy"); -// Commented out to avoid "UserTransaction is not visible from class loader" as it drops the context. -// So we have to just live with the JConsole as it is. -// destroy(false); - - // Attempt to start locally - start(true, false); - } - } - else - { - logger.debug("removeProperties() removePropertiesInternal"); - removePropertiesInternal(properties); - } - } - finally - { - localSetProperties.set(false); - nestedCall.set(false); - if (!hadWriteLock) - { - this.lock.writeLock().unlock(); - } - } - } - else - { - // A nested call indicates there is a MBean and that this method was - // NOT originally called from that MBean. - localSetProperties.set(true); - - logger.debug("removeProperties() callback removePropertiesInternal"); - removePropertiesInternal(properties); - } - } - - private void removePropertiesInternal(Collection properties) - { - // Bring down the bean. The caller may have already broadcast this across the cluster - stop(false); - doInit(); - - Map previousValues = new HashMap(properties.size() * 2); - try - { - // Set each of the properties and back up their previous values just in case - for (String property : properties) - { - String previousValue = state.getProperty(property); - this.state.removeProperty(property); - previousValues.put(property, previousValue); - } - - // Attempt to start locally - start(false, true); - - // We still haven't broadcast the start - a persist is required first so this will be done by the caller - } - catch (Exception e) - { - // Oh dear - something went wrong. So restore previous state before rethrowing - for (Map.Entry entry : previousValues.entrySet()) - { - this.state.setProperty(entry.getKey(), entry.getValue()); - } - - // Bring the bean back up across the cluster - start(true, false); - if (e instanceof RuntimeException) - { - throw (RuntimeException) e; - } - throw new IllegalStateException(e); - } - } - - private class LoggableErrorEventMonitoringThread extends Thread - { - private int counter = 0; - private AlfrescoRuntimeException caughtException = null; - - public void run() - { - while (true) - { - AbstractPropertyBackedBean.this.lock.readLock().lock(); - - try - { - if (loggableErrorEvent != null) - { - caughtException = loggableErrorEvent.getException(); - } - - } - finally - { - AbstractPropertyBackedBean.this.lock.readLock().unlock(); - } - - if (counter >= timeoutForLoggableErrorEventMonitoringThread || caughtException != null) - { - break; - } - - try - { - Thread.sleep(incrementForLoggableErrorEventMonitoringThread); - counter += incrementForLoggableErrorEventMonitoringThread; - } - catch (InterruptedException ie) - { - if (logger.isDebugEnabled()) - { - logger.debug(ie.toString()); - } - } - } - - if (caughtException != null) - { - if (logger.isErrorEnabled()) - { - logger.error(caughtException.toString()); - } - - loggableErrorEvent = null; - throw caughtException; - } - } - } - - /** - * {@inheritDoc} - */ - public final void start() - { - this.lock.writeLock().lock(); - - try - { - start(true, false); - - if (loggableErrorEvent != null) - { - AlfrescoRuntimeException caughtException = loggableErrorEvent.getException(); - - if (logger.isErrorEnabled()) - { - logger.error(caughtException.toString()); - } - - loggableErrorEvent = null; - throw caughtException; - } - else - { - (new LoggableErrorEventMonitoringThread()).start(); - } - } - finally - { - this.lock.writeLock().unlock(); - } - } - - /** - * Starts the bean, optionally broadcasting the event to remote nodes. - * - * @param broadcastNow - * Should the event be broadcast immediately? - * @param broadcastLater - * Should the event be broadcast ever? - */ - protected void start(boolean broadcastNow, boolean broadcastLater) - { - boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); - if (this.runtimeState != RuntimeState.STARTED) - { - if (!hadWriteLock) - { - this.lock.readLock().unlock(); - this.lock.writeLock().lock(); - } - try - { - switch (this.runtimeState) - { - case UNINITIALIZED: - doInit(); - // fall through - case STOPPED: - this.state.start(); - this.runtimeState = broadcastLater ? RuntimeState.PENDING_BROADCAST_START : RuntimeState.STARTED; - // fall through - case PENDING_BROADCAST_START: - if (broadcastNow) - { - this.registry.broadcastStart(this); - this.runtimeState = RuntimeState.STARTED; - } - } - } - finally - { - if (!hadWriteLock) - { - this.lock.readLock().lock(); - this.lock.writeLock().unlock(); - } - } - } - } - - /** - * {@inheritDoc} - */ - public final void stop() - { - this.lock.writeLock().lock(); - try - { - stop(true); - } - finally - { - this.lock.writeLock().unlock(); - } - } - - /** - * Stops the bean, optionally broadcasting the event to remote nodes. - * - * @param broadcast - * Should the event be broadcast? - */ - protected void stop(boolean broadcast) - { - boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); - switch (this.runtimeState) - { - case PENDING_BROADCAST_START: - case STARTED: - if (!hadWriteLock) - { - this.lock.readLock().unlock(); - this.lock.writeLock().lock(); - } - try - { - switch (this.runtimeState) - { - case STARTED: - if (broadcast) - { - this.registry.broadcastStop(this); - } - // fall through - case PENDING_BROADCAST_START: - this.state.stop(); - this.runtimeState = RuntimeState.STOPPED; - } - } - finally - { - if (!hadWriteLock) - { - this.lock.readLock().lock(); - this.lock.writeLock().unlock(); - } - } - } - } - - /** - * Uses a Spring {@link PropertyPlaceholderHelper} to resolve placeholders in the property defaults. This means - * that placeholders need not be displayed in the configuration UI or JMX console. - */ - public class DefaultResolver extends PropertyPlaceholderHelper - { - - /** - * Instantiates a new default resolver. - */ - public DefaultResolver() - { - super("${", "}", ":", true); - } - - /** - * Expands the given value, resolving any ${} placeholders using the property defaults. - * - * @param val - * the value to expand - * @return the expanded value - */ - public String resolveValue(String val) - { - Properties props = new Properties(); - - if(propertyDefaults != null) - { - for( Object key : propertyDefaults.keySet()) - { - props.setProperty((String)key, propertyDefaults.getProperty((String)key)); - } - } - - if(encryptedPropertyDefaults != null) - { - for( Object key : encryptedPropertyDefaults.keySet()) - { - props.setProperty((String)key, encryptedPropertyDefaults.getProperty((String)key)); - } - } - return replacePlaceholders(val, props); - } - - } -} + if (logger.isDebugEnabled()) + { + logger.debug("removeProperties("+properties+")"); + } + if (!nestedCall.get()) + { + nestedCall.set(true); + + boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); + if (!hadWriteLock) + { + this.lock.writeLock().lock(); + } + try + { + boolean mBeanInfoChange = !getPropertyNames().containsAll(properties); + + // When setting properties locally AND there is an MBean, the following broadcast + // results in a call to the MBean's setAttributes method, which in turn results + // in a nested call back. The call back sets the values in this bean and + // localSetProperties will be set to true. The MBean persists the changes and the + // broadcast method returns. If there is no MBean (community edition) OR when + // initiated from the MBean (say setting a value via JConsole), nothing happens + // as a result of the broadcast. + if (saveSetProperty) + { + logger.debug("removeProperties() broadcastRemoveProperties"); + this.registry.broadcastRemoveProperties(this, properties); + } + + if (localSetProperties.get()) + { + if (mBeanInfoChange) + { + // Re register the bean so new properties are visible in JConsole which does + // not check MBeanInfo for changes otherwise. + logger.debug("removeProperties() destroy"); +// Commented out to avoid "UserTransaction is not visible from class loader" as it drops the context. +// So we have to just live with the JConsole as it is. +// destroy(false); + + // Attempt to start locally + start(true, false); + } + } + else + { + logger.debug("removeProperties() removePropertiesInternal"); + removePropertiesInternal(properties); + } + } + finally + { + localSetProperties.set(false); + nestedCall.set(false); + if (!hadWriteLock) + { + this.lock.writeLock().unlock(); + } + } + } + else + { + // A nested call indicates there is a MBean and that this method was + // NOT originally called from that MBean. + localSetProperties.set(true); + + logger.debug("removeProperties() callback removePropertiesInternal"); + removePropertiesInternal(properties); + } + } + + private void removePropertiesInternal(Collection properties) + { + // Bring down the bean. The caller may have already broadcast this across the cluster + stop(false); + doInit(); + + Map previousValues = new HashMap(properties.size() * 2); + try + { + // Set each of the properties and back up their previous values just in case + for (String property : properties) + { + String previousValue = state.getProperty(property); + this.state.removeProperty(property); + previousValues.put(property, previousValue); + } + + // Attempt to start locally + start(false, true); + + // We still haven't broadcast the start - a persist is required first so this will be done by the caller + } + catch (Exception e) + { + // Oh dear - something went wrong. So restore previous state before rethrowing + for (Map.Entry entry : previousValues.entrySet()) + { + this.state.setProperty(entry.getKey(), entry.getValue()); + } + + // Bring the bean back up across the cluster + start(true, false); + if (e instanceof RuntimeException) + { + throw (RuntimeException) e; + } + throw new IllegalStateException(e); + } + } + + /** + * {@inheritDoc} + */ + public final void start() + { + this.lock.writeLock().lock(); + try + { + start(true, false); + } + finally + { + this.lock.writeLock().unlock(); + } + } + + /** + * Starts the bean, optionally broadcasting the event to remote nodes. + * + * @param broadcastNow + * Should the event be broadcast immediately? + * @param broadcastLater + * Should the event be broadcast ever? + */ + protected void start(boolean broadcastNow, boolean broadcastLater) + { + boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); + if (this.runtimeState != RuntimeState.STARTED) + { + if (!hadWriteLock) + { + this.lock.readLock().unlock(); + this.lock.writeLock().lock(); + } + try + { + switch (this.runtimeState) + { + case UNINITIALIZED: + doInit(); + // fall through + case STOPPED: + this.state.start(); + this.runtimeState = broadcastLater ? RuntimeState.PENDING_BROADCAST_START : RuntimeState.STARTED; + // fall through + case PENDING_BROADCAST_START: + if (broadcastNow) + { + this.registry.broadcastStart(this); + this.runtimeState = RuntimeState.STARTED; + } + } + } + finally + { + if (!hadWriteLock) + { + this.lock.readLock().lock(); + this.lock.writeLock().unlock(); + } + } + } + } + + /** + * {@inheritDoc} + */ + public final void stop() + { + this.lock.writeLock().lock(); + try + { + stop(true); + } + finally + { + this.lock.writeLock().unlock(); + } + } + + /** + * Stops the bean, optionally broadcasting the event to remote nodes. + * + * @param broadcast + * Should the event be broadcast? + */ + protected void stop(boolean broadcast) + { + boolean hadWriteLock = this.lock.isWriteLockedByCurrentThread(); + switch (this.runtimeState) + { + case PENDING_BROADCAST_START: + case STARTED: + if (!hadWriteLock) + { + this.lock.readLock().unlock(); + this.lock.writeLock().lock(); + } + try + { + switch (this.runtimeState) + { + case STARTED: + if (broadcast) + { + this.registry.broadcastStop(this); + } + // fall through + case PENDING_BROADCAST_START: + this.state.stop(); + this.runtimeState = RuntimeState.STOPPED; + } + } + finally + { + if (!hadWriteLock) + { + this.lock.readLock().lock(); + this.lock.writeLock().unlock(); + } + } + } + } + + /** + * Uses a Spring {@link PropertyPlaceholderHelper} to resolve placeholders in the property defaults. This means + * that placeholders need not be displayed in the configuration UI or JMX console. + */ + public class DefaultResolver extends PropertyPlaceholderHelper + { + + /** + * Instantiates a new default resolver. + */ + public DefaultResolver() + { + super("${", "}", ":", true); + } + + /** + * Expands the given value, resolving any ${} placeholders using the property defaults. + * + * @param val + * the value to expand + * @return the expanded value + */ + public String resolveValue(String val) + { + Properties props = new Properties(); + + if(propertyDefaults != null) + { + for( Object key : propertyDefaults.keySet()) + { + props.setProperty((String)key, propertyDefaults.getProperty((String)key)); + } + } + + if(encryptedPropertyDefaults != null) + { + for( Object key : encryptedPropertyDefaults.keySet()) + { + props.setProperty((String)key, encryptedPropertyDefaults.getProperty((String)key)); + } + } + return replacePlaceholders(val, props); + } + + } +} diff --git a/source/java/org/alfresco/repo/management/subsystems/LoggableErrorEvent.java b/source/java/org/alfresco/repo/management/subsystems/LoggableErrorEvent.java deleted file mode 100644 index 67fe6cf66a..0000000000 --- a/source/java/org/alfresco/repo/management/subsystems/LoggableErrorEvent.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2005-2015 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.management.subsystems; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.springframework.context.ApplicationEvent; - -/** - * Alfresco custom ApplicationEvent class used for publishing errors in subsystems. - */ -public class LoggableErrorEvent extends ApplicationEvent -{ - private static final String UNSPECIFIED_ERROR_MESSAGE = "system.loggable_error_event.unspecified_error"; - - private AlfrescoRuntimeException exception; - - /** - * Create a new LoggableErrorEvent. - * @param source the component that published the event (never {@code null}) - * @param exception the error to publish - */ - public LoggableErrorEvent(Object source, AlfrescoRuntimeException exception) - { - super(source); - this.exception = exception; - } - - /** - * Get this LoggableErrorEvent's exception. - * @return the stored exception if not null, otherwise a new RuntimeException - */ - public AlfrescoRuntimeException getException() - { - if (exception != null) - { - return exception; - } - else - { - return new AlfrescoRuntimeException(UNSPECIFIED_ERROR_MESSAGE); - } - } -} diff --git a/source/java/org/alfresco/util/PortUtil.java b/source/java/org/alfresco/util/PortUtil.java deleted file mode 100644 index a9825e5975..0000000000 --- a/source/java/org/alfresco/util/PortUtil.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2005-2015 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.util; - -import java.io.IOException; -import java.net.ServerSocket; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Alfresco port-related utility functions. - */ -public class PortUtil -{ - private static Log logger = LogFactory.getLog(PortUtil.class); - - /** - * Check if specified port is free. - * @param port port number to check - * @return true if port is free or false if it's already in use - */ - public static boolean isPortFree(int port) - { - boolean isFree = true; - ServerSocket serverSocket = null; - - try - { - serverSocket = new ServerSocket(port); - } - catch (IOException ioe) - { - isFree = false; - } - finally - { - if (serverSocket != null) - { - try - { - serverSocket.close(); - } - catch (IOException ioe) - { - if (logger.isDebugEnabled()) - { - logger.debug(ioe.toString()); - } - } - } - } - - return isFree; - } -}