All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modeshape.jcr.bus.ClusteredRepositoryChangeBus Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show newest version
/*
 * ModeShape (http://www.modeshape.org)
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * See the AUTHORS.txt file in the distribution for a full listing of
 * individual contributors.
 *
 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
 * is licensed to you 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.
 *
 * ModeShape 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.modeshape.jcr.bus;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jgroups.Address;
import org.jgroups.Channel;
import org.jgroups.ChannelListener;
import org.jgroups.Message;
import org.jgroups.ReceiverAdapter;
import org.jgroups.View;
import org.modeshape.common.SystemFailureException;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.RepositoryConfiguration;
import org.modeshape.jcr.cache.change.ChangeSet;
import org.modeshape.jcr.cache.change.ChangeSetListener;
import org.modeshape.jcr.clustering.ChannelProvider;

/**
 * Implementation of a {@link ChangeBus} which can run in a cluster, via JGroups. This bus wraps around another bus, to which it
 * delegates all "local" processing of events.
 *
 * @author Horia Chiorean
 */
@ThreadSafe
public final class ClusteredRepositoryChangeBus implements ChangeBus {

    protected static final Logger LOGGER = Logger.getLogger(ClusteredRepositoryChangeBus.class);

    /**
     * The wrapped standalone bus to which standard bus operations are delegated
     */
    protected final ChangeBus delegate;

    /**
     * The id of the process which started this bus
     */
    private final String processId;

    /**
     * The listener for channel changes.
     */
    private final Listener listener = new Listener();

    /**
     * The component that will receive the JGroups messages and broadcast them to this bus' observers.
     */
    private final Receiver receiver = new Receiver();

    /**
     * Flag that dictates whether this bus has connected to the cluster.
     */
    protected final AtomicBoolean isOpen = new AtomicBoolean(false);

    /**
     * Flag that dictates whether there are multiple participants in the cluster; if not, then the changes are propagated only to
     * the local observers.
     */
    protected final AtomicBoolean multipleAddressesInCluster = new AtomicBoolean(false);

    /**
     * The clustering configuration
     */
    protected final RepositoryConfiguration.Clustering clusteringConfiguration;

    /**
     * The JGroups channel to which all {@link #notify(ChangeSet) change notifications} will be sent and from which all changes
     * will be received and sent to the observers.
     * 

* It is important that the order of the {@link ChangeSet} instances are maintained across the cluster, and JGroups will do * this for us as long as we push all local changes into the channel and receive all local/remote changes from the channel. *

*/ private Channel channel; /** * Creates a new clustered repository bus * * @param clusteringConfiguration the bus configuration * @param delegate the local bus to which changes will be delegated * @param processId the id of the process which started this bus */ public ClusteredRepositoryChangeBus( RepositoryConfiguration.Clustering clusteringConfiguration, ChangeBus delegate, String processId ) { CheckArg.isNotNull(clusteringConfiguration, "clusteringConfiguration"); CheckArg.isNotNull(delegate, "delegate"); this.clusteringConfiguration = clusteringConfiguration; this.processId = processId; assert clusteringConfiguration.isEnabled(); this.delegate = delegate; } @Override public synchronized void start() throws Exception { String clusterName = clusteringConfiguration.getClusterName(); if (clusterName == null) { throw new IllegalStateException(BusI18n.clusterNameRequired.text()); } if (channel != null) { // Disconnect from any previous channel ... channel.removeChannelListener(listener); channel.setReceiver(null); } // Create the new channel by calling the delegate method ... channel = newChannel(); // Add a listener through which we'll know what's going on within the cluster ... channel.addChannelListener(listener); // Set the receiver through which we'll receive all of the changes ... channel.setReceiver(receiver); // Now connect to the cluster ... channel.connect(clusterName); // start the delegate delegate.start(); } private Channel newChannel() throws Exception { // Try to get the channel directly from the configuration (and its environment) ... Channel channel = clusteringConfiguration.getChannel(); if (channel != null) { return channel; } String lookupClassName = clusteringConfiguration.getChannelProviderClassName(); assert lookupClassName != null; Class lookupClass = Class.forName(lookupClassName); if (!ChannelProvider.class.isAssignableFrom(lookupClass)) { throw new IllegalArgumentException( "Invalid channel lookup class configured. Expected a subclass of org.modeshape.jcr.clustering.ChannelProvider. Actual class:" + lookupClass); } return ((ChannelProvider)lookupClass.newInstance()).getChannel(clusteringConfiguration); } @Override public boolean hasObservers() { return delegate.hasObservers(); } @Override public synchronized void shutdown() { if (channel != null) { // Mark this as not accepting any more ... isOpen.set(false); try { // Disconnect from the channel and close it ... channel.removeChannelListener(listener); channel.setReceiver(null); channel.close(); } finally { channel = null; // Now that we're not receiving any more messages, shut down the delegate delegate.shutdown(); } } } @Override public void notify( ChangeSet changeSet ) { if (changeSet == null) { return; // do nothing } if (!isOpen.get()) { // The channel is not open ... return; } if (!multipleAddressesInCluster.get()) { // We are in clustered mode, but there is only one participant in the cluster (us). // So short-circuit the cluster and just notify the local observers ... if (hasObservers()) { delegate.notify(changeSet); logReceivedOperation(changeSet); } return; } // There are multiple participants in the cluster, so send all changes out to JGroups, // letting JGroups do the ordering of messages... try { logSendOperation(changeSet); byte[] data = serialize(changeSet); Message message = new Message(null, null, data); channel.send(message); } catch (IllegalStateException e) { LOGGER.warn(BusI18n.unableToNotifyChanges, clusteringConfiguration.getClusterName(), changeSet.size(), changeSet.getWorkspaceName(), changeSet.getUserId(), changeSet.getProcessKey(), changeSet.getTimestamp()); } catch (Exception e) { // Something went wrong here (this should not happen) ... String msg = BusI18n.errorSerializingChanges.text(clusteringConfiguration.getClusterName(), changeSet.size(), changeSet.getWorkspaceName(), changeSet.getUserId(), changeSet.getProcessKey(), changeSet.getTimestamp(), changeSet); throw new SystemFailureException(msg, e); } } protected final void logSendOperation( ChangeSet changeSet ) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Process {0} sending to cluster '{1}' {2} changes on workspace {3} made by {4} from process '{5}' at {6}", processId, clusteringConfiguration.getClusterName(), changeSet.size(), changeSet.getWorkspaceName(), changeSet.getUserData(), changeSet.getProcessKey(), changeSet.getTimestamp()); } } protected final void logReceivedOperation( ChangeSet changeSet ) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Process {0} received on cluster '{1}' {2} changes on workspace {3} made by {4} from process '{5}' at {6}", processId, clusteringConfiguration.getClusterName(), changeSet.size(), changeSet.getWorkspaceName(), changeSet.getUserId(), changeSet.getProcessKey(), changeSet.getTimestamp()); } } @Override public boolean register( ChangeSetListener observer ) { return delegate.register(observer); } @Override public boolean unregister( ChangeSetListener observer ) { return delegate.unregister(observer); } protected byte[] serialize( ChangeSet changes ) throws Exception { ByteArrayOutputStream output = new ByteArrayOutputStream(); ObjectOutputStream stream = new ObjectOutputStream(output); stream.writeObject(changes); stream.close(); return output.toByteArray(); } protected ChangeSet deserialize( byte[] data ) throws Exception { ObjectInputStreamWithClassLoader input = new ObjectInputStreamWithClassLoader( new ByteArrayInputStream(data), getClass().getClassLoader()); ChangeSet toReturn = (ChangeSet) input.readObject(); input.close(); return toReturn; } protected final class Receiver extends ReceiverAdapter { @Override public void block() { isOpen.set(false); } @Override public void receive( final Message message ) { if (!hasObservers()) { return; } // We have at least one try { // Deserialize the changes ... ChangeSet changes = deserialize(message.getBuffer()); // and broadcast them delegate.notify(changes); logReceivedOperation(changes); } catch (Exception e) { // Something went wrong here (this should not happen) ... String msg = BusI18n.errorDeserializingChanges.text(clusteringConfiguration.getClusterName()); throw new SystemFailureException(msg, e); } } @Override public void suspect( Address suspectedMbr ) { LOGGER.error(BusI18n.memberOfClusterIsSuspect, clusteringConfiguration.getClusterName(), suspectedMbr); } @Override public void viewAccepted( View newView ) { LOGGER.trace("Members of '{0}' cluster have changed: {1}", clusteringConfiguration.getClusterName(), newView); if (newView.getMembers().size() > 1) { if (multipleAddressesInCluster.compareAndSet(false, true)) { LOGGER.debug("There are now multiple members of cluster '{0}'; changes will be propagated throughout the cluster", clusteringConfiguration.getClusterName()); } } else { if (multipleAddressesInCluster.compareAndSet(true, false)) { LOGGER.debug("There is only one member of cluster '{0}'; changes will be propagated locally only", clusteringConfiguration.getClusterName()); } } } } protected final class Listener implements ChannelListener { @Override public void channelClosed( Channel channel ) { isOpen.set(false); } @Override public void channelConnected( Channel channel ) { isOpen.set(true); } @Override public void channelDisconnected( Channel channel ) { isOpen.set(false); } } /** * ObjectInputStream extension that allows a different class loader to be used when resolving types. */ protected final class ObjectInputStreamWithClassLoader extends ObjectInputStream { private ClassLoader cl; public ObjectInputStreamWithClassLoader(InputStream in, ClassLoader cl) throws IOException { super(in); this.cl = cl; } @Override protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (cl == null) { return super.resolveClass(desc); } try { return Class.forName(desc.getName(), false, cl); } catch (ClassNotFoundException ex) { return super.resolveClass(desc); } } @Override public void close() throws IOException { super.close(); this.cl = null; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy