/* * Copyright (C) 2010 eXo Platform SAS. * * This 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 2.1 of * the License, or (at your option) any later version. * * This software 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 this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.services.jcr.impl; import org.exoplatform.container.BaseContainerLifecyclePlugin; import org.exoplatform.container.ExoContainer; import org.exoplatform.container.ExoContainerContext; import org.exoplatform.container.xml.InitParams; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.services.rpc.RPCException; import org.exoplatform.services.rpc.RPCService; import org.exoplatform.services.rpc.RemoteCommand; import org.exoplatform.services.rpc.TopologyChangeEvent; import org.exoplatform.services.rpc.TopologyChangeListener; import java.io.Serializable; import java.util.concurrent.CountDownLatch; /** * This class is used to be able to synchronize the cluster nodes to avoid initializing * several times the same repository which could cause exceptions and prevent the nodes to start. * * @author <a href="mailto:nicolas.filotto@exoplatform.com">Nicolas Filotto</a> * @version $Id$ */ public class RepositoryCreationSynchronizer { /** * The logger. */ private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.RepositoryCreationSynchronizer"); /** * The RPC Service used to communicate with other nodes */ private final RPCService rpcService; /** * The {@link RemoteCommand} used to indicate to the nodes that at least one workspace needs * to be initialized */ private RemoteCommand needToInitWorkspaceCommand; /** * The {@link RemoteCommand} used to know whether the node have to wait or * not */ private RemoteCommand shouldIWaitCommand; /** * Indicates whether at least one workspace has to be initialized */ private boolean needToInitWorkspace; /** * This lock is used to synchronize the start process of all the nodes */ private final CountDownLatch lock = new CountDownLatch(1); /** * Indicates whether this component has to be disabled or not */ private final boolean disabled; /** * The default constructor that should be used in a non cluster environment */ public RepositoryCreationSynchronizer(ExoContainerContext ctx) { this(null, ctx, null); } /** * The default constructor that should be used in a cluster environment */ public RepositoryCreationSynchronizer(final RPCService rpcService, ExoContainerContext ctx, InitParams params) { this.rpcService = rpcService; this.disabled = rpcService == null || (params != null && params.getValueParam("disabled") != null && Boolean.valueOf(params.getValueParam( "disabled").getValue())); if (disabled && LOG.isDebugEnabled()) { LOG.debug("The RepositoryCreationSynchronizer has been disabled"); } if (rpcService != null) { shouldIWaitCommand = rpcService.registerCommand(new RemoteCommand() { public String getId() { return "exo.jcr.component.core.RepositoryCreationSynchronizer-shouldIWaitCommand"; } public Serializable execute(Serializable[] args) throws Throwable { return shouldIWait(); } }); needToInitWorkspaceCommand = rpcService.registerCommand(new RemoteCommand() { public String getId() { return "exo.jcr.component.core.RepositoryCreationSynchronizer-needToInitWorkspaceCommand"; } public Serializable execute(Serializable[] args) throws Throwable { needToInitWorkspace(); return null; } }); final RemoteCommand releaseCommand = rpcService.registerCommand(new RemoteCommand() { public String getId() { return "exo.jcr.component.core.RepositoryCreationSynchronizer-releaseCommand"; } public Serializable execute(Serializable[] args) throws Throwable { release(); return null; } }); // Used to release all the nodes if needed once the current node is fully started // We need to wait for a full container start since upper applications could also not // support concurrent JCR initialization ctx.getContainer().addContainerLifecylePlugin(new BaseContainerLifecyclePlugin() { @Override public void startContainer(ExoContainer container) throws Exception { needToInitWorkspace = false; try { if (LOG.isDebugEnabled()) { LOG.debug("Release the other cluster nodes."); } rpcService.executeCommandOnAllNodes(releaseCommand, false); } catch (Exception e) { LOG.error("Could not release all the nodes", e); } } }); // Used to release the coordinator rpcService.registerTopologyChangeListener(new TopologyChangeListener() { public void onChange(TopologyChangeEvent event) { if (event.isCoordinator()) { release(); } } }); } } /** * Make the current thread wait until the {@link RepositoryCreationSynchronizer} * allows to proceed */ public void waitForApproval(boolean isWorkspaceInitialized) { if (disabled) { if (LOG.isTraceEnabled()) { LOG.trace("The RepositoryCreationSynchronizer has been disabled so no need to make it wait."); } return; } if (rpcService == null) { if (LOG.isTraceEnabled()) { LOG.trace("No RPCService has been defined so we assume that we are in" + " a non cluster environment so no need to make it wait."); } return; } // The RPC Service has been defined so we assume that we are in a cluster environment if (!isWorkspaceInitialized) { // Set locally the value needToInitWorkspace(); // A workspace has not been initialized, we need to notify everybody if (LOG.isDebugEnabled()) { LOG.debug("Notify all the nodes that there is at least one workspace to initialize."); } try { rpcService.executeCommandOnAllNodes(needToInitWorkspaceCommand, false); } catch (Exception e) { LOG.warn("Could not notify all the nodes that there is at least one workspace to initialize.", e); } } if (lock.getCount() <= 0) { // We already have been released so no need to wait if (LOG.isTraceEnabled()) { LOG.trace("We already have been released so no need to make it wait."); } return; } try { if (!needToInitWorkspace && isWorkspaceInitialized) { // Asking the coordinator if we have to wait since the workspace could be currently // initialized by the coordinator if (LOG.isDebugEnabled()) { LOG.debug("Ask the coordinator if the local node needs to wait."); } Object result = rpcService.executeCommandOnCoordinator(shouldIWaitCommand, 0); if (LOG.isDebugEnabled()) { LOG.debug("The response from the coordinator was " + result); } if (result instanceof Boolean && (Boolean)result) { // We wait to be release by the coordinator waitForCoordinator(); } } else { // We wait to be release by the coordinator waitForCoordinator(); } } catch (RPCException e) { LOG.warn("An error occured while executing the method waitForApproval()", e); } } /** * Make the current node wait until being released by the coordinator */ private void waitForCoordinator() { LOG.info("Waiting to be released by the coordinator"); try { lock.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * Used to know if the nodes need to wait */ private boolean shouldIWait() { return needToInitWorkspace; } /** * Indicates that there is at least one workspace to initialize */ private void needToInitWorkspace() { this.needToInitWorkspace = true; } /** * Releases the local node if needed */ private void release() { if (lock.getCount() > 0) { LOG.info("The local node has been released."); lock.countDown(); } } }