<property name="jsp-api.jar" value="${tomcat.build}/lib/jsp-api.jar"/>
<property name="el-api.jar" value="${tomcat.build}/lib/el-api.jar"/>
<property name="catalina.jar" value="${tomcat.build}/lib/catalina.jar"/>
+ <property name="catalina-tribes.jar" value="${tomcat.build}/lib/catalina-tribes.jar"/>
+ <property name="catalina-ha.jar" value="${tomcat.build}/lib/catalina-ha.jar"/>
<property name="catalina-ant.jar" value="${tomcat.build}/lib/catalina-ant.jar"/>
<property name="catalina-ant-jmx.jar" value="${tomcat.build}/lib/catalina-ant-jmx.jar"/>
<property name="tomcat-coyote.jar" value="${tomcat.build}/lib/tomcat-coyote.jar"/>
<!-- Modules -->
<exclude name="org/apache/catalina/ant/**" />
<exclude name="org/apache/catalina/cluster/**" />
+ <exclude name="org/apache/catalina/ha/**" />
+ <exclude name="org/apache/catalina/tribes/**" />
<exclude name="org/apache/catalina/launcher/**" />
<exclude name="org/apache/catalina/storeconfig/**" />
</fileset>
</jar>
+
+ <!-- Catalina GroupCom/Tribes JAR File -->
+ <jar jarfile="${catalina-tribes.jar}">
+ <fileset dir="${tomcat.classes}">
+ <exclude name="**/package.html" />
+ <exclude name="**/LocalStrings_*" />
+ <!-- Modules -->
+ <include name="org/apache/catalina/tribes/**" />
+ </fileset>
+ </jar>
+ <!-- Catalina Cluster/HA JAR File -->
+ <jar jarfile="${catalina-ha.jar}">
+ <fileset dir="${tomcat.classes}">
+ <exclude name="**/package.html" />
+ <exclude name="**/LocalStrings_*" />
+ <!-- Modules -->
+ <include name="org/apache/catalina/ha/**" />
+ </fileset>
+ </jar>
<!-- Catalina Ant Tasks JAR File -->
<jar jarfile="${catalina-ant.jar}">
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha;
+
+import java.util.Map;
+
+import org.apache.catalina.Cluster;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Valve;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.Member;
+import org.apache.commons.logging.Log;
+
+
+
+/**
+ * A <b>CatalinaCluster</b> interface allows to plug in and out the
+ * different cluster implementations
+ *
+ * @author Filip Hanik
+ * @version $Revision: 379550 $, $Date: 2006-02-21 12:06:35 -0600 (Tue, 21 Feb 2006) $
+ */
+
+public interface CatalinaCluster extends Cluster {
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * Descriptive information about this component implementation.
+ */
+ public String info = "CatalinaCluster/2.0";
+
+ /**
+ * Start the cluster, the owning container will invoke this
+ * @throws Exception - if failure to start cluster
+ */
+ public void start() throws Exception;
+
+ /**
+ * Stops the cluster, the owning container will invoke this
+ * @throws LifecycleException
+ */
+ public void stop() throws LifecycleException;
+
+ /**
+ * Returns the associates logger with this cluster.
+ *
+ * @return Log
+ */
+ public Log getLogger();
+
+ /**
+ * Sends a message to all the members in the cluster
+ * @param msg ClusterMessage
+ */
+ public void send(ClusterMessage msg);
+
+ /**
+ * Sends a message to a specific member in the cluster.
+ *
+ * @param msg ClusterMessage
+ * @param dest Member
+ */
+ public void send(ClusterMessage msg, Member dest);
+
+ /**
+ * Sends a message to a all members at local cluster domain
+ *
+ * @param msg ClusterMessage
+ */
+ public void sendClusterDomain(ClusterMessage msg);
+
+ /**
+ * Returns that cluster has members.
+ */
+ public boolean hasMembers();
+
+ /**
+ * Returns all the members currently participating in the cluster.
+ *
+ * @return Member[]
+ */
+ public Member[] getMembers();
+
+ /**
+ * Return the member that represents this node.
+ *
+ * @return Member
+ */
+ public Member getLocalMember();
+
+ public void addValve(Valve valve);
+
+ public void addClusterListener(ClusterListener listener);
+
+ public void removeClusterListener(ClusterListener listener);
+
+ public void setClusterDeployer(ClusterDeployer deployer);
+
+ public ClusterDeployer getClusterDeployer();
+
+ /**
+ * @return The map of managers
+ */
+ public Map getManagers();
+
+ public Manager getManager(String name);
+ public void removeManager(String name,Manager manager);
+ public void addManager(String name,Manager manager);
+ public String getManagerName(String name, Manager manager);
+ public Valve[] getValves();
+
+ public void setChannel(Channel channel);
+ public Channel getChannel();
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha;
+
+/**
+ * A <b>ClusterDeployer</b> interface allows to plug in and out the
+ * different deployment implementations
+ *
+ * @author Filip Hanik
+ * @version $Revision: 378050 $, $Date: 2006-02-15 12:30:02 -0600 (Wed, 15 Feb 2006) $
+ */
+import org.apache.catalina.LifecycleException;
+import java.io.IOException;
+import java.net.URL;
+import org.apache.catalina.tribes.ChannelListener;
+
+public interface ClusterDeployer extends ChannelListener {
+ /**
+ * Descriptive information about this component implementation.
+ */
+ public String info = "ClusterDeployer/1.0";
+ /**
+ * Start the cluster deployer, the owning container will invoke this
+ * @throws Exception - if failure to start cluster
+ */
+ public void start() throws Exception;
+
+ /**
+ * Stops the cluster deployer, the owning container will invoke this
+ * @throws LifecycleException
+ */
+ public void stop() throws LifecycleException;
+
+ /**
+ * Sets the deployer for this cluster deployer to use.
+ * @param deployer Deployer
+ */
+ // FIXME
+ //public void setDeployer(Deployer deployer);
+
+ /**
+ * Install a new web application, whose web application archive is at the
+ * specified URL, into this container and all the other
+ * members of the cluster with the specified context path.
+ * A context path of "" (the empty string) should be used for the root
+ * application for this container. Otherwise, the context path must
+ * start with a slash.
+ * <p>
+ * If this application is successfully installed locally,
+ * a ContainerEvent of type
+ * <code>INSTALL_EVENT</code> will be sent to all registered listeners,
+ * with the newly created <code>Context</code> as an argument.
+ *
+ * @param contextPath The context path to which this application should
+ * be installed (must be unique)
+ * @param war A URL of type "jar:" that points to a WAR file, or type
+ * "file:" that points to an unpacked directory structure containing
+ * the web application to be installed
+ *
+ * @exception IllegalArgumentException if the specified context path
+ * is malformed (it must be "" or start with a slash)
+ * @exception IllegalStateException if the specified context path
+ * is already attached to an existing web application
+ * @exception IOException if an input/output error was encountered
+ * during installation
+ */
+ public void install(String contextPath, URL war) throws IOException;
+
+ /**
+ * Remove an existing web application, attached to the specified context
+ * path. If this application is successfully removed, a
+ * ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to all
+ * registered listeners, with the removed <code>Context</code> as
+ * an argument. Deletes the web application war file and/or directory
+ * if they exist in the Host's appBase.
+ *
+ * @param contextPath The context path of the application to be removed
+ * @param undeploy boolean flag to remove web application from server
+ *
+ * @exception IllegalArgumentException if the specified context path
+ * is malformed (it must be "" or start with a slash)
+ * @exception IllegalArgumentException if the specified context path does
+ * not identify a currently installed web application
+ * @exception IOException if an input/output error occurs during
+ * removal
+ */
+ public void remove(String contextPath, boolean undeploy) throws IOException;
+
+ /**
+ * call from container Background Process
+ */
+ public void backgroundProcess();
+
+ /**
+ * Returns the cluster the cluster deployer is associated with
+ * @return CatalinaCluster
+ */
+ public CatalinaCluster getCluster();
+
+ /**
+ * Associates the cluster deployer with a cluster
+ * @param cluster CatalinaCluster
+ */
+ public void setCluster(CatalinaCluster cluster);
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha;
+
+
+
+
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.util.StringManager;
+import java.io.Serializable;
+import org.apache.catalina.tribes.Member;
+
+
+/**
+ * Receive SessionID cluster change from other backup node after primary session
+ * node is failed.
+ *
+ * @author Peter Rossbach
+ * @author Filip Hanik
+ * @version $Revision: 378258 $ $Date: 2006-02-16 08:42:35 -0600 (Thu, 16 Feb 2006) $
+ */
+public abstract class ClusterListener implements ChannelListener {
+
+ public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(ClusterListener.class);
+
+
+ //--Instance Variables--------------------------------------
+
+ /**
+ * The string manager for this package.
+ */
+ protected StringManager sm = StringManager.getManager(Constants.Package);
+
+ protected CatalinaCluster cluster = null;
+
+ //--Constructor---------------------------------------------
+
+ public ClusterListener() {
+ }
+
+ //--Instance Getters/Setters--------------------------------
+
+ public CatalinaCluster getCluster() {
+ return cluster;
+ }
+
+ public void setCluster(CatalinaCluster cluster) {
+ if (log.isDebugEnabled()) {
+ if (cluster != null)
+ log.debug("add ClusterListener " + this.toString() + " to cluster" + cluster);
+ else
+ log.debug("remove ClusterListener " + this.toString() + " from cluster");
+ }
+ this.cluster = cluster;
+ }
+
+ public boolean equals(Object listener) {
+ return super.equals(listener);
+ }
+
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ //--Logic---------------------------------------------------
+
+ public final void messageReceived(Serializable msg, Member member) {
+ if ( msg instanceof ClusterMessage ) messageReceived((ClusterMessage)msg);
+ }
+ public final boolean accept(Serializable msg, Member member) {
+ if ( msg instanceof ClusterMessage ) return true;
+ return false;
+ }
+
+
+
+ /**
+ * Callback from the cluster, when a message is received, The cluster will
+ * broadcast it invoking the messageReceived on the receiver.
+ *
+ * @param msg
+ * ClusterMessage - the message received from the cluster
+ */
+ public abstract void messageReceived(ClusterMessage msg) ;
+
+
+ /**
+ * Accept only SessionIDMessages
+ *
+ * @param msg
+ * ClusterMessage
+ * @return boolean - returns true to indicate that messageReceived should be
+ * invoked. If false is returned, the messageReceived method will
+ * not be invoked.
+ */
+ public abstract boolean accept(ClusterMessage msg) ;
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha;
+
+
+import org.apache.catalina.Manager;
+import java.io.IOException;
+import org.apache.catalina.tribes.io.ReplicationStream;
+
+
+/**
+ * The common interface used by all cluster manager.
+ * This is so that we can have a more pluggable way
+ * of swapping session managers for different algorithms.
+ *
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ */
+public interface ClusterManager extends Manager {
+
+ /**
+ * A message was received from another node, this
+ * is the callback method to implement if you are interested in
+ * receiving replication messages.
+ * @param msg - the message received.
+ */
+ public void messageDataReceived(ClusterMessage msg);
+
+ /**
+ * When the request has been completed, the replication valve
+ * will notify the manager, and the manager will decide whether
+ * any replication is needed or not.
+ * If there is a need for replication, the manager will
+ * create a session message and that will be replicated.
+ * The cluster determines where it gets sent.
+ * @param sessionId - the sessionId that just completed.
+ * @return a SessionMessage to be sent.
+ */
+ public ClusterMessage requestCompleted(String sessionId);
+
+ /**
+ * When the manager expires session not tied to a request.
+ * The cluster will periodically ask for a list of sessions
+ * that should expire and that should be sent across the wire.
+ * @return String[] The invalidated sessions
+ */
+ public String[] getInvalidatedSessions();
+
+ /**
+ * Return the name of the manager, at host /context name and at engine hostname+/context.
+ * @return String
+ * @since 5.5.10
+ */
+ public String getName();
+
+ /**
+ * Set the name of the manager, at host /context name and at engine hostname+/context
+ * @param name
+ * @since 5.5.10
+ */
+ public void setName(String name);
+
+ public CatalinaCluster getCluster();
+
+ public void setCluster(CatalinaCluster cluster);
+
+ /**
+ * @return Manager send only to same cluster domain.
+ * @since 5.5.10
+ */
+ public boolean isSendClusterDomainOnly();
+
+ /**
+ * @param sendClusterDomainOnly Flag value.
+ * @since 5.5.10
+ */
+ public void setSendClusterDomainOnly(boolean sendClusterDomainOnly);
+
+ /**
+ * @param mode The mode
+ * @since 5.5.10
+ */
+ public void setDefaultMode(boolean mode);
+
+ /**
+ * @since 5.5.10
+ */
+ public boolean isDefaultMode();
+
+ public ReplicationStream getReplicationStream(byte[] data) throws IOException;
+
+ public ReplicationStream getReplicationStream(byte[] data, int offset, int length) throws IOException;
+
+ public boolean isNotifyListenersOnReplication();
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha;
+
+import java.io.Serializable;
+import org.apache.catalina.tribes.Member;
+
+
+/**
+ * @author Filip Hanik
+ *
+ */
+public interface ClusterMessage extends Serializable {
+ public Member getAddress();
+ public void setAddress(Member member);
+ public String getUniqueId();
+ public void setUniqueId(String id);
+ public long getTimestamp();
+ public void setTimestamp(long timestamp);
+}
--- /dev/null
+package org.apache.catalina.ha;
+
+import org.apache.catalina.tribes.Member;
+
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class ClusterMessageBase implements ClusterMessage {
+
+ protected transient Member address;
+ private String uniqueId;
+ private long timestamp;
+ public ClusterMessageBase() {
+ }
+
+ /**
+ * getAddress
+ *
+ * @return Member
+ * @todo Implement this org.apache.catalina.ha.ClusterMessage method
+ */
+ public Member getAddress() {
+ return address;
+ }
+
+ public String getUniqueId() {
+ return uniqueId;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ /**
+ * setAddress
+ *
+ * @param member Member
+ * @todo Implement this org.apache.catalina.ha.ClusterMessage method
+ */
+ public void setAddress(Member member) {
+ this.address = member;
+ }
+
+ public void setUniqueId(String uniqueId) {
+ this.uniqueId = uniqueId;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999-2001,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha;
+
+
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSetBase;
+
+
+/**
+ * <p><strong>RuleSet</strong> for processing the contents of a
+ * Cluster definition element. </p>
+ *
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Revision: 387285 $ $Date: 2006-03-20 13:30:50 -0600 (Mon, 20 Mar 2006) $
+ */
+
+public class ClusterRuleSet extends RuleSetBase {
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ /**
+ * The matching pattern prefix to use for recognizing our elements.
+ */
+ protected String prefix = null;
+
+
+ // ------------------------------------------------------------ Constructor
+
+
+ /**
+ * Construct an instance of this <code>RuleSet</code> with the default
+ * matching pattern prefix.
+ */
+ public ClusterRuleSet() {
+
+ this("");
+
+ }
+
+
+ /**
+ * Construct an instance of this <code>RuleSet</code> with the specified
+ * matching pattern prefix.
+ *
+ * @param prefix Prefix for matching pattern rules (including the
+ * trailing slash character)
+ */
+ public ClusterRuleSet(String prefix) {
+ super();
+ this.namespaceURI = null;
+ this.prefix = prefix;
+ }
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+ /**
+ * <p>Add the set of Rule instances defined in this RuleSet to the
+ * specified <code>Digester</code> instance, associating them with
+ * our namespace URI (if any). This method should only be called
+ * by a Digester instance.</p>
+ *
+ * @param digester Digester instance to which the new Rule instances
+ * should be added.
+ */
+ public void addRuleInstances(Digester digester) {
+ //Cluster configuration start
+
+ digester.addObjectCreate(prefix + "Channel",
+ null, // MUST be specified in the element
+ "className");
+ digester.addSetProperties(prefix + "Channel");
+ digester.addSetNext(prefix + "Channel",
+ "setChannel",
+ "org.apache.catalina.tribes.Channel");
+
+
+ String channelPrefix = prefix + "Channel/";
+ { //channel properties
+ digester.addObjectCreate(channelPrefix + "Membership",
+ null, // MUST be specified in the element
+ "className");
+ digester.addSetProperties(channelPrefix + "Membership");
+ digester.addSetNext(channelPrefix + "Membership",
+ "setMembershipService",
+ "org.apache.catalina.tribes.MembershipService");
+
+ digester.addObjectCreate(channelPrefix + "Sender",
+ null, // MUST be specified in the element
+ "className");
+ digester.addSetProperties(channelPrefix + "Sender");
+ digester.addSetNext(channelPrefix + "Sender",
+ "setChannelSender",
+ "org.apache.catalina.tribes.ChannelSender");
+
+ digester.addObjectCreate(channelPrefix + "Sender/Transport",
+ null, // MUST be specified in the element
+ "className");
+ digester.addSetProperties(channelPrefix + "Sender/Transport");
+ digester.addSetNext(channelPrefix + "Sender/Transport",
+ "setTransport",
+ "org.apache.catalina.tribes.tcp.MultiPointSender");
+
+
+ digester.addObjectCreate(channelPrefix + "Receiver",
+ null, // MUST be specified in the element
+ "className");
+ digester.addSetProperties(channelPrefix + "Receiver");
+ digester.addSetNext(channelPrefix + "Receiver",
+ "setChannelReceiver",
+ "org.apache.catalina.tribes.ChannelReceiver");
+
+ digester.addObjectCreate(channelPrefix + "Interceptor",
+ null, // MUST be specified in the element
+ "className");
+ digester.addSetProperties(channelPrefix + "Interceptor");
+ digester.addSetNext(channelPrefix + "Interceptor",
+ "addInterceptor",
+ "org.apache.catalina.tribes.ChannelInterceptor");
+ }
+
+ digester.addObjectCreate(prefix + "Valve",
+ null, // MUST be specified in the element
+ "className");
+ digester.addSetProperties(prefix + "Valve");
+ digester.addSetNext(prefix + "Valve",
+ "addValve",
+ "org.apache.catalina.Valve");
+
+ digester.addObjectCreate(prefix + "Deployer",
+ null, // MUST be specified in the element
+ "className");
+ digester.addSetProperties(prefix + "Deployer");
+ digester.addSetNext(prefix + "Deployer",
+ "setClusterDeployer",
+ "org.apache.catalina.ha.ClusterDeployer");
+
+ digester.addObjectCreate(prefix + "Listener",
+ null, // MUST be specified in the element
+ "className");
+ digester.addSetProperties(prefix + "Listener");
+ digester.addSetNext(prefix + "Listener",
+ "addLifecycleListener",
+ "org.apache.catalina.LifecycleListener");
+
+ digester.addObjectCreate(prefix + "ClusterListener",
+ null, // MUST be specified in the element
+ "className");
+ digester.addSetProperties(prefix + "ClusterListener");
+ digester.addSetNext(prefix + "ClusterListener",
+ "addClusterListener",
+ "org.apache.catalina.ha.ClusterListener");
+ //Cluster configuration end
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha;
+
+import org.apache.catalina.Session;
+import javax.servlet.http.HttpSession;
+
+public interface ClusterSession extends Session, HttpSession {
+ /**
+ * returns true if this session is the primary session, if that is the
+ * case, the manager can expire it upon timeout.
+ * @return True if this session is primary
+ */
+ public boolean isPrimarySession();
+
+ /**
+ * Sets whether this is the primary session or not.
+ * @param primarySession Flag value
+ */
+ public void setPrimarySession(boolean primarySession);
+
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha;
+
+/**
+ * Cluster Valve Interface to mark all Cluster Valves
+ * Only those Valve can'be configured as Cluster Valves
+ * @author Peter Rossbach
+ * @version $Revision: 303842 $, $Date: 2005-04-10 11:20:46 -0500 (Sun, 10 Apr 2005) $
+ */
+public interface ClusterValve {
+ /**
+ * Returns the cluster the cluster deployer is associated with
+ * @return CatalinaCluster
+ */
+ public CatalinaCluster getCluster();
+
+ /**
+ * Associates the cluster deployer with a cluster
+ * @param cluster CatalinaCluster
+ */
+ public void setCluster(CatalinaCluster cluster);
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.ha</code>
+ * package.
+ *
+ * @author Bip Thelin
+ * @version $Revision: 302726 $, $Date: 2004-02-27 08:59:07 -0600 (Fri, 27 Feb 2004) $
+ */
+
+public final class Constants {
+ public static final String Package = "org.apache.catalina.ha";
+}
--- /dev/null
+cluster.mbean.register.already=MBean {0} already registered!
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.context;
+
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.tribes.tipis.ReplicatedMap;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.Loader;
+import org.apache.catalina.core.ApplicationContext;
+import org.apache.catalina.Globals;
+import javax.servlet.ServletContext;
+import java.util.HashMap;
+import org.apache.catalina.tribes.tipis.LazyReplicatedMap;
+
+/**
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class ReplicatedContext extends StandardContext {
+ private int mapSendOptions = Channel.SEND_OPTIONS_DEFAULT;
+ public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog( ReplicatedContext.class );
+
+ protected static long DEFAULT_REPL_TIMEOUT = 15000;//15 seconds
+
+
+
+ public synchronized void start() throws LifecycleException {
+ if ( this.started ) return;
+ try {
+ CatalinaCluster catclust = (CatalinaCluster)this.getCluster();
+ if (this.context == null) this.context = new ReplApplContext(this.getBasePath(), this);
+ if ( catclust != null ) {
+ ReplicatedMap map = new ReplicatedMap(this,catclust.getChannel(),DEFAULT_REPL_TIMEOUT,
+ getName(),getClassLoaders());
+ map.setChannelSendOptions(mapSendOptions);
+ ((ReplApplContext)this.context).setAttributeMap(map);
+ if (getAltDDName() != null) context.setAttribute(Globals.ALT_DD_ATTR, getAltDDName());
+ }
+ super.start();
+ } catch ( Exception x ) {
+ log.error("Unable to start ReplicatedContext",x);
+ throw new LifecycleException("Failed to start ReplicatedContext",x);
+ }
+ }
+
+ public synchronized void stop() throws LifecycleException
+ {
+ ReplicatedMap map = (ReplicatedMap)((ReplApplContext)this.context).getAttributeMap();
+ if ( map!=null ) {
+ map.breakdown();
+ }
+ if ( !this.started ) return;
+ try {
+ } catch ( Exception x ){
+ log.error("Unable to stop ReplicatedContext",x);
+ throw new LifecycleException("Failed to stop ReplicatedContext",x);
+ } finally {
+ super.stop();
+ }
+
+ }
+
+
+ public void setMapSendOptions(int mapSendOptions) {
+ this.mapSendOptions = mapSendOptions;
+ }
+
+ public int getMapSendOptions() {
+ return mapSendOptions;
+ }
+
+ public ClassLoader[] getClassLoaders() {
+ Loader loader = null;
+ ClassLoader classLoader = null;
+ loader = this.getLoader();
+ if (loader != null) classLoader = loader.getClassLoader();
+ if ( classLoader == null ) classLoader = Thread.currentThread().getContextClassLoader();
+ if ( classLoader == Thread.currentThread().getContextClassLoader() ) {
+ return new ClassLoader[] {classLoader};
+ } else {
+ return new ClassLoader[] {classLoader,Thread.currentThread().getContextClassLoader()};
+ }
+ }
+
+ public ServletContext getServletContext() {
+ return ((ReplApplContext)context).getFacade();
+
+ }
+
+
+ protected static class ReplApplContext extends ApplicationContext {
+ public ReplApplContext(String basePath, StandardContext context) {
+ super(basePath,context);
+ }
+
+ protected ServletContext getFacade() {
+ return super.getFacade();
+ }
+
+ public HashMap getAttributeMap() {
+ return (HashMap)this.attributes;
+ }
+ public void setAttributeMap(HashMap map) {
+ this.attributes = map;
+ }
+
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.HashMap;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterDeployer;
+import org.apache.catalina.ha.ClusterListener;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.tomcat.util.modeler.Registry;
+
+
+/**
+ * <p>
+ * A farm war deployer is a class that is able to deploy/undeploy web
+ * applications in WAR form within the cluster.
+ * </p>
+ * Any host can act as the admin, and will have three directories
+ * <ul>
+ * <li>deployDir - the directory where we watch for changes</li>
+ * <li>applicationDir - the directory where we install applications</li>
+ * <li>tempDir - a temporaryDirectory to store binary data when downloading a
+ * war from the cluster</li>
+ * </ul>
+ * Currently we only support deployment of WAR files since they are easier to
+ * send across the wire.
+ *
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Revision: 390639 $
+ */
+public class FarmWarDeployer extends ClusterListener implements ClusterDeployer, FileChangeListener {
+ /*--Static Variables----------------------------------------*/
+ public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
+ .getLog(FarmWarDeployer.class);
+ /**
+ * The descriptive information about this implementation.
+ */
+ private static final String info = "FarmWarDeployer/1.2";
+
+ /*--Instance Variables--------------------------------------*/
+ protected CatalinaCluster cluster = null;
+
+ protected boolean started = false; //default 5 seconds
+
+ protected HashMap fileFactories = new HashMap();
+
+ protected String deployDir;
+
+ protected String tempDir;
+
+ protected String watchDir;
+
+ protected boolean watchEnabled = false;
+
+ protected WarWatcher watcher = null;
+
+ /**
+ * Iteration count for background processing.
+ */
+ private int count = 0;
+
+ /**
+ * Frequency of the Farm watchDir check. Cluster wide deployment will be
+ * done once for the specified amount of backgrondProcess calls (ie, the
+ * lower the amount, the most often the checks will occur).
+ */
+ protected int processDeployFrequency = 2;
+
+ /**
+ * Path where context descriptors should be deployed.
+ */
+ protected File configBase = null;
+
+ /**
+ * The associated host.
+ */
+ protected Host host = null;
+
+ /**
+ * The host appBase.
+ */
+ protected File appBase = null;
+
+ /**
+ * MBean server.
+ */
+ protected MBeanServer mBeanServer = null;
+
+ /**
+ * The associated deployer ObjectName.
+ */
+ protected ObjectName oname = null;
+
+ /*--Constructor---------------------------------------------*/
+ public FarmWarDeployer() {
+ }
+
+ /**
+ * Return descriptive information about this deployer implementation and the
+ * corresponding version number, in the format
+ * <code><description>/<version></code>.
+ */
+ public String getInfo() {
+
+ return (info);
+
+ }
+
+ /*--Logic---------------------------------------------------*/
+ public void start() throws Exception {
+ if (started)
+ return;
+ getCluster().addClusterListener(this);
+ if (watchEnabled) {
+ watcher = new WarWatcher(this, new File(getWatchDir()));
+ if (log.isInfoEnabled())
+ log.info("Cluster deployment is watching " + getWatchDir()
+ + " for changes.");
+ }
+
+ // Check to correct engine and host setup
+ host = (Host) getCluster().getContainer();
+ Engine engine = (Engine) host.getParent();
+ try {
+ oname = new ObjectName(engine.getName() + ":type=Deployer,host="
+ + host.getName());
+ } catch (Exception e) {
+ log.error("Can't construct MBean object name" + e);
+ }
+ configBase = new File(System.getProperty("catalina.base"), "conf");
+ if (engine != null) {
+ configBase = new File(configBase, engine.getName());
+ }
+ if (host != null) {
+ configBase = new File(configBase, host.getName());
+ }
+
+ // Retrieve the MBean server
+ mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+
+ started = true;
+ count = 0;
+ if (log.isInfoEnabled())
+ log.info("Cluster FarmWarDeployer started.");
+ }
+
+ /*
+ * stop cluster wide deployments
+ *
+ * @see org.apache.catalina.cluster.ClusterDeployer#stop()
+ */
+ public void stop() throws LifecycleException {
+ started = false;
+ getCluster().removeClusterListener(this);
+ count = 0;
+ if (watcher != null) {
+ watcher.clear();
+ watcher = null;
+
+ }
+ if (log.isInfoEnabled())
+ log.info("Cluster FarmWarDeployer stopped.");
+ }
+
+ public void cleanDeployDir() {
+ throw new java.lang.UnsupportedOperationException(
+ "Method cleanDeployDir() not yet implemented.");
+ }
+
+ /**
+ * Callback from the cluster, when a message is received, The cluster will
+ * broadcast it invoking the messageReceived on the receiver.
+ *
+ * @param msg
+ * ClusterMessage - the message received from the cluster
+ */
+ public void messageReceived(ClusterMessage msg) {
+ try {
+ if (msg instanceof FileMessage && msg != null) {
+ FileMessage fmsg = (FileMessage) msg;
+ if (log.isDebugEnabled())
+ log.debug("receive cluster deployment [ path: "
+ + fmsg.getContextPath() + " war: "
+ + fmsg.getFileName() + " ]");
+ FileMessageFactory factory = getFactory(fmsg);
+ // TODO correct second try after app is in service!
+ if (factory.writeMessage(fmsg)) {
+ //last message received war file is completed
+ String name = factory.getFile().getName();
+ if (!name.endsWith(".war"))
+ name = name + ".war";
+ File deployable = new File(getDeployDir(), name);
+ try {
+ String path = fmsg.getContextPath();
+ if (!isServiced(path)) {
+ addServiced(path);
+ try {
+ remove(path);
+ factory.getFile().renameTo(deployable);
+ check(path);
+ } finally {
+ removeServiced(path);
+ }
+ if (log.isDebugEnabled())
+ log.debug("deployment from " + path
+ + " finished.");
+ } else
+ log.error("Application " + path
+ + " in used. touch war file " + name
+ + " again!");
+ } catch (Exception ex) {
+ log.error(ex);
+ } finally {
+ removeFactory(fmsg);
+ }
+ }
+ } else if (msg instanceof UndeployMessage && msg != null) {
+ try {
+ UndeployMessage umsg = (UndeployMessage) msg;
+ String path = umsg.getContextPath();
+ if (log.isDebugEnabled())
+ log.debug("receive cluster undeployment from " + path);
+ if (!isServiced(path)) {
+ addServiced(path);
+ try {
+ remove(path);
+ } finally {
+ removeServiced(path);
+ }
+ if (log.isDebugEnabled())
+ log.debug("undeployment from " + path
+ + " finished.");
+ } else
+ log.error("Application "
+ + path
+ + " in used. Sorry not remove from backup cluster nodes!");
+ } catch (Exception ex) {
+ log.error(ex);
+ }
+ }
+ } catch (java.io.IOException x) {
+ log.error("Unable to read farm deploy file message.", x);
+ }
+ }
+
+ /**
+ * create factory for all transported war files
+ *
+ * @param msg
+ * @return Factory for all app message (war files)
+ * @throws java.io.FileNotFoundException
+ * @throws java.io.IOException
+ */
+ public synchronized FileMessageFactory getFactory(FileMessage msg)
+ throws java.io.FileNotFoundException, java.io.IOException {
+ File tmpFile = new File(msg.getFileName());
+ File writeToFile = new File(getTempDir(), tmpFile.getName());
+ FileMessageFactory factory = (FileMessageFactory) fileFactories.get(msg
+ .getFileName());
+ if (factory == null) {
+ factory = FileMessageFactory.getInstance(writeToFile, true);
+ fileFactories.put(msg.getFileName(), factory);
+ }
+ return factory;
+ }
+
+ /**
+ * Remove file (war) from messages)
+ *
+ * @param msg
+ */
+ public void removeFactory(FileMessage msg) {
+ fileFactories.remove(msg.getFileName());
+ }
+
+ /**
+ * Before the cluster invokes messageReceived the cluster will ask the
+ * receiver to accept or decline the message, In the future, when messages
+ * get big, the accept method will only take a message header
+ *
+ * @param msg
+ * ClusterMessage
+ * @return boolean - returns true to indicate that messageReceived should be
+ * invoked. If false is returned, the messageReceived method will
+ * not be invoked.
+ */
+ public boolean accept(ClusterMessage msg) {
+ return (msg instanceof FileMessage) || (msg instanceof UndeployMessage);
+ }
+
+ /**
+ * Install a new web application, whose web application archive is at the
+ * specified URL, into this container and all the other members of the
+ * cluster with the specified context path. A context path of "" (the empty
+ * string) should be used for the root application for this container.
+ * Otherwise, the context path must start with a slash.
+ * <p>
+ * If this application is successfully installed locally, a ContainerEvent
+ * of type <code>INSTALL_EVENT</code> will be sent to all registered
+ * listeners, with the newly created <code>Context</code> as an argument.
+ *
+ * @param contextPath
+ * The context path to which this application should be installed
+ * (must be unique)
+ * @param war
+ * A URL of type "jar:" that points to a WAR file, or type
+ * "file:" that points to an unpacked directory structure
+ * containing the web application to be installed
+ *
+ * @exception IllegalArgumentException
+ * if the specified context path is malformed (it must be ""
+ * or start with a slash)
+ * @exception IllegalStateException
+ * if the specified context path is already attached to an
+ * existing web application
+ * @exception IOException
+ * if an input/output error was encountered during
+ * installation
+ */
+ public void install(String contextPath, URL war) throws IOException {
+ Member[] members = getCluster().getMembers();
+ Member localMember = getCluster().getLocalMember();
+ FileMessageFactory factory = FileMessageFactory.getInstance(new File(
+ war.getFile()), false);
+ FileMessage msg = new FileMessage(localMember, war.getFile(),
+ contextPath);
+ if(log.isDebugEnabled())
+ log.debug("Send cluster war deployment [ path:"
+ + contextPath + " war: " + war + " ] started.");
+ msg = factory.readMessage(msg);
+ while (msg != null) {
+ for (int i = 0; i < members.length; i++) {
+ if (log.isDebugEnabled())
+ log.debug("Send cluster war fragment [ path: "
+ + contextPath + " war: " + war + " to: " + members[i] + " ]");
+ getCluster().send(msg, members[i]);
+ }
+ msg = factory.readMessage(msg);
+ }
+ if(log.isDebugEnabled())
+ log.debug("Send cluster war deployment [ path: "
+ + contextPath + " war: " + war + " ] finished.");
+ }
+
+ /**
+ * Remove an existing web application, attached to the specified context
+ * path. If this application is successfully removed, a ContainerEvent of
+ * type <code>REMOVE_EVENT</code> will be sent to all registered
+ * listeners, with the removed <code>Context</code> as an argument.
+ * Deletes the web application war file and/or directory if they exist in
+ * the Host's appBase.
+ *
+ * @param contextPath
+ * The context path of the application to be removed
+ * @param undeploy
+ * boolean flag to remove web application from server
+ *
+ * @exception IllegalArgumentException
+ * if the specified context path is malformed (it must be ""
+ * or start with a slash)
+ * @exception IllegalArgumentException
+ * if the specified context path does not identify a
+ * currently installed web application
+ * @exception IOException
+ * if an input/output error occurs during removal
+ */
+ public void remove(String contextPath, boolean undeploy) throws IOException {
+ if (log.isInfoEnabled())
+ log.info("Cluster wide remove of web app " + contextPath);
+ Member localMember = getCluster().getLocalMember();
+ UndeployMessage msg = new UndeployMessage(localMember, System
+ .currentTimeMillis(), "Undeploy:" + contextPath + ":"
+ + System.currentTimeMillis(), contextPath, undeploy);
+ if (log.isDebugEnabled())
+ log.debug("Send cluster wide undeployment from "
+ + contextPath );
+ cluster.send(msg);
+ // remove locally
+ if (undeploy) {
+ try {
+ if (!isServiced(contextPath)) {
+ addServiced(contextPath);
+ try {
+ remove(contextPath);
+ } finally {
+ removeServiced(contextPath);
+ }
+ } else
+ log.error("Local remove from " + contextPath
+ + "failed, other manager has app in service!");
+
+ } catch (Exception ex) {
+ log.error("local remove from " + contextPath + " failed", ex);
+ }
+ }
+
+ }
+
+ /*
+ * Modifcation from watchDir war detected!
+ *
+ * @see org.apache.catalina.cluster.deploy.FileChangeListener#fileModified(java.io.File)
+ */
+ public void fileModified(File newWar) {
+ try {
+ File deployWar = new File(getDeployDir(), newWar.getName());
+ copy(newWar, deployWar);
+ String contextName = getContextName(deployWar);
+ if (log.isInfoEnabled())
+ log.info("Installing webapp[" + contextName + "] from "
+ + deployWar.getAbsolutePath());
+ try {
+ remove(contextName, false);
+ } catch (Exception x) {
+ log.error("No removal", x);
+ }
+ install(contextName, deployWar.toURL());
+ } catch (Exception x) {
+ log.error("Unable to install WAR file", x);
+ }
+ }
+
+ /*
+ * War remvoe from watchDir
+ *
+ * @see org.apache.catalina.cluster.deploy.FileChangeListener#fileRemoved(java.io.File)
+ */
+ public void fileRemoved(File removeWar) {
+ try {
+ String contextName = getContextName(removeWar);
+ if (log.isInfoEnabled())
+ log.info("Removing webapp[" + contextName + "]");
+ remove(contextName, true);
+ } catch (Exception x) {
+ log.error("Unable to remove WAR file", x);
+ }
+ }
+
+ /**
+ * Create a context path from war
+ * @param war War filename
+ * @return '/filename' or if war name is ROOT.war context name is empty string ''
+ */
+ protected String getContextName(File war) {
+ String contextName = "/"
+ + war.getName().substring(0,
+ war.getName().lastIndexOf(".war"));
+ if("/ROOT".equals(contextName))
+ contextName= "" ;
+ return contextName ;
+ }
+
+ /**
+ * Given a context path, get the config file name.
+ */
+ protected String getConfigFile(String path) {
+ String basename = null;
+ if (path.equals("")) {
+ basename = "ROOT";
+ } else {
+ basename = path.substring(1).replace('/', '#');
+ }
+ return (basename);
+ }
+
+ /**
+ * Given a context path, get the config file name.
+ */
+ protected String getDocBase(String path) {
+ String basename = null;
+ if (path.equals("")) {
+ basename = "ROOT";
+ } else {
+ basename = path.substring(1);
+ }
+ return (basename);
+ }
+
+ /**
+ * Return a File object representing the "application root" directory for
+ * our associated Host.
+ */
+ protected File getAppBase() {
+
+ if (appBase != null) {
+ return appBase;
+ }
+
+ File file = new File(host.getAppBase());
+ if (!file.isAbsolute())
+ file = new File(System.getProperty("catalina.base"), host
+ .getAppBase());
+ try {
+ appBase = file.getCanonicalFile();
+ } catch (IOException e) {
+ appBase = file;
+ }
+ return (appBase);
+
+ }
+
+ /**
+ * Invoke the remove method on the deployer.
+ */
+ protected void remove(String path) throws Exception {
+ // TODO Handle remove also work dir content !
+ // Stop the context first to be nicer
+ Context context = (Context) host.findChild(path);
+ if (context != null) {
+ if(log.isDebugEnabled())
+ log.debug("Undeploy local context " +path );
+ ((Lifecycle) context).stop();
+ File war = new File(getAppBase(), getDocBase(path) + ".war");
+ File dir = new File(getAppBase(), getDocBase(path));
+ File xml = new File(configBase, getConfigFile(path) + ".xml");
+ if (war.exists()) {
+ war.delete();
+ } else if (dir.exists()) {
+ undeployDir(dir);
+ } else {
+ xml.delete();
+ }
+ // Perform new deployment and remove internal HostConfig state
+ check(path);
+ }
+
+ }
+
+ /**
+ * Delete the specified directory, including all of its contents and
+ * subdirectories recursively.
+ *
+ * @param dir
+ * File object representing the directory to be deleted
+ */
+ protected void undeployDir(File dir) {
+
+ String files[] = dir.list();
+ if (files == null) {
+ files = new String[0];
+ }
+ for (int i = 0; i < files.length; i++) {
+ File file = new File(dir, files[i]);
+ if (file.isDirectory()) {
+ undeployDir(file);
+ } else {
+ file.delete();
+ }
+ }
+ dir.delete();
+
+ }
+
+ /*
+ * Call watcher to check for deploy changes
+ *
+ * @see org.apache.catalina.cluster.ClusterDeployer#backgroundProcess()
+ */
+ public void backgroundProcess() {
+ if (started) {
+ count = (count + 1) % processDeployFrequency;
+ if (count == 0 && watchEnabled) {
+ watcher.check();
+ }
+ }
+
+ }
+
+ /*--Deployer Operations ------------------------------------*/
+
+ /**
+ * Invoke the check method on the deployer.
+ */
+ protected void check(String name) throws Exception {
+ String[] params = { name };
+ String[] signature = { "java.lang.String" };
+ mBeanServer.invoke(oname, "check", params, signature);
+ }
+
+ /**
+ * Invoke the check method on the deployer.
+ */
+ protected boolean isServiced(String name) throws Exception {
+ String[] params = { name };
+ String[] signature = { "java.lang.String" };
+ Boolean result = (Boolean) mBeanServer.invoke(oname, "isServiced",
+ params, signature);
+ return result.booleanValue();
+ }
+
+ /**
+ * Invoke the check method on the deployer.
+ */
+ protected void addServiced(String name) throws Exception {
+ String[] params = { name };
+ String[] signature = { "java.lang.String" };
+ mBeanServer.invoke(oname, "addServiced", params, signature);
+ }
+
+ /**
+ * Invoke the check method on the deployer.
+ */
+ protected void removeServiced(String name) throws Exception {
+ String[] params = { name };
+ String[] signature = { "java.lang.String" };
+ mBeanServer.invoke(oname, "removeServiced", params, signature);
+ }
+
+ /*--Instance Getters/Setters--------------------------------*/
+ public CatalinaCluster getCluster() {
+ return cluster;
+ }
+
+ public void setCluster(CatalinaCluster cluster) {
+ this.cluster = cluster;
+ }
+
+ public boolean equals(Object listener) {
+ return super.equals(listener);
+ }
+
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ public String getDeployDir() {
+ return deployDir;
+ }
+
+ public void setDeployDir(String deployDir) {
+ this.deployDir = deployDir;
+ }
+
+ public String getTempDir() {
+ return tempDir;
+ }
+
+ public void setTempDir(String tempDir) {
+ this.tempDir = tempDir;
+ }
+
+ public String getWatchDir() {
+ return watchDir;
+ }
+
+ public void setWatchDir(String watchDir) {
+ this.watchDir = watchDir;
+ }
+
+ public boolean isWatchEnabled() {
+ return watchEnabled;
+ }
+
+ public boolean getWatchEnabled() {
+ return watchEnabled;
+ }
+
+ public void setWatchEnabled(boolean watchEnabled) {
+ this.watchEnabled = watchEnabled;
+ }
+
+ /**
+ * Return the frequency of watcher checks.
+ */
+ public int getProcessDeployFrequency() {
+
+ return (this.processDeployFrequency);
+
+ }
+
+ /**
+ * Set the watcher checks frequency.
+ *
+ * @param processExpiresFrequency
+ * the new manager checks frequency
+ */
+ public void setProcessDeployFrequency(int processExpiresFrequency) {
+
+ if (processExpiresFrequency <= 0) {
+ return;
+ }
+ this.processDeployFrequency = processExpiresFrequency;
+ }
+
+ /**
+ * Copy a file to the specified temp directory.
+ * @param from copy from temp
+ * @param to to host appBase directory
+ * @return true, copy successful
+ */
+ protected boolean copy(File from, File to) {
+ try {
+ if (!to.exists())
+ to.createNewFile();
+ java.io.FileInputStream is = new java.io.FileInputStream(from);
+ java.io.FileOutputStream os = new java.io.FileOutputStream(to,
+ false);
+ byte[] buf = new byte[4096];
+ while (true) {
+ int len = is.read(buf);
+ if (len < 0)
+ break;
+ os.write(buf, 0, len);
+ }
+ is.close();
+ os.close();
+ } catch (IOException e) {
+ log.error("Unable to copy file from:" + from + " to:" + to, e);
+ return false;
+ }
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+import java.io.File;
+
+public interface FileChangeListener {
+ public void fileModified(File f);
+ public void fileRemoved(File f);
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+
+import java.io.Serializable;
+
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.ha.ClusterMessageBase;
+
+/**
+ * Contains the data for a file being transferred over TCP, this is
+ * essentially a fragment of a file, read and written by the FileMessageFactory
+ * @author Filip Hanik
+ * @version 1.0
+ */
+
+public class FileMessage extends ClusterMessageBase implements ClusterMessage, Serializable {
+ private int messageNumber;
+ private byte[] data;
+ private int dataLength;
+
+ private long totalLength;
+ private long totalNrOfMsgs;
+ private String fileName;
+ private String contextPath;
+
+ public FileMessage(Member source,
+ String fileName,
+ String contextPath) {
+ this.address=source;
+ this.fileName=fileName;
+ this.contextPath=contextPath;
+ }
+
+ /*
+ public void writeExternal(ObjectOutput out) throws IOException {
+
+ }
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+
+ }
+ */
+
+ public int getMessageNumber() {
+ return messageNumber;
+ }
+ public void setMessageNumber(int messageNumber) {
+ this.messageNumber = messageNumber;
+ }
+ public long getTotalNrOfMsgs() {
+ return totalNrOfMsgs;
+ }
+ public void setTotalNrOfMsgs(long totalNrOfMsgs) {
+ this.totalNrOfMsgs = totalNrOfMsgs;
+ }
+ public byte[] getData() {
+ return data;
+ }
+ public void setData(byte[] data, int length) {
+ this.data = data;
+ this.dataLength = length;
+ }
+ public int getDataLength() {
+ return dataLength;
+ }
+ public void setDataLength(int dataLength) {
+ this.dataLength = dataLength;
+ }
+ public long getTotalLength() {
+ return totalLength;
+ }
+ public void setTotalLength(long totalLength) {
+ this.totalLength = totalLength;
+ }
+
+ public String getUniqueId() {
+ StringBuffer result = new StringBuffer(getFileName());
+ result.append("#-#");
+ result.append(getMessageNumber());
+ result.append("#-#");
+ result.append(System.currentTimeMillis());
+ return result.toString();
+ }
+
+
+ public String getFileName() {
+ return fileName;
+ }
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+ public String getContextPath() {
+ return contextPath;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+
+/**
+ * This factory is used to read files and write files by splitting them up into
+ * smaller messages. So that entire files don't have to be read into memory.
+ * <BR>
+ * The factory can be used as a reader or writer but not both at the same time.
+ * When done reading or writing the factory will close the input or output
+ * streams and mark the factory as closed. It is not possible to use it after
+ * that. <BR>
+ * To force a cleanup, call cleanup() from the calling object. <BR>
+ * This class is not thread safe.
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class FileMessageFactory {
+ /*--Static Variables----------------------------------------*/
+ public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
+ .getLog(FileMessageFactory.class);
+
+ /**
+ * The number of bytes that we read from file
+ */
+ public static final int READ_SIZE = 1024 * 10; //10kb
+
+ /**
+ * The file that we are reading/writing
+ */
+ protected File file = null;
+
+ /**
+ * True means that we are writing with this factory. False means that we are
+ * reading with this factory
+ */
+ protected boolean openForWrite;
+
+ /**
+ * Once the factory is used, it can not be reused.
+ */
+ protected boolean closed = false;
+
+ /**
+ * When openForWrite=false, the input stream is held by this variable
+ */
+ protected FileInputStream in;
+
+ /**
+ * When openForWrite=true, the output stream is held by this variable
+ */
+ protected FileOutputStream out;
+
+ /**
+ * The number of messages we have read or written
+ */
+ protected int nrOfMessagesProcessed = 0;
+
+ /**
+ * The total size of the file
+ */
+ protected long size = 0;
+
+ /**
+ * The total number of packets that we split this file into
+ */
+ protected long totalNrOfMessages = 0;
+
+ /**
+ * The bytes that we hold the data in, not thread safe.
+ */
+ protected byte[] data = new byte[READ_SIZE];
+
+ /**
+ * Private constructor, either instantiates a factory to read or write. <BR>
+ * When openForWrite==true, then a the file, f, will be created and an
+ * output stream is opened to write to it. <BR>
+ * When openForWrite==false, an input stream is opened, the file has to
+ * exist.
+ *
+ * @param f
+ * File - the file to be read/written
+ * @param openForWrite
+ * boolean - true means we are writing to the file, false means
+ * we are reading from the file
+ * @throws FileNotFoundException -
+ * if the file to be read doesn't exist
+ * @throws IOException -
+ * if the system fails to open input/output streams to the file
+ * or if it fails to create the file to be written to.
+ */
+ private FileMessageFactory(File f, boolean openForWrite)
+ throws FileNotFoundException, IOException {
+ this.file = f;
+ this.openForWrite = openForWrite;
+ if (log.isDebugEnabled())
+ log.debug("open file " + f + " write " + openForWrite);
+ if (openForWrite) {
+ if (!file.exists())
+ file.createNewFile();
+ out = new FileOutputStream(f);
+ } else {
+ size = file.length();
+ totalNrOfMessages = (size / READ_SIZE) + 1;
+ in = new FileInputStream(f);
+ }//end if
+
+ }
+
+ /**
+ * Creates a factory to read or write from a file. When opening for read,
+ * the readMessage can be invoked, and when opening for write the
+ * writeMessage can be invoked.
+ *
+ * @param f
+ * File - the file to be read or written
+ * @param openForWrite
+ * boolean - true, means we are writing to the file, false means
+ * we are reading from it
+ * @throws FileNotFoundException -
+ * if the file to be read doesn't exist
+ * @throws IOException -
+ * if it fails to create the file that is to be written
+ * @return FileMessageFactory
+ */
+ public static FileMessageFactory getInstance(File f, boolean openForWrite)
+ throws FileNotFoundException, IOException {
+ return new FileMessageFactory(f, openForWrite);
+ }
+
+ /**
+ * Reads file data into the file message and sets the size, totalLength,
+ * totalNrOfMsgs and the message number <BR>
+ * If EOF is reached, the factory returns null, and closes itself, otherwise
+ * the same message is returned as was passed in. This makes sure that not
+ * more memory is ever used. To remember, neither the file message or the
+ * factory are thread safe. dont hand off the message to one thread and read
+ * the same with another.
+ *
+ * @param f
+ * FileMessage - the message to be populated with file data
+ * @throws IllegalArgumentException -
+ * if the factory is for writing or is closed
+ * @throws IOException -
+ * if a file read exception occurs
+ * @return FileMessage - returns the same message passed in as a parameter,
+ * or null if EOF
+ */
+ public FileMessage readMessage(FileMessage f)
+ throws IllegalArgumentException, IOException {
+ checkState(false);
+ int length = in.read(data);
+ if (length == -1) {
+ cleanup();
+ return null;
+ } else {
+ f.setData(data, length);
+ f.setTotalLength(size);
+ f.setTotalNrOfMsgs(totalNrOfMessages);
+ f.setMessageNumber(++nrOfMessagesProcessed);
+ return f;
+ }//end if
+ }
+
+ /**
+ * Writes a message to file. If (msg.getMessageNumber() ==
+ * msg.getTotalNrOfMsgs()) the output stream will be closed after writing.
+ *
+ * @param msg
+ * FileMessage - message containing data to be written
+ * @throws IllegalArgumentException -
+ * if the factory is opened for read or closed
+ * @throws IOException -
+ * if a file write error occurs
+ * @return returns true if the file is complete and outputstream is closed,
+ * false otherwise.
+ */
+ public boolean writeMessage(FileMessage msg)
+ throws IllegalArgumentException, IOException {
+ if (!openForWrite)
+ throw new IllegalArgumentException(
+ "Can't write message, this factory is reading.");
+ if (log.isDebugEnabled())
+ log.debug("Message " + msg + " data " + msg.getData()
+ + " data length " + msg.getDataLength() + " out " + out);
+ if (out != null) {
+ out.write(msg.getData(), 0, msg.getDataLength());
+ nrOfMessagesProcessed++;
+ out.flush();
+ if (msg.getMessageNumber() == msg.getTotalNrOfMsgs()) {
+ out.close();
+ cleanup();
+ return true;
+ }//end if
+ } else {
+ if (log.isWarnEnabled())
+ log.warn("Receive Message again -- Sender ActTimeout to short [ path: "
+ + msg.getContextPath()
+ + " war: "
+ + msg.getFileName()
+ + " data: "
+ + msg.getData()
+ + " data length: " + msg.getDataLength() + " ]");
+ }
+ return false;
+ }//writeMessage
+
+ /**
+ * Closes the factory, its streams and sets all its references to null
+ */
+ public void cleanup() {
+ if (in != null)
+ try {
+ in.close();
+ } catch (Exception ignore) {
+ }
+ if (out != null)
+ try {
+ out.close();
+ } catch (Exception ignore) {
+ }
+ in = null;
+ out = null;
+ size = 0;
+ closed = true;
+ data = null;
+ nrOfMessagesProcessed = 0;
+ totalNrOfMessages = 0;
+ }
+
+ /**
+ * Check to make sure the factory is able to perform the function it is
+ * asked to do. Invoked by readMessage/writeMessage before those methods
+ * proceed.
+ *
+ * @param openForWrite
+ * boolean
+ * @throws IllegalArgumentException
+ */
+ protected void checkState(boolean openForWrite)
+ throws IllegalArgumentException {
+ if (this.openForWrite != openForWrite) {
+ cleanup();
+ if (openForWrite)
+ throw new IllegalArgumentException(
+ "Can't write message, this factory is reading.");
+ else
+ throw new IllegalArgumentException(
+ "Can't read message, this factory is writing.");
+ }
+ if (this.closed) {
+ cleanup();
+ throw new IllegalArgumentException("Factory has been closed.");
+ }
+ }
+
+ /**
+ * Example usage.
+ *
+ * @param args
+ * String[], args[0] - read from filename, args[1] write to
+ * filename
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+
+ System.out
+ .println("Usage: FileMessageFactory fileToBeRead fileToBeWritten");
+ System.out
+ .println("Usage: This will make a copy of the file on the local file system");
+ FileMessageFactory read = getInstance(new File(args[0]), false);
+ FileMessageFactory write = getInstance(new File(args[1]), true);
+ FileMessage msg = new FileMessage(null, args[0], args[0]);
+ msg = read.readMessage(msg);
+ System.out.println("Expecting to write " + msg.getTotalNrOfMsgs()
+ + " messages.");
+ int cnt = 0;
+ while (msg != null) {
+ write.writeMessage(msg);
+ cnt++;
+ msg = read.readMessage(msg);
+ }//while
+ System.out.println("Actually wrote " + cnt + " messages.");
+ }///main
+
+ public File getFile() {
+ return file;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.tribes.Member;
+import java.io.Serializable;
+public class UndeployMessage implements ClusterMessage,Serializable {
+ private Member address;
+ private long timestamp;
+ private String uniqueId;
+ private String contextPath;
+ private boolean undeploy;
+ private int resend = 0;
+ private int compress = 0;
+
+ public UndeployMessage() {} //for serialization
+ public UndeployMessage(Member address,
+ long timestamp,
+ String uniqueId,
+ String contextPath,
+ boolean undeploy) {
+ this.address = address;
+ this.timestamp= timestamp;
+ this.undeploy = undeploy;
+ this.uniqueId = uniqueId;
+ this.undeploy = undeploy;
+ this.contextPath = contextPath;
+ }
+
+ public Member getAddress() {
+ return address;
+ }
+
+ public void setAddress(Member address) {
+ this.address = address;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public String getUniqueId() {
+ return uniqueId;
+ }
+
+ public void setUniqueId(String uniqueId) {
+ this.uniqueId = uniqueId;
+ }
+
+ public String getContextPath() {
+ return contextPath;
+ }
+
+ public void setContextPath(String contextPath) {
+ this.contextPath = contextPath;
+ }
+
+ public boolean getUndeploy() {
+ return undeploy;
+ }
+
+ public void setUndeploy(boolean undeploy) {
+ this.undeploy = undeploy;
+ }
+ /**
+ * @return Returns the compress.
+ * @since 5.5.10
+ */
+ public int getCompress() {
+ return compress;
+ }
+ /**
+ * @param compress The compress to set.
+ * @since 5.5.10
+ */
+ public void setCompress(int compress) {
+ this.compress = compress;
+ }
+ /**
+ * @return Returns the resend.
+ * @since 5.5.10
+ */
+ public int getResend() {
+ return resend;
+ }
+ /**
+ * @param resend The resend to set.
+ * @since 5.5.10
+ */
+ public void setResend(int resend) {
+ this.resend = resend;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Iterator;
+
+/**
+ * <p>
+ * The <b>WarWatcher </b> watches the deployDir for changes made to the
+ * directory (adding new WAR files->deploy or remove WAR files->undeploy) And
+ * notifies a listener of the changes made
+ * </p>
+ *
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version 1.1
+ */
+
+public class WarWatcher {
+
+ /*--Static Variables----------------------------------------*/
+ public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
+ .getLog(WarWatcher.class);
+
+ /*--Instance Variables--------------------------------------*/
+ /**
+ * Directory to watch for war files
+ */
+ protected File watchDir = null;
+
+ /**
+ * Parent to be notified of changes
+ */
+ protected FileChangeListener listener = null;
+
+ /**
+ * Currently deployed files
+ */
+ protected Map currentStatus = new HashMap();
+
+ /*--Constructor---------------------------------------------*/
+
+ public WarWatcher() {
+ }
+
+ public WarWatcher(FileChangeListener listener, File watchDir) {
+ this.listener = listener;
+ this.watchDir = watchDir;
+ }
+
+ /*--Logic---------------------------------------------------*/
+
+ /**
+ * check for modification and send notifcation to listener
+ */
+ public void check() {
+ if (log.isInfoEnabled())
+ log.info("check cluster wars at " + watchDir);
+ File[] list = watchDir.listFiles(new WarFilter());
+ if (list == null)
+ list = new File[0];
+ //first make sure all the files are listed in our current status
+ for (int i = 0; i < list.length; i++) {
+ addWarInfo(list[i]);
+ }
+
+ //check all the status codes and update the FarmDeployer
+ for (Iterator i = currentStatus.entrySet().iterator(); i.hasNext();) {
+ Map.Entry entry = (Map.Entry) i.next();
+ WarInfo info = (WarInfo) entry.getValue();
+ int check = info.check();
+ if (check == 1) {
+ listener.fileModified(info.getWar());
+ } else if (check == -1) {
+ listener.fileRemoved(info.getWar());
+ //no need to keep in memory
+ currentStatus.remove(info.getWar());
+ }
+ }
+
+ }
+
+ /**
+ * add cluster war to the watcher state
+ * @param warfile
+ */
+ protected void addWarInfo(File warfile) {
+ WarInfo info = (WarInfo) currentStatus.get(warfile.getAbsolutePath());
+ if (info == null) {
+ info = new WarInfo(warfile);
+ info.setLastState(-1); //assume file is non existent
+ currentStatus.put(warfile.getAbsolutePath(), info);
+ }
+ }
+
+ /**
+ * clear watcher state
+ */
+ public void clear() {
+ currentStatus.clear();
+ }
+
+ /**
+ * @return Returns the watchDir.
+ */
+ public File getWatchDir() {
+ return watchDir;
+ }
+
+ /**
+ * @param watchDir
+ * The watchDir to set.
+ */
+ public void setWatchDir(File watchDir) {
+ this.watchDir = watchDir;
+ }
+
+ /**
+ * @return Returns the listener.
+ */
+ public FileChangeListener getListener() {
+ return listener;
+ }
+
+ /**
+ * @param listener
+ * The listener to set.
+ */
+ public void setListener(FileChangeListener listener) {
+ this.listener = listener;
+ }
+
+ /*--Inner classes-------------------------------------------*/
+
+ /**
+ * File name filter for war files
+ */
+ protected class WarFilter implements java.io.FilenameFilter {
+ public boolean accept(File path, String name) {
+ if (name == null)
+ return false;
+ return name.endsWith(".war");
+ }
+ }
+
+ /**
+ * File information on existing WAR files
+ */
+ protected class WarInfo {
+ protected File war = null;
+
+ protected long lastChecked = 0;
+
+ protected long lastState = 0;
+
+ public WarInfo(File war) {
+ this.war = war;
+ this.lastChecked = war.lastModified();
+ if (!war.exists())
+ lastState = -1;
+ }
+
+ public boolean modified() {
+ return war.exists() && war.lastModified() > lastChecked;
+ }
+
+ public boolean exists() {
+ return war.exists();
+ }
+
+ /**
+ * Returns 1 if the file has been added/modified, 0 if the file is
+ * unchanged and -1 if the file has been removed
+ *
+ * @return int 1=file added; 0=unchanged; -1=file removed
+ */
+ public int check() {
+ //file unchanged by default
+ int result = 0;
+
+ if (modified()) {
+ //file has changed - timestamp
+ result = 1;
+ lastState = result;
+ } else if ((!exists()) && (!(lastState == -1))) {
+ //file was removed
+ result = -1;
+ lastState = result;
+ } else if ((lastState == -1) && exists()) {
+ //file was added
+ result = 1;
+ lastState = result;
+ }
+ this.lastChecked = System.currentTimeMillis();
+ return result;
+ }
+
+ public File getWar() {
+ return war;
+ }
+
+ public int hashCode() {
+ return war.getAbsolutePath().hashCode();
+ }
+
+ public boolean equals(Object other) {
+ if (other instanceof WarInfo) {
+ WarInfo wo = (WarInfo) other;
+ return wo.getWar().equals(getWar());
+ } else {
+ return false;
+ }
+ }
+
+ protected void setLastState(int lastState) {
+ this.lastState = lastState;
+ }
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<mbeans-descriptors>
+
+ <mbean name="SimpleTcpCluster"
+ className="org.apache.catalina.mbeans.ClassNameMBean"
+ description="Tcp Cluster implementation"
+ domain="Catalina"
+ group="Cluster"
+ type="org.apache.catalina.ha.tcp.SimpleTcpCluster">
+
+ <attribute name="protocolStack"
+ description="JavaGroups protocol stack selection"
+ type="java.lang.String"/>
+
+ </mbean>
+
+
+ <mbean name="SimpleTcpReplicationManager"
+ className="org.apache.catalina.mbeans.ClassNameMBean"
+ description="Clustered implementation of the Manager interface"
+ domain="Catalina"
+ group="Manager"
+ type="org.apache.catalina.ha.tcp.SimpleTcpReplicationManager">
+
+ <attribute name="algorithm"
+ description="The message digest algorithm to be used when generating
+ session identifiers"
+ type="java.lang.String"/>
+
+ <attribute name="checkInterval"
+ description="The interval (in seconds) between checks for expired
+ sessions"
+ type="int"/>
+
+ <attribute name="className"
+ description="Fully qualified class name of the managed object"
+ type="java.lang.String"
+ writeable="false"/>
+
+ <attribute name="distributable"
+ description="The distributable flag for Sessions created by this
+ Manager"
+ type="boolean"/>
+
+ <attribute name="entropy"
+ description="A String initialization parameter used to increase the
+ entropy of the initialization of our random number
+ generator"
+ type="java.lang.String"/>
+
+ <attribute name="managedResource"
+ description="The managed resource this MBean is associated with"
+ type="java.lang.Object"/>
+
+ <attribute name="maxActiveSessions"
+ description="The maximum number of active Sessions allowed, or -1
+ for no limit"
+ type="int"/>
+
+ <attribute name="maxInactiveInterval"
+ description="The default maximum inactive interval for Sessions
+ created by this Manager"
+ type="int"/>
+
+ <attribute name="name"
+ description="The descriptive name of this Manager implementation
+ (for logging)"
+ type="java.lang.String"
+ writeable="false"/>
+
+ </mbean>
+
+
+
+<mbean name="ReplicationValve"
+ className="org.apache.catalina.mbeans.ClassNameMBean"
+ description="Valve for simple tcp replication"
+ domain="Catalina"
+ group="Valve"
+ type="org.apache.catalina.ha.tcp.ReplicationValve">
+
+ <attribute name="className"
+ description="Fully qualified class name of the managed object"
+ type="java.lang.String"
+ writeable="false"/>
+
+ <attribute name="debug"
+ description="The debugging detail level for this component"
+ type="int"/>
+
+ </mbean>
+
+
+</mbeans-descriptors>
--- /dev/null
+<body>
+
+<p>This package contains code for Clustering, the base class
+of a Cluster is <code>org.apache.catalina.Cluster</code> implementations
+of this class is done when implementing a new Cluster protocol</p>
+
+<p>The only Cluster protocol currently implemented is a JavaGroups based<br>
+ <b>JGCluster.java</b>
+</p>
+
+</body>
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Loader;
+import org.apache.catalina.Session;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.session.StandardManager;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.io.ReplicationStream;
+import org.apache.catalina.tribes.tipis.LazyReplicatedMap;
+import org.apache.catalina.tribes.Channel;
+
+/**
+ *@author Filip Hanik
+ *@version 1.0
+ */
+public class BackupManager extends StandardManager implements ClusterManager
+{
+ public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog( BackupManager.class );
+
+ protected static long DEFAULT_REPL_TIMEOUT = 15000;//15 seconds
+
+ /** Set to true if we don't want the sessions to expire on shutdown */
+ protected boolean mExpireSessionsOnShutdown = true;
+
+ /**
+ * The name of this manager
+ */
+ protected String name;
+
+ /**
+ * A reference to the cluster
+ */
+ protected CatalinaCluster cluster;
+
+ /**
+ * Should listeners be notified?
+ */
+ private boolean notifyListenersOnReplication;
+ /**
+ *
+ */
+ private int mapSendOptions = Channel.SEND_OPTIONS_SYNCHRONIZED_ACK|Channel.SEND_OPTIONS_USE_ACK;
+
+ /**
+ * Constructor, just calls super()
+ *
+ */
+ public BackupManager() {
+ super();
+ }
+
+
+//******************************************************************************/
+// ClusterManager Interface
+//******************************************************************************/
+
+ public void messageDataReceived(ClusterMessage msg) {
+ }
+
+ public boolean isSendClusterDomainOnly() {
+ return false;
+ }
+
+ /**
+ * @param sendClusterDomainOnly The sendClusterDomainOnly to set.
+ */
+ public void setSendClusterDomainOnly(boolean sendClusterDomainOnly) {
+ }
+
+ /**
+ * @return Returns the defaultMode.
+ */
+ public boolean isDefaultMode() {
+ return false;
+ }
+ /**
+ * @param defaultMode The defaultMode to set.
+ */
+ public void setDefaultMode(boolean defaultMode) {
+ }
+
+ public void setExpireSessionsOnShutdown(boolean expireSessionsOnShutdown)
+ {
+ mExpireSessionsOnShutdown = expireSessionsOnShutdown;
+ }
+
+ public void setCluster(CatalinaCluster cluster) {
+ if(log.isDebugEnabled())
+ log.debug("Cluster associated with SimpleTcpReplicationManager");
+ this.cluster = cluster;
+ }
+
+ public boolean getExpireSessionsOnShutdown()
+ {
+ return mExpireSessionsOnShutdown;
+ }
+
+
+ /**
+ * Override persistence since they don't go hand in hand with replication for now.
+ */
+ public void unload() throws IOException {
+ }
+
+ public ClusterMessage requestCompleted(String sessionId) {
+ if ( !this.started ) return null;
+ LazyReplicatedMap map = (LazyReplicatedMap)sessions;
+ map.replicate(sessionId,false);
+ return null;
+ }
+
+
+//=========================================================================
+// OVERRIDE THESE METHODS TO IMPLEMENT THE REPLICATION
+//=========================================================================
+
+ public Session createEmptySession() {
+ return new DeltaSession(this);
+ }
+
+ public ClassLoader[] getClassLoaders() {
+ return ClusterManagerBase.getClassLoaders(this.container);
+ }
+
+ /**
+ * Open Stream and use correct ClassLoader (Container) Switch
+ * ThreadClassLoader
+ *
+ * @param data
+ * @return The object input stream
+ * @throws IOException
+ */
+ public ReplicationStream getReplicationStream(byte[] data) throws IOException {
+ return getReplicationStream(data,0,data.length);
+ }
+
+ public ReplicationStream getReplicationStream(byte[] data, int offset, int length) throws IOException {
+ ByteArrayInputStream fis = new ByteArrayInputStream(data, offset, length);
+ return new ReplicationStream(fis, getClassLoaders());
+ }
+
+
+
+
+ public String getName() {
+ return this.name;
+ }
+ /**
+ * Prepare for the beginning of active use of the public methods of this
+ * component. This method should be called after <code>configure()</code>,
+ * and before any of the public methods of the component are utilized.<BR>
+ * Starts the cluster communication channel, this will connect with the other nodes
+ * in the cluster, and request the current session state to be transferred to this node.
+ * @exception IllegalStateException if this component has already been
+ * started
+ * @exception LifecycleException if this component detects a fatal error
+ * that prevents this component from being used
+ */
+ public void start() throws LifecycleException {
+ if ( this.started ) return;
+ try {
+ CatalinaCluster catclust = (CatalinaCluster)cluster;
+ catclust.addManager(getName(), this);
+ LazyReplicatedMap map = new LazyReplicatedMap(this,
+ catclust.getChannel(),
+ DEFAULT_REPL_TIMEOUT,
+ getMapName(),
+ getClassLoaders());
+ map.setChannelSendOptions(mapSendOptions);
+ this.sessions = map;
+ super.start();
+ } catch ( Exception x ) {
+ log.error("Unable to start BackupManager",x);
+ throw new LifecycleException("Failed to start BackupManager",x);
+ }
+ }
+
+ public String getMapName() {
+ CatalinaCluster catclust = (CatalinaCluster)cluster;
+ String name = catclust.getManagerName(getName(),this)+"-"+"";
+ if ( log.isDebugEnabled() ) log.debug("Backup manager, Setting map name to:"+name);
+ return name;
+ }
+
+ /**
+ * Gracefully terminate the active use of the public methods of this
+ * component. This method should be the last one called on a given
+ * instance of this component.<BR>
+ * This will disconnect the cluster communication channel and stop the listener thread.
+ * @exception IllegalStateException if this component has not been started
+ * @exception LifecycleException if this component detects a fatal error
+ * that needs to be reported
+ */
+ public void stop() throws LifecycleException
+ {
+
+ LazyReplicatedMap map = (LazyReplicatedMap)sessions;
+ if ( map!=null ) {
+ map.breakdown();
+ }
+ if ( !this.started ) return;
+ try {
+ } catch ( Exception x ){
+ log.error("Unable to stop BackupManager",x);
+ throw new LifecycleException("Failed to stop BackupManager",x);
+ } finally {
+ super.stop();
+ }
+ cluster.removeManager(getName(),this);
+
+ }
+
+ public void setDistributable(boolean dist) {
+ this.distributable = dist;
+ }
+
+ public boolean getDistributable() {
+ return distributable;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ public boolean isNotifyListenersOnReplication() {
+ return notifyListenersOnReplication;
+ }
+ public void setNotifyListenersOnReplication(boolean notifyListenersOnReplication) {
+ this.notifyListenersOnReplication = notifyListenersOnReplication;
+ }
+
+ public void setMapSendOptions(int mapSendOptions) {
+ this.mapSendOptions = mapSendOptions;
+ }
+
+ /*
+ * @see org.apache.catalina.ha.ClusterManager#getCluster()
+ */
+ public CatalinaCluster getCluster() {
+ return cluster;
+ }
+
+ public int getMapSendOptions() {
+ return mapSendOptions;
+ }
+
+ public String[] getInvalidatedSessions() {
+ return new String[0];
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.session;
+
+import org.apache.catalina.ha.ClusterManager;
+import java.beans.PropertyChangeListener;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.session.ManagerBase;
+import org.apache.catalina.Loader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import org.apache.catalina.tribes.io.ReplicationStream;
+import org.apache.catalina.Container;
+
+/**
+ *
+ * @author Filip Hanik
+ * @version $Revision: 380100 $ $Date: 2006-02-23 06:08:14 -0600 (Thu, 23 Feb 2006) $
+ */
+
+public abstract class ClusterManagerBase extends ManagerBase implements Lifecycle, PropertyChangeListener, ClusterManager{
+
+
+ public static ClassLoader[] getClassLoaders(Container container) {
+ Loader loader = null;
+ ClassLoader classLoader = null;
+ if (container != null) loader = container.getLoader();
+ if (loader != null) classLoader = loader.getClassLoader();
+ else classLoader = Thread.currentThread().getContextClassLoader();
+ if ( classLoader == Thread.currentThread().getContextClassLoader() ) {
+ return new ClassLoader[] {classLoader};
+ } else {
+ return new ClassLoader[] {classLoader,Thread.currentThread().getContextClassLoader()};
+ }
+ }
+
+
+ public ClassLoader[] getClassLoaders() {
+ return getClassLoaders(container);
+ }
+
+ /**
+ * Open Stream and use correct ClassLoader (Container) Switch
+ * ThreadClassLoader
+ *
+ * @param data
+ * @return The object input stream
+ * @throws IOException
+ */
+ public ReplicationStream getReplicationStream(byte[] data) throws IOException {
+ return getReplicationStream(data,0,data.length);
+ }
+
+ public ReplicationStream getReplicationStream(byte[] data, int offset, int length) throws IOException {
+ ByteArrayInputStream fis = new ByteArrayInputStream(data, offset, length);
+ return new ReplicationStream(fis, getClassLoaders());
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.session;
+
+import java.util.Map;
+
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.ha.*;
+
+/**
+ * Receive replicated SessionMessage form other cluster node.
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Revision: 378258 $ $Date: 2006-02-16 08:42:35 -0600 (Thu, 16 Feb 2006) $
+ */
+public class ClusterSessionListener extends ClusterListener {
+
+ /**
+ * The descriptive information about this implementation.
+ */
+ protected static final String info = "org.apache.catalina.session.ClusterSessionListener/1.1";
+
+ //--Constructor---------------------------------------------
+
+ public ClusterSessionListener() {
+ }
+
+ //--Logic---------------------------------------------------
+
+ /**
+ * Return descriptive information about this implementation.
+ */
+ public String getInfo() {
+
+ return (info);
+
+ }
+
+ /**
+ * Callback from the cluster, when a message is received, The cluster will
+ * broadcast it invoking the messageReceived on the receiver.
+ *
+ * @param myobj
+ * ClusterMessage - the message received from the cluster
+ */
+ public void messageReceived(ClusterMessage myobj) {
+ if (myobj != null && myobj instanceof SessionMessage) {
+ SessionMessage msg = (SessionMessage) myobj;
+ String ctxname = msg.getContextName();
+ //check if the message is a EVT_GET_ALL_SESSIONS,
+ //if so, wait until we are fully started up
+ Map managers = cluster.getManagers() ;
+ if (ctxname == null) {
+ java.util.Iterator i = managers.keySet().iterator();
+ while (i.hasNext()) {
+ String key = (String) i.next();
+ ClusterManager mgr = (ClusterManager) managers.get(key);
+ if (mgr != null)
+ mgr.messageDataReceived(msg);
+ else {
+ //this happens a lot before the system has started
+ // up
+ if (log.isDebugEnabled())
+ log.debug("Context manager doesn't exist:"
+ + key);
+ }
+ }
+ } else {
+ ClusterManager mgr = (ClusterManager) managers.get(ctxname);
+ if (mgr != null)
+ mgr.messageDataReceived(msg);
+ else if (log.isWarnEnabled())
+ log.warn("Context manager doesn't exist:" + ctxname);
+ }
+ }
+ return;
+ }
+
+ /**
+ * Accept only SessionMessage
+ *
+ * @param msg
+ * ClusterMessage
+ * @return boolean - returns true to indicate that messageReceived should be
+ * invoked. If false is returned, the messageReceived method will
+ * not be invoked.
+ */
+ public boolean accept(ClusterMessage msg) {
+ return (msg instanceof SessionMessage);
+ }
+}
+
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.session;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.ha.session</code>
+ * package.
+ *
+ * @author Peter Rossbach Pero
+ */
+
+public class Constants {
+
+ public static final String Package = "org.apache.catalina.ha.session";
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.session;
+
+import java.beans.PropertyChangeEvent;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+
+import org.apache.catalina.Cluster;
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Session;
+import org.apache.catalina.Valve;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.ha.tcp.ReplicationValve;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.io.ReplicationStream;
+import org.apache.catalina.util.LifecycleSupport;
+import org.apache.catalina.util.StringManager;
+
+/**
+ * The DeltaManager manages replicated sessions by only replicating the deltas
+ * in data. For applications written to handle this, the DeltaManager is the
+ * optimal way of replicating data.
+ *
+ * This code is almost identical to StandardManager with a difference in how it
+ * persists sessions and some modifications to it.
+ *
+ * <b>IMPLEMENTATION NOTE </b>: Correct behavior of session storing and
+ * reloading depends upon external calls to the <code>start()</code> and
+ * <code>stop()</code> methods of this class at the correct times.
+ *
+ * @author Filip Hanik
+ * @author Craig R. McClanahan
+ * @author Jean-Francois Arcand
+ * @author Peter Rossbach
+ * @version $Revision: 380100 $ $Date: 2006-02-23 06:08:14 -0600 (Thu, 23 Feb 2006) $
+ */
+
+public class DeltaManager extends ClusterManagerBase{
+
+ // ---------------------------------------------------- Security Classes
+ public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(DeltaManager.class);
+
+ /**
+ * The string manager for this package.
+ */
+ protected static StringManager sm = StringManager.getManager(Constants.Package);
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * The descriptive information about this implementation.
+ */
+ private static final String info = "DeltaManager/2.1";
+
+ /**
+ * Has this component been started yet?
+ */
+ private boolean started = false;
+
+ /**
+ * The descriptive name of this Manager implementation (for logging).
+ */
+ protected static String managerName = "DeltaManager";
+ protected String name = null;
+ protected boolean defaultMode = false;
+ private CatalinaCluster cluster = null;
+
+ /**
+ * cached replication valve cluster container!
+ */
+ private ReplicationValve replicationValve = null ;
+
+ /**
+ * The lifecycle event support for this component.
+ */
+ protected LifecycleSupport lifecycle = new LifecycleSupport(this);
+
+ /**
+ * The maximum number of active Sessions allowed, or -1 for no limit.
+ */
+ private int maxActiveSessions = -1;
+ private boolean expireSessionsOnShutdown = false;
+ private boolean notifyListenersOnReplication = true;
+ private boolean notifySessionListenersOnReplication = true;
+ private boolean stateTransfered = false ;
+ private int stateTransferTimeout = 60;
+ private boolean sendAllSessions = true;
+ private boolean sendClusterDomainOnly = true ;
+ private int sendAllSessionsSize = 1000 ;
+
+ /**
+ * wait time between send session block (default 2 sec)
+ */
+ private int sendAllSessionsWaitTime = 2 * 1000 ;
+ private ArrayList receivedMessageQueue = new ArrayList() ;
+ private boolean receiverQueue = false ;
+ private boolean stateTimestampDrop = true ;
+ private long stateTransferCreateSendTime;
+
+ // ------------------------------------------------------------------ stats attributes
+
+ int rejectedSessions = 0;
+ private long sessionReplaceCounter = 0 ;
+ long processingTime = 0;
+ private long counterReceive_EVT_GET_ALL_SESSIONS = 0 ;
+ private long counterSend_EVT_ALL_SESSION_DATA = 0 ;
+ private long counterReceive_EVT_ALL_SESSION_DATA = 0 ;
+ private long counterReceive_EVT_SESSION_CREATED = 0 ;
+ private long counterReceive_EVT_SESSION_EXPIRED = 0;
+ private long counterReceive_EVT_SESSION_ACCESSED = 0 ;
+ private long counterReceive_EVT_SESSION_DELTA = 0;
+ private long counterSend_EVT_GET_ALL_SESSIONS = 0 ;
+ private long counterSend_EVT_SESSION_CREATED = 0;
+ private long counterSend_EVT_SESSION_DELTA = 0 ;
+ private long counterSend_EVT_SESSION_ACCESSED = 0;
+ private long counterSend_EVT_SESSION_EXPIRED = 0;
+ private int counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0 ;
+ private int counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0 ;
+ private int counterNoStateTransfered = 0 ;
+
+
+ // ------------------------------------------------------------- Constructor
+ public DeltaManager() {
+ super();
+ }
+
+ // ------------------------------------------------------------- Properties
+
+ /**
+ * Return descriptive information about this Manager implementation and the
+ * corresponding version number, in the format
+ * <code><description>/<version></code>.
+ */
+ public String getInfo() {
+ return info;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Return the descriptive short name of this Manager implementation.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return Returns the counterSend_EVT_GET_ALL_SESSIONS.
+ */
+ public long getCounterSend_EVT_GET_ALL_SESSIONS() {
+ return counterSend_EVT_GET_ALL_SESSIONS;
+ }
+
+ /**
+ * @return Returns the counterSend_EVT_SESSION_ACCESSED.
+ */
+ public long getCounterSend_EVT_SESSION_ACCESSED() {
+ return counterSend_EVT_SESSION_ACCESSED;
+ }
+
+ /**
+ * @return Returns the counterSend_EVT_SESSION_CREATED.
+ */
+ public long getCounterSend_EVT_SESSION_CREATED() {
+ return counterSend_EVT_SESSION_CREATED;
+ }
+
+ /**
+ * @return Returns the counterSend_EVT_SESSION_DELTA.
+ */
+ public long getCounterSend_EVT_SESSION_DELTA() {
+ return counterSend_EVT_SESSION_DELTA;
+ }
+
+ /**
+ * @return Returns the counterSend_EVT_SESSION_EXPIRED.
+ */
+ public long getCounterSend_EVT_SESSION_EXPIRED() {
+ return counterSend_EVT_SESSION_EXPIRED;
+ }
+
+ /**
+ * @return Returns the counterSend_EVT_ALL_SESSION_DATA.
+ */
+ public long getCounterSend_EVT_ALL_SESSION_DATA() {
+ return counterSend_EVT_ALL_SESSION_DATA;
+ }
+
+ /**
+ * @return Returns the counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE.
+ */
+ public int getCounterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE() {
+ return counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE;
+ }
+
+ /**
+ * @return Returns the counterReceive_EVT_ALL_SESSION_DATA.
+ */
+ public long getCounterReceive_EVT_ALL_SESSION_DATA() {
+ return counterReceive_EVT_ALL_SESSION_DATA;
+ }
+
+ /**
+ * @return Returns the counterReceive_EVT_GET_ALL_SESSIONS.
+ */
+ public long getCounterReceive_EVT_GET_ALL_SESSIONS() {
+ return counterReceive_EVT_GET_ALL_SESSIONS;
+ }
+
+ /**
+ * @return Returns the counterReceive_EVT_SESSION_ACCESSED.
+ */
+ public long getCounterReceive_EVT_SESSION_ACCESSED() {
+ return counterReceive_EVT_SESSION_ACCESSED;
+ }
+
+ /**
+ * @return Returns the counterReceive_EVT_SESSION_CREATED.
+ */
+ public long getCounterReceive_EVT_SESSION_CREATED() {
+ return counterReceive_EVT_SESSION_CREATED;
+ }
+
+ /**
+ * @return Returns the counterReceive_EVT_SESSION_DELTA.
+ */
+ public long getCounterReceive_EVT_SESSION_DELTA() {
+ return counterReceive_EVT_SESSION_DELTA;
+ }
+
+ /**
+ * @return Returns the counterReceive_EVT_SESSION_EXPIRED.
+ */
+ public long getCounterReceive_EVT_SESSION_EXPIRED() {
+ return counterReceive_EVT_SESSION_EXPIRED;
+ }
+
+
+ /**
+ * @return Returns the counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE.
+ */
+ public int getCounterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE() {
+ return counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE;
+ }
+
+ /**
+ * @return Returns the processingTime.
+ */
+ public long getProcessingTime() {
+ return processingTime;
+ }
+
+ /**
+ * @return Returns the sessionReplaceCounter.
+ */
+ public long getSessionReplaceCounter() {
+ return sessionReplaceCounter;
+ }
+
+ /**
+ * Number of session creations that failed due to maxActiveSessions
+ *
+ * @return The count
+ */
+ public int getRejectedSessions() {
+ return rejectedSessions;
+ }
+
+ public void setRejectedSessions(int rejectedSessions) {
+ this.rejectedSessions = rejectedSessions;
+ }
+
+ /**
+ * @return Returns the counterNoStateTransfered.
+ */
+ public int getCounterNoStateTransfered() {
+ return counterNoStateTransfered;
+ }
+
+ public int getReceivedQueueSize() {
+ return receivedMessageQueue.size() ;
+ }
+
+ /**
+ * @return Returns the stateTransferTimeout.
+ */
+ public int getStateTransferTimeout() {
+ return stateTransferTimeout;
+ }
+ /**
+ * @param timeoutAllSession The timeout
+ */
+ public void setStateTransferTimeout(int timeoutAllSession) {
+ this.stateTransferTimeout = timeoutAllSession;
+ }
+
+ /**
+ * is session state transfered complete?
+ *
+ */
+ public boolean getStateTransfered() {
+ return stateTransfered;
+ }
+
+ /**
+ * set that state ist complete transfered
+ * @param stateTransfered
+ */
+ public void setStateTransfered(boolean stateTransfered) {
+ this.stateTransfered = stateTransfered;
+ }
+
+ /**
+ * @return Returns the sendAllSessionsWaitTime in msec
+ */
+ public int getSendAllSessionsWaitTime() {
+ return sendAllSessionsWaitTime;
+ }
+
+ /**
+ * @param sendAllSessionsWaitTime The sendAllSessionsWaitTime to set at msec.
+ */
+ public void setSendAllSessionsWaitTime(int sendAllSessionsWaitTime) {
+ this.sendAllSessionsWaitTime = sendAllSessionsWaitTime;
+ }
+
+ /**
+ * @return Returns the sendClusterDomainOnly.
+ */
+ public boolean isSendClusterDomainOnly() {
+ return sendClusterDomainOnly;
+ }
+
+ /**
+ * @param sendClusterDomainOnly The sendClusterDomainOnly to set.
+ */
+ public void setSendClusterDomainOnly(boolean sendClusterDomainOnly) {
+ this.sendClusterDomainOnly = sendClusterDomainOnly;
+ }
+
+ /**
+ * @return Returns the stateTimestampDrop.
+ */
+ public boolean isStateTimestampDrop() {
+ return stateTimestampDrop;
+ }
+
+ /**
+ * @param isTimestampDrop The new flag value
+ */
+ public void setStateTimestampDrop(boolean isTimestampDrop) {
+ this.stateTimestampDrop = isTimestampDrop;
+ }
+
+ /**
+ * Return the maximum number of active Sessions allowed, or -1 for no limit.
+ */
+ public int getMaxActiveSessions() {
+ return (this.maxActiveSessions);
+ }
+
+ /**
+ * Set the maximum number of actives Sessions allowed, or -1 for no limit.
+ *
+ * @param max
+ * The new maximum number of sessions
+ */
+ public void setMaxActiveSessions(int max) {
+ int oldMaxActiveSessions = this.maxActiveSessions;
+ this.maxActiveSessions = max;
+ support.firePropertyChange("maxActiveSessions", new Integer(oldMaxActiveSessions), new Integer(this.maxActiveSessions));
+ }
+
+ /**
+ *
+ * @return Returns the sendAllSessions.
+ */
+ public boolean isSendAllSessions() {
+ return sendAllSessions;
+ }
+
+ /**
+ * @param sendAllSessions The sendAllSessions to set.
+ */
+ public void setSendAllSessions(boolean sendAllSessions) {
+ this.sendAllSessions = sendAllSessions;
+ }
+
+ /**
+ * @return Returns the sendAllSessionsSize.
+ */
+ public int getSendAllSessionsSize() {
+ return sendAllSessionsSize;
+ }
+
+ /**
+ * @param sendAllSessionsSize The sendAllSessionsSize to set.
+ */
+ public void setSendAllSessionsSize(int sendAllSessionsSize) {
+ this.sendAllSessionsSize = sendAllSessionsSize;
+ }
+
+ /**
+ * @return Returns the notifySessionListenersOnReplication.
+ */
+ public boolean isNotifySessionListenersOnReplication() {
+ return notifySessionListenersOnReplication;
+ }
+
+ /**
+ * @param notifyListenersCreateSessionOnReplication The notifySessionListenersOnReplication to set.
+ */
+ public void setNotifySessionListenersOnReplication(boolean notifyListenersCreateSessionOnReplication) {
+ this.notifySessionListenersOnReplication = notifyListenersCreateSessionOnReplication;
+ }
+
+
+ public boolean isExpireSessionsOnShutdown() {
+ return expireSessionsOnShutdown;
+ }
+
+ public void setExpireSessionsOnShutdown(boolean expireSessionsOnShutdown) {
+ this.expireSessionsOnShutdown = expireSessionsOnShutdown;
+ }
+
+ public boolean isNotifyListenersOnReplication() {
+ return notifyListenersOnReplication;
+ }
+
+ public void setNotifyListenersOnReplication(boolean notifyListenersOnReplication) {
+ this.notifyListenersOnReplication = notifyListenersOnReplication;
+ }
+
+
+ /**
+ * @return Returns the defaultMode.
+ */
+ public boolean isDefaultMode() {
+ return defaultMode;
+ }
+ /**
+ * @param defaultMode The defaultMode to set.
+ */
+ public void setDefaultMode(boolean defaultMode) {
+ this.defaultMode = defaultMode;
+ }
+
+ public CatalinaCluster getCluster() {
+ return cluster;
+ }
+
+ public void setCluster(CatalinaCluster cluster) {
+ this.cluster = cluster;
+ }
+
+ /**
+ * Set the Container with which this Manager has been associated. If it is a
+ * Context (the usual case), listen for changes to the session timeout
+ * property.
+ *
+ * @param container
+ * The associated Container
+ */
+ public void setContainer(Container container) {
+ // De-register from the old Container (if any)
+ if ((this.container != null) && (this.container instanceof Context))
+ ((Context) this.container).removePropertyChangeListener(this);
+
+ // Default processing provided by our superclass
+ super.setContainer(container);
+
+ // Register with the new Container (if any)
+ if ((this.container != null) && (this.container instanceof Context)) {
+ setMaxInactiveInterval(((Context) this.container).getSessionTimeout() * 60);
+ ((Context) this.container).addPropertyChangeListener(this);
+ }
+
+ }
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * Construct and return a new session object, based on the default settings
+ * specified by this Manager's properties. The session id will be assigned
+ * by this method, and available via the getId() method of the returned
+ * session. If a new session cannot be created for any reason, return
+ * <code>null</code>.
+ *
+ * @exception IllegalStateException
+ * if a new session cannot be instantiated for any reason
+ *
+ * Construct and return a new session object, based on the default settings
+ * specified by this Manager's properties. The session id will be assigned
+ * by this method, and available via the getId() method of the returned
+ * session. If a new session cannot be created for any reason, return
+ * <code>null</code>.
+ *
+ * @exception IllegalStateException
+ * if a new session cannot be instantiated for any reason
+ */
+ public Session createSession(String sessionId) {
+ return createSession(sessionId, true);
+ }
+
+ /**
+ * create new session with check maxActiveSessions and send session creation
+ * to other cluster nodes.
+ *
+ * @param distribute
+ * @return The session
+ */
+ public Session createSession(String sessionId, boolean distribute) {
+ if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) {
+ rejectedSessions++;
+ throw new IllegalStateException(sm.getString("deltaManager.createSession.ise"));
+ }
+ DeltaSession session = (DeltaSession) super.createSession(sessionId) ;
+ if (distribute) {
+ sendCreateSession(session.getId(), session);
+ }
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("deltaManager.createSession.newSession",session.getId(), new Integer(sessions.size())));
+ return (session);
+
+ }
+
+ /**
+ * Send create session evt to all backup node
+ * @param sessionId
+ * @param session
+ */
+ protected void sendCreateSession(String sessionId, DeltaSession session) {
+ if(cluster.getMembers().length > 0 ) {
+ SessionMessage msg =
+ new SessionMessageImpl(getName(),
+ SessionMessage.EVT_SESSION_CREATED,
+ null,
+ sessionId,
+ sessionId + "-" + System.currentTimeMillis());
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.sendMessage.newSession",name, sessionId));
+ msg.setTimestamp(session.getCreationTime());
+ counterSend_EVT_SESSION_CREATED++;
+ send(msg);
+ }
+ }
+
+ /**
+ * Send messages to other backup member (domain or all)
+ * @param msg Session message
+ */
+ protected void send(SessionMessage msg) {
+ if(cluster != null) {
+ if(isSendClusterDomainOnly())
+ cluster.sendClusterDomain(msg);
+ else
+ cluster.send(msg);
+ }
+ }
+
+ /**
+ * Create DeltaSession
+ * @see org.apache.catalina.Manager#createEmptySession()
+ */
+ public Session createEmptySession() {
+ return getNewDeltaSession() ;
+ }
+
+ /**
+ * Get new session class to be used in the doLoad() method.
+ */
+ protected DeltaSession getNewDeltaSession() {
+ return new DeltaSession(this);
+ }
+
+ /**
+ * Load Deltarequest from external node
+ * Load the Class at container classloader
+ * @see DeltaRequest#readExternal(java.io.ObjectInput)
+ * @param session
+ * @param data message data
+ * @return The request
+ * @throws ClassNotFoundException
+ * @throws IOException
+ */
+ protected DeltaRequest deserializeDeltaRequest(DeltaSession session, byte[] data) throws ClassNotFoundException, IOException {
+ ReplicationStream ois = getReplicationStream(data);
+ session.getDeltaRequest().readExternal(ois);
+ ois.close();
+ return session.getDeltaRequest();
+ }
+
+ /**
+ * serialize DeltaRequest
+ * @see DeltaRequest#writeExternal(java.io.ObjectOutput)
+ *
+ * @param deltaRequest
+ * @return serialized delta request
+ * @throws IOException
+ */
+ protected byte[] serializeDeltaRequest(DeltaRequest deltaRequest) throws IOException {
+ return deltaRequest.serialize();
+ }
+
+ /**
+ * Load sessions from other cluster node.
+ * FIXME replace currently sessions with same id without notifcation.
+ * FIXME SSO handling is not really correct with the session replacement!
+ * @exception ClassNotFoundException
+ * if a serialized class cannot be found during the reload
+ * @exception IOException
+ * if an input/output error occurs
+ */
+ protected void deserializeSessions(byte[] data) throws ClassNotFoundException,IOException {
+
+ // Initialize our internal data structures
+ //sessions.clear(); //should not do this
+ // Open an input stream to the specified pathname, if any
+ ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
+ ObjectInputStream ois = null;
+ // Load the previously unloaded active sessions
+ try {
+ ois = getReplicationStream(data);
+ Integer count = (Integer) ois.readObject();
+ int n = count.intValue();
+ for (int i = 0; i < n; i++) {
+ DeltaSession session = (DeltaSession) createEmptySession();
+ session.readObjectData(ois);
+ session.setManager(this);
+ session.setValid(true);
+ session.setPrimarySession(false);
+ //in case the nodes in the cluster are out of
+ //time synch, this will make sure that we have the
+ //correct timestamp, isValid returns true, cause
+ // accessCount=1
+ session.access();
+ //make sure that the session gets ready to expire if
+ // needed
+ session.setAccessCount(0);
+ session.resetDeltaRequest();
+ // FIXME How inform other session id cache like SingleSignOn
+ // increment sessionCounter to correct stats report
+ if (findSession(session.getIdInternal()) == null ) {
+ sessionCounter++;
+ } else {
+ sessionReplaceCounter++;
+ // FIXME better is to grap this sessions again !
+ if (log.isWarnEnabled()) log.warn(sm.getString("deltaManager.loading.existing.session",session.getIdInternal()));
+ }
+ add(session);
+ }
+ } catch (ClassNotFoundException e) {
+ log.error(sm.getString("deltaManager.loading.cnfe", e), e);
+ throw e;
+ } catch (IOException e) {
+ log.error(sm.getString("deltaManager.loading.ioe", e), e);
+ throw e;
+ } finally {
+ // Close the input stream
+ try {
+ if (ois != null) ois.close();
+ } catch (IOException f) {
+ // ignored
+ }
+ ois = null;
+ if (originalLoader != null) Thread.currentThread().setContextClassLoader(originalLoader);
+ }
+
+ }
+
+
+
+ /**
+ * Save any currently active sessions in the appropriate persistence
+ * mechanism, if any. If persistence is not supported, this method returns
+ * without doing anything.
+ *
+ * @exception IOException
+ * if an input/output error occurs
+ */
+ protected byte[] serializeSessions(Session[] currentSessions) throws IOException {
+
+ // Open an output stream to the specified pathname, if any
+ ByteArrayOutputStream fos = null;
+ ObjectOutputStream oos = null;
+
+ try {
+ fos = new ByteArrayOutputStream();
+ oos = new ObjectOutputStream(new BufferedOutputStream(fos));
+ oos.writeObject(new Integer(currentSessions.length));
+ for(int i=0 ; i < currentSessions.length;i++) {
+ ((DeltaSession)currentSessions[i]).writeObjectData(oos);
+ }
+ // Flush and close the output stream
+ oos.flush();
+ } catch (IOException e) {
+ log.error(sm.getString("deltaManager.unloading.ioe", e), e);
+ throw e;
+ } finally {
+ if (oos != null) {
+ try {
+ oos.close();
+ } catch (IOException f) {
+ ;
+ }
+ oos = null;
+ }
+ }
+ // send object data as byte[]
+ return fos.toByteArray();
+ }
+
+ // ------------------------------------------------------ Lifecycle Methods
+
+ /**
+ * Add a lifecycle event listener to this component.
+ *
+ * @param listener
+ * The listener to add
+ */
+ public void addLifecycleListener(LifecycleListener listener) {
+ lifecycle.addLifecycleListener(listener);
+ }
+
+ /**
+ * Get the lifecycle listeners associated with this lifecycle. If this
+ * Lifecycle has no listeners registered, a zero-length array is returned.
+ */
+ public LifecycleListener[] findLifecycleListeners() {
+ return lifecycle.findLifecycleListeners();
+ }
+
+ /**
+ * Remove a lifecycle event listener from this component.
+ *
+ * @param listener
+ * The listener to remove
+ */
+ public void removeLifecycleListener(LifecycleListener listener) {
+ lifecycle.removeLifecycleListener(listener);
+ }
+
+ /**
+ * Prepare for the beginning of active use of the public methods of this
+ * component. This method should be called after <code>configure()</code>,
+ * and before any of the public methods of the component are utilized.
+ *
+ * @exception LifecycleException
+ * if this component detects a fatal error that prevents this
+ * component from being used
+ */
+ public void start() throws LifecycleException {
+ if (!initialized) init();
+
+ // Validate and update our current component state
+ if (started) {
+ return;
+ }
+ started = true;
+ lifecycle.fireLifecycleEvent(START_EVENT, null);
+
+ // Force initialization of the random number generator
+ generateSessionId();
+
+ // Load unloaded sessions, if any
+ try {
+ //the channel is already running
+ Cluster cluster = getCluster() ;
+ // stop remove cluster binding
+ //wow, how many nested levels of if statements can we have ;)
+ if(cluster == null) {
+ Container context = getContainer() ;
+ if(context != null && context instanceof Context) {
+ Container host = context.getParent() ;
+ if(host != null && host instanceof Host) {
+ cluster = host.getCluster();
+ if(cluster != null && cluster instanceof CatalinaCluster) {
+ setCluster((CatalinaCluster) cluster) ;
+ } else {
+ Container engine = host.getParent() ;
+ if(engine != null && engine instanceof Engine) {
+ cluster = engine.getCluster();
+ if(cluster != null && cluster instanceof CatalinaCluster) {
+ setCluster((CatalinaCluster) cluster) ;
+ }
+ } else {
+ cluster = null ;
+ }
+ }
+ }
+ }
+ }
+ if (cluster == null) {
+ log.error(sm.getString("deltaManager.noCluster", getName()));
+ return;
+ } else {
+ if (log.isInfoEnabled()) {
+ String type = "unknown" ;
+ if( cluster.getContainer() instanceof Host){
+ type = "Host" ;
+ } else if( cluster.getContainer() instanceof Engine){
+ type = "Engine" ;
+ }
+ log.info(sm.getString("deltaManager.registerCluster", getName(), type, cluster.getClusterName()));
+ }
+ }
+ if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.startClustering", getName()));
+ //to survice context reloads, as only a stop/start is called, not
+ // createManager
+ ((CatalinaCluster)cluster).addManager(getName(), this);
+
+ getAllClusterSessions();
+
+ } catch (Throwable t) {
+ log.error(sm.getString("deltaManager.managerLoad"), t);
+ }
+ }
+
+ /**
+ * get from first session master the backup from all clustered sessions
+ * @see #findSessionMasterMember()
+ */
+ public synchronized void getAllClusterSessions() {
+ if (cluster != null && cluster.getMembers().length > 0) {
+ long beforeSendTime = System.currentTimeMillis();
+ Member mbr = findSessionMasterMember();
+ if(mbr == null) { // No domain member found
+ return;
+ }
+ SessionMessage msg = new SessionMessageImpl(this.getName(),SessionMessage.EVT_GET_ALL_SESSIONS, null, "GET-ALL","GET-ALL-" + getName());
+ // set reference time
+ stateTransferCreateSendTime = beforeSendTime ;
+ // request session state
+ counterSend_EVT_GET_ALL_SESSIONS++;
+ stateTransfered = false ;
+ // FIXME This send call block the deploy thread, when sender waitForAck is enabled
+ try {
+ synchronized(receivedMessageQueue) {
+ receiverQueue = true ;
+ }
+ cluster.send(msg, mbr);
+ if (log.isWarnEnabled()) log.warn(sm.getString("deltaManager.waitForSessionState",getName(), mbr));
+ // FIXME At sender ack mode this method check only the state transfer and resend is a problem!
+ waitForSendAllSessions(beforeSendTime);
+ } finally {
+ synchronized(receivedMessageQueue) {
+ for (Iterator iter = receivedMessageQueue.iterator(); iter.hasNext();) {
+ SessionMessage smsg = (SessionMessage) iter.next();
+ if (!stateTimestampDrop) {
+ messageReceived(smsg, smsg.getAddress() != null ? (Member) smsg.getAddress() : null);
+ } else {
+ if (smsg.getEventType() != SessionMessage.EVT_GET_ALL_SESSIONS && smsg.getTimestamp() >= stateTransferCreateSendTime) {
+ // FIXME handle EVT_GET_ALL_SESSIONS later
+ messageReceived(smsg,smsg.getAddress() != null ? (Member) smsg.getAddress() : null);
+ } else {
+ if (log.isWarnEnabled()) {
+ log.warn(sm.getString("deltaManager.dropMessage",getName(), smsg.getEventTypeString(),new Date(stateTransferCreateSendTime), new Date(smsg.getTimestamp())));
+ }
+ }
+ }
+ }
+ receivedMessageQueue.clear();
+ receiverQueue = false ;
+ }
+ }
+ } else {
+ if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.noMembers", getName()));
+ }
+ }
+
+ /**
+ * Register cross context session at replication valve thread local
+ * @param session cross context session
+ */
+ protected void registerSessionAtReplicationValve(DeltaSession session) {
+ if(replicationValve == null) {
+ if(container instanceof StandardContext && ((StandardContext)container).getCrossContext()) {
+ Cluster cluster = getCluster() ;
+ if(cluster != null && cluster instanceof CatalinaCluster) {
+ Valve[] valves = ((CatalinaCluster)cluster).getValves();
+ if(valves != null && valves.length > 0) {
+ for(int i=0; replicationValve == null && i < valves.length ; i++ ){
+ if(valves[i] instanceof ReplicationValve) replicationValve = (ReplicationValve)valves[i] ;
+ }//for
+
+ if(replicationValve == null && log.isDebugEnabled()) {
+ log.debug("no ReplicationValve found for CrossContext Support");
+ }//endif
+ }//end if
+ }//endif
+ }//end if
+ }//end if
+ if(replicationValve != null) {
+ replicationValve.registerReplicationSession(session);
+ }
+ }
+
+ /**
+ * Find the master of the session state
+ * @return master member of sessions
+ */
+ protected Member findSessionMasterMember() {
+ Member mbr = null;
+ Member mbrs[] = cluster.getMembers();
+ if(mbrs.length != 0 ) mbr = mbrs[0];
+ if(mbr == null && log.isWarnEnabled()) log.warn(sm.getString("deltaManager.noMasterMember",getName(), ""));
+ if(mbr != null && log.isDebugEnabled()) log.warn(sm.getString("deltaManager.foundMasterMember",getName(), mbr));
+ return mbr;
+ }
+
+ /**
+ * Wait that cluster session state is transfer or timeout after 60 Sec
+ * With stateTransferTimeout == -1 wait that backup is transfered (forever mode)
+ */
+ protected void waitForSendAllSessions(long beforeSendTime) {
+ long reqStart = System.currentTimeMillis();
+ long reqNow = reqStart ;
+ boolean isTimeout = false;
+ if(getStateTransferTimeout() > 0) {
+ // wait that state is transfered with timeout check
+ do {
+ try {
+ Thread.sleep(100);
+ } catch (Exception sleep) {
+ //
+ }
+ reqNow = System.currentTimeMillis();
+ isTimeout = ((reqNow - reqStart) > (1000 * getStateTransferTimeout()));
+ } while ((!getStateTransfered()) && (!isTimeout));
+ } else {
+ if(getStateTransferTimeout() == -1) {
+ // wait that state is transfered
+ do {
+ try {
+ Thread.sleep(100);
+ } catch (Exception sleep) {
+ }
+ } while ((!getStateTransfered()));
+ reqNow = System.currentTimeMillis();
+ }
+ }
+ if (isTimeout || (!getStateTransfered())) {
+ counterNoStateTransfered++ ;
+ log.error(sm.getString("deltaManager.noSessionState",getName(),new Date(beforeSendTime),new Long(reqNow - beforeSendTime)));
+ } else {
+ if (log.isInfoEnabled())
+ log.info(sm.getString("deltaManager.sessionReceived",getName(), new Date(beforeSendTime), new Long(reqNow - beforeSendTime)));
+ }
+ }
+
+ /**
+ * Gracefully terminate the active use of the public methods of this
+ * component. This method should be the last one called on a given instance
+ * of this component.
+ *
+ * @exception LifecycleException
+ * if this component detects a fatal error that needs to be
+ * reported
+ */
+ public void stop() throws LifecycleException {
+
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("deltaManager.stopped", getName()));
+
+
+ // Validate and update our current component state
+ if (!started)
+ throw new LifecycleException(sm.getString("deltaManager.notStarted"));
+ lifecycle.fireLifecycleEvent(STOP_EVENT, null);
+ started = false;
+
+ // Expire all active sessions
+ if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.expireSessions", getName()));
+ Session sessions[] = findSessions();
+ for (int i = 0; i < sessions.length; i++) {
+ DeltaSession session = (DeltaSession) sessions[i];
+ if (!session.isValid())
+ continue;
+ try {
+ session.expire(true, isExpireSessionsOnShutdown());
+ } catch (Throwable ignore) {
+ ;
+ }
+ }
+
+ // Require a new random number generator if we are restarted
+ this.random = null;
+ getCluster().removeManager(getName(),this);
+ replicationValve = null;
+ if (initialized) {
+ destroy();
+ }
+ }
+
+ // ----------------------------------------- PropertyChangeListener Methods
+
+ /**
+ * Process property change events from our associated Context.
+ *
+ * @param event
+ * The property change event that has occurred
+ */
+ public void propertyChange(PropertyChangeEvent event) {
+
+ // Validate the source of this event
+ if (!(event.getSource() instanceof Context))
+ return;
+ // Process a relevant property change
+ if (event.getPropertyName().equals("sessionTimeout")) {
+ try {
+ setMaxInactiveInterval(((Integer) event.getNewValue()).intValue() * 60);
+ } catch (NumberFormatException e) {
+ log.error(sm.getString("deltaManager.sessionTimeout", event.getNewValue()));
+ }
+ }
+
+ }
+
+ // -------------------------------------------------------- Replication
+ // Methods
+
+ /**
+ * A message was received from another node, this is the callback method to
+ * implement if you are interested in receiving replication messages.
+ *
+ * @param cmsg -
+ * the message received.
+ */
+ public void messageDataReceived(ClusterMessage cmsg) {
+ if (cmsg != null && cmsg instanceof SessionMessage) {
+ SessionMessage msg = (SessionMessage) cmsg;
+ switch (msg.getEventType()) {
+ case SessionMessage.EVT_GET_ALL_SESSIONS:
+ case SessionMessage.EVT_SESSION_CREATED:
+ case SessionMessage.EVT_SESSION_EXPIRED:
+ case SessionMessage.EVT_SESSION_ACCESSED:
+ case SessionMessage.EVT_SESSION_DELTA: {
+ synchronized(receivedMessageQueue) {
+ if(receiverQueue) {
+ receivedMessageQueue.add(msg);
+ return ;
+ }
+ }
+ break;
+ }
+ default: {
+ //we didn't queue, do nothing
+ break;
+ }
+ } //switch
+
+ messageReceived(msg, msg.getAddress() != null ? (Member) msg.getAddress() : null);
+ }
+ }
+
+ /**
+ * When the request has been completed, the replication valve will notify
+ * the manager, and the manager will decide whether any replication is
+ * needed or not. If there is a need for replication, the manager will
+ * create a session message and that will be replicated. The cluster
+ * determines where it gets sent.
+ *
+ * @param sessionId -
+ * the sessionId that just completed.
+ * @return a SessionMessage to be sent,
+ */
+ public ClusterMessage requestCompleted(String sessionId) {
+ try {
+ DeltaSession session = (DeltaSession) findSession(sessionId);
+ DeltaRequest deltaRequest = session.getDeltaRequest();
+ SessionMessage msg = null;
+ boolean isDeltaRequest = false ;
+ synchronized(deltaRequest) {
+ isDeltaRequest = deltaRequest.getSize() > 0 ;
+ if (isDeltaRequest) {
+ counterSend_EVT_SESSION_DELTA++;
+ byte[] data = serializeDeltaRequest(deltaRequest);
+ msg = new SessionMessageImpl(getName(),
+ SessionMessage.EVT_SESSION_DELTA,
+ data,
+ sessionId,
+ sessionId + "-" + System.currentTimeMillis());
+ session.resetDeltaRequest();
+ }
+ }
+ if(!isDeltaRequest) {
+ if(!session.isPrimarySession()) {
+ counterSend_EVT_SESSION_ACCESSED++;
+ msg = new SessionMessageImpl(getName(),
+ SessionMessage.EVT_SESSION_ACCESSED,
+ null,
+ sessionId,
+ sessionId + "-" + System.currentTimeMillis());
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("deltaManager.createMessage.accessChangePrimary",getName(), sessionId));
+ }
+ }
+ } else { // log only outside synch block!
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("deltaManager.createMessage.delta",getName(), sessionId));
+ }
+ }
+ session.setPrimarySession(true);
+ //check to see if we need to send out an access message
+ if ((msg == null)) {
+ long replDelta = System.currentTimeMillis() - session.getLastTimeReplicated();
+ if (replDelta > (getMaxInactiveInterval() * 1000)) {
+ counterSend_EVT_SESSION_ACCESSED++;
+ msg = new SessionMessageImpl(getName(),
+ SessionMessage.EVT_SESSION_ACCESSED,
+ null,
+ sessionId,
+ sessionId + "-" + System.currentTimeMillis());
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("deltaManager.createMessage.access", getName(),sessionId));
+ }
+ }
+
+ }
+
+ //update last replicated time
+ if (msg != null) session.setLastTimeReplicated(System.currentTimeMillis());
+ return msg;
+ } catch (IOException x) {
+ log.error(sm.getString("deltaManager.createMessage.unableCreateDeltaRequest",sessionId), x);
+ return null;
+ }
+
+ }
+ /**
+ * Reset manager statistics
+ */
+ public synchronized void resetStatistics() {
+ processingTime = 0 ;
+ expiredSessions = 0 ;
+ rejectedSessions = 0 ;
+ sessionReplaceCounter = 0 ;
+ counterNoStateTransfered = 0 ;
+ maxActive = getActiveSessions() ;
+ sessionCounter = getActiveSessions() ;
+ counterReceive_EVT_ALL_SESSION_DATA = 0;
+ counterReceive_EVT_GET_ALL_SESSIONS = 0;
+ counterReceive_EVT_SESSION_ACCESSED = 0 ;
+ counterReceive_EVT_SESSION_CREATED = 0 ;
+ counterReceive_EVT_SESSION_DELTA = 0 ;
+ counterReceive_EVT_SESSION_EXPIRED = 0 ;
+ counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0;
+ counterSend_EVT_ALL_SESSION_DATA = 0;
+ counterSend_EVT_GET_ALL_SESSIONS = 0;
+ counterSend_EVT_SESSION_ACCESSED = 0 ;
+ counterSend_EVT_SESSION_CREATED = 0 ;
+ counterSend_EVT_SESSION_DELTA = 0 ;
+ counterSend_EVT_SESSION_EXPIRED = 0 ;
+ counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0;
+
+ }
+
+ // -------------------------------------------------------- persistence handler
+
+ public void load() {
+
+ }
+
+ public void unload() {
+
+ }
+
+ // -------------------------------------------------------- expire
+
+ /**
+ * send session expired to other cluster nodes
+ *
+ * @param id
+ * session id
+ */
+ protected void sessionExpired(String id) {
+ counterSend_EVT_SESSION_EXPIRED++ ;
+ SessionMessage msg = new SessionMessageImpl(getName(),SessionMessage.EVT_SESSION_EXPIRED, null, id, id+ "-EXPIRED-MSG");
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.createMessage.expire",getName(), id));
+ send(msg);
+ }
+
+ /**
+ * Exipre all find sessions.
+ */
+ public void expireAllLocalSessions()
+ {
+ long timeNow = System.currentTimeMillis();
+ Session sessions[] = findSessions();
+ int expireDirect = 0 ;
+ int expireIndirect = 0 ;
+
+ if(log.isDebugEnabled()) log.debug("Start expire all sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
+ for (int i = 0; i < sessions.length; i++) {
+ if (sessions[i] instanceof DeltaSession) {
+ DeltaSession session = (DeltaSession) sessions[i];
+ if (session.isPrimarySession()) {
+ if (session.isValid()) {
+ session.expire();
+ expireDirect++;
+ } else {
+ expireIndirect++;
+ }//end if
+ }//end if
+ }//end if
+ }//for
+ long timeEnd = System.currentTimeMillis();
+ if(log.isDebugEnabled()) log.debug("End expire sessions " + getName() + " exipre processingTime " + (timeEnd - timeNow) + " expired direct sessions: " + expireDirect + " expired direct sessions: " + expireIndirect);
+
+ }
+
+ /**
+ * When the manager expires session not tied to a request. The cluster will
+ * periodically ask for a list of sessions that should expire and that
+ * should be sent across the wire.
+ *
+ * @return The invalidated sessions array
+ */
+ public String[] getInvalidatedSessions() {
+ return new String[0];
+ }
+
+ // -------------------------------------------------------- message receive
+
+ /**
+ * Test that sender and local domain is the same
+ */
+ protected boolean checkSenderDomain(SessionMessage msg,Member sender) {
+ boolean sameDomain= true;
+ if (!sameDomain && log.isWarnEnabled()) {
+ log.warn(sm.getString("deltaManager.receiveMessage.fromWrongDomain",
+ new Object[] {getName(),
+ msg.getEventTypeString(),
+ sender,
+ "",
+ "" }));
+ }
+ return sameDomain ;
+ }
+
+ /**
+ * This method is called by the received thread when a SessionMessage has
+ * been received from one of the other nodes in the cluster.
+ *
+ * @param msg -
+ * the message received
+ * @param sender -
+ * the sender of the message, this is used if we receive a
+ * EVT_GET_ALL_SESSION message, so that we only reply to the
+ * requesting node
+ */
+ protected void messageReceived(SessionMessage msg, Member sender) {
+ if(isSendClusterDomainOnly() && !checkSenderDomain(msg,sender)) {
+ return;
+ }
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ try {
+
+ ClassLoader[] loaders = getClassLoaders();
+ if ( loaders != null && loaders.length > 0) Thread.currentThread().setContextClassLoader(loaders[0]);
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.eventType",getName(), msg.getEventTypeString(), sender));
+
+ switch (msg.getEventType()) {
+ case SessionMessage.EVT_GET_ALL_SESSIONS: {
+ handleGET_ALL_SESSIONS(msg,sender);
+ break;
+ }
+ case SessionMessage.EVT_ALL_SESSION_DATA: {
+ handleALL_SESSION_DATA(msg,sender);
+ break;
+ }
+ case SessionMessage.EVT_ALL_SESSION_TRANSFERCOMPLETE: {
+ handleALL_SESSION_TRANSFERCOMPLETE(msg,sender);
+ break;
+ }
+ case SessionMessage.EVT_SESSION_CREATED: {
+ handleSESSION_CREATED(msg,sender);
+ break;
+ }
+ case SessionMessage.EVT_SESSION_EXPIRED: {
+ handleSESSION_EXPIRED(msg,sender);
+ break;
+ }
+ case SessionMessage.EVT_SESSION_ACCESSED: {
+ handleSESSION_ACCESSED(msg,sender);
+ break;
+ }
+ case SessionMessage.EVT_SESSION_DELTA: {
+ handleSESSION_DELTA(msg,sender);
+ break;
+ }
+ default: {
+ //we didn't recognize the message type, do nothing
+ break;
+ }
+ } //switch
+ } catch (Exception x) {
+ log.error(sm.getString("deltaManager.receiveMessage.error",getName()), x);
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextLoader);
+ }
+ }
+
+ // -------------------------------------------------------- message receiver handler
+
+
+ /**
+ * handle receive session state is complete transfered
+ * @param msg
+ * @param sender
+ */
+ protected void handleALL_SESSION_TRANSFERCOMPLETE(SessionMessage msg, Member sender) {
+ counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE++ ;
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.transfercomplete",getName(), sender.getHost(), new Integer(sender.getPort())));
+ stateTransferCreateSendTime = msg.getTimestamp() ;
+ stateTransfered = true ;
+ }
+
+ /**
+ * handle receive session delta
+ * @param msg
+ * @param sender
+ * @throws IOException
+ * @throws ClassNotFoundException
+ */
+ protected void handleSESSION_DELTA(SessionMessage msg, Member sender) throws IOException, ClassNotFoundException {
+ counterReceive_EVT_SESSION_DELTA++;
+ byte[] delta = msg.getSession();
+ DeltaSession session = (DeltaSession) findSession(msg.getSessionID());
+ if (session != null) {
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.delta",getName(), msg.getSessionID()));
+ DeltaRequest dreq = deserializeDeltaRequest(session, delta);
+ dreq.execute(session, notifyListenersOnReplication);
+ session.setPrimarySession(false);
+ }
+ }
+
+ /**
+ * handle receive session is access at other node ( primary session is now false)
+ * @param msg
+ * @param sender
+ * @throws IOException
+ */
+ protected void handleSESSION_ACCESSED(SessionMessage msg,Member sender) throws IOException {
+ counterReceive_EVT_SESSION_ACCESSED++;
+ DeltaSession session = (DeltaSession) findSession(msg.getSessionID());
+ if (session != null) {
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.accessed",getName(), msg.getSessionID()));
+ session.access();
+ session.setPrimarySession(false);
+ session.endAccess();
+ }
+ }
+
+ /**
+ * handle receive session is expire at other node ( expire session also here)
+ * @param msg
+ * @param sender
+ * @throws IOException
+ */
+ protected void handleSESSION_EXPIRED(SessionMessage msg,Member sender) throws IOException {
+ counterReceive_EVT_SESSION_EXPIRED++;
+ DeltaSession session = (DeltaSession) findSession(msg.getSessionID());
+ if (session != null) {
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.expired",getName(), msg.getSessionID()));
+ session.expire(notifySessionListenersOnReplication, false);
+ }
+ }
+
+ /**
+ * handle receive new session is created at other node (create backup - primary false)
+ * @param msg
+ * @param sender
+ */
+ protected void handleSESSION_CREATED(SessionMessage msg,Member sender) {
+ counterReceive_EVT_SESSION_CREATED++;
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.createNewSession",getName(), msg.getSessionID()));
+ DeltaSession session = (DeltaSession) createEmptySession();
+ session.setManager(this);
+ session.setValid(true);
+ session.setPrimarySession(false);
+ session.setCreationTime(msg.getTimestamp());
+ session.access();
+ if(notifySessionListenersOnReplication)
+ session.setId(msg.getSessionID());
+ else
+ session.setIdInternal(msg.getSessionID());
+ session.resetDeltaRequest();
+ session.endAccess();
+
+ }
+
+ /**
+ * handle receive sessions from other not ( restart )
+ * @param msg
+ * @param sender
+ * @throws ClassNotFoundException
+ * @throws IOException
+ */
+ protected void handleALL_SESSION_DATA(SessionMessage msg,Member sender) throws ClassNotFoundException, IOException {
+ counterReceive_EVT_ALL_SESSION_DATA++;
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.allSessionDataBegin",getName()));
+ byte[] data = msg.getSession();
+ deserializeSessions(data);
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.allSessionDataAfter",getName()));
+ //stateTransferred = true;
+ }
+
+ /**
+ * handle receive that other node want all sessions ( restart )
+ * a) send all sessions with one message
+ * b) send session at blocks
+ * After sending send state is complete transfered
+ * @param msg
+ * @param sender
+ * @throws IOException
+ */
+ protected void handleGET_ALL_SESSIONS(SessionMessage msg, Member sender) throws IOException {
+ counterReceive_EVT_GET_ALL_SESSIONS++;
+ //get a list of all the session from this manager
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.unloadingBegin", getName()));
+ // Write the number of active sessions, followed by the details
+ // get all sessions and serialize without sync
+ Session[] currentSessions = findSessions();
+ long findSessionTimestamp = System.currentTimeMillis() ;
+ if (isSendAllSessions()) {
+ sendSessions(sender, currentSessions, findSessionTimestamp);
+ } else {
+ // send session at blocks
+ int len = currentSessions.length < getSendAllSessionsSize() ? currentSessions.length : getSendAllSessionsSize();
+ Session[] sendSessions = new Session[len];
+ for (int i = 0; i < currentSessions.length; i += getSendAllSessionsSize()) {
+ len = i + getSendAllSessionsSize() > currentSessions.length ? currentSessions.length - i : getSendAllSessionsSize();
+ System.arraycopy(currentSessions, i, sendSessions, 0, len);
+ sendSessions(sender, sendSessions,findSessionTimestamp);
+ if (getSendAllSessionsWaitTime() > 0) {
+ try {
+ Thread.sleep(getSendAllSessionsWaitTime());
+ } catch (Exception sleep) {
+ }
+ }//end if
+ }//for
+ }//end if
+
+ SessionMessage newmsg = new SessionMessageImpl(name,SessionMessage.EVT_ALL_SESSION_TRANSFERCOMPLETE, null,"SESSION-STATE-TRANSFERED", "SESSION-STATE-TRANSFERED"+ getName());
+ newmsg.setTimestamp(findSessionTimestamp);
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.createMessage.allSessionTransfered",getName()));
+ counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE++;
+ cluster.send(newmsg, sender);
+ }
+
+
+ /**
+ * send a block of session to sender
+ * @param sender
+ * @param currentSessions
+ * @param sendTimestamp
+ * @throws IOException
+ */
+ protected void sendSessions(Member sender, Session[] currentSessions,long sendTimestamp) throws IOException {
+ byte[] data = serializeSessions(currentSessions);
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.unloadingAfter",getName()));
+ SessionMessage newmsg = new SessionMessageImpl(name,SessionMessage.EVT_ALL_SESSION_DATA, data,"SESSION-STATE", "SESSION-STATE-" + getName());
+ newmsg.setTimestamp(sendTimestamp);
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.createMessage.allSessionData",getName()));
+ counterSend_EVT_ALL_SESSION_DATA++;
+ cluster.send(newmsg, sender);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.session;
+
+/**
+ * This class is used to track the series of actions that happens when
+ * a request is executed. These actions will then translate into invokations of methods
+ * on the actual session.
+ * This class is NOT thread safe. One DeltaRequest per session
+ * @author <a href="mailto:fhanik@apache.org">Filip Hanik</a>
+ * @version 1.0
+ */
+
+import java.io.Externalizable;
+import java.security.Principal;
+import java.util.LinkedList;
+
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.catalina.util.StringManager;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+
+public class DeltaRequest implements Externalizable {
+
+ public static org.apache.commons.logging.Log log =
+ org.apache.commons.logging.LogFactory.getLog( DeltaRequest.class );
+
+ /**
+ * The string manager for this package.
+ */
+ protected static StringManager sm = StringManager
+ .getManager(Constants.Package);
+
+ public static final int TYPE_ATTRIBUTE = 0;
+ public static final int TYPE_PRINCIPAL = 1;
+ public static final int TYPE_ISNEW = 2;
+ public static final int TYPE_MAXINTERVAL = 3;
+
+ public static final int ACTION_SET = 0;
+ public static final int ACTION_REMOVE = 1;
+
+ public static final String NAME_PRINCIPAL = "__SET__PRINCIPAL__";
+ public static final String NAME_MAXINTERVAL = "__SET__MAXINTERVAL__";
+ public static final String NAME_ISNEW = "__SET__ISNEW__";
+
+ private String sessionId;
+ private LinkedList actions = new LinkedList();
+ private LinkedList actionPool = new LinkedList();
+
+ private boolean recordAllActions = false;
+
+ public DeltaRequest() {
+
+ }
+
+ public DeltaRequest(String sessionId, boolean recordAllActions) {
+ this.recordAllActions=recordAllActions;
+ if(sessionId != null)
+ setSessionId(sessionId);
+ }
+
+
+ public void setAttribute(String name, Object value) {
+ int action = (value==null)?ACTION_REMOVE:ACTION_SET;
+ addAction(TYPE_ATTRIBUTE,action,name,value);
+ }
+
+ public void removeAttribute(String name) {
+ int action = ACTION_REMOVE;
+ addAction(TYPE_ATTRIBUTE,action,name,null);
+ }
+
+ public void setMaxInactiveInterval(int interval) {
+ int action = ACTION_SET;
+ addAction(TYPE_MAXINTERVAL,action,NAME_MAXINTERVAL,new Integer(interval));
+ }
+
+ /**
+ * convert principal at SerializablePrincipal for backup nodes.
+ * Only support principals from type {@link GenericPrincipal GenericPrincipal}
+ * @param p Session principal
+ * @see GenericPrincipal
+ */
+ public void setPrincipal(Principal p) {
+ int action = (p==null)?ACTION_REMOVE:ACTION_SET;
+ SerializablePrincipal sp = null;
+ if ( p != null ) {
+ if(p instanceof GenericPrincipal) {
+ sp = SerializablePrincipal.createPrincipal((GenericPrincipal)p);
+ if(log.isDebugEnabled())
+ log.debug(sm.getString("deltaRequest.showPrincipal", p.getName() , getSessionId()));
+ } else
+ log.error(sm.getString("deltaRequest.wrongPrincipalClass",p.getClass().getName()));
+ }
+ addAction(TYPE_PRINCIPAL,action,NAME_PRINCIPAL,sp);
+ }
+
+ public void setNew(boolean n) {
+ int action = ACTION_SET;
+ addAction(TYPE_ISNEW,action,NAME_ISNEW,new Boolean(n));
+ }
+
+ protected synchronized void addAction(int type,
+ int action,
+ String name,
+ Object value) {
+ AttributeInfo info = null;
+ if ( this.actionPool.size() > 0 ) {
+ try {
+ info = (AttributeInfo) actionPool.removeFirst();
+ }catch ( Exception x ) {
+ log.error("Unable to remove element:",x);
+ info = new AttributeInfo(type, action, name, value);
+ }
+ info.init(type,action,name,value);
+ } else {
+ info = new AttributeInfo(type, action, name, value);
+ }
+ //if we have already done something to this attribute, make sure
+ //we don't send multiple actions across the wire
+ if ( !recordAllActions) {
+ try {
+ actions.remove(info);
+ } catch (java.util.NoSuchElementException x) {
+ //do nothing, we wanted to remove it anyway
+ }
+ }
+ //add the action
+ actions.addLast(info);
+ }
+
+ public void execute(DeltaSession session) {
+ execute(session,true);
+ }
+
+ public synchronized void execute(DeltaSession session, boolean notifyListeners) {
+ if ( !this.sessionId.equals( session.getId() ) )
+ throw new java.lang.IllegalArgumentException("Session id mismatch, not executing the delta request");
+ session.access();
+ for ( int i=0; i<actions.size(); i++ ) {
+ AttributeInfo info = (AttributeInfo)actions.get(i);
+ switch ( info.getType() ) {
+ case TYPE_ATTRIBUTE: {
+ if ( info.getAction() == ACTION_SET ) {
+ if ( log.isTraceEnabled() ) log.trace("Session.setAttribute('"+info.getName()+"', '"+info.getValue()+"')");
+ session.setAttribute(info.getName(), info.getValue(),notifyListeners,false);
+ } else {
+ if ( log.isTraceEnabled() ) log.trace("Session.removeAttribute('"+info.getName()+"')");
+ session.removeAttribute(info.getName(),notifyListeners,false);
+ }
+
+ break;
+ }//case
+ case TYPE_ISNEW: {
+ if ( log.isTraceEnabled() ) log.trace("Session.setNew('"+info.getValue()+"')");
+ session.setNew(((Boolean)info.getValue()).booleanValue(),false);
+ break;
+ }//case
+ case TYPE_MAXINTERVAL: {
+ if ( log.isTraceEnabled() ) log.trace("Session.setMaxInactiveInterval('"+info.getValue()+"')");
+ session.setMaxInactiveInterval(((Integer)info.getValue()).intValue(),false);
+ break;
+ }//case
+ case TYPE_PRINCIPAL: {
+ Principal p = null;
+ if ( info.getAction() == ACTION_SET ) {
+ SerializablePrincipal sp = (SerializablePrincipal)info.getValue();
+ p = (Principal)sp.getPrincipal(session.getManager().getContainer().getRealm());
+ }
+ session.setPrincipal(p,false);
+ break;
+ }//case
+ default : throw new java.lang.IllegalArgumentException("Invalid attribute info type="+info);
+ }//switch
+ }//for
+ session.endAccess();
+ reset();
+ }
+
+ public synchronized void reset() {
+ while ( actions.size() > 0 ) {
+ try {
+ AttributeInfo info = (AttributeInfo) actions.removeFirst();
+ info.recycle();
+ actionPool.addLast(info);
+ }catch ( Exception x ) {
+ log.error("Unable to remove element",x);
+ }
+ }
+ actions.clear();
+ }
+
+ public String getSessionId() {
+ return sessionId;
+ }
+ public void setSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ if ( sessionId == null ) {
+ new Exception("Session Id is null for setSessionId").fillInStackTrace().printStackTrace();
+ }
+ }
+ public int getSize() {
+ return actions.size();
+ }
+
+ public synchronized void clear() {
+ actions.clear();
+ actionPool.clear();
+ }
+
+ public synchronized void readExternal(java.io.ObjectInput in) throws IOException,ClassNotFoundException {
+ //sessionId - String
+ //recordAll - boolean
+ //size - int
+ //AttributeInfo - in an array
+ reset();
+ sessionId = in.readUTF();
+ recordAllActions = in.readBoolean();
+ int cnt = in.readInt();
+ if (actions == null)
+ actions = new LinkedList();
+ else
+ actions.clear();
+ for (int i = 0; i < cnt; i++) {
+ AttributeInfo info = null;
+ if (this.actionPool.size() > 0) {
+ try {
+ info = (AttributeInfo) actionPool.removeFirst();
+ } catch ( Exception x ) {
+ log.error("Unable to remove element",x);
+ info = new AttributeInfo(-1,-1,null,null);
+ }
+ }
+ else {
+ info = new AttributeInfo(-1,-1,null,null);
+ }
+ info.readExternal(in);
+ actions.addLast(info);
+ }//for
+ }
+
+
+
+ public synchronized void writeExternal(java.io.ObjectOutput out ) throws java.io.IOException {
+ //sessionId - String
+ //recordAll - boolean
+ //size - int
+ //AttributeInfo - in an array
+ out.writeUTF(getSessionId());
+ out.writeBoolean(recordAllActions);
+ out.writeInt(getSize());
+ for ( int i=0; i<getSize(); i++ ) {
+ AttributeInfo info = (AttributeInfo)actions.get(i);
+ info.writeExternal(out);
+ }
+ }
+
+ /**
+ * serialize DeltaRequest
+ * @see DeltaRequest#writeExternal(java.io.ObjectOutput)
+ *
+ * @param deltaRequest
+ * @return serialized delta request
+ * @throws IOException
+ */
+ protected byte[] serialize() throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(bos);
+ writeExternal(oos);
+ oos.flush();
+ oos.close();
+ return bos.toByteArray();
+ }
+
+ private static class AttributeInfo implements java.io.Externalizable {
+ private String name = null;
+ private Object value = null;
+ private int action;
+ private int type;
+
+ public AttributeInfo() {}
+
+ public AttributeInfo(int type,
+ int action,
+ String name,
+ Object value) {
+ super();
+ init(type,action,name,value);
+ }
+
+ public void init(int type,
+ int action,
+ String name,
+ Object value) {
+ this.name = name;
+ this.value = value;
+ this.action = action;
+ this.type = type;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public int getAction() {
+ return action;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void recycle() {
+ name = null;
+ value = null;
+ type=-1;
+ action=-1;
+ }
+
+ public boolean equals(Object o) {
+ if ( ! (o instanceof AttributeInfo ) ) return false;
+ AttributeInfo other = (AttributeInfo)o;
+ return other.getName().equals(this.getName());
+ }
+
+ public synchronized void readExternal(java.io.ObjectInput in ) throws IOException,ClassNotFoundException {
+ //type - int
+ //action - int
+ //name - String
+ //hasvalue - boolean
+ //value - object
+ type = in.readInt();
+ action = in.readInt();
+ name = in.readUTF();
+ boolean hasValue = in.readBoolean();
+ if ( hasValue ) value = in.readObject();
+ }
+
+ public synchronized void writeExternal(java.io.ObjectOutput out) throws IOException {
+ //type - int
+ //action - int
+ //name - String
+ //hasvalue - boolean
+ //value - object
+ out.writeInt(getType());
+ out.writeInt(getAction());
+ out.writeUTF(getName());
+ out.writeBoolean(getValue()!=null);
+ if (getValue()!=null) out.writeObject(getValue());
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer("AttributeInfo[type=");
+ buf.append(getType()).append(", action=").append(getAction());
+ buf.append(", name=").append(getName()).append(", value=").append(getValue());
+ buf.append(", addr=").append(super.toString()).append("]");
+ return buf.toString();
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.session;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterSession;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.catalina.session.StandardSession;
+import org.apache.catalina.tribes.io.ReplicationStream;
+import org.apache.catalina.tribes.tipis.ReplicatedMapEntry;
+import org.apache.catalina.util.Enumerator;
+import org.apache.catalina.util.StringManager;
+import org.apache.catalina.session.StandardManager;
+import org.apache.catalina.session.ManagerBase;
+
+/**
+ *
+ * Similar to the StandardSession except that this session will keep
+ * track of deltas during a request.
+ *
+ * @author Filip Hanik
+ * @version $Revision: 372887 $ $Date: 2006-01-27 09:58:58 -0600 (Fri, 27 Jan 2006) $
+ */
+
+public class DeltaSession extends StandardSession implements Externalizable,ClusterSession,ReplicatedMapEntry {
+
+ public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(DeltaSession.class);
+
+ /**
+ * The string manager for this package.
+ */
+ protected static StringManager sm = StringManager.getManager(Constants.Package);
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * only the primary session will expire, or be able to expire due to
+ * inactivity. This is set to false as soon as I receive this session over
+ * the wire in a session message. That means that someone else has made a
+ * request on another server.
+ */
+ private transient boolean isPrimarySession = true;
+
+ /**
+ * The delta request contains all the action info
+ *
+ */
+ private transient DeltaRequest deltaRequest = null;
+
+ /**
+ * Last time the session was replicatd, used for distributed expiring of
+ * session
+ */
+ private transient long lastTimeReplicated = System.currentTimeMillis();
+
+
+ protected Lock diffLock = new ReentrantReadWriteLock().writeLock();
+
+ private long version;
+
+ // ----------------------------------------------------------- Constructors
+
+ /**
+ * Construct a new Session associated with the specified Manager.
+ *
+ * @param manager
+ * The manager with which this Session is associated
+ */
+ public DeltaSession() {
+ this(null);
+ }
+
+ public DeltaSession(Manager manager) {
+ super(manager);
+ this.resetDeltaRequest();
+ }
+
+ // ----------------------------------------------------- ReplicatedMapEntry
+
+ /**
+ * Has the object changed since last replication
+ * and is not in a locked state
+ * @return boolean
+ */
+ public boolean isDirty() {
+ return getDeltaRequest().getSize()>0;
+ }
+
+ /**
+ * If this returns true, the map will extract the diff using getDiff()
+ * Otherwise it will serialize the entire object.
+ * @return boolean
+ */
+ public boolean isDiffable() {
+ return true;
+ }
+
+ /**
+ * Returns a diff and sets the dirty map to false
+ * @return byte[]
+ * @throws IOException
+ */
+ public byte[] getDiff() throws IOException {
+ return getDeltaRequest().serialize();
+ }
+
+ public ClassLoader[] getClassLoaders() {
+ if ( manager instanceof BackupManager ) return ((BackupManager)manager).getClassLoaders();
+ else if ( manager instanceof ClusterManagerBase ) return ((ClusterManagerBase)manager).getClassLoaders();
+ else if ( manager instanceof StandardManager ) {
+ StandardManager sm = (StandardManager)manager;
+ return ClusterManagerBase.getClassLoaders(sm.getContainer());
+ } else if ( manager instanceof ManagerBase ) {
+ ManagerBase mb = (ManagerBase)manager;
+ return ClusterManagerBase.getClassLoaders(mb.getContainer());
+ }//end if
+ return null;
+ }
+
+ /**
+ * Applies a diff to an existing object.
+ * @param diff byte[]
+ * @param offset int
+ * @param length int
+ * @throws IOException
+ */
+ public void applyDiff(byte[] diff, int offset, int length) throws IOException, ClassNotFoundException {
+ ReplicationStream stream = ((ClusterManager)getManager()).getReplicationStream(diff,offset,length);
+ getDeltaRequest().readExternal(stream);
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ ClassLoader[] loaders = getClassLoaders();
+ if ( loaders != null && loaders.length >0 ) Thread.currentThread().setContextClassLoader(loaders[0]);
+ getDeltaRequest().execute(this);
+ }finally {
+ Thread.currentThread().setContextClassLoader(contextLoader);
+ }
+ }
+
+ /**
+ * Resets the current diff state and resets the dirty flag
+ */
+ public void resetDiff() {
+ resetDeltaRequest();
+ }
+
+ /**
+ * Lock during serialization
+ */
+ public void lock() {
+ diffLock.lock();
+ }
+
+ /**
+ * Unlock after serialization
+ */
+ public void unlock() {
+ diffLock.unlock();
+ }
+
+ public void setOwner(Object owner) {
+ if ( owner instanceof ClusterManager && getManager()==null) {
+ ClusterManager cm = (ClusterManager)owner;
+ this.setManager(cm);
+ this.setValid(true);
+ this.setPrimarySession(false);
+ this.access();
+ if (cm.isNotifyListenersOnReplication()) this.setId(getIdInternal());
+ this.resetDeltaRequest();
+ this.endAccess();
+ }
+ }
+ // ----------------------------------------------------- Session Properties
+
+ /**
+ * returns true if this session is the primary session, if that is the case,
+ * the manager can expire it upon timeout.
+ */
+ public boolean isPrimarySession() {
+ return isPrimarySession;
+ }
+
+ /**
+ * Sets whether this is the primary session or not.
+ *
+ * @param primarySession
+ * Flag value
+ */
+ public void setPrimarySession(boolean primarySession) {
+ this.isPrimarySession = primarySession;
+ }
+
+ /**
+ * Set the session identifier for this session without notify listeners.
+ *
+ * @param id
+ * The new session identifier
+ */
+ public void setIdInternal(String id) {
+ super.setId(id);
+ resetDeltaRequest();
+ }
+
+ /**
+ * Set the session identifier for this session.
+ *
+ * @param id
+ * The new session identifier
+ */
+ public void setId(String id) {
+ setIdInternal(id);
+ }
+
+
+
+ /**
+ * Return the last client access time without invalidation check
+ * @see #getLastAccessedTime().
+ */
+ public long getLastAccessedTimeInternal() {
+ return (this.lastAccessedTime);
+ }
+
+
+
+ public void setMaxInactiveInterval(int interval, boolean addDeltaRequest) {
+ super.maxInactiveInterval = interval;
+ if (isValid && interval == 0) {
+ expire();
+ } else {
+ if (addDeltaRequest && (deltaRequest != null))
+ deltaRequest.setMaxInactiveInterval(interval);
+ }
+ }
+
+ /**
+ * Set the <code>isNew</code> flag for this session.
+ *
+ * @param isNew
+ * The new value for the <code>isNew</code> flag
+ */
+ public void setNew(boolean isNew) {
+ setNew(isNew, true);
+ }
+
+ public void setNew(boolean isNew, boolean addDeltaRequest) {
+ super.setNew(isNew);
+ if (addDeltaRequest && (deltaRequest != null))
+ deltaRequest.setNew(isNew);
+ }
+
+ /**
+ * Set the authenticated Principal that is associated with this Session.
+ * This provides an <code>Authenticator</code> with a means to cache a
+ * previously authenticated Principal, and avoid potentially expensive
+ * <code>Realm.authenticate()</code> calls on every request.
+ *
+ * @param principal
+ * The new Principal, or <code>null</code> if none
+ */
+ public void setPrincipal(Principal principal) {
+ setPrincipal(principal, true);
+ }
+
+ public void setPrincipal(Principal principal, boolean addDeltaRequest) {
+ try {
+ lock();
+ super.setPrincipal(principal);
+ if (addDeltaRequest && (deltaRequest != null))
+ deltaRequest.setPrincipal(principal);
+ } finally {
+ unlock();
+ }
+ }
+
+ /**
+ * Return the <code>isValid</code> flag for this session.
+ */
+ public boolean isValid() {
+ if (this.expiring) {
+ return true;
+ }
+ if (!this.isValid) {
+ return false;
+ }
+ if (accessCount.get() > 0) {
+ return true;
+ }
+ if (maxInactiveInterval >= 0) {
+ long timeNow = System.currentTimeMillis();
+ int timeIdle = (int) ( (timeNow - thisAccessedTime) / 1000L);
+ if (isPrimarySession()) {
+ if (timeIdle >= maxInactiveInterval) {
+ expire(true);
+ }
+ } else {
+ if (timeIdle >= (2 * maxInactiveInterval)) {
+ //if the session has been idle twice as long as allowed,
+ //the primary session has probably crashed, and no other
+ //requests are coming in. that is why we do this. otherwise
+ //we would have a memory leak
+ expire(true, false);
+ }
+ }
+ }
+ return (this.isValid);
+ }
+
+ // ------------------------------------------------- Session Public Methods
+
+ /**
+ * Perform the internal processing required to invalidate this session,
+ * without triggering an exception if the session has already expired.
+ *
+ * @param notify
+ * Should we notify listeners about the demise of this session?
+ */
+ public void expire(boolean notify) {
+ expire(notify, true);
+ }
+
+ public void expire(boolean notify, boolean notifyCluster) {
+ String expiredId = getIdInternal();
+ super.expire(notify);
+
+ if (notifyCluster) {
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("deltaSession.notifying",
+ ((DeltaManager)manager).getName(),
+ new Boolean(isPrimarySession()),
+ expiredId));
+ if ( manager instanceof DeltaManager ) {
+ ( (DeltaManager) manager).sessionExpired(expiredId);
+ }
+ }
+ }
+
+ /**
+ * Release all object references, and initialize instance variables, in
+ * preparation for reuse of this object.
+ */
+ public void recycle() {
+ super.recycle();
+ deltaRequest.clear();
+ }
+
+
+ /**
+ * Return a string representation of this object.
+ */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("DeltaSession[");
+ sb.append(id);
+ sb.append("]");
+ return (sb.toString());
+ }
+
+ // ------------------------------------------------ Session Package Methods
+
+ public synchronized void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
+ readObjectData(in);
+ }
+
+
+ /**
+ * Read a serialized version of the contents of this session object from the
+ * specified object input stream, without requiring that the StandardSession
+ * itself have been serialized.
+ *
+ * @param stream
+ * The object input stream to read from
+ *
+ * @exception ClassNotFoundException
+ * if an unknown class is specified
+ * @exception IOException
+ * if an input/output error occurs
+ */
+ public void readObjectData(ObjectInput stream) throws ClassNotFoundException, IOException {
+ readObject(stream);
+ }
+
+ /**
+ * Write a serialized version of the contents of this session object to the
+ * specified object output stream, without requiring that the
+ * StandardSession itself have been serialized.
+ *
+ * @param stream
+ * The object output stream to write to
+ *
+ * @exception IOException
+ * if an input/output error occurs
+ */
+ public void writeObjectData(ObjectOutput stream) throws IOException {
+ writeObject(stream);
+ }
+
+ public void resetDeltaRequest() {
+ if (deltaRequest == null) {
+ deltaRequest = new DeltaRequest(getIdInternal(), false);
+ } else {
+ deltaRequest.reset();
+ deltaRequest.setSessionId(getIdInternal());
+ }
+ }
+
+ public DeltaRequest getDeltaRequest() {
+ if (deltaRequest == null) resetDeltaRequest();
+ return deltaRequest;
+ }
+
+ // ------------------------------------------------- HttpSession Properties
+
+ // ----------------------------------------------HttpSession Public Methods
+
+
+
+ /**
+ * Remove the object bound with the specified name from this session. If the
+ * session does not have an object bound with this name, this method does
+ * nothing.
+ * <p>
+ * After this method executes, and if the object implements
+ * <code>HttpSessionBindingListener</code>, the container calls
+ * <code>valueUnbound()</code> on the object.
+ *
+ * @param name
+ * Name of the object to remove from this session.
+ * @param notify
+ * Should we notify interested listeners that this attribute is
+ * being removed?
+ *
+ * @exception IllegalStateException
+ * if this method is called on an invalidated session
+ */
+ public void removeAttribute(String name, boolean notify) {
+ removeAttribute(name, notify, true);
+ }
+
+ public void removeAttribute(String name, boolean notify,boolean addDeltaRequest) {
+ // Validate our current state
+ if (!isValid()) throw new IllegalStateException(sm.getString("standardSession.removeAttribute.ise"));
+ removeAttributeInternal(name, notify, addDeltaRequest);
+ }
+
+ /**
+ * Bind an object to this session, using the specified name. If an object of
+ * the same name is already bound to this session, the object is replaced.
+ * <p>
+ * After this method executes, and if the object implements
+ * <code>HttpSessionBindingListener</code>, the container calls
+ * <code>valueBound()</code> on the object.
+ *
+ * @param name
+ * Name to which the object is bound, cannot be null
+ * @param value
+ * Object to be bound, cannot be null
+ *
+ * @exception IllegalArgumentException
+ * if an attempt is made to add a non-serializable object in
+ * an environment marked distributable.
+ * @exception IllegalStateException
+ * if this method is called on an invalidated session
+ */
+ public void setAttribute(String name, Object value) {
+ setAttribute(name, value, true, true);
+ }
+
+ public void setAttribute(String name, Object value, boolean notify,boolean addDeltaRequest) {
+
+ // Name cannot be null
+ if (name == null) throw new IllegalArgumentException(sm.getString("standardSession.setAttribute.namenull"));
+
+ // Null value is the same as removeAttribute()
+ if (value == null) {
+ removeAttribute(name);
+ return;
+ }
+
+ try {
+ lock();
+ super.setAttribute(name,value, notify);
+ if (addDeltaRequest && (deltaRequest != null)) deltaRequest.setAttribute(name, value);
+ } finally {
+ unlock();
+ }
+ }
+
+ // -------------------------------------------- HttpSession Private Methods
+
+ /**
+ * Read a serialized version of this session object from the specified
+ * object input stream.
+ * <p>
+ * <b>IMPLEMENTATION NOTE </b>: The reference to the owning Manager is not
+ * restored by this method, and must be set explicitly.
+ *
+ * @param stream
+ * The input stream to read from
+ *
+ * @exception ClassNotFoundException
+ * if an unknown class is specified
+ * @exception IOException
+ * if an input/output error occurs
+ */
+ private void readObject(ObjectInput stream) throws ClassNotFoundException, IOException {
+
+ // Deserialize the scalar instance variables (except Manager)
+ authType = null; // Transient only
+ creationTime = ( (Long) stream.readObject()).longValue();
+ lastAccessedTime = ( (Long) stream.readObject()).longValue();
+ maxInactiveInterval = ( (Integer) stream.readObject()).intValue();
+ isNew = ( (Boolean) stream.readObject()).booleanValue();
+ isValid = ( (Boolean) stream.readObject()).booleanValue();
+ thisAccessedTime = ( (Long) stream.readObject()).longValue();
+ version = ( (Long) stream.readObject()).longValue();
+ boolean hasPrincipal = stream.readBoolean();
+ principal = null;
+ if (hasPrincipal) {
+ principal = SerializablePrincipal.readPrincipal(stream,getManager().getContainer().getRealm());
+ }
+
+ // setId((String) stream.readObject());
+ id = (String) stream.readObject();
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaSession.readSession", id));
+
+ // Deserialize the attribute count and attribute values
+ if (attributes == null) attributes = new Hashtable();
+ int n = ( (Integer) stream.readObject()).intValue();
+ boolean isValidSave = isValid;
+ isValid = true;
+ for (int i = 0; i < n; i++) {
+ String name = (String) stream.readObject();
+ Object value = (Object) stream.readObject();
+ if ( (value instanceof String) && (value.equals(NOT_SERIALIZED)))
+ continue;
+ attributes.put(name, value);
+ }
+ isValid = isValidSave;
+
+ if (listeners == null) {
+ listeners = new ArrayList();
+ }
+
+ if (notes == null) {
+ notes = new Hashtable();
+ }
+ }
+
+ public synchronized void writeExternal(ObjectOutput out ) throws java.io.IOException {
+ writeObject(out);
+ }
+
+
+ /**
+ * Write a serialized version of this session object to the specified object
+ * output stream.
+ * <p>
+ * <b>IMPLEMENTATION NOTE </b>: The owning Manager will not be stored in the
+ * serialized representation of this Session. After calling
+ * <code>readObject()</code>, you must set the associated Manager
+ * explicitly.
+ * <p>
+ * <b>IMPLEMENTATION NOTE </b>: Any attribute that is not Serializable will
+ * be unbound from the session, with appropriate actions if it implements
+ * HttpSessionBindingListener. If you do not want any such attributes, be
+ * sure the <code>distributable</code> property of the associated Manager
+ * is set to <code>true</code>.
+ *
+ * @param stream
+ * The output stream to write to
+ *
+ * @exception IOException
+ * if an input/output error occurs
+ */
+ private void writeObject(ObjectOutput stream) throws IOException {
+
+ // Write the scalar instance variables (except Manager)
+ stream.writeObject(new Long(creationTime));
+ stream.writeObject(new Long(lastAccessedTime));
+ stream.writeObject(new Integer(maxInactiveInterval));
+ stream.writeObject(new Boolean(isNew));
+ stream.writeObject(new Boolean(isValid));
+ stream.writeObject(new Long(thisAccessedTime));
+ stream.writeObject(new Long(version));
+ stream.writeBoolean(getPrincipal() != null);
+ if (getPrincipal() != null) {
+ SerializablePrincipal.writePrincipal((GenericPrincipal) principal,stream);
+ }
+
+ stream.writeObject(id);
+ if (log.isDebugEnabled()) log.debug(sm.getString("deltaSession.writeSession", id));
+
+ // Accumulate the names of serializable and non-serializable attributes
+ String keys[] = keys();
+ ArrayList saveNames = new ArrayList();
+ ArrayList saveValues = new ArrayList();
+ for (int i = 0; i < keys.length; i++) {
+ Object value = null;
+ value = attributes.get(keys[i]);
+ if (value == null)
+ continue;
+ else if (value instanceof Serializable) {
+ saveNames.add(keys[i]);
+ saveValues.add(value);
+ }
+ }
+
+ // Serialize the attribute count and the Serializable attributes
+ int n = saveNames.size();
+ stream.writeObject(new Integer(n));
+ for (int i = 0; i < n; i++) {
+ stream.writeObject( (String) saveNames.get(i));
+ try {
+ stream.writeObject(saveValues.get(i));
+ } catch (NotSerializableException e) {
+ log.error(sm.getString("standardSession.notSerializable",saveNames.get(i), id), e);
+ stream.writeObject(NOT_SERIALIZED);
+ log.error(" storing attribute '" + saveNames.get(i)+ "' with value NOT_SERIALIZED");
+ }
+ }
+
+ }
+
+ // -------------------------------------------------------- Private Methods
+
+
+
+ /**
+ * Return the value of an attribute without a check for validity.
+ */
+ protected Object getAttributeInternal(String name) {
+ return (attributes.get(name));
+ }
+
+ protected void removeAttributeInternal(String name, boolean notify,
+ boolean addDeltaRequest) {
+ try {
+ lock();
+ // Remove this attribute from our collection
+ Object value = attributes.get(name);
+ if (value == null) return;
+
+ super.removeAttributeInternal(name,notify);
+ if (addDeltaRequest && (deltaRequest != null)) deltaRequest.removeAttribute(name);
+
+ }finally {
+ unlock();
+ }
+ }
+
+ protected long getLastTimeReplicated() {
+ return lastTimeReplicated;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setLastTimeReplicated(long lastTimeReplicated) {
+ this.lastTimeReplicated = lastTimeReplicated;
+ }
+
+ public void setVersion(long version) {
+ this.version = version;
+ }
+
+ protected void setAccessCount(int count) {
+ super.accessCount.set(count);
+ }
+}
+
+// -------------------------------------------------------------- Private Class
+
+/**
+ * This class is a dummy implementation of the <code>HttpSessionContext</code>
+ * interface, to conform to the requirement that such an object be returned when
+ * <code>HttpSession.getSessionContext()</code> is called.
+ *
+ * @author Craig R. McClanahan
+ *
+ * @deprecated As of Java Servlet API 2.1 with no replacement. The interface
+ * will be removed in a future version of this API.
+ */
+
+final class StandardSessionContext
+ implements HttpSessionContext {
+
+ private HashMap dummy = new HashMap();
+
+ /**
+ * Return the session identifiers of all sessions defined within this
+ * context.
+ *
+ * @deprecated As of Java Servlet API 2.1 with no replacement. This method
+ * must return an empty <code>Enumeration</code> and will be
+ * removed in a future version of the API.
+ */
+ public Enumeration getIds() {
+ return (new Enumerator(dummy));
+ }
+
+ /**
+ * Return the <code>HttpSession</code> associated with the specified
+ * session identifier.
+ *
+ * @param id
+ * Session identifier for which to look up a session
+ *
+ * @deprecated As of Java Servlet API 2.1 with no replacement. This method
+ * must return null and will be removed in a future version of
+ * the API.
+ */
+ public HttpSession getSession(String id) {
+ return (null);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.session;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.ha.ClusterValve;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.session.ManagerBase;
+import org.apache.catalina.util.LifecycleSupport;
+import org.apache.catalina.util.StringManager;
+import org.apache.catalina.valves.ValveBase;
+
+/**
+ * Valve to handle Tomcat jvmRoute takeover using mod_jk module after node
+ * failure. After a node crashed the next request going to other cluster node.
+ * Now the answering from apache is slower ( make some error handshaking. Very
+ * bad with apache at my windows.). We rewrite now the cookie jsessionid
+ * information to the backup cluster node. After the next response all client
+ * request goes direct to the backup node. The change sessionid send also to all
+ * other cluster nodes. Well, now the session stickyness work directly to the
+ * backup node and traffic don't go back too restarted cluster nodes!
+ *
+ * At all cluster node you must configure the as ClusterListener since 5.5.10
+ * {@link org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener JvmRouteSessionIDBinderListener}
+ * or before with
+ * org.apache.catalina.ha.session.JvmRouteSessionIDBinderListenerLifecycle.
+ *
+ * Add this Valve to your host definition at conf/server.xml .
+ *
+ * Since 5.5.10 as direct cluster valve:<br/>
+ * <pre>
+ * <Cluster>
+ * <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve" />
+ * </Cluster>
+ * </pre>
+ * <br />
+ * Before 5.5.10 as Host element:<br/>
+ * <pre>
+ * <Hostr>
+ * <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve" />
+ * </Hostr>
+ * </pre>
+ *
+ * Trick:<br/>
+ * You can enable this mod_jk turnover mode via JMX before you drop a node to all backup nodes!
+ * Set enable true on all JvmRouteBinderValve backups, disable worker at mod_jk
+ * and then drop node and restart it! Then enable mod_jk Worker and disable JvmRouteBinderValves again.
+ * This use case means that only requested session are migrated.
+ *
+ * @author Peter Rossbach
+ * @version $Revision: 326110 $ $Date: 2005-10-18 09:08:36 -0500 (Tue, 18 Oct 2005) $
+ */
+public class JvmRouteBinderValve extends ValveBase implements ClusterValve, Lifecycle {
+
+ /*--Static Variables----------------------------------------*/
+ public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
+ .getLog(JvmRouteBinderValve.class);
+
+ /**
+ * The descriptive information about this implementation.
+ */
+ protected static final String info = "org.apache.catalina.ha.session.JvmRouteBinderValve/1.2";
+
+ /*--Instance Variables--------------------------------------*/
+
+ /**
+ * the cluster
+ */
+ protected CatalinaCluster cluster;
+
+ /**
+ * The string manager for this package.
+ */
+ protected StringManager sm = StringManager.getManager(Constants.Package);
+
+ /**
+ * Has this component been started yet?
+ */
+ protected boolean started = false;
+
+ /**
+ * enabled this component
+ */
+ protected boolean enabled = true;
+
+ /**
+ * number of session that no at this tomcat instanz hosted
+ */
+ protected long numberOfSessions = 0;
+
+ protected String sessionIdAttribute = "org.apache.catalina.ha.session.JvmRouteOrignalSessionID";
+
+ /**
+ * The lifecycle event support for this component.
+ */
+ protected LifecycleSupport lifecycle = new LifecycleSupport(this);
+
+ /*--Logic---------------------------------------------------*/
+
+ /**
+ * Return descriptive information about this implementation.
+ */
+ public String getInfo() {
+
+ return (info);
+
+ }
+
+ /**
+ * set session id attribute to failed node for request.
+ *
+ * @return Returns the sessionIdAttribute.
+ */
+ public String getSessionIdAttribute() {
+ return sessionIdAttribute;
+ }
+
+ /**
+ * get name of failed reqeust session attribute
+ *
+ * @param sessionIdAttribute
+ * The sessionIdAttribute to set.
+ */
+ public void setSessionIdAttribute(String sessionIdAttribute) {
+ this.sessionIdAttribute = sessionIdAttribute;
+ }
+
+ /**
+ * @return Returns the number of migrated sessions.
+ */
+ public long getNumberOfSessions() {
+ return numberOfSessions;
+ }
+
+ /**
+ * @return Returns the enabled.
+ */
+ public boolean getEnabled() {
+ return enabled;
+ }
+
+ /**
+ * @param enabled
+ * The enabled to set.
+ */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Detect possible the JVMRoute change at cluster backup node..
+ *
+ * @param request
+ * tomcat request being processed
+ * @param response
+ * tomcat response being processed
+ * @exception IOException
+ * if an input/output error has occurred
+ * @exception ServletException
+ * if a servlet error has occurred
+ */
+ public void invoke(Request request, Response response) throws IOException,
+ ServletException {
+
+ if (getEnabled()
+ && getCluster() != null
+ && request.getContext() != null
+ && request.getContext().getDistributable() ) {
+ // valve cluster can access manager - other cluster handle turnover
+ // at host level - hopefully!
+ Manager manager = request.getContext().getManager();
+ if (manager != null && manager instanceof ClusterManager
+ && getCluster().getManager(((ClusterManager)manager).getName()) != null)
+ handlePossibleTurnover(request, response);
+ }
+ // Pass this request on to the next valve in our pipeline
+ getNext().invoke(request, response);
+ }
+
+ /**
+ * handle possible session turn over.
+ *
+ * @see JvmRouteBinderValve#handleJvmRoute(Request, Response, String, String)
+ * @param request current request
+ * @param response current response
+ */
+ protected void handlePossibleTurnover(Request request, Response response) {
+ Session session = request.getSessionInternal(false);
+ if (session != null) {
+ long t1 = System.currentTimeMillis();
+ String jvmRoute = getLocalJvmRoute(request);
+ if (jvmRoute == null) {
+ if (log.isWarnEnabled())
+ log.warn(sm.getString("jvmRoute.missingJvmRouteAttribute"));
+ return;
+ }
+ handleJvmRoute( request, response,session.getIdInternal(), jvmRoute);
+ if (log.isDebugEnabled()) {
+ long t2 = System.currentTimeMillis();
+ long time = t2 - t1;
+ log.debug(sm.getString("jvmRoute.turnoverInfo", new Long(time)));
+ }
+ }
+ }
+
+ /**
+ * get jvmroute from engine
+ *
+ * @param request current request
+ * @return return jvmRoute from ManagerBase or null
+ */
+ protected String getLocalJvmRoute(Request request) {
+ Manager manager = getManager(request);
+ if(manager instanceof ManagerBase)
+ return ((ManagerBase) manager).getJvmRoute();
+ return null ;
+ }
+
+ /**
+ * get Cluster DeltaManager
+ *
+ * @param request current request
+ * @return manager or null
+ */
+ protected Manager getManager(Request request) {
+ Manager manager = request.getContext().getManager();
+ if (log.isDebugEnabled()) {
+ if(manager != null)
+ log.debug(sm.getString("jvmRoute.foundManager", manager, request.getContext().getName()));
+ else
+ log.debug(sm.getString("jvmRoute.notFoundManager", manager, request.getContext().getName()));
+ }
+ return manager;
+ }
+
+ /**
+ * @return Returns the cluster.
+ */
+ public CatalinaCluster getCluster() {
+ return cluster;
+ }
+
+ /**
+ * @param cluster The cluster to set.
+ */
+ public void setCluster(CatalinaCluster cluster) {
+ this.cluster = cluster;
+ }
+
+ /**
+ * Handle jvmRoute stickyness after tomcat instance failed. After this
+ * correction a new Cookie send to client with new jvmRoute and the
+ * SessionID change propage to the other cluster nodes.
+ *
+ * @param request current request
+ * @param response
+ * Tomcat Response
+ * @param sessionId
+ * request SessionID from Cookie
+ * @param localJvmRoute
+ * local jvmRoute
+ */
+ protected void handleJvmRoute(
+ Request request, Response response,String sessionId, String localJvmRoute) {
+ // get requested jvmRoute.
+ String requestJvmRoute = null;
+ int index = sessionId.indexOf(".");
+ if (index > 0) {
+ requestJvmRoute = sessionId
+ .substring(index + 1, sessionId.length());
+ }
+ if (requestJvmRoute != null && !requestJvmRoute.equals(localJvmRoute)) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jvmRoute.failover", requestJvmRoute,
+ localJvmRoute, sessionId));
+ }
+ // OK - turnover the session ?
+ String newSessionID = sessionId.substring(0, index) + "."
+ + localJvmRoute;
+ Session catalinaSession = null;
+ try {
+ catalinaSession = getManager(request).findSession(sessionId);
+ } catch (IOException e) {
+ // Hups!
+ }
+ if (catalinaSession != null) {
+ changeSessionID(request, response, sessionId, newSessionID,
+ catalinaSession);
+ numberOfSessions++;
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jvmRoute.cannotFindSession",
+ sessionId));
+ }
+ }
+ }
+ }
+
+ /**
+ * change session id and send to all cluster nodes
+ *
+ * @param request current request
+ * @param response current response
+ * @param sessionId
+ * original session id
+ * @param newSessionID
+ * new session id for node migration
+ * @param catalinaSession
+ * current session with original session id
+ */
+ protected void changeSessionID(Request request,
+ Response response, String sessionId, String newSessionID, Session catalinaSession) {
+ lifecycle.fireLifecycleEvent("Before session migration",
+ catalinaSession);
+ request.setRequestedSessionId(newSessionID);
+ catalinaSession.setId(newSessionID);
+ if (catalinaSession instanceof DeltaSession)
+ ((DeltaSession) catalinaSession).resetDeltaRequest();
+ if(request.isRequestedSessionIdFromCookie()) setNewSessionCookie(request, response,newSessionID);
+ // set orginal sessionid at request, to allow application detect the
+ // change
+ if (sessionIdAttribute != null && !"".equals(sessionIdAttribute)) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jvmRoute.set.orignalsessionid",sessionIdAttribute,sessionId));
+ }
+ request.setAttribute(sessionIdAttribute, sessionId);
+ }
+ // now sending the change to all other clusternode!
+ ClusterManager manager = (ClusterManager)catalinaSession.getManager();
+ sendSessionIDClusterBackup(manager,request,sessionId, newSessionID);
+ lifecycle
+ .fireLifecycleEvent("After session migration", catalinaSession);
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jvmRoute.changeSession", sessionId,
+ newSessionID));
+ }
+ }
+
+ /**
+ * Send the changed Sessionid to all clusternodes.
+ *
+ * @see JvmRouteSessionIDBinderListener#messageReceived(ClusterMessage)
+ * @param manager
+ * ClusterManager
+ * @param sessionId
+ * current failed sessionid
+ * @param newSessionID
+ * new session id, bind to the new cluster node
+ */
+ protected void sendSessionIDClusterBackup(ClusterManager manager,Request request,String sessionId,
+ String newSessionID) {
+ SessionIDMessage msg = new SessionIDMessage();
+ msg.setOrignalSessionID(sessionId);
+ msg.setBackupSessionID(newSessionID);
+ Context context = request.getContext();
+ msg.setContextPath(context.getPath());
+ msg.setHost(context.getParent().getName());
+ if(manager.isSendClusterDomainOnly())
+ cluster.sendClusterDomain(msg);
+ else
+ cluster.send(msg);
+ }
+
+ /**
+ * Sets a new cookie for the given session id and response and see
+ * {@link org.apache.catalina.connector.Request#configureSessionCookie(javax.servlet.http.Cookie)}
+ *
+ * @param request current request
+ * @param response Tomcat Response
+ * @param sessionId The session id
+ */
+ protected void setNewSessionCookie(Request request,
+ Response response, String sessionId) {
+ if (response != null) {
+ Context context = request.getContext();
+ if (context.getCookies()) {
+ // set a new session cookie
+ Cookie newCookie = new Cookie(Globals.SESSION_COOKIE_NAME,
+ sessionId);
+ newCookie.setMaxAge(-1);
+ String contextPath = null;
+ if (!response.getConnector().getEmptySessionPath()
+ && (context != null)) {
+ contextPath = context.getEncodedPath();
+ }
+ if ((contextPath != null) && (contextPath.length() > 0)) {
+ newCookie.setPath(contextPath);
+ } else {
+ newCookie.setPath("/");
+ }
+ if (request.isSecure()) {
+ newCookie.setSecure(true);
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("jvmRoute.newSessionCookie",
+ sessionId, Globals.SESSION_COOKIE_NAME, newCookie
+ .getPath(), new Boolean(newCookie
+ .getSecure())));
+ }
+ response.addCookie(newCookie);
+ }
+ }
+ }
+
+ // ------------------------------------------------------ Lifecycle Methods
+
+ /**
+ * Add a lifecycle event listener to this component.
+ *
+ * @param listener
+ * The listener to add
+ */
+ public void addLifecycleListener(LifecycleListener listener) {
+
+ lifecycle.addLifecycleListener(listener);
+
+ }
+
+ /**
+ * Get the lifecycle listeners associated with this lifecycle. If this
+ * Lifecycle has no listeners registered, a zero-length array is returned.
+ */
+ public LifecycleListener[] findLifecycleListeners() {
+
+ return lifecycle.findLifecycleListeners();
+
+ }
+
+ /**
+ * Remove a lifecycle event listener from this component.
+ *
+ * @param listener
+ * The listener to add
+ */
+ public void removeLifecycleListener(LifecycleListener listener) {
+
+ lifecycle.removeLifecycleListener(listener);
+
+ }
+
+ /**
+ * Prepare for the beginning of active use of the public methods of this
+ * component. This method should be called after <code>configure()</code>,
+ * and before any of the public methods of the component are utilized.
+ *
+ * @exception LifecycleException
+ * if this component detects a fatal error that prevents this
+ * component from being used
+ */
+ public void start() throws LifecycleException {
+
+ // Validate and update our current component state
+ if (started)
+ throw new LifecycleException(sm
+ .getString("jvmRoute.valve.alreadyStarted"));
+ lifecycle.fireLifecycleEvent(START_EVENT, null);
+ started = true;
+ if (cluster == null) {
+ Container hostContainer = getContainer();
+ // compatibility with JvmRouteBinderValve version 1.1
+ // ( setup at context.xml or context.xml.default )
+ if (!(hostContainer instanceof Host)) {
+ if (log.isWarnEnabled())
+ log.warn(sm.getString("jvmRoute.configure.warn"));
+ hostContainer = hostContainer.getParent();
+ }
+ if (hostContainer instanceof Host
+ && ((Host) hostContainer).getCluster() != null) {
+ cluster = (CatalinaCluster) ((Host) hostContainer).getCluster();
+ } else {
+ Container engine = hostContainer.getParent() ;
+ if (engine instanceof Engine
+ && ((Engine) engine).getCluster() != null) {
+ cluster = (CatalinaCluster) ((Engine) engine).getCluster();
+ }
+ }
+ }
+ if (cluster == null) {
+ throw new RuntimeException("No clustering support at container "
+ + container.getName());
+ }
+
+ if (log.isInfoEnabled())
+ log.info(sm.getString("jvmRoute.valve.started"));
+
+ }
+
+ /**
+ * Gracefully terminate the active use of the public methods of this
+ * component. This method should be the last one called on a given instance
+ * of this component.
+ *
+ * @exception LifecycleException
+ * if this component detects a fatal error that needs to be
+ * reported
+ */
+ public void stop() throws LifecycleException {
+
+ // Validate and update our current component state
+ if (!started)
+ throw new LifecycleException(sm
+ .getString("jvmRoute.valve.notStarted"));
+ lifecycle.fireLifecycleEvent(STOP_EVENT, null);
+ started = false;
+ cluster = null;
+ numberOfSessions = 0;
+ if (log.isInfoEnabled())
+ log.info(sm.getString("jvmRoute.valve.stopped"));
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.session;
+
+import javax.management.DynamicMBean;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterListener;
+import org.apache.catalina.util.StringManager;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * Register new JvmRouteSessionIDBinderListener to receive Session ID changes.
+ *
+ * add following at your server.xml Host section
+ *
+ * <pre>
+ * <Host >...
+ * <Listener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderLifecycleListener" />
+ * <Cluster ...>
+ * </Host>
+ * </pre>
+ * FIXME add Engine support
+ * @deprecated
+ * @author Peter Rossbach
+ */
+public class JvmRouteSessionIDBinderLifecycleListener implements
+ LifecycleListener {
+ private static Log log = LogFactory
+ .getLog(JvmRouteSessionIDBinderLifecycleListener.class);
+
+ /**
+ * The descriptive information string for this implementation.
+ */
+ private static final String info = "org.apache.catalina.ha.session.JvmRouteSessionIDBinderLifecycleListener/1.0";
+
+ /**
+ * The string resources for this package.
+ */
+ protected static final StringManager sm = StringManager
+ .getManager(Constants.Package);
+
+ private boolean enabled = true;
+
+ private MBeanServer mserver = null;
+
+ private Registry registry = null;
+
+ private ClusterListener sessionMoverListener;
+
+ /*
+ * start and stop cluster
+ *
+ * @see org.apache.catalina.LifecycleListener#lifecycleEvent(org.apache.catalina.LifecycleEvent)
+ */
+ public void lifecycleEvent(LifecycleEvent event) {
+
+ if (enabled && event.getSource() instanceof StandardHost) {
+
+ if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("jvmRoute.listener.started"));
+ startSessionIDListener((StandardHost) event.getSource());
+ } else if (Lifecycle.BEFORE_STOP_EVENT.equals(event.getType())) {
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("jvmRoute.listener.stopped"));
+ stopSessionIDListener((StandardHost) event.getSource());
+ }
+ }
+ }
+
+ /**
+ * stop sessionID binder at cluster
+ *
+ * @param host
+ * clustered host
+ */
+ protected void stopSessionIDListener(StandardHost host) {
+ if (sessionMoverListener != null) {
+ CatalinaCluster cluster = (CatalinaCluster) host.getCluster();
+ cluster.removeClusterListener(sessionMoverListener);
+ if (mserver != null) {
+ try {
+ ObjectName objectName = getObjectName(host);
+ mserver.unregisterMBean(objectName);
+ } catch (Exception e) {
+ log.error(e);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param host
+ * @return The object name
+ * @throws MalformedObjectNameException
+ */
+ protected ObjectName getObjectName(StandardHost host) throws MalformedObjectNameException {
+ ObjectName objectName = new ObjectName(
+ host.getDomain()
+ + ":type=Listener,name=JvmRouteSessionIDBinderListener,host=" + host.getName());
+ return objectName;
+ }
+
+ /**
+ * start sessionID mover at cluster
+ *
+ * @param host
+ * clustered host
+ */
+ protected void startSessionIDListener(StandardHost host) {
+ try {
+ ObjectName objectName = null;
+ getMBeanServer();
+ objectName = getObjectName(host);
+ if (mserver.isRegistered(objectName)) {
+ if (log.isInfoEnabled())
+ log.info(sm.getString("jvmRoute.run.already"));
+ return;
+ }
+ sessionMoverListener = new JvmRouteSessionIDBinderListener();
+ mserver.registerMBean(getManagedBean(sessionMoverListener),
+ objectName);
+ CatalinaCluster cluster = (CatalinaCluster) host.getCluster();
+ sessionMoverListener.setCluster(cluster);
+ ((JvmRouteSessionIDBinderListener) sessionMoverListener).start();
+ } catch (Exception ex) {
+ log.error(ex.getMessage(), ex);
+ }
+ }
+
+ protected MBeanServer getMBeanServer() throws Exception {
+ if (mserver == null) {
+ if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
+ mserver = (MBeanServer) MBeanServerFactory
+ .findMBeanServer(null).get(0);
+ } else {
+ mserver = MBeanServerFactory.createMBeanServer();
+ }
+ registry = Registry.getRegistry(null, null);
+ registry.loadMetadata(this.getClass().getResourceAsStream(
+ "mbeans-descriptors.xml"));
+ }
+ return (mserver);
+ }
+
+ /**
+ * Returns the ModelMBean
+ *
+ * @param object
+ * The Object to get the ModelMBean for
+ * @return The ModelMBean
+ * @throws Exception
+ * If an error occurs this constructors throws this exception
+ */
+ protected DynamicMBean getManagedBean(Object object) throws Exception {
+ DynamicMBean mbean = null;
+ if (registry != null) {
+ ManagedBean managedBean = registry.findManagedBean(object
+ .getClass().getName());
+ mbean = managedBean.createMBean(object);
+ }
+ return mbean;
+ }
+
+ /**
+ * @return Returns the enabled.
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * @param enabled
+ * The enabled to set.
+ */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Return descriptive information about this Listener implementation and the
+ * corresponding version number, in the format
+ * <code><description>/<version></code>.
+ */
+ public String getInfo() {
+
+ return (info);
+
+ }
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.session;
+
+import java.io.IOException;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Session;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.core.StandardEngine;
+import org.apache.catalina.ha.*;
+
+/**
+ * Receive SessionID cluster change from other backup node after primary session
+ * node is failed.
+ *
+ * @author Peter Rossbach
+ * @version $Revision: 378258 $ $Date: 2006-02-16 08:42:35 -0600 (Thu, 16 Feb 2006) $
+ */
+public class JvmRouteSessionIDBinderListener extends ClusterListener {
+
+ /**
+ * The descriptive information about this implementation.
+ */
+ protected static final String info = "org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener/1.1";
+
+ //--Instance Variables--------------------------------------
+
+
+ protected boolean started = false;
+
+ /**
+ * number of session that goes to this cluster node
+ */
+ private long numberOfSessions = 0;
+
+ //--Constructor---------------------------------------------
+
+ public JvmRouteSessionIDBinderListener() {
+ }
+
+ //--Logic---------------------------------------------------
+
+ /**
+ * Return descriptive information about this implementation.
+ */
+ public String getInfo() {
+
+ return (info);
+
+ }
+
+ /**
+ * @return Returns the numberOfSessions.
+ */
+ public long getNumberOfSessions() {
+ return numberOfSessions;
+ }
+
+ /**
+ * Add this Mover as Cluster Listener ( receiver)
+ *
+ * @throws LifecycleException
+ */
+ public void start() throws LifecycleException {
+ if (started)
+ return;
+ getCluster().addClusterListener(this);
+ started = true;
+ if (log.isInfoEnabled())
+ log.info(sm.getString("jvmRoute.clusterListener.started"));
+ }
+
+ /**
+ * Remove this from Cluster Listener
+ *
+ * @throws LifecycleException
+ */
+ public void stop() throws LifecycleException {
+ started = false;
+ getCluster().removeClusterListener(this);
+ if (log.isInfoEnabled())
+ log.info(sm.getString("jvmRoute.clusterListener.stopped"));
+ }
+
+ /**
+ * Callback from the cluster, when a message is received, The cluster will
+ * broadcast it invoking the messageReceived on the receiver.
+ *
+ * @param msg
+ * ClusterMessage - the message received from the cluster
+ */
+ public void messageReceived(ClusterMessage msg) {
+ if (msg instanceof SessionIDMessage && msg != null) {
+ SessionIDMessage sessionmsg = (SessionIDMessage) msg;
+ if (log.isDebugEnabled())
+ log.debug(sm.getString(
+ "jvmRoute.receiveMessage.sessionIDChanged", sessionmsg
+ .getOrignalSessionID(), sessionmsg
+ .getBackupSessionID(), sessionmsg
+ .getContextPath()));
+ Container container = getCluster().getContainer();
+ Container host = null ;
+ if(container instanceof Engine) {
+ host = container.findChild(sessionmsg.getHost());
+ } else {
+ host = container ;
+ }
+ if (host != null) {
+ Context context = (Context) host.findChild(sessionmsg
+ .getContextPath());
+ if (context != null) {
+ try {
+ Session session = context.getManager().findSession(
+ sessionmsg.getOrignalSessionID());
+ if (session != null) {
+ session.setId(sessionmsg.getBackupSessionID());
+ } else if (log.isInfoEnabled())
+ log.info(sm.getString("jvmRoute.lostSession",
+ sessionmsg.getOrignalSessionID(),
+ sessionmsg.getContextPath()));
+ } catch (IOException e) {
+ log.error(e);
+ }
+
+ } else if (log.isErrorEnabled())
+ log.error(sm.getString("jvmRoute.contextNotFound",
+ sessionmsg.getContextPath(), ((StandardEngine) host
+ .getParent()).getJvmRoute()));
+ } else if (log.isErrorEnabled())
+ log.error(sm.getString("jvmRoute.hostNotFound", sessionmsg.getContextPath()));
+ }
+ return;
+ }
+
+ /**
+ * Accept only SessionIDMessages
+ *
+ * @param msg
+ * ClusterMessage
+ * @return boolean - returns true to indicate that messageReceived should be
+ * invoked. If false is returned, the messageReceived method will
+ * not be invoked.
+ */
+ public boolean accept(ClusterMessage msg) {
+ return (msg instanceof SessionIDMessage);
+ }
+}
+
--- /dev/null
+deltaManager.createSession.ise=createSession: Too many active sessions
+deltaManager.createSession.newSession=Created a DeltaSession with Id [{0}] Total count={1}
+deltaManager.createMessage.access=Manager [{0}]: create session message [{1}] access.
+deltaManager.createMessage.accessChangePrimary=Manager [{0}]: create session message [{1}] access to change primary.
+deltaManager.createMessage.allSessionData=Manager [{0}] send all session data.
+deltaManager.createMessage.allSessionTransfered=Manager [{0}] send all session data transfered
+deltaManager.createMessage.delta=Manager [{0}]: create session message [{1}] delta request.
+deltaManager.createMessage.expire=Manager [{0}]: create session message [{1}] expire.
+deltaManager.createMessage.unableCreateDeltaRequest=Unable to serialize delta request for sessionid [{0}]
+deltaManager.dropMessage=Manager [{0}]: Drop message {1} inside GET_ALL_SESSIONS sync phase start date {2} message date {3}
+deltaManager.foundMasterMember=Found for context [{0}] the replication master member [{1}]
+deltaManager.loading.cnfe=ClassNotFoundException while loading persisted sessions: {0}
+deltaManager.loading.existing.session=overload existing session {0}
+deltaManager.loading.ioe=IOException while loading persisted sessions: {0}
+deltaManager.loading.withContextClassLoader=Manager [{0}]: Loading the object data with a context class loader.
+deltaManager.loading.withoutClassLoader=Manager [{0}]: Loading the object data without a context class loader.
+deltaManager.managerLoad=Exception loading sessions from persistent storage
+deltaManager.noCluster=Starting... no cluster associated with this context: [{0}]
+deltaManager.noMasterMember=Starting... with no other member for context [{0}] at domain [{1}]
+deltaManager.noMembers=Manager [{0}]: skipping state transfer. No members active in cluster group.
+deltaManager.noSessionState=Manager [{0}]: No session state send at {1} received, timing out after {2} ms.
+deltaManager.notStarted=Manager has not yet been started
+deltaManager.sendMessage.newSession=Manager [{0}] send new session ({1})
+deltaManager.expireSessions=Manager [{0}] expiring sessions upon shutdown
+deltaManager.receiveMessage.accessed=Manager [{0}]: received session [{1}] accessed.
+deltaManager.receiveMessage.createNewSession=Manager [{0}]: received session [{1}] created.
+deltaManager.receiveMessage.delta=Manager [{0}]: received session [{1}] delta.
+deltaManager.receiveMessage.error=Manager [{0}]: Unable to receive message through TCP channel
+deltaManager.receiveMessage.eventType=Manager [{0}]: Received SessionMessage of type=({1}) from [{2}]
+deltaManager.receiveMessage.expired=Manager [{0}]: received session [{1}] expired.
+deltaManager.receiveMessage.transfercomplete=Manager [{0}] received from node [{1}:{2}] session state transfered.
+deltaManager.receiveMessage.unloadingAfter=Manager [{0}]: unloading sessions complete
+deltaManager.receiveMessage.unloadingBegin=Manager [{0}]: start unloading sessions
+deltaManager.receiveMessage.allSessionDataAfter=Manager [{0}]: session state deserialized
+deltaManager.receiveMessage.allSessionDataBegin=Manager [{0}]: received session state data
+deltaManager.receiveMessage.fromWrongDomain=Manager [{0}]: Received wrong SessionMessage of type=({1}) from [{2}] with domain [{3}] (localdomain [{4}]
+deltaManager.registerCluster=Register manager {0} to cluster element {1} with name {2}
+deltaManager.sessionReceived=Manager [{0}]; session state send at {1} received in {2} ms.
+deltaManager.sessionTimeout=Invalid session timeout setting {0}
+deltaManager.startClustering=Starting clustering manager at {0}
+deltaManager.stopped=Manager [{0}] is stopping
+deltaManager.unloading.ioe=IOException while saving persisted sessions: {0}
+deltaManager.waitForSessionState=Manager [{0}], requesting session state from {1}. This operation will timeout if no session state has been received within 60 seconds.
+deltaRequest.showPrincipal=Principal [{0}] is set to session {1}
+deltaRequest.wrongPrincipalClass=DeltaManager only support GenericPrincipal. Your realm used principal class {0}.
+deltaSession.notifying=Notifying cluster of expiration primary={0} sessionId [{1}]
+deltaSession.valueBound.ex=Session bound listener throw an exception
+deltaSession.valueBinding.ex=Session binding listener throw an exception
+deltaSession.valueUnbound.ex=Session unbound listener throw an exception
+deltaSession.readSession=readObject() loading session [{0}]
+deltaSession.readAttribute=session [{0}] loading attribute '{1}' with value '{2}'
+deltaSession.writeSession=writeObject() storing session [{0}]
+jvmRoute.cannotFindSession=Can't find session [{0}]
+jvmRoute.changeSession=Changed session from [{0}] to [{1}]
+jvmRoute.clusterListener.started=Cluster JvmRouteSessionIDBinderListener started
+jvmRoute.clusterListener.stopped=Cluster JvmRouteSessionIDBinderListener stopped
+jvmRoute.configure.warn=Please, setup your JvmRouteBinderValve at host valve, not at context valve!
+jvmRoute.contextNotFound=Context [{0}] not found at node [{1}]!
+jvmRoute.failover=Detected a failover with different jvmRoute - orginal route: [{0}] new one: [{1}] at session id [{2}]
+jvmRoute.foundManager=Found Cluster DeltaManager {0} at {1}
+jvmRoute.hostNotFound=No host found [{0}]
+jvmRoute.listener.started=SessionID Binder Listener started
+jvmRoute.listener.stopped=SessionID Binder Listener stopped
+jvmRoute.lostSession=Lost Session [{0}] at path [{1}]
+jvmRoute.missingJvmRouteAttribute=No engine jvmRoute attribute configured!
+jvmRoute.newSessionCookie=Setting cookie with session id [{0}] name: [{1}] path: [{2}] secure: [{3}]
+jvmRoute.notFoundManager=Not found Cluster DeltaManager {0} at {1}
+jvmRoute.receiveMessage.sessionIDChanged=Cluster JvmRouteSessionIDBinderListener received orginal session ID [{0}] set to new id [{1}] for context path [{2}]
+jvmRoute.run.already=jvmRoute SessionID receiver run already
+jvmRoute.skipURLSessionIDs=Skip reassign jvm route check, sessionid comes from URL!
+jvmRoute.turnoverInfo=Turnover Check time {0} msec
+jvmRoute.valve.alreadyStarted=jvmRoute backup sessionID correction is started
+jvmRoute.valve.notStarted=jvmRoute backup sessionID correction run already
+jvmRoute.valve.started=JvmRouteBinderValve started
+jvmRoute.valve.stopped=JvmRouteBinderValve stopped
+jvmRoute.set.orignalsessionid=Set Orginal Session id at request attriute {0} value: {1}
+standardSession.getId.ise=getId: Session already invalidated
+standardSession.attributeEvent=Session attribute event listener threw exception
+standardSession.attributeEvent=Session attribute event listener threw exception
+standardSession.bindingEvent=Session binding event listener threw exception
+standardSession.invalidate.ise=invalidate: Session already invalidated
+standardSession.isNew.ise=isNew: Session already invalidated
+standardSession.getAttribute.ise=getAttribute: Session already invalidated
+standardSession.getAttributeNames.ise=getAttributeNames: Session already invalidated
+standardSession.getCreationTime.ise=getCreationTime: Session already invalidated
+standardSession.getLastAccessedTime.ise=getLastAccessedTime: Session already invalidated
+standardSession.getId.ise=getId: Session already invalidated
+standardSession.getMaxInactiveInterval.ise=getMaxInactiveInterval: Session already invalidated
+standardSession.getValueNames.ise=getValueNames: Session already invalidated
+standardSession.notSerializable=Cannot serialize session attribute {0} for session {1}
+standardSession.removeAttribute.ise=removeAttribute: Session already invalidated
+standardSession.sessionEvent=Session event listener threw exception
+standardSession.setAttribute.iae=setAttribute: Non-serializable attribute
+standardSession.setAttribute.ise=setAttribute: Session already invalidated
+standardSession.setAttribute.namenull=setAttribute: name parameter cannot be null
+standardSession.sessionCreated=Created Session id = {0}
--- /dev/null
+
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.session;
+
+/**
+ * Title: Tomcat Session Replication for Tomcat 4.0 <BR>
+ * Description: A very simple straight forward implementation of
+ * session replication of servers in a cluster.<BR>
+ * This session replication is implemented "live". By live
+ * I mean, when a session attribute is added into a session on Node A
+ * a message is broadcasted to other messages and setAttribute is called on the replicated
+ * sessions.<BR>
+ * A full description of this implementation can be found under
+ * <href="http://www.filip.net/tomcat/">Filip's Tomcat Page</a><BR>
+ *
+ * Copyright: See apache license
+ * @author Filip Hanik
+ * @version $Revision: 303842 $ $Date: 2005-04-10 11:20:46 -0500 (Sun, 10 Apr 2005) $
+ * Description:<BR>
+ * The ReplicatedSession class is a simple extension of the StandardSession class
+ * It overrides a few methods (setAttribute, removeAttribute, expire, access) and has
+ * hooks into the InMemoryReplicationManager to broadcast and receive events from the cluster.<BR>
+ * This class inherits the readObjectData and writeObject data methods from the StandardSession
+ * and does not contain any serializable elements in addition to the inherited ones from the StandardSession
+ *
+ */
+import org.apache.catalina.Manager;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.security.Principal;
+
+public class ReplicatedSession extends org.apache.catalina.session.StandardSession
+implements org.apache.catalina.ha.ClusterSession{
+
+ private transient Manager mManager = null;
+ protected boolean isDirty = false;
+ private transient long lastAccessWasDistributed = System.currentTimeMillis();
+ private boolean isPrimarySession=true;
+
+
+ public ReplicatedSession(Manager manager) {
+ super(manager);
+ mManager = manager;
+ }
+
+
+ public boolean isDirty()
+ {
+ return isDirty;
+ }
+
+ public void setIsDirty(boolean dirty)
+ {
+ isDirty = dirty;
+ }
+
+
+ public void setLastAccessWasDistributed(long time) {
+ lastAccessWasDistributed = time;
+ }
+
+ public long getLastAccessWasDistributed() {
+ return lastAccessWasDistributed;
+ }
+
+
+ public void removeAttribute(String name) {
+ setIsDirty(true);
+ super.removeAttribute(name);
+ }
+
+ /**
+ * see parent description,
+ * plus we also notify other nodes in the cluster
+ */
+ public void removeAttribute(String name, boolean notify) {
+ setIsDirty(true);
+ super.removeAttribute(name,notify);
+ }
+
+
+ /**
+ * Sets an attribute and notifies the other nodes in the cluster
+ */
+ public void setAttribute(String name, Object value)
+ {
+ if ( value == null ) {
+ removeAttribute(name);
+ return;
+ }
+ if (!(value instanceof java.io.Serializable))
+ throw new java.lang.IllegalArgumentException("Value for attribute "+name+" is not serializable.");
+ setIsDirty(true);
+ super.setAttribute(name,value);
+ }
+
+ public void setMaxInactiveInterval(int interval) {
+ setIsDirty(true);
+ super.setMaxInactiveInterval(interval);
+ }
+
+
+ /**
+ * Sets the manager for this session
+ * @param mgr - the servers InMemoryReplicationManager
+ */
+ public void setManager(SimpleTcpReplicationManager mgr)
+ {
+ mManager = mgr;
+ super.setManager(mgr);
+ }
+
+
+ /**
+ * Set the authenticated Principal that is associated with this Session.
+ * This provides an <code>Authenticator</code> with a means to cache a
+ * previously authenticated Principal, and avoid potentially expensive
+ * <code>Realm.authenticate()</code> calls on every request.
+ *
+ * @param principal The new Principal, or <code>null</code> if none
+ */
+ public void setPrincipal(Principal principal) {
+ super.setPrincipal(principal);
+ setIsDirty(true);
+ }
+
+ public void expire() {
+ SimpleTcpReplicationManager mgr =(SimpleTcpReplicationManager)getManager();
+ mgr.sessionInvalidated(getIdInternal());
+ setIsDirty(true);
+ super.expire();
+ }
+
+ public void invalidate() {
+ SimpleTcpReplicationManager mgr =(SimpleTcpReplicationManager)getManager();
+ mgr.sessionInvalidated(getIdInternal());
+ setIsDirty(true);
+ super.invalidate();
+ }
+
+
+ /**
+ * Read a serialized version of the contents of this session object from
+ * the specified object input stream, without requiring that the
+ * StandardSession itself have been serialized.
+ *
+ * @param stream The object input stream to read from
+ *
+ * @exception ClassNotFoundException if an unknown class is specified
+ * @exception IOException if an input/output error occurs
+ */
+ public void readObjectData(ObjectInputStream stream)
+ throws ClassNotFoundException, IOException {
+
+ super.readObjectData(stream);
+
+ }
+
+
+ /**
+ * Write a serialized version of the contents of this session object to
+ * the specified object output stream, without requiring that the
+ * StandardSession itself have been serialized.
+ *
+ * @param stream The object output stream to write to
+ *
+ * @exception IOException if an input/output error occurs
+ */
+ public void writeObjectData(ObjectOutputStream stream)
+ throws IOException {
+
+ super.writeObjectData(stream);
+
+ }
+
+ public void setId(String id, boolean tellNew) {
+
+ if ((this.id != null) && (manager != null))
+ manager.remove(this);
+
+ this.id = id;
+
+ if (manager != null)
+ manager.add(this);
+ if (tellNew) tellNew();
+ }
+
+
+
+
+
+
+
+
+ /**
+ * returns true if this session is the primary session, if that is the
+ * case, the manager can expire it upon timeout.
+ */
+ public boolean isPrimarySession() {
+ return isPrimarySession;
+ }
+
+ /**
+ * Sets whether this is the primary session or not.
+ * @param primarySession Flag value
+ */
+ public void setPrimarySession(boolean primarySession) {
+ this.isPrimarySession=primarySession;
+ }
+
+
+
+
+ /**
+ * Implements a log method to log through the manager
+ */
+ protected void log(String message) {
+
+ if ((mManager != null) && (mManager instanceof SimpleTcpReplicationManager)) {
+ ((SimpleTcpReplicationManager) mManager).log.debug("ReplicatedSession: " + message);
+ } else {
+ System.out.println("ReplicatedSession: " + message);
+ }
+
+ }
+
+ protected void log(String message, Throwable x) {
+
+ if ((mManager != null) && (mManager instanceof SimpleTcpReplicationManager)) {
+ ((SimpleTcpReplicationManager) mManager).log.error("ReplicatedSession: " + message,x);
+ } else {
+ System.out.println("ReplicatedSession: " + message);
+ x.printStackTrace();
+ }
+
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer("ReplicatedSession id=");
+ buf.append(getIdInternal()).append(" ref=").append(super.toString()).append("\n");
+ java.util.Enumeration e = getAttributeNames();
+ while ( e.hasMoreElements() ) {
+ String name = (String)e.nextElement();
+ Object value = getAttribute(name);
+ buf.append("\tname=").append(name).append("; value=").append(value).append("\n");
+ }
+ buf.append("\tLastAccess=").append(getLastAccessedTime()).append("\n");
+ return buf.toString();
+ }
+ public int getAccessCount() {
+ return accessCount.get();
+ }
+ public void setAccessCount(int accessCount) {
+ this.accessCount.set(accessCount);
+ }
+ public long getLastAccessedTime() {
+ return lastAccessedTime;
+ }
+ public void setLastAccessedTime(long lastAccessedTime) {
+ this.lastAccessedTime = lastAccessedTime;
+ }
+ public long getThisAccessedTime() {
+ return thisAccessedTime;
+ }
+ public void setThisAccessedTime(long thisAccessedTime) {
+ this.thisAccessedTime = thisAccessedTime;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.session;
+
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.catalina.Realm;
+
+
+/**
+ * Generic implementation of <strong>java.security.Principal</strong> that
+ * is available for use by <code>Realm</code> implementations.
+ * The GenericPrincipal does NOT implement serializable and I didn't want to change that implementation
+ * hence I implemented this one instead.
+ * @author Filip Hanik
+ * @version $Revision: 303587 $ $Date: 2004-12-09 08:36:43 -0600 (Thu, 09 Dec 2004) $
+ */
+import org.apache.catalina.realm.GenericPrincipal;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+public class SerializablePrincipal implements java.io.Serializable {
+
+
+ // ----------------------------------------------------------- Constructors
+
+ public SerializablePrincipal()
+ {
+ super();
+ }
+ /**
+ * Construct a new Principal, associated with the specified Realm, for the
+ * specified username and password.
+ *
+ * @param realm The Realm that owns this Principal
+ * @param name The username of the user represented by this Principal
+ * @param password Credentials used to authenticate this user
+ */
+ public SerializablePrincipal(Realm realm, String name, String password) {
+
+ this(realm, name, password, null);
+
+ }
+
+
+ /**
+ * Construct a new Principal, associated with the specified Realm, for the
+ * specified username and password, with the specified role names
+ * (as Strings).
+ *
+ * @param realm The Realm that owns this principal
+ * @param name The username of the user represented by this Principal
+ * @param password Credentials used to authenticate this user
+ * @param roles List of roles (must be Strings) possessed by this user
+ */
+ public SerializablePrincipal(Realm realm, String name, String password,
+ List roles) {
+
+ super();
+ this.realm = realm;
+ this.name = name;
+ this.password = password;
+ if (roles != null) {
+ this.roles = new String[roles.size()];
+ this.roles = (String[]) roles.toArray(this.roles);
+ if (this.roles.length > 0)
+ Arrays.sort(this.roles);
+ }
+
+ }
+
+
+ // ------------------------------------------------------------- Properties
+
+
+ /**
+ * The username of the user represented by this Principal.
+ */
+ protected String name = null;
+
+ public String getName() {
+ return (this.name);
+ }
+
+
+ /**
+ * The authentication credentials for the user represented by
+ * this Principal.
+ */
+ protected String password = null;
+
+ public String getPassword() {
+ return (this.password);
+ }
+
+
+ /**
+ * The Realm with which this Principal is associated.
+ */
+ protected transient Realm realm = null;
+
+ public Realm getRealm() {
+ return (this.realm);
+ }
+
+ public void setRealm(Realm realm) {
+ this.realm = realm;
+ }
+
+
+
+
+ /**
+ * The set of roles associated with this user.
+ */
+ protected String roles[] = new String[0];
+
+ public String[] getRoles() {
+ return (this.roles);
+ }
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+
+
+ /**
+ * Return a String representation of this object, which exposes only
+ * information that should be public.
+ */
+ public String toString() {
+
+ StringBuffer sb = new StringBuffer("SerializablePrincipal[");
+ sb.append(this.name);
+ sb.append("]");
+ return (sb.toString());
+
+ }
+
+ public static SerializablePrincipal createPrincipal(GenericPrincipal principal)
+ {
+ if ( principal==null) return null;
+ return new SerializablePrincipal(principal.getRealm(),
+ principal.getName(),
+ principal.getPassword(),
+ principal.getRoles()!=null?Arrays.asList(principal.getRoles()):null);
+ }
+
+ public GenericPrincipal getPrincipal( Realm realm )
+ {
+ return new GenericPrincipal(realm,name,password,getRoles()!=null?Arrays.asList(getRoles()):null);
+ }
+
+ public static GenericPrincipal readPrincipal(ObjectInput in, Realm realm) throws java.io.IOException{
+ String name = in.readUTF();
+ boolean hasPwd = in.readBoolean();
+ String pwd = null;
+ if ( hasPwd ) pwd = in.readUTF();
+ int size = in.readInt();
+ String[] roles = new String[size];
+ for ( int i=0; i<size; i++ ) roles[i] = in.readUTF();
+ return new GenericPrincipal(realm,name,pwd,Arrays.asList(roles));
+ }
+
+ public static void writePrincipal(GenericPrincipal p, ObjectOutput out) throws java.io.IOException {
+ out.writeUTF(p.getName());
+ out.writeBoolean(p.getPassword()!=null);
+ if ( p.getPassword()!= null ) out.writeUTF(p.getPassword());
+ String[] roles = p.getRoles();
+ if ( roles == null ) roles = new String[0];
+ out.writeInt(roles.length);
+ for ( int i=0; i<roles.length; i++ ) out.writeUTF(roles[i]);
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.session;
+
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.ha.ClusterMessageBase;
+
+/**
+ * Session id change cluster message
+ *
+ * @author Peter Rossbach
+ *
+ * @version $Revision: 326110 $ $Date: 2005-10-18 09:08:36 -0500 (Tue, 18 Oct 2005) $
+ */
+public class SessionIDMessage extends ClusterMessageBase implements ClusterMessage {
+
+ private int messageNumber;
+
+ private String orignalSessionID;
+
+ private String backupSessionID;
+
+ private String host ;
+ private String contextPath;
+
+ public String getUniqueId() {
+ StringBuffer result = new StringBuffer(getOrignalSessionID());
+ result.append("#-#");
+ result.append(getHost());
+ result.append("#-#");
+ result.append(getContextPath());
+ result.append("#-#");
+ result.append(getMessageNumber());
+ result.append("#-#");
+ result.append(System.currentTimeMillis());
+ return result.toString();
+ }
+
+ /**
+ * @return Returns the host.
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * @param host The host to set.
+ */
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ /**
+ * @return Returns the contextPath.
+ */
+ public String getContextPath() {
+ return contextPath;
+ }
+ /**
+ * @param contextPath The contextPath to set.
+ */
+ public void setContextPath(String contextPath) {
+ this.contextPath = contextPath;
+ }
+ /**
+ * @return Returns the messageNumber.
+ */
+ public int getMessageNumber() {
+ return messageNumber;
+ }
+
+ /**
+ * @param messageNumber
+ * The messageNumber to set.
+ */
+ public void setMessageNumber(int messageNumber) {
+ this.messageNumber = messageNumber;
+ }
+
+
+ /**
+ * @return Returns the backupSessionID.
+ */
+ public String getBackupSessionID() {
+ return backupSessionID;
+ }
+
+ /**
+ * @param backupSessionID
+ * The backupSessionID to set.
+ */
+ public void setBackupSessionID(String backupSessionID) {
+ this.backupSessionID = backupSessionID;
+ }
+
+ /**
+ * @return Returns the orignalSessionID.
+ */
+ public String getOrignalSessionID() {
+ return orignalSessionID;
+ }
+
+ /**
+ * @param orignalSessionID
+ * The orignalSessionID to set.
+ */
+ public void setOrignalSessionID(String orignalSessionID) {
+ this.orignalSessionID = orignalSessionID;
+ }
+
+
+
+
+}
+
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.session;
+import org.apache.catalina.ha.ClusterMessage;
+
+/**
+ *
+ * <B>Class Description:</B><BR>
+ * The SessionMessage class is a class that is used when a session has been
+ * created, modified, expired in a Tomcat cluster node.<BR>
+ *
+ * The following events are currently available:
+ * <ul>
+ * <li><pre>public static final int EVT_SESSION_CREATED</pre><li>
+ * <li><pre>public static final int EVT_SESSION_ACCESSED</pre><li>
+ * <li><pre>public static final int EVT_ATTRIBUTE_ADDED</pre><li>
+ * <li><pre>public static final int EVT_ATTRIBUTE_REMOVED</pre><li>
+ * <li><pre>public static final int EVT_SESSION_EXPIRED_WONOTIFY</pre><li>
+ * <li><pre>public static final int EVT_SESSION_EXPIRED_WNOTIFY</pre><li>
+ * <li><pre>public static final int EVT_GET_ALL_SESSIONS</pre><li>
+ * <li><pre>public static final int EVT_SET_USER_PRINCIPAL</pre><li>
+ * <li><pre>public static final int EVT_SET_SESSION_NOTE</pre><li>
+ * <li><pre>public static final int EVT_REMOVE_SESSION_NOTE</pre><li>
+ * </ul>
+ *
+ */
+
+public interface SessionMessage extends ClusterMessage, java.io.Serializable
+{
+
+ /**
+ * Event type used when a session has been created on a node
+ */
+ public static final int EVT_SESSION_CREATED = 1;
+ /**
+ * Event type used when a session has expired
+ */
+ public static final int EVT_SESSION_EXPIRED = 2;
+
+ /**
+ * Event type used when a session has been accessed (ie, last access time
+ * has been updated. This is used so that the replicated sessions will not expire
+ * on the network
+ */
+ public static final int EVT_SESSION_ACCESSED = 3;
+ /**
+ * Event type used when a server comes online for the first time.
+ * The first thing the newly started server wants to do is to grab the
+ * all the sessions from one of the nodes and keep the same state in there
+ */
+ public static final int EVT_GET_ALL_SESSIONS = 4;
+ /**
+ * Event type used when an attribute has been added to a session,
+ * the attribute will be sent to all the other nodes in the cluster
+ */
+ public static final int EVT_SESSION_DELTA = 13;
+
+ /**
+ * When a session state is transferred, this is the event.
+ */
+ public static final int EVT_ALL_SESSION_DATA = 12;
+
+ /**
+ * When a session state is complete transferred, this is the event.
+ */
+ public static final int EVT_ALL_SESSION_TRANSFERCOMPLETE = 14;
+
+
+
+ public String getContextName();
+
+ public String getEventTypeString();
+
+ /**
+ * returns the event type
+ * @return one of the event types EVT_XXXX
+ */
+ public int getEventType();
+ /**
+ * @return the serialized data for the session
+ */
+ public byte[] getSession();
+ /**
+ * @return the session ID for the session
+ */
+ public String getSessionID();
+
+
+
+}//SessionMessage
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.session;
+
+
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.ha.ClusterMessageBase;
+
+/**
+ * Session cluster message
+ *
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ *
+ * @version $Revision: 326110 $ $Date: 2005-10-18 09:08:36 -0500 (Tue, 18 Oct 2005) $
+ */
+public class SessionMessageImpl extends ClusterMessageBase implements SessionMessage, java.io.Serializable {
+
+ public SessionMessageImpl() {
+ }
+
+
+ /*
+
+ * Private serializable variables to keep the messages state
+ */
+ private int mEvtType = -1;
+ private byte[] mSession;
+ private String mSessionID;
+
+ private String mContextName;
+ private long serializationTimestamp;
+ private boolean timestampSet = false ;
+ private String uniqueId;
+
+
+ private SessionMessageImpl( String contextName,
+ int eventtype,
+ byte[] session,
+ String sessionID)
+ {
+ mEvtType = eventtype;
+ mSession = session;
+ mSessionID = sessionID;
+ mContextName = contextName;
+ uniqueId = sessionID;
+ }
+
+ /**
+ * Creates a session message. Depending on what event type you want this
+ * message to represent, you populate the different parameters in the constructor<BR>
+ * The following rules apply dependent on what event type argument you use:<BR>
+ * <B>EVT_SESSION_CREATED</B><BR>
+ * The parameters: session, sessionID must be set.<BR>
+ * <B>EVT_SESSION_EXPIRED</B><BR>
+ * The parameters: sessionID must be set.<BR>
+ * <B>EVT_SESSION_ACCESSED</B><BR>
+ * The parameters: sessionID must be set.<BR>
+ * <B>EVT_SESSION_EXPIRED_XXXX</B><BR>
+ * The parameters: sessionID must be set.<BR>
+ * <B>EVT_SESSION_DELTA</B><BR>
+ * Send attribute delta (add,update,remove attribute or principal, ...).<BR>
+ * <B>EVT_ALL_SESSION_DATA</B><BR>
+ * Send complete serializes session list<BR>
+ * <B>EVT_ALL_SESSION_TRANSFERCOMPLETE</B><BR>
+ * send that all session state information are transfered
+ * after GET_ALL_SESSION received from this sender.<BR>
+ * @param contextName - the name of the context (application
+ * @param eventtype - one of the 8 event type defined in this class
+ * @param session - the serialized byte array of the session itself
+ * @param sessionID - the id that identifies this session
+ * @param uniqueID - the id that identifies this message
+ */
+ public SessionMessageImpl( String contextName,
+ int eventtype,
+ byte[] session,
+ String sessionID,
+ String uniqueID)
+ {
+ this(contextName,eventtype,session,sessionID);
+ uniqueId = uniqueID;
+ }
+
+ /**
+ * returns the event type
+ * @return one of the event types EVT_XXXX
+ */
+ public int getEventType() { return mEvtType; }
+
+ /**
+ * @return the serialized data for the session
+ */
+ public byte[] getSession() { return mSession;}
+
+ /**
+ * @return the session ID for the session
+ */
+ public String getSessionID(){ return mSessionID; }
+
+ /**
+ * set message send time but only the first setting works (one shot)
+ */
+ public void setTimestamp(long time) {
+ synchronized(this) {
+ if(!timestampSet) {
+ serializationTimestamp=time;
+ timestampSet = true ;
+ }
+ }
+ }
+
+ public long getTimestamp() { return serializationTimestamp;}
+
+ /**
+ * clear text event type name (for logging purpose only)
+ * @return the event type in a string representating, useful for debugging
+ */
+ public String getEventTypeString()
+ {
+ switch (mEvtType)
+ {
+ case EVT_SESSION_CREATED : return "SESSION-MODIFIED";
+ case EVT_SESSION_EXPIRED : return "SESSION-EXPIRED";
+ case EVT_SESSION_ACCESSED : return "SESSION-ACCESSED";
+ case EVT_GET_ALL_SESSIONS : return "SESSION-GET-ALL";
+ case EVT_SESSION_DELTA : return "SESSION-DELTA";
+ case EVT_ALL_SESSION_DATA : return "ALL-SESSION-DATA";
+ case EVT_ALL_SESSION_TRANSFERCOMPLETE : return "SESSION-STATE-TRANSFERED";
+ default : return "UNKNOWN-EVENT-TYPE";
+ }
+ }
+
+ public String getContextName() {
+ return mContextName;
+ }
+ public String getUniqueId() {
+ return uniqueId;
+ }
+ public void setUniqueId(String uniqueId) {
+ this.uniqueId = uniqueId;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.session;
+
+import java.io.IOException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Session;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.catalina.session.StandardManager;
+import org.apache.catalina.tribes.io.ReplicationStream;
+import java.io.ByteArrayInputStream;
+import org.apache.catalina.Loader;
+
+/**
+ * Title: Tomcat Session Replication for Tomcat 4.0 <BR>
+ * Description: A very simple straight forward implementation of
+ * session replication of servers in a cluster.<BR>
+ * This session replication is implemented "live". By live
+ * I mean, when a session attribute is added into a session on Node A
+ * a message is broadcasted to other messages and setAttribute is called on the
+ * replicated sessions.<BR>
+ * A full description of this implementation can be found under
+ * <href="http://www.filip.net/tomcat/">Filip's Tomcat Page</a><BR>
+ *
+ * Copyright: See apache license
+ * Company: www.filip.net
+ * @author <a href="mailto:mail@filip.net">Filip Hanik</a>
+ * @author Bela Ban (modifications for synchronous replication)
+ * @version 1.0 for TC 4.0
+ * Description: The InMemoryReplicationManager is a session manager that replicated
+ * session information in memory. It uses <a href="www.javagroups.com">JavaGroups</a> as
+ * a communication protocol to ensure guaranteed and ordered message delivery.
+ * JavaGroups also provides a very flexible protocol stack to ensure that the replication
+ * can be used in any environment.
+ * <BR><BR>
+ * The InMemoryReplicationManager extends the StandardManager hence it allows for us
+ * to inherit all the basic session management features like expiration, session listeners etc
+ * <BR><BR>
+ * To communicate with other nodes in the cluster, the InMemoryReplicationManager sends out 7 different type of multicast messages
+ * all defined in the SessionMessage class.<BR>
+ * When a session is replicated (not an attribute added/removed) the session is serialized into
+ * a byte array using the StandardSession.readObjectData, StandardSession.writeObjectData methods.
+ */
+public class SimpleTcpReplicationManager extends StandardManager implements ClusterManager
+{
+ public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog( SimpleTcpReplicationManager.class );
+
+ //the channel configuration
+ protected String mChannelConfig = null;
+
+ //the group name
+ protected String mGroupName = "TomcatReplication";
+
+ //somehow start() gets called more than once
+ protected boolean mChannelStarted = false;
+
+ //log to screen
+ protected boolean mPrintToScreen = true;
+
+ protected boolean defaultMode = false;
+
+ protected boolean mManagerRunning = false;
+
+ /** Use synchronous rather than asynchronous replication. Every session modification (creation, change, removal etc)
+ * will be sent to all members. The call will then wait for max milliseconds, or forever (if timeout is 0) for
+ * all responses.
+ */
+ protected boolean synchronousReplication=true;
+
+ /** Set to true if we don't want the sessions to expire on shutdown */
+ protected boolean mExpireSessionsOnShutdown = true;
+
+ protected boolean useDirtyFlag = false;
+
+ protected String name;
+
+ protected boolean distributable = true;
+
+ protected CatalinaCluster cluster;
+
+ protected java.util.HashMap invalidatedSessions = new java.util.HashMap();
+
+ /**
+ * Flag to keep track if the state has been transferred or not
+ * Assumes false.
+ */
+ protected boolean stateTransferred = false;
+ private boolean notifyListenersOnReplication;
+ private boolean sendClusterDomainOnly = true ;
+
+ /**
+ * Constructor, just calls super()
+ *
+ */
+ public SimpleTcpReplicationManager()
+ {
+ super();
+ }
+
+ public boolean isSendClusterDomainOnly() {
+ return sendClusterDomainOnly;
+ }
+
+ /**
+ * @param sendClusterDomainOnly The sendClusterDomainOnly to set.
+ */
+ public void setSendClusterDomainOnly(boolean sendClusterDomainOnly) {
+ this.sendClusterDomainOnly = sendClusterDomainOnly;
+ }
+
+ /**
+ * @return Returns the defaultMode.
+ */
+ public boolean isDefaultMode() {
+ return defaultMode;
+ }
+ /**
+ * @param defaultMode The defaultMode to set.
+ */
+ public void setDefaultMode(boolean defaultMode) {
+ this.defaultMode = defaultMode;
+ }
+
+ public boolean isManagerRunning()
+ {
+ return mManagerRunning;
+ }
+
+ public void setUseDirtyFlag(boolean usedirtyflag)
+ {
+ this.useDirtyFlag = usedirtyflag;
+ }
+
+ public void setExpireSessionsOnShutdown(boolean expireSessionsOnShutdown)
+ {
+ mExpireSessionsOnShutdown = expireSessionsOnShutdown;
+ }
+
+ public void setCluster(CatalinaCluster cluster) {
+ if(log.isDebugEnabled())
+ log.debug("Cluster associated with SimpleTcpReplicationManager");
+ this.cluster = cluster;
+ }
+
+ public boolean getExpireSessionsOnShutdown()
+ {
+ return mExpireSessionsOnShutdown;
+ }
+
+ public void setPrintToScreen(boolean printtoscreen)
+ {
+ if(log.isDebugEnabled())
+ log.debug("Setting screen debug to:"+printtoscreen);
+ mPrintToScreen = printtoscreen;
+ }
+
+ public void setSynchronousReplication(boolean flag)
+ {
+ synchronousReplication=flag;
+ }
+
+ /**
+ * Override persistence since they don't go hand in hand with replication for now.
+ */
+ public void unload() throws IOException {
+ if ( !getDistributable() ) {
+ super.unload();
+ }
+ }
+
+ /**
+ * Creates a HTTP session.
+ * Most of the code in here is copied from the StandardManager.
+ * This is not pretty, yeah I know, but it was necessary since the
+ * StandardManager had hard coded the session instantiation to the a
+ * StandardSession, when we actually want to instantiate a ReplicatedSession<BR>
+ * If the call comes from the Tomcat servlet engine, a SessionMessage goes out to the other
+ * nodes in the cluster that this session has been created.
+ * @param notify - if set to true the other nodes in the cluster will be notified.
+ * This flag is needed so that we can create a session before we deserialize
+ * a replicated one
+ *
+ * @see ReplicatedSession
+ */
+ protected Session createSession(String sessionId, boolean notify, boolean setId)
+ {
+
+ //inherited from the basic manager
+ if ((getMaxActiveSessions() >= 0) &&
+ (sessions.size() >= getMaxActiveSessions()))
+ throw new IllegalStateException(sm.getString("standardManager.createSession.ise"));
+
+
+ Session session = new ReplicatedSession(this);
+
+ // Initialize the properties of the new session and return it
+ session.setNew(true);
+ session.setValid(true);
+ session.setCreationTime(System.currentTimeMillis());
+ session.setMaxInactiveInterval(this.maxInactiveInterval);
+ if(sessionId == null)
+ sessionId = generateSessionId();
+ if ( setId ) session.setId(sessionId);
+ if ( notify && (cluster!=null) ) {
+ ((ReplicatedSession)session).setIsDirty(true);
+ }
+ return (session);
+ }//createSession
+
+ //=========================================================================
+ // OVERRIDE THESE METHODS TO IMPLEMENT THE REPLICATION
+ //=========================================================================
+
+ /**
+ * Construct and return a new session object, based on the default
+ * settings specified by this Manager's properties. The session
+ * id will be assigned by this method, and available via the getId()
+ * method of the returned session. If a new session cannot be created
+ * for any reason, return <code>null</code>.
+ *
+ * @exception IllegalStateException if a new session cannot be
+ * instantiated for any reason
+ */
+ public Session createSession(String sessionId)
+ {
+ //create a session and notify the other nodes in the cluster
+ Session session = createSession(sessionId,getDistributable(),true);
+ add(session);
+ return session;
+ }
+
+ public void sessionInvalidated(String sessionId) {
+ synchronized ( invalidatedSessions ) {
+ invalidatedSessions.put(sessionId, sessionId);
+ }
+ }
+
+ public String[] getInvalidatedSessions() {
+ synchronized ( invalidatedSessions ) {
+ String[] result = new String[invalidatedSessions.size()];
+ invalidatedSessions.values().toArray(result);
+ return result;
+ }
+
+ }
+
+ public ClusterMessage requestCompleted(String sessionId)
+ {
+ if ( !getDistributable() ) {
+ log.warn("Received requestCompleted message, although this context["+
+ getName()+"] is not distributable. Ignoring message");
+ return null;
+ }
+ //notify javagroups
+ try
+ {
+ if ( invalidatedSessions.get(sessionId) != null ) {
+ synchronized ( invalidatedSessions ) {
+ invalidatedSessions.remove(sessionId);
+ SessionMessage msg = new SessionMessageImpl(name,
+ SessionMessage.EVT_SESSION_EXPIRED,
+ null,
+ sessionId,
+ sessionId);
+ return msg;
+ }
+ } else {
+ ReplicatedSession session = (ReplicatedSession) findSession(
+ sessionId);
+ if (session != null) {
+ //return immediately if the session is not dirty
+ if (useDirtyFlag && (!session.isDirty())) {
+ //but before we return doing nothing,
+ //see if we should send
+ //an updated last access message so that
+ //sessions across cluster dont expire
+ long interval = session.getMaxInactiveInterval();
+ long lastaccdist = System.currentTimeMillis() -
+ session.getLastAccessWasDistributed();
+ if ( ((interval*1000) / lastaccdist)< 3 ) {
+ SessionMessage accmsg = new SessionMessageImpl(name,
+ SessionMessage.EVT_SESSION_ACCESSED,
+ null,
+ sessionId,
+ sessionId);
+ session.setLastAccessWasDistributed(System.currentTimeMillis());
+ return accmsg;
+ }
+ return null;
+ }
+
+ session.setIsDirty(false);
+ if (log.isDebugEnabled()) {
+ try {
+ log.debug("Sending session to cluster=" + session);
+ }
+ catch (Exception ignore) {}
+ }
+ SessionMessage msg = new SessionMessageImpl(name,
+ SessionMessage.EVT_SESSION_CREATED,
+ writeSession(session),
+ session.getIdInternal(),
+ session.getIdInternal());
+ return msg;
+ } //end if
+ }//end if
+ }
+ catch (Exception x )
+ {
+ log.error("Unable to replicate session",x);
+ }
+ return null;
+ }
+
+ /**
+ * Serialize a session into a byte array<BR>
+ * This method simple calls the writeObjectData method on the session
+ * and returns the byte data from that call
+ * @param session - the session to be serialized
+ * @return a byte array containing the session data, null if the serialization failed
+ */
+ protected byte[] writeSession( Session session )
+ {
+ try
+ {
+ java.io.ByteArrayOutputStream session_data = new java.io.ByteArrayOutputStream();
+ java.io.ObjectOutputStream session_out = new java.io.ObjectOutputStream(session_data);
+ session_out.flush();
+ boolean hasPrincipal = session.getPrincipal() != null;
+ session_out.writeBoolean(hasPrincipal);
+ if ( hasPrincipal )
+ {
+ session_out.writeObject(SerializablePrincipal.createPrincipal((GenericPrincipal)session.getPrincipal()));
+ }//end if
+ ((ReplicatedSession)session).writeObjectData(session_out);
+ return session_data.toByteArray();
+
+ }
+ catch ( Exception x )
+ {
+ log.error("Failed to serialize the session!",x);
+ }
+ return null;
+ }
+
+ /**
+ * Open Stream and use correct ClassLoader (Container) Switch
+ * ThreadClassLoader
+ *
+ * @param data
+ * @return The object input stream
+ * @throws IOException
+ */
+ public ReplicationStream getReplicationStream(byte[] data) throws IOException {
+ return getReplicationStream(data,0,data.length);
+ }
+
+ public ReplicationStream getReplicationStream(byte[] data, int offset, int length) throws IOException {
+ ByteArrayInputStream fis =null;
+ ReplicationStream ois = null;
+ Loader loader = null;
+ ClassLoader classLoader = null;
+ //fix to be able to run the DeltaManager
+ //stand alone without a container.
+ //use the Threads context class loader
+ if (container != null)
+ loader = container.getLoader();
+ if (loader != null)
+ classLoader = loader.getClassLoader();
+ else
+ classLoader = Thread.currentThread().getContextClassLoader();
+ //end fix
+ fis = new ByteArrayInputStream(data, offset, length);
+ if ( classLoader == Thread.currentThread().getContextClassLoader() ) {
+ ois = new ReplicationStream(fis, new ClassLoader[] {classLoader});
+ } else {
+ ois = new ReplicationStream(fis, new ClassLoader[] {classLoader,Thread.currentThread().getContextClassLoader()});
+ }
+ return ois;
+ }
+
+
+
+
+ /**
+ * Reinstantiates a serialized session from the data passed in.
+ * This will first call createSession() so that we get a fresh instance with all
+ * the managers set and all the transient fields validated.
+ * Then it calls Session.readObjectData(byte[]) to deserialize the object
+ * @param data - a byte array containing session data
+ * @return a valid Session object, null if an error occurs
+ *
+ */
+ protected Session readSession( byte[] data, String sessionId )
+ {
+ try
+ {
+ ReplicationStream session_in = getReplicationStream(data);
+
+ Session session = sessionId!=null?this.findSession(sessionId):null;
+ boolean isNew = (session==null);
+ //clear the old values from the existing session
+ if ( session!=null ) {
+ ReplicatedSession rs = (ReplicatedSession)session;
+ rs.expire(false); //cleans up the previous values, since we are not doing removes
+ session = null;
+ }//end if
+
+ if (session==null) {
+ session = createSession(null,false, false);
+ sessions.remove(session.getIdInternal());
+ }
+
+
+ boolean hasPrincipal = session_in.readBoolean();
+ SerializablePrincipal p = null;
+ if ( hasPrincipal )
+ p = (SerializablePrincipal)session_in.readObject();
+ ((ReplicatedSession)session).readObjectData(session_in);
+ if ( hasPrincipal )
+ session.setPrincipal(p.getPrincipal(getContainer().getRealm()));
+ ((ReplicatedSession)session).setId(sessionId,isNew);
+ ReplicatedSession rsession = (ReplicatedSession)session;
+ rsession.setAccessCount(1);
+ session.setManager(this);
+ session.setValid(true);
+ rsession.setLastAccessedTime(System.currentTimeMillis());
+ rsession.setThisAccessedTime(System.currentTimeMillis());
+ ((ReplicatedSession)session).setAccessCount(0);
+ session.setNew(false);
+ if(log.isTraceEnabled())
+ log.trace("Session loaded id="+sessionId +
+ " actualId="+session.getId()+
+ " exists="+this.sessions.containsKey(sessionId)+
+ " valid="+rsession.isValid());
+ return session;
+
+ }
+ catch ( Exception x )
+ {
+ log.error("Failed to deserialize the session!",x);
+ }
+ return null;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+ /**
+ * Prepare for the beginning of active use of the public methods of this
+ * component. This method should be called after <code>configure()</code>,
+ * and before any of the public methods of the component are utilized.<BR>
+ * Starts the cluster communication channel, this will connect with the other nodes
+ * in the cluster, and request the current session state to be transferred to this node.
+ * @exception IllegalStateException if this component has already been
+ * started
+ * @exception LifecycleException if this component detects a fatal error
+ * that prevents this component from being used
+ */
+ public void start() throws LifecycleException {
+ mManagerRunning = true;
+ super.start();
+ //start the javagroups channel
+ try {
+ //the channel is already running
+ if ( mChannelStarted ) return;
+ if(log.isInfoEnabled())
+ log.info("Starting clustering manager...:"+getName());
+ if ( cluster == null ) {
+ log.error("Starting... no cluster associated with this context:"+getName());
+ return;
+ }
+ cluster.addManager(getName(),this);
+
+ if (cluster.getMembers().length > 0) {
+ Member mbr = cluster.getMembers()[0];
+ SessionMessage msg =
+ new SessionMessageImpl(this.getName(),
+ SessionMessage.EVT_GET_ALL_SESSIONS,
+ null,
+ "GET-ALL",
+ "GET-ALL-"+this.getName());
+ cluster.send(msg, mbr);
+ if(log.isWarnEnabled())
+ log.warn("Manager["+getName()+"], requesting session state from "+mbr+
+ ". This operation will timeout if no session state has been received within "+
+ "60 seconds");
+ long reqStart = System.currentTimeMillis();
+ long reqNow = 0;
+ boolean isTimeout=false;
+ do {
+ try {
+ Thread.sleep(100);
+ }catch ( Exception sleep) {}
+ reqNow = System.currentTimeMillis();
+ isTimeout=((reqNow-reqStart)>(1000*60));
+ } while ( (!isStateTransferred()) && (!isTimeout));
+ if ( isTimeout || (!isStateTransferred()) ) {
+ log.error("Manager["+getName()+"], No session state received, timing out.");
+ }else {
+ if(log.isInfoEnabled())
+ log.info("Manager["+getName()+"], session state received in "+(reqNow-reqStart)+" ms.");
+ }
+ } else {
+ if(log.isInfoEnabled())
+ log.info("Manager["+getName()+"], skipping state transfer. No members active in cluster group.");
+ }//end if
+ mChannelStarted = true;
+ } catch ( Exception x ) {
+ log.error("Unable to start SimpleTcpReplicationManager",x);
+ }
+ }
+
+ /**
+ * Gracefully terminate the active use of the public methods of this
+ * component. This method should be the last one called on a given
+ * instance of this component.<BR>
+ * This will disconnect the cluster communication channel and stop the listener thread.
+ * @exception IllegalStateException if this component has not been started
+ * @exception LifecycleException if this component detects a fatal error
+ * that needs to be reported
+ */
+ public void stop() throws LifecycleException
+ {
+ mManagerRunning = false;
+ mChannelStarted = false;
+ super.stop();
+ //stop the javagroup channel
+ try
+ {
+ this.sessions.clear();
+ cluster.removeManager(getName(),this);
+// mReplicationListener.stopListening();
+// mReplicationTransmitter.stop();
+// service.stop();
+// service = null;
+ }
+ catch ( Exception x )
+ {
+ log.error("Unable to stop SimpleTcpReplicationManager",x);
+ }
+ }
+
+ public void setDistributable(boolean dist) {
+ this.distributable = dist;
+ }
+
+ public boolean getDistributable() {
+ return distributable;
+ }
+
+ /**
+ * This method is called by the received thread when a SessionMessage has
+ * been received from one of the other nodes in the cluster.
+ * @param msg - the message received
+ * @param sender - the sender of the message, this is used if we receive a
+ * EVT_GET_ALL_SESSION message, so that we only reply to
+ * the requesting node
+ */
+ protected void messageReceived( SessionMessage msg, Member sender ) {
+ try {
+ if(log.isInfoEnabled()) {
+ log.debug("Received SessionMessage of type="+msg.getEventTypeString());
+ log.debug("Received SessionMessage sender="+sender);
+ }
+ switch ( msg.getEventType() ) {
+ case SessionMessage.EVT_GET_ALL_SESSIONS: {
+ //get a list of all the session from this manager
+ Object[] sessions = findSessions();
+ java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream();
+ java.io.ObjectOutputStream oout = new java.io.ObjectOutputStream(bout);
+ oout.writeInt(sessions.length);
+ for (int i=0; i<sessions.length; i++){
+ ReplicatedSession ses = (ReplicatedSession)sessions[i];
+ oout.writeUTF(ses.getIdInternal());
+ byte[] data = writeSession(ses);
+ oout.writeObject(data);
+ }//for
+ //don't send a message if we don't have to
+ oout.flush();
+ oout.close();
+ byte[] data = bout.toByteArray();
+ SessionMessage newmsg = new SessionMessageImpl(name,
+ SessionMessage.EVT_ALL_SESSION_DATA,
+ data, "SESSION-STATE","SESSION-STATE-"+getName());
+ cluster.send(newmsg, sender);
+ break;
+ }
+ case SessionMessage.EVT_ALL_SESSION_DATA: {
+ java.io.ByteArrayInputStream bin =
+ new java.io.ByteArrayInputStream(msg.getSession());
+ java.io.ObjectInputStream oin = new java.io.ObjectInputStream(bin);
+ int size = oin.readInt();
+ for ( int i=0; i<size; i++) {
+ String id = oin.readUTF();
+ byte[] data = (byte[])oin.readObject();
+ Session session = readSession(data,id);
+ }//for
+ stateTransferred=true;
+ break;
+ }
+ case SessionMessage.EVT_SESSION_CREATED: {
+ Session session = this.readSession(msg.getSession(),msg.getSessionID());
+ if ( log.isDebugEnabled() ) {
+ log.debug("Received replicated session=" + session +
+ " isValid=" + session.isValid());
+ }
+ break;
+ }
+ case SessionMessage.EVT_SESSION_EXPIRED: {
+ Session session = findSession(msg.getSessionID());
+ if ( session != null ) {
+ session.expire();
+ this.remove(session);
+ }//end if
+ break;
+ }
+ case SessionMessage.EVT_SESSION_ACCESSED :{
+ Session session = findSession(msg.getSessionID());
+ if ( session != null ) {
+ session.access();
+ session.endAccess();
+ }
+ break;
+ }
+ default: {
+ //we didn't recognize the message type, do nothing
+ break;
+ }
+ }//switch
+ }
+ catch ( Exception x )
+ {
+ log.error("Unable to receive message through TCP channel",x);
+ }
+ }
+
+ public void messageDataReceived(ClusterMessage cmsg) {
+ try {
+ if ( cmsg instanceof SessionMessage ) {
+ SessionMessage msg = (SessionMessage)cmsg;
+ messageReceived(msg,
+ msg.getAddress() != null ? (Member) msg.getAddress() : null);
+ }
+ } catch(Throwable ex){
+ log.error("InMemoryReplicationManager.messageDataReceived()", ex);
+ }//catch
+ }
+
+ public boolean isStateTransferred() {
+ return stateTransferred;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ public boolean isNotifyListenersOnReplication() {
+ return notifyListenersOnReplication;
+ }
+ public void setNotifyListenersOnReplication(boolean notifyListenersOnReplication) {
+ this.notifyListenersOnReplication = notifyListenersOnReplication;
+ }
+
+
+ /*
+ * @see org.apache.catalina.ha.ClusterManager#getCluster()
+ */
+ public CatalinaCluster getCluster() {
+ return cluster;
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mbeans-descriptors PUBLIC
+ "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+ "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+<mbeans-descriptors>
+ <mbean name="JvmRouteBinderValve" description="mod_jk jvmRoute jsessionid cookie backup correction" domain="Catalina"
+ group="Valve" type="org.apache.catalina.ha.session.JvmRouteBinderValve">
+ <attribute name="className"
+ description="Fully qualified class name of the managed object"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="info"
+ description="describe version" type="java.lang.String" writeable="false"/>
+ <attribute name="enabled"
+ description="enable a jvm Route check" type="boolean"/>
+ <attribute name="numberOfSessions"
+ description="number of jvmRoute session corrections" type="long" writeable="false"/>
+ <attribute name="sessionIdAttribute"
+ description="Name of attribute with sessionid value before turnover a session"
+ type="java.lang.String"
+ />
+ <operation name="start" description="Stops the Cluster JvmRouteBinderValve"
+ impact="ACTION" returnType="void"/>
+ <operation name="stop" description="Stops the Cluster JvmRouteBinderValve"
+ impact="ACTION" returnType="void"/>
+ </mbean>
+ <mbean name="JvmRouteSessionIDBinderListener"
+ description="Monitors the jvmRoute activity"
+ domain="Catalina"
+ group="Listener"
+ type="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener">
+ <attribute name="info"
+ description="describe version" type="java.lang.String" writeable="false"/>
+ <attribute name="numberOfSessions"
+ description="number of jvmRoute session corrections"
+ type="long"
+ writeable="false"/>
+ </mbean>
+
+ <mbean name="DeltaManager"
+ description="Cluster Manager implementation of the Manager interface"
+ domain="Catalina"
+ group="Manager"
+ type="org.apache.catalina.ha.session.DeltaManager">
+
+ <attribute name="info"
+ description="describe version"
+ type="java.lang.String"
+ writeable="false"/>
+
+ <attribute name="algorithm"
+ description="The message digest algorithm to be used when generating
+ session identifiers"
+ type="java.lang.String"/>
+
+ <attribute name="randomFile"
+ description="File source of random - /dev/urandom or a pipe"
+ type="java.lang.String"/>
+
+ <attribute name="className"
+ description="Fully qualified class name of the managed object"
+ type="java.lang.String"
+ writeable="false"/>
+
+ <attribute name="distributable"
+ description="The distributable flag for Sessions created by this
+ Manager"
+ type="boolean"/>
+
+ <attribute name="entropy"
+ description="A String initialization parameter used to increase the
+ entropy of the initialization of our random number
+ generator"
+ type="java.lang.String"/>
+
+ <attribute name="maxActiveSessions"
+ description="The maximum number of active Sessions allowed, or -1
+ for no limit"
+ type="int"/>
+
+ <attribute name="maxInactiveInterval"
+ description="The default maximum inactive interval for Sessions
+ created by this Manager"
+ type="int"/>
+
+ <attribute name="processExpiresFrequency"
+ description="The frequency of the manager checks (expiration and passivation)"
+ type="int"/>
+
+ <attribute name="sessionIdLength"
+ description="The session id length (in bytes) of Sessions
+ created by this Manager"
+ type="int"/>
+
+ <attribute name="name"
+ description="The descriptive name of this Manager implementation
+ (for logging)"
+ type="java.lang.String"
+ writeable="false"/>
+
+ <attribute name="activeSessions"
+ description="Number of active sessions at this moment"
+ type="int"
+ writeable="false"/>
+
+ <attribute name="sessionCounter"
+ description="Total number of sessions created by this manager"
+ type="int" />
+
+ <attribute name="sessionReplaceCounter"
+ description="Total number of replaced sessions that load from external nodes"
+ type="long" />
+
+ <attribute name="maxActive"
+ description="Maximum number of active sessions so far"
+ type="int" />
+
+ <attribute name="sessionMaxAliveTime"
+ description="Longest time an expired session had been alive"
+ type="int" />
+
+ <attribute name="sessionAverageAliveTime"
+ description="Average time an expired session had been alive"
+ type="int" />
+
+ <attribute name="sendClusterDomainOnly"
+ is="true"
+ description="The sendClusterDomainOnly flag send sessions only to members as same cluster domain"
+ type="boolean"/>
+
+ <attribute name="rejectedSessions"
+ description="Number of sessions we rejected due to maxActive beeing reached"
+ type="int" />
+
+ <attribute name="expiredSessions"
+ description="Number of sessions that expired ( doesn't include explicit invalidations )"
+ type="int" />
+
+ <attribute name="stateTransferTimeout"
+ description="state transfer timeout in sec"
+ type="int"/>
+
+ <attribute name="processingTime"
+ description="Time spent doing housekeeping and expiration"
+ type="long" />
+
+ <attribute name="duplicates"
+ description="Number of duplicated session ids generated"
+ type="int" />
+
+ <attribute name="counterReceive_EVT_GET_ALL_SESSIONS"
+ description="Count receive EVT_GET_ALL_SESSIONS messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterReceive_EVT_ALL_SESSION_DATA"
+ description="Count receive EVT_ALL_SESSION_DATA messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterReceive_EVT_SESSION_CREATED"
+ description="Count receive EVT_SESSION_CREATED messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterReceive_EVT_SESSION_DELTA"
+ description="Count receive EVT_SESSION_DELTA messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterReceive_EVT_SESSION_ACCESSED"
+ description="Count receive EVT_SESSION_ACCESSED messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterReceive_EVT_SESSION_EXPIRED"
+ description="Count receive EVT_SESSION_EXPIRED messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE"
+ description="Count receive EVT_ALL_SESSION_TRANSFERCOMPLETE messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterSend_EVT_GET_ALL_SESSIONS"
+ description="Count send EVT_GET_ALL_SESSIONS messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterSend_EVT_ALL_SESSION_DATA"
+ description="Count send EVT_ALL_SESSION_DATA messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterSend_EVT_SESSION_CREATED"
+ description="Count send EVT_SESSION_CREATED messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterSend_EVT_SESSION_DELTA"
+ description="Count send EVT_SESSION_DELTA messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterSend_EVT_SESSION_ACCESSED"
+ description="Count send EVT_SESSION_ACCESSED messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterSend_EVT_SESSION_EXPIRED"
+ description="Count send EVT_SESSION_EXPIRED messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE"
+ description="Count send EVT_ALL_SESSION_TRANSFERCOMPLETE messages"
+ type="long"
+ writeable="false" />
+
+ <attribute name="counterNoStateTransfered"
+ description="Count the failed session transfers noStateTransfered"
+ type="int"
+ writeable="false" />
+
+ <attribute name="receivedQueueSize"
+ description="length of receive queue size when session received from other node"
+ type="int"
+ writeable="false" />
+
+ <attribute name="expireSessionsOnShutdown"
+ is="true"
+ description="exipre all sessions cluster wide as one node goes down"
+ type="boolean" />
+
+ <attribute name="notifyListenersOnReplication"
+ is="true"
+ description="Send session attribute change events on backup nodes"
+ type="boolean" />
+
+ <attribute name="notifySessionListenersOnReplication"
+ is="true"
+ description="Send session start/stop events on backup nodes"
+ type="boolean" />
+
+ <attribute name="sendAllSessions"
+ is="true"
+ description="Send all sessions at one big block"
+ type="boolean" />
+
+ <attribute name="sendAllSessionsSize"
+ description="session block size when sendAllSessions=false (default=1000)"
+ type="int" />
+
+ <attribute name="sendAllSessionsWaitTime"
+ description="wait time between send session block (default 2 sec)"
+ type="int" />
+
+ <operation name="listSessionIds"
+ description="Return the list of active session ids"
+ impact="ACTION"
+ returnType="java.lang.String">
+ </operation>
+
+ <operation name="getSessionAttribute"
+ description="Return a session attribute"
+ impact="ACTION"
+ returnType="java.lang.String">
+ <parameter name="sessionId"
+ description="Id of the session"
+ type="java.lang.String"/>
+ <parameter name="key"
+ description="key of the attribute"
+ type="java.lang.String"/>
+ </operation>
+
+ <operation name="expireSession"
+ description="Expire a session"
+ impact="ACTION"
+ returnType="void">
+ <parameter name="sessionId"
+ description="Id of the session"
+ type="java.lang.String"/>
+ </operation>
+
+ <operation name="getLastAccessedTime"
+ description="Get the last access time"
+ impact="ACTION"
+ returnType="java.lang.String">
+ <parameter name="sessionId"
+ description="Id of the session"
+ type="java.lang.String"/>
+ </operation>
+
+ <operation name="expireAllLocalSessions"
+ description="Exipre all active local sessions and replicate the invalid sessions"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="processExpires"
+ description="force process to expire sessions"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="getAllClusterSessions"
+ description="send to oldest cluster member that this node need all cluster sessions (resync member)"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+</mbeans-descriptors>
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.tcp;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.ha.tcp</code>
+ * package.
+ *
+ * @author Peter Rossbach
+ * @version $Revision: 303753 $ $Date: 2005-03-14 15:24:30 -0600 (Mon, 14 Mar 2005) $
+ */
+
+public class Constants {
+
+ public static final String Package = "org.apache.catalina.ha.tcp";
+
+}
--- /dev/null
+AsyncSocketSender.create.thread=Create sender [{0}:{1,number,integer}] queue thread to tcp background replication
+AsyncSocketSender.queue.message=Queue message to [{0}:{1,number,integer}] id=[{2}] size={3}
+AsyncSocketSender.send.error=Unable to asynchronously send session with id=[{0}] - message will be ignored.
+AsyncSocketSender.queue.empty=Queue in sender [{0}:{1,number,integer}] returned null element!
+cluster.mbean.register.already=MBean {0} already registered!
+FastAsyncSocketSender.setThreadPriority=[{0}:{1,number,integer}] set priority to {2}
+FastAsyncSocketSender.min.exception=[{0}:{1,number,integer}] new priority {2} < MIN_PRIORITY
+FastAsyncSocketSender.max.exception=[{0}:{1,number,integer}] new priority {2} > MAX_PRIORITY
+IDataSender.ack.eof=EOF reached at local port [{0}:{1,number,integer}]
+IDataSender.ack.receive=Got ACK at local port [{0}:{1,number,integer}]
+IDataSender.ack.missing=Unable to read acknowledgement from [{0}:{1,number,integer}] in {2,number,integer} ms. Disconnecting socket, and trying again.
+IDataSender.ack.read=Read wait ack char '{2}' [{0}:{1,number,integer}]
+IDataSender.ack.start=Waiting for ACK message [{0}:{1,number,integer}]
+IDataSender.ack.wrong=Missing correct ACK after 10 bytes read at local port [{0}:{1,number,integer}]
+IDataSender.closeSocket=Sender close socket to [{0}:{1,number,integer}] (close count {2,number,integer})
+IDataSender.connect=Sender connect to [{0}:{1,number,integer}] (connect count {2,number,integer})
+IDataSender.create=Create sender [{0}:{1,number,integer}]
+IDataSender.disconnect=Sender disconnect from [{0}:{1,number,integer}] (disconnect count {2,number,integer})
+IDataSender.message.disconnect=Message transfered: Sender can't disconnect from [{0}:{1,number,integer}]
+IDataSender.message.create=Message transfered: Sender can't create current socket [{0}:{1,number,integer}]
+IDataSender.openSocket=Sender open socket to [{0}:{1,number,integer}] (open count {2,number,integer})
+IDataSender.openSocket.failure=Open sender socket [{0}:{1,number,integer}] failure! (open failure count {2,number,integer})
+IDataSender.send.again=Send data again to [{0}:{1,number,integer}]
+IDataSender.send.crash=Send message crashed [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+IDataSender.send.message=Send message to [{0}:{1,number,integer}] id=[{2}] size={3,number,integer}
+IDataSender.send.lost=Message lost: [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+IDataSender.senderModes.Configured=Configured a data replication sender for mode {0}
+IDataSender.senderModes.Instantiate=Can't instantiate a data replication sender of class {0}
+IDataSender.senderModes.Missing=Can't configure a data replication sender for mode {0}
+IDataSender.senderModes.Resources=Can't load data replication sender mapping list
+IDataSender.stats=Send stats from [{0}:{1,number,integer}], Nr of bytes sent={2,number,integer} over {3} = {4,number,integer} bytes/request, processing time {5,number,integer} msec, avg processing time {6,number,integer} msec
+PoolSocketSender.senderQueue.sender.failed=PoolSocketSender create new sender to [{0}:{1,number,integer}] failed
+PoolSocketSender.noMoreSender=No socket sender available for client [{0}:{1,number,integer}] did it disappeared?
+ReplicationTransmitter.getProperty=get property {0}
+ReplicationTransmitter.setProperty=set property {0}: {1} old value {2}
+ReplicationTransmitter.started=Start ClusterSender at cluster {0} with name {1}
+ReplicationTransmitter.stopped=Stopped ClusterSender at cluster {0} with name {1}
+ReplicationValve.crossContext.add=add Cross Context session replication container to replicationValve threadlocal
+ReplicationValve.crossContext.registerSession=register Cross context session id={0} from context {1}
+ReplicationValve.crossContext.remove=remove Cross Context session replication container from replicationValve threadlocal
+ReplicationValve.crossContext.sendDelta=send Cross Context session delta from context {0}.
+ReplicationValve.filter.loading=Loading request filters={0}
+ReplicationValve.filter.token=Request filter={0}
+ReplicationValve.filter.token.failure=Unable to compile filter={0}
+ReplicationValve.invoke.uri=Invoking replication request on {0}
+ReplicationValve.nocluster=No cluster configured for this request.
+ReplicationValve.resetDeltaRequest=Cluster is standalone: reset Session Request Delta at context {0}
+ReplicationValve.send.failure=Unable to perform replication request.
+ReplicationValve.send.invalid.failure=Unable to send session [id={0}] invalid message over cluster.
+ReplicationValve.session.found=Context {0}: Found session {1} but it isn't a ClusterSession.
+ReplicationValve.session.indicator=Context {0}: Primarity of session {0} in request attribute {1} is {2}.
+ReplicationValve.session.invalid=Context {0}: Requested session {1} is invalid, removed or not replicated at this node.
+ReplicationValve.stats=Average request time= {0} ms for Cluster overhead time={1} ms for {2} requests {3} filter requests {4} send requests {5} cross context requests (Request={6} ms Cluster={7} ms).
+SimpleTcpCluster.event.log=Cluster receive listener event {0} with data {1}
+SimpleTcpCluster.getProperty=get property {0}
+SimpleTcpCluster.setProperty=set property {0}: {1} old value {2}
+SimpleTcpCluster.default.addClusterListener=Add Default ClusterListener at cluster {0}
+SimpleTcpCluster.default.addClusterValves=Add Default ClusterValves at cluster {0}
+SimpleTcpCluster.default.addClusterReceiver=Add Default ClusterReceiver at cluster {0}
+SimpleTcpCluster.default.addClusterSender=Add Default ClusterSender at cluster {0}
+SimpleTcpCluster.default.addMembershipService=Add Default Membership Service at cluster {0}
+SimpleTcpCluster.log.receive=RECEIVE {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4} {5}
+SimpleTcpCluster.log.send=SEND {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4}
+SimpleTcpCluster.log.send.all=SEND {0,date}:{0,time} {1,number} - {2}
+SocketReplictionListener.allreadyExists=ServerSocket [{0}:{1}] allready started!
+SocketReplictionListener.accept.failure=ServerSocket [{0}:{1}] - Exception to start thread or accept server socket
+SocketReplictionListener.open=Open Socket at [{0}:{1}]
+SocketReplictionListener.openclose.failure=ServerSocket [{0}:{1}] - Exception to open or close server socket
+SocketReplictionListener.portbusy=Port busy at [{0}:{i}] - reason [{2}]
+SocketReplictionListener.serverSocket.notExists=Fatal error: Receiver socket not bound address={0} port={1} maxport={2}
+SocketReplictionListener.timeout=Receiver ServerSocket no started [{0}:{1}] - reason: timeout={2} or listen={3}
+SocketReplictionListener.unlockSocket.failure=UnLocksocket failure at ServerSocket [{0:{1}]
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.tcp;
+
+import java.io.IOException;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Iterator;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.Context;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.ha.ClusterSession;
+import org.apache.catalina.ha.ClusterValve;
+import org.apache.catalina.ha.session.DeltaManager;
+import org.apache.catalina.ha.session.DeltaSession;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.util.StringManager;
+import org.apache.catalina.valves.ValveBase;
+
+/**
+ * <p>Implementation of a Valve that logs interesting contents from the
+ * specified Request (before processing) and the corresponding Response
+ * (after processing). It is especially useful in debugging problems
+ * related to headers and cookies.</p>
+ *
+ * <p>This Valve may be attached to any Container, depending on the granularity
+ * of the logging you wish to perform.</p>
+ *
+ * <p>primaryIndicator=true, then the request attribute <i>org.apache.catalina.ha.tcp.isPrimarySession.</i>
+ * is set true, when request processing is at sessions primary node.
+ * </p>
+ *
+ * @author Craig R. McClanahan
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Revision: 375709 $ $Date: 2006-02-07 15:13:25 -0600 (Tue, 07 Feb 2006) $
+ */
+
+public class ReplicationValve
+ extends ValveBase implements ClusterValve {
+
+ private static org.apache.commons.logging.Log log =
+ org.apache.commons.logging.LogFactory.getLog( ReplicationValve.class );
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * The descriptive information related to this implementation.
+ */
+ private static final String info =
+ "org.apache.catalina.ha.tcp.ReplicationValve/2.0";
+
+
+ /**
+ * The StringManager for this package.
+ */
+ protected static StringManager sm =
+ StringManager.getManager(Constants.Package);
+
+ private CatalinaCluster cluster = null ;
+
+ /**
+ * holds file endings to not call for like images and others
+ */
+ protected java.util.regex.Pattern[] reqFilters = new java.util.regex.Pattern[0];
+
+ /**
+ * Orginal filter
+ */
+ protected String filter ;
+
+ /**
+ * crossContext session container
+ */
+ protected ThreadLocal crossContextSessions = new ThreadLocal() ;
+
+ /**
+ * doProcessingStats (default = off)
+ */
+ protected boolean doProcessingStats = false;
+
+ protected long totalRequestTime = 0;
+ protected long totalSendTime = 0;
+ protected long nrOfRequests = 0;
+ protected long lastSendTime = 0;
+ protected long nrOfFilterRequests = 0;
+ protected long nrOfSendRequests = 0;
+ protected long nrOfCrossContextSendRequests = 0;
+
+ /**
+ * must primary change indicator set
+ */
+ protected boolean primaryIndicator = false ;
+
+ /**
+ * Name of primary change indicator as request attribute
+ */
+ protected String primaryIndicatorName = "org.apache.catalina.ha.tcp.isPrimarySession";
+
+ // ------------------------------------------------------------- Properties
+
+ public ReplicationValve() {
+ }
+
+ /**
+ * Return descriptive information about this Valve implementation.
+ */
+ public String getInfo() {
+
+ return (info);
+
+ }
+
+ /**
+ * @return Returns the cluster.
+ */
+ public CatalinaCluster getCluster() {
+ return cluster;
+ }
+
+ /**
+ * @param cluster The cluster to set.
+ */
+ public void setCluster(CatalinaCluster cluster) {
+ this.cluster = cluster;
+ }
+
+ /**
+ * @return Returns the filter
+ */
+ public String getFilter() {
+ return filter ;
+ }
+
+ /**
+ * compile filter string to regular expressions
+ * @see Pattern#compile(java.lang.String)
+ * @param filter
+ * The filter to set.
+ */
+ public void setFilter(String filter) {
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("ReplicationValve.filter.loading", filter));
+ this.filter = filter;
+ StringTokenizer t = new StringTokenizer(filter, ";");
+ this.reqFilters = new Pattern[t.countTokens()];
+ int i = 0;
+ while (t.hasMoreTokens()) {
+ String s = t.nextToken();
+ if (log.isTraceEnabled())
+ log.trace(sm.getString("ReplicationValve.filter.token", s));
+ try {
+ reqFilters[i++] = Pattern.compile(s);
+ } catch (Exception x) {
+ log.error(sm.getString("ReplicationValve.filter.token.failure",
+ s), x);
+ }
+ }
+ }
+
+ /**
+ * @return Returns the primaryIndicator.
+ */
+ public boolean isPrimaryIndicator() {
+ return primaryIndicator;
+ }
+
+ /**
+ * @param primaryIndicator The primaryIndicator to set.
+ */
+ public void setPrimaryIndicator(boolean primaryIndicator) {
+ this.primaryIndicator = primaryIndicator;
+ }
+
+ /**
+ * @return Returns the primaryIndicatorName.
+ */
+ public String getPrimaryIndicatorName() {
+ return primaryIndicatorName;
+ }
+
+ /**
+ * @param primaryIndicatorName The primaryIndicatorName to set.
+ */
+ public void setPrimaryIndicatorName(String primaryIndicatorName) {
+ this.primaryIndicatorName = primaryIndicatorName;
+ }
+
+ /**
+ * Calc processing stats
+ */
+ public boolean isDoProcessingStats() {
+ return doProcessingStats;
+ }
+
+ /**
+ * Set Calc processing stats
+ * @see #resetStatistics()
+ */
+ public void setDoProcessingStats(boolean doProcessingStats) {
+ this.doProcessingStats = doProcessingStats;
+ }
+
+ /**
+ * @return Returns the lastSendTime.
+ */
+ public long getLastSendTime() {
+ return lastSendTime;
+ }
+
+ /**
+ * @return Returns the nrOfRequests.
+ */
+ public long getNrOfRequests() {
+ return nrOfRequests;
+ }
+
+ /**
+ * @return Returns the nrOfFilterRequests.
+ */
+ public long getNrOfFilterRequests() {
+ return nrOfFilterRequests;
+ }
+
+ /**
+ * @return Returns the nrOfCrossContextSendRequests.
+ */
+ public long getNrOfCrossContextSendRequests() {
+ return nrOfCrossContextSendRequests;
+ }
+
+ /**
+ * @return Returns the nrOfSendRequests.
+ */
+ public long getNrOfSendRequests() {
+ return nrOfSendRequests;
+ }
+
+ /**
+ * @return Returns the totalRequestTime.
+ */
+ public long getTotalRequestTime() {
+ return totalRequestTime;
+ }
+
+ /**
+ * @return Returns the totalSendTime.
+ */
+ public long getTotalSendTime() {
+ return totalSendTime;
+ }
+
+ /**
+ * @return Returns the reqFilters.
+ */
+ protected java.util.regex.Pattern[] getReqFilters() {
+ return reqFilters;
+ }
+
+ /**
+ * @param reqFilters The reqFilters to set.
+ */
+ protected void setReqFilters(java.util.regex.Pattern[] reqFilters) {
+ this.reqFilters = reqFilters;
+ }
+
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * Register all cross context sessions inside endAccess.
+ * Use a list with contains check, that the Portlet API can include a lot of fragments from same or
+ * different applications with session changes.
+ *
+ * @param session cross context session
+ */
+ public void registerReplicationSession(DeltaSession session) {
+ List sessions = (List)crossContextSessions.get();
+ if(sessions != null) {
+ if(!sessions.contains(session)) {
+ if(log.isDebugEnabled())
+ log.debug(sm.getString("ReplicationValve.crossContext.registerSession",
+ session.getIdInternal(),
+ session.getManager().getContainer().getName()));
+ sessions.add(session);
+ }
+ }
+ }
+
+ /**
+ * Log the interesting request parameters, invoke the next Valve in the
+ * sequence, and log the interesting response parameters.
+ *
+ * @param request The servlet request to be processed
+ * @param response The servlet response to be created
+ *
+ * @exception IOException if an input/output error occurs
+ * @exception ServletException if a servlet error occurs
+ */
+ public void invoke(Request request, Response response)
+ throws IOException, ServletException
+ {
+ long totalstart = 0;
+
+ //this happens before the request
+ if(isDoProcessingStats()) {
+ totalstart = System.currentTimeMillis();
+ }
+ if (primaryIndicator) {
+ createPrimaryIndicator(request) ;
+ }
+ Context context = request.getContext();
+ boolean isCrossContext = context != null
+ && context instanceof StandardContext
+ && ((StandardContext) context).getCrossContext();
+ try {
+ if(isCrossContext) {
+ if(log.isDebugEnabled())
+ log.debug(sm.getString("ReplicationValve.crossContext.add"));
+ //FIXME add Pool of Arraylists
+ crossContextSessions.set(new ArrayList());
+ }
+ getNext().invoke(request, response);
+ Manager manager = request.getContext().getManager();
+ if (manager != null && manager instanceof ClusterManager) {
+ ClusterManager clusterManager = (ClusterManager) manager;
+ CatalinaCluster containerCluster = (CatalinaCluster) getContainer().getCluster();
+ if (containerCluster == null) {
+ if (log.isWarnEnabled())
+ log.warn(sm.getString("ReplicationValve.nocluster"));
+ return;
+ }
+ // valve cluster can access manager - other cluster handle replication
+ // at host level - hopefully!
+ if(containerCluster.getManager(clusterManager.getName()) == null)
+ return ;
+ if(containerCluster.hasMembers()) {
+ sendReplicationMessage(request, totalstart, isCrossContext, clusterManager, containerCluster);
+ } else {
+ resetReplicationRequest(request,isCrossContext);
+ }
+ }
+ } finally {
+ // Array must be remove: Current master request send endAccess at recycle.
+ // Don't register this request session again!
+ if(isCrossContext) {
+ if(log.isDebugEnabled())
+ log.debug(sm.getString("ReplicationValve.crossContext.remove"));
+ // crossContextSessions.remove() only exist at Java 5
+ // register ArrayList at a pool
+ crossContextSessions.set(null);
+ }
+ }
+ }
+
+
+ /**
+ * reset the active statitics
+ */
+ public void resetStatistics() {
+ totalRequestTime = 0 ;
+ totalSendTime = 0 ;
+ lastSendTime = 0 ;
+ nrOfFilterRequests = 0 ;
+ nrOfRequests = 0 ;
+ nrOfSendRequests = 0;
+ nrOfCrossContextSendRequests = 0;
+ }
+
+ /**
+ * Return a String rendering of this object.
+ */
+ public String toString() {
+
+ StringBuffer sb = new StringBuffer("ReplicationValve[");
+ if (container != null)
+ sb.append(container.getName());
+ sb.append("]");
+ return (sb.toString());
+
+ }
+
+ // --------------------------------------------------------- Protected Methods
+
+ /**
+ * @param request
+ * @param totalstart
+ * @param isCrossContext
+ * @param clusterManager
+ * @param containerCluster
+ */
+ protected void sendReplicationMessage(Request request, long totalstart, boolean isCrossContext, ClusterManager clusterManager, CatalinaCluster containerCluster) {
+ //this happens after the request
+ long start = 0;
+ if(isDoProcessingStats()) {
+ start = System.currentTimeMillis();
+ }
+ try {
+ // send invalid sessions
+ // DeltaManager returns String[0]
+ if (!(clusterManager instanceof DeltaManager))
+ sendInvalidSessions(clusterManager, containerCluster);
+ // send replication
+ sendSessionReplicationMessage(request, clusterManager, containerCluster);
+ if(isCrossContext)
+ sendCrossContextSession(containerCluster);
+ } catch (Exception x) {
+ // FIXME we have a lot of sends, but the trouble with one node stops the correct replication to other nodes!
+ log.error(sm.getString("ReplicationValve.send.failure"), x);
+ } finally {
+ // FIXME this stats update are not cheap!!
+ if(isDoProcessingStats()) {
+ updateStats(totalstart,start);
+ }
+ }
+ }
+
+ /**
+ * Send all changed cross context sessions to backups
+ * @param containerCluster
+ */
+ protected void sendCrossContextSession(CatalinaCluster containerCluster) {
+ Object sessions = crossContextSessions.get();
+ if(sessions != null && sessions instanceof List
+ && ((List)sessions).size() >0) {
+ for(Iterator iter = ((List)sessions).iterator(); iter.hasNext() ;) {
+ Session session = (Session)iter.next();
+ if(log.isDebugEnabled())
+ log.debug(sm.getString("ReplicationValve.crossContext.sendDelta",
+ session.getManager().getContainer().getName() ));
+ sendMessage(session,(ClusterManager)session.getManager(),containerCluster);
+ if(isDoProcessingStats()) {
+ nrOfCrossContextSendRequests++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Fix memory leak for long sessions with many changes, when no backup member exists!
+ * @param request current request after responce is generated
+ * @param isCrossContext check crosscontext threadlocal
+ */
+ protected void resetReplicationRequest(Request request, boolean isCrossContext) {
+ Session contextSession = request.getSessionInternal(false);
+ if(contextSession != null & contextSession instanceof DeltaSession){
+ resetDeltaRequest(contextSession);
+ ((DeltaSession)contextSession).setPrimarySession(true);
+ }
+ if(isCrossContext) {
+ Object sessions = crossContextSessions.get();
+ if(sessions != null && sessions instanceof List
+ && ((List)sessions).size() >0) {
+ Iterator iter = ((List)sessions).iterator();
+ for(; iter.hasNext() ;) {
+ Session session = (Session)iter.next();
+ resetDeltaRequest(session);
+ if(session instanceof DeltaSession)
+ ((DeltaSession)contextSession).setPrimarySession(true);
+
+ }
+ }
+ }
+ }
+
+ /**
+ * Reset DeltaRequest from session
+ * @param session HttpSession from current request or cross context session
+ */
+ protected void resetDeltaRequest(Session session) {
+ if(log.isDebugEnabled()) {
+ log.debug(sm.getString("ReplicationValve.resetDeltaRequest" ,
+ session.getManager().getContainer().getName() ));
+ }
+ ((DeltaSession)session).resetDeltaRequest();
+ }
+
+ /**
+ * Send Cluster Replication Request
+ * @param request current request
+ * @param manager session manager
+ * @param cluster replication cluster
+ */
+ protected void sendSessionReplicationMessage(Request request,
+ ClusterManager manager, CatalinaCluster cluster) {
+ Session session = request.getSessionInternal(false);
+ if (session != null) {
+ String uri = request.getDecodedRequestURI();
+ // request without session change
+ if (!isRequestWithoutSessionChange(uri)) {
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("ReplicationValve.invoke.uri", uri));
+ sendMessage(session,manager,cluster);
+ } else
+ if(isDoProcessingStats())
+ nrOfFilterRequests++;
+ }
+
+ }
+
+ /**
+ * Send message delta message from request session
+ * @param request current request
+ * @param manager session manager
+ * @param cluster replication cluster
+ */
+ protected void sendMessage(Session session,
+ ClusterManager manager, CatalinaCluster cluster) {
+ String id = session.getIdInternal();
+ if (id != null) {
+ send(manager, cluster, id);
+ }
+ }
+
+ /**
+ * send manager requestCompleted message to cluster
+ * @param manager SessionManager
+ * @param cluster replication cluster
+ * @param sessionId sessionid from the manager
+ * @see DeltaManager#requestCompleted(String)
+ * @see SimpleTcpCluster#send(ClusterMessage)
+ */
+ protected void send(ClusterManager manager, CatalinaCluster cluster, String sessionId) {
+ ClusterMessage msg = manager.requestCompleted(sessionId);
+ if (msg != null) {
+ if(manager.isSendClusterDomainOnly()) {
+ cluster.sendClusterDomain(msg);
+ } else {
+ cluster.send(msg);
+ }
+ if(isDoProcessingStats())
+ nrOfSendRequests++;
+ }
+ }
+
+ /**
+ * check for session invalidations
+ * @param manager
+ * @param cluster
+ */
+ protected void sendInvalidSessions(ClusterManager manager, CatalinaCluster cluster) {
+ String[] invalidIds=manager.getInvalidatedSessions();
+ if ( invalidIds.length > 0 ) {
+ for ( int i=0;i<invalidIds.length; i++ ) {
+ try {
+ send(manager,cluster,invalidIds[i]);
+ } catch ( Exception x ) {
+ log.error(sm.getString("ReplicationValve.send.invalid.failure",invalidIds[i]),x);
+ }
+ }
+ }
+ }
+
+ /**
+ * is request without possible session change
+ * @param uri The request uri
+ * @return True if no session change
+ */
+ protected boolean isRequestWithoutSessionChange(String uri) {
+
+ boolean filterfound = false;
+
+ for (int i = 0; (i < reqFilters.length) && (!filterfound); i++) {
+ java.util.regex.Matcher matcher = reqFilters[i].matcher(uri);
+ filterfound = matcher.matches();
+ }
+ return filterfound;
+ }
+
+ /**
+ * protocol cluster replications stats
+ * @param requestTime
+ * @param clusterTime
+ */
+ protected void updateStats(long requestTime, long clusterTime) {
+ synchronized(this) {
+ lastSendTime=System.currentTimeMillis();
+ totalSendTime+=lastSendTime - clusterTime;
+ totalRequestTime+=lastSendTime - requestTime;
+ nrOfRequests++;
+ }
+ if(log.isInfoEnabled()) {
+ if ( (nrOfRequests % 100) == 0 ) {
+ log.info(sm.getString("ReplicationValve.stats",
+ new Object[]{
+ new Long(totalRequestTime/nrOfRequests),
+ new Long(totalSendTime/nrOfRequests),
+ new Long(nrOfRequests),
+ new Long(nrOfSendRequests),
+ new Long(nrOfCrossContextSendRequests),
+ new Long(nrOfFilterRequests),
+ new Long(totalRequestTime),
+ new Long(totalSendTime)}));
+ }
+ }
+ }
+
+
+ /**
+ * Mark Request that processed at primary node with attribute
+ * primaryIndicatorName
+ *
+ * @param request
+ * @throws IOException
+ */
+ protected void createPrimaryIndicator(Request request) throws IOException {
+ String id = request.getRequestedSessionId();
+ if ((id != null) && (id.length() > 0)) {
+ Manager manager = request.getContext().getManager();
+ Session session = manager.findSession(id);
+ if (session instanceof ClusterSession) {
+ ClusterSession cses = (ClusterSession) session;
+ if (cses != null) {
+ Boolean isPrimary = new Boolean(cses.isPrimarySession());
+ if (log.isDebugEnabled())
+ log.debug(sm.getString(
+ "ReplicationValve.session.indicator", request.getContext().getName(),id,
+ primaryIndicatorName, isPrimary));
+ request.setAttribute(primaryIndicatorName, isPrimary);
+ }
+ } else {
+ if (log.isDebugEnabled()) {
+ if (session != null) {
+ log.debug(sm.getString(
+ "ReplicationValve.session.found", request.getContext().getName(),id));
+ } else {
+ log.debug(sm.getString(
+ "ReplicationValve.session.invalid", request.getContext().getName(),id));
+ }
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.catalina.ha.tcp;
+
+import org.apache.catalina.tribes.Member;
+
+/**
+ * @author Peter Rossbach
+ * @version $Revision: 303987 $ $Date: 2005-07-08 15:50:30 -0500 (Fri, 08 Jul 2005) $
+ */
+public class SendMessageData {
+
+ private Object message ;
+ private Member destination ;
+ private Exception exception ;
+
+
+ /**
+ * @param message
+ * @param destination
+ * @param exception
+ */
+ public SendMessageData(Object message, Member destination,
+ Exception exception) {
+ super();
+ this.message = message;
+ this.destination = destination;
+ this.exception = exception;
+ }
+
+ /**
+ * @return Returns the destination.
+ */
+ public Member getDestination() {
+ return destination;
+ }
+ /**
+ * @param destination The destination to set.
+ */
+ public void setDestination(Member destination) {
+ this.destination = destination;
+ }
+ /**
+ * @return Returns the exception.
+ */
+ public Exception getException() {
+ return exception;
+ }
+ /**
+ * @param exception The exception to set.
+ */
+ public void setException(Exception exception) {
+ this.exception = exception;
+ }
+ /**
+ * @return Returns the message.
+ */
+ public Object getMessage() {
+ return message;
+ }
+ /**
+ * @param message The message to set.
+ */
+ public void setMessage(Object message) {
+ this.message = message;
+ }
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.tcp;
+
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Valve;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.ha.ClusterListener;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.ha.ClusterValve;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.group.GroupChannel;
+
+import org.apache.catalina.ha.session.DeltaManager;
+import org.apache.catalina.ha.util.IDynamicProperty;
+import org.apache.catalina.util.LifecycleSupport;
+import org.apache.catalina.util.StringManager;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tomcat.util.IntrospectionUtils;
+
+/**
+ * A <b>Cluster </b> implementation using simple multicast. Responsible for
+ * setting up a cluster and provides callers with a valid multicast
+ * receiver/sender.
+ *
+ * FIXME remove install/remove/start/stop context dummys
+ * FIXME wrote testcases
+ *
+ * @author Filip Hanik
+ * @author Remy Maucherat
+ * @author Peter Rossbach
+ * @version $Revision: 379550 $, $Date: 2006-02-21 12:06:35 -0600 (Tue, 21 Feb 2006) $
+ */
+public class SimpleTcpCluster
+ implements CatalinaCluster, Lifecycle, LifecycleListener, IDynamicProperty,
+ MembershipListener, ChannelListener{
+
+ public static Log log = LogFactory.getLog(SimpleTcpCluster.class);
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * Descriptive information about this component implementation.
+ */
+ protected static final String info = "SimpleTcpCluster/2.2";
+
+ public static final String BEFORE_MEMBERREGISTER_EVENT = "before_member_register";
+
+ public static final String AFTER_MEMBERREGISTER_EVENT = "after_member_register";
+
+ public static final String BEFORE_MANAGERREGISTER_EVENT = "before_manager_register";
+
+ public static final String AFTER_MANAGERREGISTER_EVENT = "after_manager_register";
+
+ public static final String BEFORE_MANAGERUNREGISTER_EVENT = "before_manager_unregister";
+
+ public static final String AFTER_MANAGERUNREGISTER_EVENT = "after_manager_unregister";
+
+ public static final String BEFORE_MEMBERUNREGISTER_EVENT = "before_member_unregister";
+
+ public static final String AFTER_MEMBERUNREGISTER_EVENT = "after_member_unregister";
+
+ public static final String SEND_MESSAGE_FAILURE_EVENT = "send_message_failure";
+
+ public static final String RECEIVE_MESSAGE_FAILURE_EVENT = "receive_message_failure";
+
+ /**
+ * Group channel.
+ */
+ protected Channel channel = new GroupChannel();
+
+
+ /**
+ * Name for logging purpose
+ */
+ protected String clusterImpName = "SimpleTcpCluster";
+
+ /**
+ * The string manager for this package.
+ */
+ protected StringManager sm = StringManager.getManager(Constants.Package);
+
+ /**
+ * The cluster name to join
+ */
+ protected String clusterName ;
+
+ /**
+ * The Container associated with this Cluster.
+ */
+ protected Container container = null;
+
+ /**
+ * The lifecycle event support for this component.
+ */
+ protected LifecycleSupport lifecycle = new LifecycleSupport(this);
+
+ /**
+ * Has this component been started?
+ */
+ protected boolean started = false;
+
+ /**
+ * The property change support for this component.
+ */
+ protected PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+ /**
+ * The context name <->manager association for distributed contexts.
+ */
+ protected Map managers = new HashMap();
+
+ private String managerClassName = "org.apache.catalina.ha.session.DeltaManager";
+
+
+ private List valves = new ArrayList();
+
+ private org.apache.catalina.ha.ClusterDeployer clusterDeployer;
+
+ /**
+ * Listeners of messages
+ */
+ protected List clusterListeners = new ArrayList();
+
+ /**
+ * Comment for <code>notifyLifecycleListenerOnFailure</code>
+ */
+ private boolean notifyLifecycleListenerOnFailure = false;
+
+ /**
+ * dynamic sender <code>properties</code>
+ */
+ private Map properties = new HashMap();
+
+ private int channelSendOptions =
+ Channel.SEND_OPTIONS_ASYNCHRONOUS |
+ Channel.SEND_OPTIONS_SYNCHRONIZED_ACK |
+ Channel.SEND_OPTIONS_USE_ACK;
+
+ // ------------------------------------------------------------- Properties
+
+ public SimpleTcpCluster() {
+ }
+
+ /**
+ * Return descriptive information about this Cluster implementation and the
+ * corresponding version number, in the format
+ * <code><description>/<version></code>.
+ */
+ public String getInfo() {
+ return (info);
+ }
+
+ /**
+ * Set the name of the cluster to join, if no cluster with this name is
+ * present create one.
+ *
+ * @param clusterName
+ * The clustername to join
+ */
+ public void setClusterName(String clusterName) {
+ this.clusterName = clusterName;
+ }
+
+ /**
+ * Return the name of the cluster that this Server is currently configured
+ * to operate within.
+ *
+ * @return The name of the cluster associated with this server
+ */
+ public String getClusterName() {
+ if(clusterName == null && container != null)
+ return container.getName() ;
+ return clusterName;
+ }
+
+ /**
+ * Set the Container associated with our Cluster
+ *
+ * @param container
+ * The Container to use
+ */
+ public void setContainer(Container container) {
+ Container oldContainer = this.container;
+ this.container = container;
+ support.firePropertyChange("container", oldContainer, this.container);
+ }
+
+ /**
+ * Get the Container associated with our Cluster
+ *
+ * @return The Container associated with our Cluster
+ */
+ public Container getContainer() {
+ return (this.container);
+ }
+
+ /**
+ * @return Returns the notifyLifecycleListenerOnFailure.
+ */
+ public boolean isNotifyLifecycleListenerOnFailure() {
+ return notifyLifecycleListenerOnFailure;
+ }
+
+ /**
+ * @param notifyListenerOnFailure
+ * The notifyLifecycleListenerOnFailure to set.
+ */
+ public void setNotifyLifecycleListenerOnFailure(
+ boolean notifyListenerOnFailure) {
+ boolean oldNotifyListenerOnFailure = this.notifyLifecycleListenerOnFailure;
+ this.notifyLifecycleListenerOnFailure = notifyListenerOnFailure;
+ support.firePropertyChange("notifyLifecycleListenerOnFailure",
+ oldNotifyListenerOnFailure,
+ this.notifyLifecycleListenerOnFailure);
+ }
+
+ public String getManagerClassName() {
+ if(managerClassName != null)
+ return managerClassName;
+ return (String)getProperty("manager.className");
+ }
+
+ public void setManagerClassName(String managerClassName) {
+ this.managerClassName = managerClassName;
+ }
+
+ /**
+ * Add cluster valve
+ * Cluster Valves are only add to container when cluster is started!
+ * @param valve The new cluster Valve.
+ */
+ public void addValve(Valve valve) {
+ if (valve instanceof ClusterValve)
+ valves.add(valve);
+ }
+
+ /**
+ * get all cluster valves
+ * @return current cluster valves
+ */
+ public Valve[] getValves() {
+ return (Valve[]) valves.toArray(new Valve[valves.size()]);
+ }
+
+ /**
+ * Get the cluster listeners associated with this cluster. If this Array has
+ * no listeners registered, a zero-length array is returned.
+ */
+ public ClusterListener[] findClusterListeners() {
+ if (clusterListeners.size() > 0) {
+ ClusterListener[] listener = new ClusterListener[clusterListeners.size()];
+ clusterListeners.toArray(listener);
+ return listener;
+ } else
+ return new ClusterListener[0];
+
+ }
+
+ /**
+ * add cluster message listener and register cluster to this listener
+ *
+ * @see org.apache.catalina.ha.CatalinaCluster#addClusterListener(org.apache.catalina.ha.MessageListener)
+ */
+ public void addClusterListener(ClusterListener listener) {
+ if (listener != null && !clusterListeners.contains(listener)) {
+ clusterListeners.add(listener);
+ listener.setCluster(this);
+ }
+ }
+
+ /**
+ * remove message listener and deregister Cluster from listener
+ *
+ * @see org.apache.catalina.ha.CatalinaCluster#removeClusterListener(org.apache.catalina.ha.MessageListener)
+ */
+ public void removeClusterListener(ClusterListener listener) {
+ if (listener != null) {
+ clusterListeners.remove(listener);
+ listener.setCluster(null);
+ }
+ }
+
+ /**
+ * get current Deployer
+ */
+ public org.apache.catalina.ha.ClusterDeployer getClusterDeployer() {
+ return clusterDeployer;
+ }
+
+ /**
+ * set a new Deployer, must be set before cluster started!
+ */
+ public void setClusterDeployer(
+ org.apache.catalina.ha.ClusterDeployer clusterDeployer) {
+ this.clusterDeployer = clusterDeployer;
+ }
+
+ public void setChannel(Channel channel) {
+ this.channel = channel;
+ }
+
+ /**
+ * has members
+ */
+ protected boolean hasMembers = false;
+ public boolean hasMembers() {
+ return hasMembers;
+ }
+
+ /**
+ * Get all current cluster members
+ * @return all members or empty array
+ */
+ public Member[] getMembers() {
+ return channel.getMembers();
+ }
+
+ /**
+ * Return the member that represents this node.
+ *
+ * @return Member
+ */
+ public Member getLocalMember() {
+ return channel.getLocalMember(true);
+ }
+
+ // ------------------------------------------------------------- dynamic
+ // manager property handling
+
+ /**
+ * JMX hack to direct use at jconsole
+ *
+ * @param name
+ * @param value
+ */
+ public void setProperty(String name, String value) {
+ setProperty(name, (Object) value);
+ }
+
+ /**
+ * set config attributes with reflect and propagate to all managers
+ *
+ * @param name
+ * @param value
+ */
+ public void setProperty(String name, Object value) {
+ if (log.isTraceEnabled())
+ log.trace(sm.getString("SimpleTcpCluster.setProperty", name, value,
+ properties.get(name)));
+
+ properties.put(name, value);
+ if(started) {
+ // FIXME Hmm, is that correct when some DeltaManagers are direct configured inside Context?
+ // Why we not support it for other elements, like sender, receiver or membership?
+ // Must we restart element after change?
+ if (name.startsWith("manager")) {
+ String key = name.substring("manager".length() + 1);
+ String pvalue = value.toString();
+ for (Iterator iter = managers.values().iterator(); iter.hasNext();) {
+ Manager manager = (Manager) iter.next();
+ if(manager instanceof DeltaManager && ((ClusterManager) manager).isDefaultMode()) {
+ IntrospectionUtils.setProperty(manager, key, pvalue );
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * get current config
+ *
+ * @param key
+ * @return The property
+ */
+ public Object getProperty(String key) {
+ if (log.isTraceEnabled())
+ log.trace(sm.getString("SimpleTcpCluster.getProperty", key));
+ return properties.get(key);
+ }
+
+ /**
+ * Get all properties keys
+ *
+ * @return An iterator over the property names.
+ */
+ public Iterator getPropertyNames() {
+ return properties.keySet().iterator();
+ }
+
+ /**
+ * remove a configured property.
+ *
+ * @param key
+ */
+ public void removeProperty(String key) {
+ properties.remove(key);
+ }
+
+ /**
+ * transfer properties from cluster configuration to subelement bean.
+ * @param prefix
+ * @param bean
+ */
+ protected void transferProperty(String prefix, Object bean) {
+ if (prefix != null) {
+ for (Iterator iter = getPropertyNames(); iter.hasNext();) {
+ String pkey = (String) iter.next();
+ if (pkey.startsWith(prefix)) {
+ String key = pkey.substring(prefix.length() + 1);
+ Object value = getProperty(pkey);
+ IntrospectionUtils.setProperty(bean, key, value.toString());
+ }
+ }
+ }
+ }
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * @return Returns the managers.
+ */
+ public Map getManagers() {
+ return managers;
+ }
+
+ public Channel getChannel() {
+ return channel;
+ }
+
+ /**
+ * Create new Manager without add to cluster (comes with start the manager)
+ *
+ * @param name
+ * Context Name of this manager
+ * @see org.apache.catalina.Cluster#createManager(java.lang.String)
+ * @see #addManager(String, Manager)
+ * @see DeltaManager#start()
+ */
+ public synchronized Manager createManager(String name) {
+ if (log.isDebugEnabled()) log.debug("Creating ClusterManager for context " + name + " using class " + getManagerClassName());
+ Manager manager = null;
+ ClassLoader oldCtxLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(SimpleTcpCluster.class.getClassLoader());
+ manager = (Manager) getClass().getClassLoader().loadClass(getManagerClassName()).newInstance();
+ } catch (Exception x) {
+ log.error("Unable to load class for replication manager", x);
+ manager = new org.apache.catalina.ha.session.DeltaManager();
+ } finally {
+ Thread.currentThread().setContextClassLoader(oldCtxLoader);
+ if(manager != null) {
+ manager.setDistributable(true);
+ if (manager instanceof ClusterManager) {
+ ClusterManager cmanager = (ClusterManager) manager ;
+ cmanager.setDefaultMode(true);
+ cmanager.setName(getManagerName(name,manager));
+ cmanager.setCluster(this);
+ }
+ }
+ }
+ return manager;
+ }
+
+ /**
+ * remove an application form cluster replication bus
+ *
+ * @see org.apache.catalina.ha.CatalinaCluster#removeManager(java.lang.String,Manager)
+ */
+ public void removeManager(String name,Manager manager) {
+ if (manager != null) {
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(BEFORE_MANAGERUNREGISTER_EVENT,manager);
+ managers.remove(getManagerName(name,manager));
+ if (manager instanceof ClusterManager) ((ClusterManager) manager).setCluster(null);
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(AFTER_MANAGERUNREGISTER_EVENT, manager);
+ }
+ }
+
+ /**
+ * add an application to cluster replication bus
+ *
+ * @param name
+ * of the context
+ * @param manager
+ * manager to register
+ * @see org.apache.catalina.ha.CatalinaCluster#addManager(java.lang.String,
+ * org.apache.catalina.Manager)
+ */
+ public void addManager(String name, Manager manager) {
+ if (!manager.getDistributable()) {
+ log.warn("Manager with name " + name + " is not distributable, can't add as cluster manager");
+ return;
+ }
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(BEFORE_MANAGERREGISTER_EVENT, manager);
+ String clusterName = getManagerName(name, manager);
+ if (manager instanceof ClusterManager) {
+ ClusterManager cmanager = (ClusterManager) manager ;
+ cmanager.setName(clusterName);
+ cmanager.setCluster(this);
+ if(cmanager.isDefaultMode()) transferProperty("manager",cmanager);
+ }
+ managers.put(clusterName, manager);
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(AFTER_MANAGERREGISTER_EVENT, manager);
+ }
+
+ /**
+ * @param name
+ * @param manager
+ * @return
+ */
+ public String getManagerName(String name, Manager manager) {
+ String clusterName = name ;
+ if(getContainer() instanceof Engine) {
+ Container context = manager.getContainer() ;
+ if(context != null && context instanceof Context) {
+ Container host = ((Context)context).getParent();
+ if(host != null && host instanceof Host)
+ clusterName = host.getName() + name ;
+ }
+ }
+ return clusterName;
+ }
+
+ /*
+ * Get Manager
+ *
+ * @see org.apache.catalina.ha.CatalinaCluster#getManager(java.lang.String)
+ */
+ public Manager getManager(String name) {
+ return (Manager) managers.get(name);
+ }
+
+ // ------------------------------------------------------ Lifecycle Methods
+
+ /**
+ * Execute a periodic task, such as reloading, etc. This method will be
+ * invoked inside the classloading context of this container. Unexpected
+ * throwables will be caught and logged.
+ * @see org.apache.catalina.ha.deploy.FarmWarDeployer#backgroundProcess()
+ * @see ReplicationTransmitter#backgroundProcess()
+ */
+ public void backgroundProcess() {
+ if (clusterDeployer != null) clusterDeployer.backgroundProcess();
+ //send a heartbeat through the channel
+ if ( channel !=null ) channel.heartbeat();
+ }
+
+ /**
+ * Add a lifecycle event listener to this component.
+ *
+ * @param listener
+ * The listener to add
+ */
+ public void addLifecycleListener(LifecycleListener listener) {
+ lifecycle.addLifecycleListener(listener);
+ }
+
+ /**
+ * Get the lifecycle listeners associated with this lifecycle. If this
+ * Lifecycle has no listeners registered, a zero-length array is returned.
+ */
+ public LifecycleListener[] findLifecycleListeners() {
+
+ return lifecycle.findLifecycleListeners();
+
+ }
+
+ /**
+ * Remove a lifecycle event listener from this component.
+ *
+ * @param listener
+ * The listener to remove
+ */
+ public void removeLifecycleListener(LifecycleListener listener) {
+ lifecycle.removeLifecycleListener(listener);
+ }
+
+ /**
+ * Use as base to handle start/stop/periodic Events from host. Currently
+ * only log the messages as trace level.
+ *
+ * @see org.apache.catalina.LifecycleListener#lifecycleEvent(org.apache.catalina.LifecycleEvent)
+ */
+ public void lifecycleEvent(LifecycleEvent lifecycleEvent) {
+ if (log.isTraceEnabled())
+ log.trace(sm.getString("SimpleTcpCluster.event.log", lifecycleEvent.getType(), lifecycleEvent.getData()));
+ }
+
+ // ------------------------------------------------------ public
+
+ /**
+ * Prepare for the beginning of active use of the public methods of this
+ * component. This method should be called after <code>configure()</code>,
+ * and before any of the public methods of the component are utilized. <BR>
+ * Starts the cluster communication channel, this will connect with the
+ * other nodes in the cluster, and request the current session state to be
+ * transferred to this node.
+ *
+ * @exception IllegalStateException
+ * if this component has already been started
+ * @exception LifecycleException
+ * if this component detects a fatal error that prevents this
+ * component from being used
+ */
+ public void start() throws LifecycleException {
+ if (started)
+ throw new LifecycleException(sm.getString("cluster.alreadyStarted"));
+ if (log.isInfoEnabled()) log.info("Cluster is about to start");
+
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, this);
+ try {
+ if ( clusterDeployer != null ) clusterDeployer.setCluster(this);
+ this.registerClusterValve();
+ if ( channel == null ) channel = new GroupChannel();
+ channel.addMembershipListener(this);
+ channel.addChannelListener(this);
+ channel.start(channel.DEFAULT);
+ if (clusterDeployer != null) clusterDeployer.start();
+ this.started = true;
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(AFTER_START_EVENT, this);
+ } catch (Exception x) {
+ log.error("Unable to start cluster.", x);
+ throw new LifecycleException(x);
+ }
+ }
+
+ /**
+ * register all cluster valve to host or engine
+ * @throws Exception
+ * @throws ClassNotFoundException
+ */
+ protected void registerClusterValve() throws Exception {
+ if(container != null ) {
+ for (Iterator iter = valves.iterator(); iter.hasNext();) {
+ ClusterValve valve = (ClusterValve) iter.next();
+ if (log.isDebugEnabled())
+ log.debug("Invoking addValve on " + getContainer()
+ + " with class=" + valve.getClass().getName());
+ if (valve != null) {
+ IntrospectionUtils.callMethodN(getContainer(), "addValve",
+ new Object[] { valve },
+ new Class[] { org.apache.catalina.Valve.class });
+
+ }
+ valve.setCluster(this);
+ }
+ }
+ }
+
+ /**
+ * unregister all cluster valve to host or engine
+ * @throws Exception
+ * @throws ClassNotFoundException
+ */
+ protected void unregisterClusterValve() throws Exception {
+ for (Iterator iter = valves.iterator(); iter.hasNext();) {
+ ClusterValve valve = (ClusterValve) iter.next();
+ if (log.isDebugEnabled())
+ log.debug("Invoking removeValve on " + getContainer()
+ + " with class=" + valve.getClass().getName());
+ if (valve != null) {
+ IntrospectionUtils.callMethodN(getContainer(), "removeValve",
+ new Object[] { valve }, new Class[] { org.apache.catalina.Valve.class });
+ }
+ valve.setCluster(this);
+ }
+ }
+
+ /**
+ * Gracefully terminate the active cluster component.<br/>
+ * This will disconnect the cluster communication channel, stop the
+ * listener and deregister the valves from host or engine.<br/><br/>
+ * <b>Note:</b><br/>The sub elements receiver, sender, membership,
+ * listener or valves are not removed. You can easily start the cluster again.
+ *
+ * @exception IllegalStateException
+ * if this component has not been started
+ * @exception LifecycleException
+ * if this component detects a fatal error that needs to be
+ * reported
+ */
+ public void stop() throws LifecycleException {
+
+ if (!started)
+ throw new IllegalStateException(sm.getString("cluster.notStarted"));
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, this);
+
+ if (clusterDeployer != null) clusterDeployer.stop();
+ this.managers.clear();
+ try {
+ if ( clusterDeployer != null ) clusterDeployer.setCluster(null);
+ channel.stop(Channel.DEFAULT);
+ channel.removeChannelListener(this);
+ channel.removeMembershipListener(this);
+ this.unregisterClusterValve();
+ } catch (Exception x) {
+ log.error("Unable to stop cluster valve.", x);
+ }
+ started = false;
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, this);
+ }
+
+
+
+
+ /**
+ * send message to all cluster members
+ * @param msg message to transfer
+ *
+ * @see org.apache.catalina.ha.CatalinaCluster#send(org.apache.catalina.ha.ClusterMessage)
+ */
+ public void send(ClusterMessage msg) {
+ send(msg, null);
+ }
+
+ /**
+ * send message to all cluster members same cluster domain
+ *
+ * @see org.apache.catalina.ha.CatalinaCluster#send(org.apache.catalina.ha.ClusterMessage)
+ */
+ public void sendClusterDomain(ClusterMessage msg) {
+ send(msg,null);
+ }
+
+
+ /**
+ * send a cluster message to one member
+ *
+ * @param msg message to transfer
+ * @param dest Receiver member
+ * @see org.apache.catalina.ha.CatalinaCluster#send(org.apache.catalina.ha.ClusterMessage,
+ * org.apache.catalina.ha.Member)
+ */
+ public void send(ClusterMessage msg, Member dest) {
+ try {
+ msg.setAddress(getLocalMember());
+ if (dest != null) {
+ if (!getLocalMember().equals(dest)) {
+ channel.send(new Member[] {dest}, msg,channelSendOptions);
+ } else
+ log.error("Unable to send message to local member " + msg);
+ } else {
+ channel.send(channel.getMembers(),msg,channelSendOptions);
+ }
+ } catch (Exception x) {
+ log.error("Unable to send message through cluster sender.", x);
+ }
+ }
+
+ /**
+ * New cluster member is registered
+ *
+ * @see org.apache.catalina.ha.MembershipListener#memberAdded(org.apache.catalina.ha.Member)
+ */
+ public void memberAdded(Member member) {
+ try {
+ hasMembers = channel.hasMembers();
+ if (log.isInfoEnabled()) log.info("Replication member added:" + member);
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(BEFORE_MEMBERREGISTER_EVENT, member);
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(AFTER_MEMBERREGISTER_EVENT, member);
+ } catch (Exception x) {
+ log.error("Unable to connect to replication system.", x);
+ }
+
+ }
+
+ /**
+ * Cluster member is gone
+ *
+ * @see org.apache.catalina.ha.MembershipListener#memberDisappeared(org.apache.catalina.ha.Member)
+ */
+ public void memberDisappeared(Member member) {
+ try {
+ hasMembers = channel.hasMembers();
+ if (log.isInfoEnabled()) log.info("Received member disappeared:" + member);
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(BEFORE_MEMBERUNREGISTER_EVENT, member);
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(AFTER_MEMBERUNREGISTER_EVENT, member);
+ } catch (Exception x) {
+ log.error("Unable remove cluster node from replication system.", x);
+ }
+ }
+
+ // --------------------------------------------------------- receiver
+ // messages
+
+ /**
+ * notify all listeners from receiving a new message is not ClusterMessage
+ * emitt Failure Event to LifecylceListener
+ *
+ * @param message
+ * receveived Message
+ */
+ public boolean accept(Serializable msg, Member sender) {
+ return (msg instanceof ClusterMessage);
+ }
+
+
+ public void messageReceived(Serializable message, Member sender) {
+ ClusterMessage fwd = (ClusterMessage)message;
+ fwd.setAddress(sender);
+ messageReceived(fwd);
+ }
+
+ public void messageReceived(ClusterMessage message) {
+
+ long start = 0;
+ if (log.isDebugEnabled() && message != null)
+ log.debug("Assuming clocks are synched: Replication for "
+ + message.getUniqueId() + " took="
+ + (System.currentTimeMillis() - (message).getTimestamp())
+ + " ms.");
+
+ //invoke all the listeners
+ boolean accepted = false;
+ if (message != null) {
+ for (Iterator iter = clusterListeners.iterator(); iter.hasNext();) {
+ ClusterListener listener = (ClusterListener) iter.next();
+ if (listener.accept(message)) {
+ accepted = true;
+ listener.messageReceived(message);
+ }
+ }
+ }
+ if (!accepted && log.isDebugEnabled()) {
+ if (notifyLifecycleListenerOnFailure) {
+ Member dest = message.getAddress();
+ // Notify our interested LifecycleListeners
+ lifecycle.fireLifecycleEvent(RECEIVE_MESSAGE_FAILURE_EVENT,
+ new SendMessageData(message, dest, null));
+ }
+ log.debug("Message " + message.toString() + " from type "
+ + message.getClass().getName()
+ + " transfered but no listener registered");
+ }
+ return;
+ }
+
+ // --------------------------------------------------------- Logger
+
+ public Log getLogger() {
+ return log;
+ }
+
+
+
+
+ // ------------------------------------------------------------- deprecated
+
+ /**
+ *
+ * @see org.apache.catalina.Cluster#setProtocol(java.lang.String)
+ */
+ public void setProtocol(String protocol) {
+ }
+
+ /**
+ * @see org.apache.catalina.Cluster#getProtocol()
+ */
+ public String getProtocol() {
+ return null;
+ }
+
+ /**
+ * @see org.apache.catalina.Cluster#startContext(java.lang.String)
+ */
+ public void startContext(String contextPath) throws IOException {
+
+ }
+
+ /**
+ * @see org.apache.catalina.Cluster#installContext(java.lang.String, java.net.URL)
+ */
+ public void installContext(String contextPath, URL war) {
+
+ }
+
+ /**
+ * @see org.apache.catalina.Cluster#stop(java.lang.String)
+ */
+ public void stop(String contextPath) throws IOException {
+
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mbeans-descriptors PUBLIC
+ "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+ "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+<mbeans-descriptors>
+
+ <mbean name="SimpleTcpCluster"
+ description="Tcp Cluster implementation"
+ domain="Catalina"
+ group="Cluster"
+ type="org.apache.catalina.ha.tcp.SimpleTcpCluster">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="notifyLifecycleListenerOnFailure"
+ description="notify lifecycleListener from message transfer failure"
+ is="true"
+ type="boolean"/>
+ <attribute name="clusterName"
+ description="name of cluster"
+ type="java.lang.String"/>
+ <attribute name="managerClassName"
+ description="session mananager classname"
+ type="java.lang.String"/>
+ <attribute name="clusterLogName"
+ description="Name of cluster transfer log device"
+ type="java.lang.String"/>
+ <attribute name="doClusterLog"
+ is="true"
+ description="enable cluster log transfer logging"
+ type="boolean"/>
+ <operation name="setProperty"
+ description="set a property to all cluster managers (with prefix 'manager.')"
+ impact="ACTION"
+ returnType="void">
+ <parameter name="key"
+ description="Property name"
+ type="java.lang.String"/>
+ <parameter name="value"
+ description="Property value"
+ type="java.lang.String"/>
+ </operation>
+
+ <operation name="send"
+ description="send message to all cluster members"
+ impact="ACTION"
+ returnType="void">
+ <parameter name="message"
+ description="replication message"
+ type="org.apache.catalina.ha.ClusterMessage"/>
+ </operation>
+
+ <operation name="sendClusterDomain"
+ description="send message to all cluster members with same domain"
+ impact="ACTION"
+ returnType="void">
+ <parameter name="message"
+ description="replication message"
+ type="org.apache.catalina.ha.ClusterMessage"/>
+ </operation>
+
+ <operation name="sendToMember"
+ description="send message to one cluster member"
+ impact="ACTION"
+ returnType="void">
+ <parameter name="message"
+ description="replication message"
+ type="org.apache.catalina..cluster.ClusterMessage"/>
+ <parameter name="member"
+ description="cluster member"
+ type="java.lang.String"/>
+ </operation>
+
+ <operation name="start"
+ description="Start the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="stop"
+ description="Stop the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="ClusterReceiverBase"
+ description="Tcp Cluster ReplicationListener implementation"
+ domain="Catalina"
+ group="Cluster"
+ type="org.apache.catalina.ha.tcp.ClusterReceiverBase">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="tcpListenAddress"
+ description="tcp listener address"
+ type="java.lang.String"/>
+ <attribute name="tcpListenPort"
+ description="tcp listener port"
+ type="int"/>
+ <attribute name="tcpThreadCount"
+ description="number of tcp listener worker threads"
+ type="int"/>
+ <attribute name="tcpSelectorTimeout"
+ description="tcp listener Selector timeout"
+ type="long"/>
+ <attribute name="nrOfMsgsReceived"
+ description="number of messages received from other nodes"
+ type="long"
+ writeable="false"/>
+ <attribute name="receivedTime"
+ description="total time message send time"
+ type="long"
+ writeable="false"/>
+ <attribute name="receivedProcessingTime"
+ description="received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minReceivedProcessingTime"
+ description="minimal received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgReceivedProcessingTime"
+ description="received processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxReceivedProcessingTime"
+ description="maximal received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doReceivedProcessingStats"
+ description="create received processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="avgTotalReceivedBytes"
+ description="received totalReceivedBytes / nrOfMsgsReceived"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalReceivedBytes"
+ description="number of bytes received"
+ type="long"
+ writeable="false"/>
+ <attribute name="sendAck"
+ description="send ack after data received"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="compress"
+ description="data received compressed"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="doListen"
+ description="is port really started"
+ is="true"
+ type="boolean"
+ writeable="false" />
+
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="start"
+ description="Start the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="stop"
+ description="Stop the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="SocketReplicationListener"
+ description="Tcp Cluster SocketReplicationListener implementation"
+ domain="Catalina"
+ group="Cluster"
+ type="org.apache.catalina.ha.tcp.SocketReplicationListener">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="tcpListenAddress"
+ description="tcp listener address"
+ type="java.lang.String"/>
+ <attribute name="tcpListenPort"
+ description="tcp listener port"
+ type="int"/>
+ <attribute name="tcpListenMaxPort"
+ description="max tcp listen used port"
+ type="int"/>
+ <attribute name="tcpListenTimeout"
+ description="max tcp listen timeout (sec) wait for ServerSocket start"
+ type="int"/>
+ <attribute name="nrOfMsgsReceived"
+ description="number of messages received from other nodes"
+ type="long"
+ writeable="false"/>
+ <attribute name="receivedTime"
+ description="total time message send time"
+ type="long"
+ writeable="false"/>
+ <attribute name="receivedProcessingTime"
+ description="received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minReceivedProcessingTime"
+ description="minimal received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgReceivedProcessingTime"
+ description="received processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxReceivedProcessingTime"
+ description="maximal received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doReceivedProcessingStats"
+ description="create received processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="avgTotalReceivedBytes"
+ description="received totalReceivedBytes / nrOfMsgsReceived"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalReceivedBytes"
+ description="number of bytes received"
+ type="long"
+ writeable="false"/>
+ <attribute name="sendAck"
+ description="send ack after data received"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="compress"
+ description="data received compressed"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="doListen"
+ description="is port really started"
+ is="true"
+ type="boolean"
+ writeable="false" />
+
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="start"
+ description="Start the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="stop"
+ description="Stop the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="ReplicationTransmitter"
+ description="Tcp replication transmitter"
+ domain="Catalina"
+ group="ClusterSender"
+ type="org.apache.catalina.ha.tcp.ReplicationTransmitter">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="replicationMode"
+ description="replication mode (synchnous,pooled.asynchnous,fastasyncqueue)"
+ type="java.lang.String"/>
+ <attribute name="ackTimeout"
+ description="acknowledge timeout"
+ type="long"/>
+ <attribute name="autoConnect"
+ description="is sender disabled, fork a new socket"
+ is="true"
+ type="boolean" />
+ <attribute name="waitForAck"
+ description="Wait for ack after data send"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="processingTime"
+ description="sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minProcessingTime"
+ description="minimal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgProcessingTime"
+ description="processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxProcessingTime"
+ description="maximal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doTransmitterProcessingStats"
+ description="create processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="nrOfRequests"
+ description="number of send messages to other members"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalBytes"
+ description="number of bytes transfered"
+ type="long"
+ writeable="false"/>
+ <attribute name="failureCounter"
+ description="number of wrong transfers"
+ type="long"
+ writeable="false"/>
+ <attribute name="senderObjectNames"
+ description="get all sender object names"
+ type="[Ljavax.management.ObjectName;"
+ writeable="false"/>
+ <operation name="start"
+ description="Start the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="stop"
+ description="Stop the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="checkKeepAlive"
+ description="Check all sender connection for close socket (keepalive)"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ </mbean>
+
+ <mbean name="AsyncSocketSender"
+ description="Async Cluster Sender"
+ domain="Catalina"
+ group="IDataSender"
+ type="org.apache.catalina.ha.tcp.AsyncSocketSender">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="address"
+ description="sender ip address"
+ type="java.net.InetAddress"
+ writeable="false"/>
+ <attribute name="port"
+ description="sender port"
+ type="int"
+ writeable="false" />
+ <attribute name="suspect"
+ description="Socket is gone"
+ type="boolean"/>
+ <attribute name="waitForAck"
+ description="Wait for ack after data send"
+ is="true"
+ type="boolean"
+ writeable="false"/>
+ <attribute name="ackTimeout"
+ description="acknowledge timeout"
+ type="long"/>
+ <attribute name="avgMessageSize"
+ writeable="false"
+ description="avg message size (totalbytes/nrOfRequests"
+ type="long"/>
+ <attribute name="queueSize"
+ writeable="false"
+ description="queue size"
+ type="int"/>
+ <attribute name="queuedNrOfBytes"
+ writeable="false"
+ description="number of bytes over all queued messages"
+ type="long"/>
+ <attribute name="messageTransferStarted"
+ description="message is in transfer"
+ type="boolean"
+ is="true"
+ writeable="false"/>
+ <attribute name="keepAliveTimeout"
+ description="active socket keep alive timeout"
+ type="long"/>
+ <attribute name="keepAliveMaxRequestCount"
+ description="max request over this socket"
+ type="int"/>
+ <attribute name="keepAliveCount"
+ description="keep Alive request count"
+ type="int"
+ writeable="false"/>
+ <attribute name="keepAliveConnectTime"
+ description="Connect time for keep alive"
+ type="long"
+ writeable="false"/>
+ <attribute name="resend"
+ description="after send failure make a resend"
+ is="true"
+ type="boolean" />
+ <attribute name="connected"
+ is="true"
+ description="socket connected"
+ type="boolean"
+ writeable="false"/>
+ <attribute name="nrOfRequests"
+ description="number of send messages to other members"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalBytes"
+ description="number of bytes transfered"
+ type="long"
+ writeable="false"/>
+ <attribute name="processingTime"
+ description="sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minProcessingTime"
+ description="minimal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgProcessingTime"
+ description="processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxProcessingTime"
+ description="maximal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doProcessingStats"
+ description="create processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="waitAckTime"
+ description="sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minWaitAckTime"
+ description="minimal sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgWaitAckTime"
+ description="waitAck time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxWaitAckTime"
+ description="maximal sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doWaitAckStats"
+ description="create waitAck time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="connectCounter"
+ description="counts connects"
+ type="long"
+ writeable="false"/>
+ <attribute name="disconnectCounter"
+ description="counts disconnects"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketOpenCounter"
+ description="counts open socket (KeepAlive and connects)"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketOpenFailureCounter"
+ description="counts open socket failures"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketCloseCounter"
+ description="counts closed socket (KeepAlive and disconnects)"
+ type="long"
+ writeable="false"/>
+ <attribute name="missingAckCounter"
+ description="counts missing ack"
+ type="long"
+ writeable="false"/>
+ <attribute name="dataResendCounter"
+ description="counts data resends"
+ type="long"
+ writeable="false"/>
+ <attribute name="dataFailureCounter"
+ description="counts data send failures"
+ type="long"
+ writeable="false"/>
+ <attribute name="inQueueCounter"
+ description="counts all queued messages"
+ type="long"
+ writeable="false"/>
+ <attribute name="outQueueCounter"
+ description="counts all successfully sended messages"
+ type="long"
+ writeable="false"/>
+ <operation name="connect"
+ description="connect to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="disconnect"
+ description="disconnect to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="checkKeepAlive"
+ description="Check connection for close socket"
+ impact="ACTION"
+ returnType="boolean">
+ </operation>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="FastAsyncSocketSender"
+ description="Fast Async Cluster Sender"
+ domain="Catalina"
+ group="IDataSender"
+ type="org.apache.catalina.ha.tcp.FastAsyncSocketSender">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="threadPriority"
+ description="change queue thread priority"
+ type="int"/>
+ <attribute name="address"
+ description="sender ip address"
+ type="java.net.InetAddress"
+ writeable="false"/>
+ <attribute name="port"
+ description="sender port"
+ type="int"
+ writeable="false" />
+ <attribute name="suspect"
+ description="Socket is gone"
+ type="boolean"/>
+ <attribute name="waitForAck"
+ description="Wait for ack after data send"
+ is="true"
+ type="boolean"
+ writeable="false"/>
+ <attribute name="ackTimeout"
+ description="acknowledge timeout"
+ type="long"/>
+ <attribute name="avgMessageSize"
+ writeable="false"
+ description="avg message size (totalbytes/nrOfRequests"
+ type="long" />
+ <attribute name="queueSize"
+ writeable="false"
+ description="queue size"
+ type="int"/>
+ <attribute name="queuedNrOfBytes"
+ writeable="false"
+ description="number of bytes over all queued messages"
+ type="long"/>
+ <attribute name="messageTransferStarted"
+ description="message is in transfer"
+ type="boolean"
+ is="true"
+ writeable="false"/>
+ <attribute name="keepAliveTimeout"
+ description="active socket keep alive timeout"
+ type="long"/>
+ <attribute name="keepAliveMaxRequestCount"
+ description="max request over this socket"
+ type="int"/>
+ <attribute name="queueAddWaitTimeout"
+ description="add wait timeout (default 10000 msec)"
+ type="long"/>
+ <attribute name="queueRemoveWaitTimeout"
+ description="remove wait timeout (default 30000 msec)"
+ type="long"/>
+ <attribute name="maxQueueLength"
+ description="max queue length"
+ type="int"/>
+ <attribute name="queueTimeWait"
+ description="remember queue wait times"
+ is="true"
+ type="boolean"/>
+ <attribute name="queueCheckLock"
+ description="check to lost locks"
+ is="true"
+ type="boolean"/>
+ <attribute name="queueDoStats"
+ description="activated queue stats"
+ is="true"
+ type="boolean"/>
+ <attribute name="keepAliveCount"
+ description="keep Alive request count"
+ type="int"
+ writeable="false"/>
+ <attribute name="keepAliveConnectTime"
+ description="Connect time for keep alive"
+ type="long"
+ writeable="false"/>
+ <attribute name="resend"
+ description="after send failure make a resend"
+ is="true"
+ type="boolean" />
+ <attribute name="connected"
+ is="true"
+ description="socket connected"
+ type="boolean"
+ writeable="false"/>
+ <attribute name="nrOfRequests"
+ description="number of send messages to other members"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalBytes"
+ description="number of bytes transfered"
+ type="long"
+ writeable="false"/>
+ <attribute name="processingTime"
+ description="sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minProcessingTime"
+ description="minimal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgProcessingTime"
+ description="processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxProcessingTime"
+ description="maximal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doProcessingStats"
+ description="create Processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="waitAckTime"
+ description="sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minWaitAckTime"
+ description="minimal sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgWaitAckTime"
+ description="waitAck time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxWaitAckTime"
+ description="maximal sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doWaitAckStats"
+ description="create waitAck time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="connectCounter"
+ description="counts connects"
+ type="long"
+ writeable="false"/>
+ <attribute name="disconnectCounter"
+ description="counts disconnects"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketOpenCounter"
+ description="counts open socket (KeepAlive and connects)"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketOpenFailureCounter"
+ description="counts open socket failures"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketCloseCounter"
+ description="counts closed socket (KeepAlive and disconnects)"
+ type="long"
+ writeable="false"/>
+ <attribute name="missingAckCounter"
+ description="counts missing ack"
+ type="long"
+ writeable="false"/>
+ <attribute name="dataResendCounter"
+ description="counts data resends"
+ type="long"
+ writeable="false"/>
+ <attribute name="dataFailureCounter"
+ description="counts data send failures"
+ type="long"
+ writeable="false"/>
+ <attribute name="inQueueCounter"
+ description="counts all queued messages"
+ type="long"
+ writeable="false"/>
+ <attribute name="outQueueCounter"
+ description="counts all successfully sended messages"
+ type="long"
+ writeable="false"/>
+ <attribute name="queueAddWaitTime"
+ description="queue add wait time (tomcat thread waits)"
+ type="long"
+ writeable="false"/>
+ <attribute name="queueRemoveWaitTime"
+ description="queue remove wait time (queue thread waits)"
+ type="long"
+ writeable="false"/>
+ <operation name="connect"
+ description="connect to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="disconnect"
+ description="disconnect to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="checkKeepAlive"
+ description="Check connection for close socket"
+ impact="ACTION"
+ returnType="boolean">
+ </operation>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="PooledSocketSender"
+ description="Pooled Cluster Sender"
+ domain="Catalina"
+ group="IDataSender"
+ type="org.apache.catalina.ha.tcp.PooledSocketSender">
+ <attribute name="address"
+ description="sender ip address"
+ type="java.net.InetAddress"
+ writeable="false"/>
+ <attribute name="port"
+ description="sender port"
+ type="int"
+ writeable="false" />
+ <attribute name="suspect"
+ description="Socket is gone"
+ type="boolean"/>
+ <attribute name="ackTimeout"
+ description="acknowledge timeout"
+ type="long"/>
+ <attribute name="waitForAck"
+ description="Wait for ack after data send"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="maxPoolSocketLimit"
+ description="Max parallel sockets"
+ type="int"/>
+ <attribute name="keepAliveTimeout"
+ description="active socket keep alive timeout"
+ type="long"/>
+ <attribute name="keepAliveMaxRequestCount"
+ description="max request over this socket"
+ type="int"/>
+ <attribute name="resend"
+ description="after send failure make a resend"
+ is="true"
+ type="boolean" />
+ <attribute name="connected"
+ is="true"
+ description="socket connected"
+ type="boolean"
+ writeable="false"/>
+ <attribute name="avgMessageSize"
+ writeable="false"
+ description="avg message size (totalbytes/nrOfRequests"
+ type="long"/>
+ <attribute name="nrOfRequests"
+ description="number of send messages to other members"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalBytes"
+ description="number of bytes transfered"
+ type="long"
+ writeable="false"/>
+ <attribute name="connectCounter"
+ description="counts connects"
+ type="long"
+ writeable="false"/>
+ <attribute name="disconnectCounter"
+ description="counts disconnects"
+ type="long"
+ writeable="false"/>
+ <operation name="connect"
+ description="start Queue to connect to ohter replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="disconnect"
+ description="stop Queue to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="SocketSender"
+ description="Sync Cluster Sender"
+ domain="Catalina"
+ group="IDataSender"
+ type="org.apache.catalina.ha.tcp.SocketSender">
+ <attribute name="address"
+ description="sender ip address"
+ type="java.net.InetAddress"
+ writeable="false"/>
+ <attribute name="port"
+ description="sender port"
+ type="int"
+ writeable="false" />
+ <attribute name="suspect"
+ description="Socket is gone"
+ type="boolean"/>
+ <attribute name="ackTimeout"
+ description="acknowledge timeout"
+ type="long"/>
+ <attribute name="waitForAck"
+ description="Wait for ack after data send"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="keepAliveTimeout"
+ description="active socket keep alive timeout"
+ type="long"/>
+ <attribute name="keepAliveMaxRequestCount"
+ description="max request over this socket"
+ type="int"/>
+ <attribute name="messageTransferStarted"
+ description="message is in transfer"
+ type="boolean"
+ is="true"
+ writeable="false"/>
+ <attribute name="keepAliveCount"
+ description="keep Alive request count"
+ type="int"
+ writeable="false"/>
+ <attribute name="keepAliveConnectTime"
+ description="Connect time for keep alive"
+ type="long"
+ writeable="false"/>
+ <attribute name="resend"
+ description="after send failure make a resend"
+ is="true"
+ type="boolean" />
+ <attribute name="connected"
+ is="true"
+ description="socket connected"
+ type="boolean"
+ writeable="false"/>
+ <attribute name="avgMessageSize"
+ writeable="false"
+ description="avg message size (totalbytes/nrOfRequests"
+ type="long"/>
+ <attribute name="nrOfRequests"
+ description="number of send messages to other members"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalBytes"
+ description="number of bytes transfered"
+ type="long"
+ writeable="false"/>
+ <attribute name="processingTime"
+ description="sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minProcessingTime"
+ description="minimal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgProcessingTime"
+ description="processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxProcessingTime"
+ description="maximal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doProcessingStats"
+ description="create Processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="waitAckTime"
+ description="sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minWaitAckTime"
+ description="minimal sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgWaitAckTime"
+ description="waitAck time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxWaitAckTime"
+ description="maximal sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doWaitAckStats"
+ description="create waitAck time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="connectCounter"
+ description="counts connects"
+ type="long"
+ writeable="false"/>
+ <attribute name="disconnectCounter"
+ description="counts disconnects"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketCloseCounter"
+ description="counts closed socket (KeepAlive and disconnects)"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketOpenFailureCounter"
+ description="counts open socket failures"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketOpenCounter"
+ description="counts open socket (KeepAlive and connects)"
+ type="long"
+ writeable="false"/>
+ <attribute name="missingAckCounter"
+ description="counts missing ack"
+ type="long"
+ writeable="false"/>
+ <attribute name="dataResendCounter"
+ description="counts data resends"
+ type="long"
+ writeable="false"/>
+ <attribute name="dataFailureCounter"
+ description="counts data send failures"
+ type="long"
+ writeable="false"/>
+ <operation name="connect"
+ description="connect to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="disconnect"
+ description="disconnect to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="checkKeepAlive"
+ description="Check connection for close socket"
+ impact="ACTION"
+ returnType="boolean">
+ </operation>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="ReplicationValve"
+ description="Valve for simple tcp replication"
+ domain="Catalina"
+ group="Valve"
+ type="org.apache.catalina.ha.tcp.ReplicationValve">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="filter"
+ description="resource filter to disable session replication check"
+ type="java.lang.String"/>
+ <attribute name="primaryIndicator"
+ is="true"
+ description="set indicator that request processing is at primary session node"
+ type="boolean"/>
+ <attribute name="primaryIndicatorName"
+ description="Request attribute name to indicate that request processing is at primary session node"
+ type="java.lang.String"/>
+ <attribute name="doProcessingStats"
+ is="true"
+ description="active statistics counting"
+ type="boolean"/>
+ <attribute name="nrOfRequests"
+ description="number of replicated requests"
+ type="long"
+ writeable="false"/>
+ <attribute name="nrOfFilterRequests"
+ description="number of filtered requests"
+ type="long"
+ writeable="false"/>
+ <attribute name="nrOfSendRequests"
+ description="number of send requests"
+ type="long"
+ writeable="false"/>
+ <attribute name="nrOfCrossContextSendRequests"
+ description="number of send cross context session requests"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalRequestTime"
+ description="total replicated request time"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalSendTime"
+ description="total replicated send time"
+ type="long"
+ writeable="false"/>
+ <attribute name="lastSendTime"
+ description="last replicated request time"
+ type="long"
+ writeable="false"/>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+
+</mbeans-descriptors>
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.util;
+
+import java.util.Iterator;
+
+/**
+ * @author Peter Rossbach
+ * @version $Revision: 304032 $, $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $
+ */
+
+public interface IDynamicProperty {
+
+ /**
+ * set config attributes with reflect
+ *
+ * @param name
+ * @param value
+ */
+ public void setProperty(String name, Object value) ;
+
+ /**
+ * get current config
+ *
+ * @param key
+ * @return The property
+ */
+ public Object getProperty(String key) ;
+ /**
+ * Get all properties keys
+ *
+ * @return An iterator over the property names
+ */
+ public Iterator getPropertyNames() ;
+
+ /**
+ * remove a configured property.
+ *
+ * @param key
+ */
+ public void removeProperty(String key) ;
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.io.Serializable;
+import java.io.Externalizable;
+import java.io.ObjectInput;
+import java.io.IOException;
+import java.io.ObjectOutput;
+
+/**
+ * A byte message is not serialized and deserialized by the channel
+ * instead it is sent as a byte array<br>
+ * By default Tribes uses java serialization when it receives an object
+ * to be sent over the wire. Java serialization is not the most
+ * efficient of serializing data, and Tribes might not even
+ * have access to the correct class loaders to deserialize the object properly.
+ * <br>
+ * The ByteMessage class is a class where the channel when it receives it will
+ * not attempt to perform serialization, instead it will simply stream the <code>getMessage()</code>
+ * bytes.<br>
+ * If you are using multiple applications on top of Tribes you should add some sort of header
+ * so that you can decide with the <code>ChannelListener.accept()</code> whether this message was intended
+ * for you.
+ * @author Filip Hanik
+ * @version $Revision: 304032 $, $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $
+ */
+
+public class ByteMessage implements Serializable, Externalizable {
+ /**
+ * Storage for the message to be sent
+ */
+ private byte[] message;
+
+
+ /**
+ * Creates an empty byte message
+ * Constructor also for deserialization
+ */
+ public ByteMessage() {
+ }
+
+ /**
+ * Creates a byte message wit h
+ * @param data byte[] - the message contents
+ */
+ public ByteMessage(byte[] data) {
+ message = data;
+ }
+
+ /**
+ * Returns the message contents of this byte message
+ * @return byte[] - message contents, can be null
+ */
+ public byte[] getMessage() {
+ return message;
+ }
+
+ /**
+ * Sets the message contents of this byte message
+ * @param message byte[]
+ */
+ public void setMessage(byte[] message) {
+ this.message = message;
+ }
+
+ /**
+ * @see java.io.Externalizable#readExternal
+ * @param in ObjectInput
+ * @throws IOException
+ */
+ public void readExternal(ObjectInput in ) throws IOException {
+ int length = in.readInt();
+ message = new byte[length];
+ in.read(message,0,length);
+ }
+
+ /**
+ * @see java.io.Externalizable#writeExternal
+ * @param out ObjectOutput
+ * @throws IOException
+ */
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeInt(message!=null?message.length:0);
+ if ( message!=null ) out.write(message,0,message.length);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.io.Serializable;
+
+/**
+ * Channel interface<br>
+ * A channel is a representation of a group of nodes all participating in some sort of
+ * communication with each other.<br>
+ * The channel is the main API class for Tribes, this is essentially the only class
+ * that an application needs to be aware of. Through the channel the application can:<br>
+ * 1. send messages<br>
+ * 2. receive message (by registering a <code>ChannelListener</code><br>
+ * 3. get all members of the group <code>getMembers()</code><br>
+ * 4. receive notifications of members added and members disappeared by
+ * registerering a <code>MembershipListener</code><br>
+ * <br>
+ * The channel has 5 major components:<br>
+ * 1. Data receiver, with a built in thread pool to receive messages from other peers<br>
+ * 2. Data sender, an implementation for sending data using NIO or java.io<br>
+ * 3. Membership listener,listens for membership broadcasts<br>
+ * 4. Membership broadcaster, broadcasts membership pings.<br>
+ * 5. Channel interceptors, the ability to manipulate messages as they are sent or arrive<br><br>
+ * The channel layout is:
+ * <pre><code>
+ * ChannelListener_1..ChannelListener_N MembershipListener_1..MembershipListener_N [Application Layer]
+ * \ \ / /
+ * \ \ / /
+ * \ \ / /
+ * \ \ / /
+ * \ \ / /
+ * \ \ / /
+ * ---------------------------------------
+ * |
+ * |
+ * Channel
+ * |
+ * ChannelInterceptor_1
+ * | [Channel stack]
+ * ChannelInterceptor_N
+ * |
+ * Coordinator (implements MessageListener,MembershipListener,ChannelInterceptor)
+ * --------------------
+ * / | \
+ * / | \
+ * / | \
+ * / | \
+ * / | \
+ * MembershipService ChannelSender ChannelReceiver [IO layer]
+ * </code></pre>
+ *
+ * For example usage @see org.apache.catalina.tribes.group.GroupChannel
+ * @author Filip Hanik
+ * @version $Revision: 304032 $, $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $
+ */
+public interface Channel {
+
+ /**
+ * Start and stop sequences can be controlled by these constants
+ * This allows you to start separate components of the channel <br>
+ * DEFAULT - starts or stops all components in the channel
+ * @see #start(int)
+ * @see #stop(int)
+ */
+ public static final int DEFAULT = 15;
+
+ /**
+ * Start and stop sequences can be controlled by these constants
+ * This allows you to start separate components of the channel <br>
+ * SND_RX_SEQ - starts or stops the data receiver. Start means opening a server socket
+ * in case of a TCP implementation
+ * @see #start(int)
+ * @see #stop(int)
+ */
+ public static final int SND_RX_SEQ = 1;
+
+ /**
+ * Start and stop sequences can be controlled by these constants
+ * This allows you to start separate components of the channel <br>
+ * SND_TX_SEQ - starts or stops the data sender. This should not open any sockets,
+ * as sockets are opened on demand when a message is being sent
+ * @see #start(int)
+ * @see #stop(int)
+ */
+ public static final int SND_TX_SEQ = 2;
+
+ /**
+ * Start and stop sequences can be controlled by these constants
+ * This allows you to start separate components of the channel <br>
+ * MBR_RX_SEQ - starts or stops the membership listener. In a multicast implementation
+ * this will open a datagram socket and join a group and listen for membership messages
+ * members joining
+ * @see #start(int)
+ * @see #stop(int)
+ */
+ public static final int MBR_RX_SEQ = 4;
+
+ /**
+ * Start and stop sequences can be controlled by these constants
+ * This allows you to start separate components of the channel <br>
+ * MBR_TX_SEQ - starts or stops the membership broadcaster. In a multicast implementation
+ * this will open a datagram socket and join a group and broadcast the local member information
+ * @see #start(int)
+ * @see #stop(int)
+ */
+ public static final int MBR_TX_SEQ = 8;
+
+ /**
+ * Send options, when a message is sent, it can have an option flag
+ * to trigger certain behavior. Most flags are used to trigger channel interceptors
+ * as the message passes through the channel stack. <br>
+ * However, there are four default flags that every channel implementation must implement<br>
+ * SEND_OPTIONS_BYTE_MESSAGE - The message is a pure byte message and no marshalling or unmarshalling will
+ * be performed.<br>
+ *
+ * @see #send(Member[], Serializable , int)
+ * @see #send(Member[], Serializable, int, ErrorHandler)
+ */
+ public static final int SEND_OPTIONS_BYTE_MESSAGE = 0x0001;
+
+ /**
+ * Send options, when a message is sent, it can have an option flag
+ * to trigger certain behavior. Most flags are used to trigger channel interceptors
+ * as the message passes through the channel stack. <br>
+ * However, there are four default flags that every channel implementation must implement<br>
+ * SEND_OPTIONS_USE_ACK - Message is sent and an ACK is received when the message has been received by the recipient<br>
+ * If no ack is received, the message is not considered successful<br>
+ * @see #send(Member[], Serializable , int)
+ * @see #send(Member[], Serializable, int, ErrorHandler)
+ */
+ public static final int SEND_OPTIONS_USE_ACK = 0x0002;
+
+ /**
+ * Send options, when a message is sent, it can have an option flag
+ * to trigger certain behavior. Most flags are used to trigger channel interceptors
+ * as the message passes through the channel stack. <br>
+ * However, there are four default flags that every channel implementation must implement<br>
+ * SEND_OPTIONS_SYNCHRONIZED_ACK - Message is sent and an ACK is received when the message has been received and
+ * processed by the recipient<br>
+ * If no ack is received, the message is not considered successful<br>
+ * @see #send(Member[], Serializable , int)
+ * @see #send(Member[], Serializable, int, ErrorHandler)
+ */
+ public static final int SEND_OPTIONS_SYNCHRONIZED_ACK = 0x0004;
+
+ /**
+ * Send options, when a message is sent, it can have an option flag
+ * to trigger certain behavior. Most flags are used to trigger channel interceptors
+ * as the message passes through the channel stack. <br>
+ * However, there are four default flags that every channel implementation must implement<br>
+ * SEND_OPTIONS_ASYNCHRONOUS - Message is sent and an ACK is received when the message has been received and
+ * processed by the recipient<br>
+ * If no ack is received, the message is not considered successful<br>
+ * @see #send(Member[], Serializable , int)
+ * @see #send(Member[], Serializable, int, ErrorHandler)
+ */
+ public static final int SEND_OPTIONS_ASYNCHRONOUS = 0x0008;
+
+
+ /**
+ * Send options, when a message is sent, it can have an option flag
+ * to trigger certain behavior. Most flags are used to trigger channel interceptors
+ * as the message passes through the channel stack. <br>
+ * However, there are four default flags that every channel implementation must implement<br>
+ * SEND_OPTIONS_DEFAULT - the default sending options, just a helper variable. <br>
+ * The default is <code>int SEND_OPTIONS_DEFAULT = SEND_OPTIONS_USE_ACK;</code><br>
+ * @see #SEND_OPTIONS_USE_ACK
+ * @see #send(Member[], Serializable , int)
+ * @see #send(Member[], Serializable, int, ErrorHandler)
+ */
+ public static final int SEND_OPTIONS_DEFAULT = SEND_OPTIONS_USE_ACK;
+
+
+ /**
+ * Adds an interceptor to the channel message chain.
+ * @param interceptor ChannelInterceptor
+ */
+ public void addInterceptor(ChannelInterceptor interceptor);
+
+ /**
+ * Starts up the channel. This can be called multiple times for individual services to start
+ * The svc parameter can be the logical or value of any constants
+ * @param svc int value of <BR>
+ * DEFAULT - will start all services <BR>
+ * MBR_RX_SEQ - starts the membership receiver <BR>
+ * MBR_TX_SEQ - starts the membership broadcaster <BR>
+ * SND_TX_SEQ - starts the replication transmitter<BR>
+ * SND_RX_SEQ - starts the replication receiver<BR>
+ * <b>Note:</b> In order for the membership broadcaster to
+ * transmit the correct information, it has to be started after the replication receiver.
+ * @throws ChannelException if a startup error occurs or the service is already started or an error occurs.
+ */
+ public void start(int svc) throws ChannelException;
+
+ /**
+ * Shuts down the channel. This can be called multiple times for individual services to shutdown
+ * The svc parameter can be the logical or value of any constants
+ * @param svc int value of <BR>
+ * DEFAULT - will shutdown all services <BR>
+ * MBR_RX_SEQ - stops the membership receiver <BR>
+ * MBR_TX_SEQ - stops the membership broadcaster <BR>
+ * SND_TX_SEQ - stops the replication transmitter<BR>
+ * SND_RX_SEQ - stops the replication receiver<BR>
+ * @throws ChannelException if a startup error occurs or the service is already stopped or an error occurs.
+ */
+ public void stop(int svc) throws ChannelException;
+
+ /**
+ * Send a message to one or more members in the cluster
+ * @param destination Member[] - the destinations, can not be null or zero length, the reason for that
+ * is that a membership change can occur and at that time the application is uncertain what group the message
+ * actually got sent to.
+ * @param msg Serializable - the message to send, has to be serializable, or a <code>ByteMessage</code> to
+ * send a pure byte array
+ * @param options int - sender options, see class documentation for each interceptor that is configured in order to trigger interceptors
+ * @return a unique Id that identifies the message that is sent
+ * @see ByteMessage
+ * @see #SEND_OPTIONS_USE_ACK
+ * @see #SEND_OPTIONS_ASYNCHRONOUS
+ * @see #SEND_OPTIONS_SYNCHRONIZED_ACK
+ */
+ public UniqueId send(Member[] destination, Serializable msg, int options) throws ChannelException;
+
+ /**
+ * Send a message to one or more members in the cluster
+ * @param destination Member[] - the destinations, null or zero length means all
+ * @param msg ClusterMessage - the message to send
+ * @param options int - sender options, see class documentation
+ * @param handler ErrorHandler - handle errors through a callback, rather than throw it
+ * @return a unique Id that identifies the message that is sent
+ * @exception ChannelException - if a serialization error happens.
+ */
+ public UniqueId send(Member[] destination, Serializable msg, int options, ErrorHandler handler) throws ChannelException;
+
+ /**
+ * Sends a heart beat through the interceptor stacks
+ * Use this method to alert interceptors and other components to
+ * clean up garbage, timed out messages etc.<br>
+ * If you application has a background thread, then you can save one thread,
+ * by configuring your channel to not use an internal heartbeat thread
+ * and invoking this method.
+ * @see #setHeartbeat(boolean)
+ */
+ public void heartbeat();
+
+ /**
+ * Enables or disables internal heartbeat.
+ * @param enable boolean - default value is implementation specific
+ * @see #heartbeat()
+ */
+ public void setHeartbeat(boolean enable);
+
+ /**
+ * Add a membership listener, will get notified when a new member joins, leaves or crashes
+ * <br>If the membership listener implements the Heartbeat interface
+ * the <code>heartbeat()</code> method will be invoked when the heartbeat runs on the channel
+ * @param listener MembershipListener
+ * @see MembershipListener
+ */
+ public void addMembershipListener(MembershipListener listener);
+
+ /**
+ * Add a channel listener, this is a callback object when messages are received
+ * <br>If the channel listener implements the Heartbeat interface
+ * the <code>heartbeat()</code> method will be invoked when the heartbeat runs on the channel
+ * @param listener ChannelListener
+ * @see ChannelListener
+ * @see Heartbeat
+ */
+ public void addChannelListener(ChannelListener listener);
+
+ /**
+ * remove a membership listener, listeners are removed based on Object.hashCode and Object.equals
+ * @param listener MembershipListener
+ * @see MembershipListener
+ */
+ public void removeMembershipListener(MembershipListener listener);
+ /**
+ * remove a channel listener, listeners are removed based on Object.hashCode and Object.equals
+ * @param listener ChannelListener
+ * @see ChannelListener
+ */
+ public void removeChannelListener(ChannelListener listener);
+
+ /**
+ * Returns true if there are any members in the group,
+ * this call is the same as <code>getMembers().length>0</code>
+ * @return boolean - true if there are any members automatically discovered
+ */
+ public boolean hasMembers() ;
+
+ /**
+ * Get all current group members
+ * @return all members or empty array, never null
+ */
+ public Member[] getMembers() ;
+
+ /**
+ * Return the member that represents this node. This is also the data
+ * that gets broadcasted through the membership broadcaster component
+ * @param incAlive - optimization, true if you want it to calculate alive time
+ * since the membership service started.
+ * @return Member
+ */
+ public Member getLocalMember(boolean incAlive);
+
+ /**
+ * Returns the member from the membership service with complete and
+ * recent data. Some implementations might serialize and send
+ * membership information along with a message, and instead of sending
+ * complete membership details, only send the primary identifier for the member
+ * but not the payload or other information. When such message is received
+ * the application can retrieve the cached member through this call.<br>
+ * In most cases, this is not necessary.
+ * @param mbr Member
+ * @return Member
+ */
+ public Member getMember(Member mbr);
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.util.ArrayList;
+
+/**
+ * Channel Exception<br>
+ * A channel exception is thrown when an internal error happens
+ * somewhere in the channel. <br>
+ * When a global error happens, the cause can be retrieved using <code>getCause()</code><br><br>
+ * If an application is sending a message and some of the recipients fail to receive it,
+ * the application can retrieve what recipients failed by using the <code>getFaultyMembers()</code>
+ * method. This way, an application will always know if a message was delivered successfully or not.
+ * @author Filip Hanik
+ * @version $Revision: 304032 $, $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $
+ */
+
+public class ChannelException extends Exception {
+ /*
+ * Holds a list of faulty members
+ */
+ private ArrayList faultyMembers=null;
+
+ /**
+ * Constructor, creates a ChannelException
+ * @see java.lang.Exception#Exception()
+ */
+ public ChannelException() {
+ super();
+ }
+
+ /**
+ * Constructor, creates a ChannelException with an error message
+ * @see java.lang.Exception#Exception(String)
+ */
+ public ChannelException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor, creates a ChannelException with an error message and a cause
+ * @param message String
+ * @param cause Throwable
+ * @see java.lang.Exception#Exception(String,Throwable)
+ */
+ public ChannelException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructor, creates a ChannelException with a cause
+ * @param cause Throwable
+ * @see java.lang.Exception#Exception(Throwable)
+ */
+ public ChannelException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Returns the message for this exception
+ * @return String
+ * @see java.lang.Exception#getMessage()
+ */
+ public String getMessage() {
+ StringBuffer buf = new StringBuffer(super.getMessage());
+ if (faultyMembers==null || faultyMembers.size() == 0 ) {
+ buf.append("; No faulty members identified.");
+ } else {
+ buf.append("; Faulty members:");
+ for ( int i=0; i<faultyMembers.size(); i++ ) {
+ FaultyMember mbr = (FaultyMember)faultyMembers.get(i);
+ buf.append(mbr.getMember().getName());
+ buf.append("; ");
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Adds a faulty member, and the reason the member failed.
+ * @param mbr Member
+ * @param x Exception
+ */
+ public void addFaultyMember(Member mbr, Exception x ) {
+ addFaultyMember(new FaultyMember(mbr,x));
+ }
+
+ /**
+ * Adds a list of faulty members
+ * @param mbrs FaultyMember[]
+ */
+ public void addFaultyMember(FaultyMember[] mbrs) {
+ for (int i=0; mbrs!=null && i<mbrs.length; i++ ) {
+ addFaultyMember(mbrs[i]);
+ }
+ }
+
+ /**
+ * Adds a faulty member
+ * @param mbr FaultyMember
+ */
+ public void addFaultyMember(FaultyMember mbr) {
+ if ( this.faultyMembers==null ) this.faultyMembers = new ArrayList();
+ faultyMembers.add(mbr);
+ }
+
+ /**
+ * Returns an array of members that failed and the reason they failed.
+ * @return FaultyMember[]
+ */
+ public FaultyMember[] getFaultyMembers() {
+ if ( this.faultyMembers==null ) return new FaultyMember[0];
+ return (FaultyMember[])faultyMembers.toArray(new FaultyMember[faultyMembers.size()]);
+ }
+
+ /**
+ *
+ * <p>Title: FaultyMember class</p>
+ *
+ * <p>Description: Represent a failure to a specific member when a message was sent
+ * to more than one member</p>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+ public static class FaultyMember {
+ protected Exception cause;
+ protected Member member;
+ public FaultyMember(Member mbr, Exception x) {
+ this.member = mbr;
+ this.cause = x;
+ }
+
+ public Member getMember() {
+ return member;
+ }
+
+ public Exception getCause() {
+ return cause;
+ }
+
+ public String toString() {
+ return "FaultyMember:"+member.toString();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import org.apache.catalina.tribes.group.InterceptorPayload;
+
+/**
+ * A ChannelInterceptor is an interceptor that intercepts
+ * messages and membership messages in the channel stack.
+ * This allows interceptors to modify the message or perform
+ * other actions when a message is sent or received.<br>
+ * Interceptors are tied together in a linked list.
+ * @see org.apache.catalina.tribes.group.ChannelInterceptorBase
+ * @author Filip Hanik
+ * @version $Revision: 304032 $, $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $
+ */
+
+public interface ChannelInterceptor extends MembershipListener, Heartbeat {
+
+ /**
+ * An interceptor can react to a message based on a set bit on the
+ * message options. <br>
+ * When a message is sent, the options can be retrieved from ChannelMessage.getOptions()
+ * and if the bit is set, this interceptor will react to it.<br>
+ * A simple evaluation if an interceptor should react to the message would be:<br>
+ * <code>boolean react = (getOptionFlag() == (getOptionFlag() & ChannelMessage.getOptions()));</code><br>
+ * The default option is 0, meaning there is no way for the application to trigger the
+ * interceptor. The interceptor itself will decide.<br>
+ * @return int
+ * @see ChannelMessage#getOptions()
+ */
+ public int getOptionFlag();
+
+ /**
+ * Sets the option flag
+ * @param flag int
+ * @see #getOptionFlag()
+ */
+ public void setOptionFlag(int flag);
+
+ /**
+ * Set the next interceptor in the list of interceptors
+ * @param next ChannelInterceptor
+ */
+ public void setNext(ChannelInterceptor next) ;
+
+ /**
+ * Retrieve the next interceptor in the list
+ * @return ChannelInterceptor - returns the next interceptor in the list or null if no more interceptors exist
+ */
+ public ChannelInterceptor getNext();
+
+ /**
+ * Set the previous interceptor in the list
+ * @param previous ChannelInterceptor
+ */
+ public void setPrevious(ChannelInterceptor previous);
+
+ /**
+ * Retrieve the previous interceptor in the list
+ * @return ChannelInterceptor - returns the previous interceptor in the list or null if no more interceptors exist
+ */
+ public ChannelInterceptor getPrevious();
+
+ /**
+ * The <code>sendMessage</code> method is called when a message is being sent to one more destinations.
+ * The interceptor can modify any of the parameters and then pass on the message down the stack by
+ * invoking <code>getNext().sendMessage(destination,msg,payload)</code><br>
+ * Alternatively the interceptor can stop the message from being sent by not invoking
+ * <code>getNext().sendMessage(destination,msg,payload)</code><br>
+ * If the message is to be sent asynchronous the application can be notified of completion and
+ * errors by passing in an error handler attached to a payload object.<br>
+ * The ChannelMessage.getAddress contains Channel.getLocalMember, and can be overwritten
+ * to simulate a message sent from another node.<br>
+ * @param destination Member[] - the destination for this message
+ * @param msg ChannelMessage - the message to be sent
+ * @param payload InterceptorPayload - the payload, carrying an error handler and future useful data, can be null
+ * @throws ChannelException
+ * @see ErrorHandler
+ * @see InterceptorPayload
+ */
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException;
+
+ /**
+ * the <code>messageReceived</code> is invoked when a message is received.
+ * <code>ChannelMessage.getAddress()</code> is the sender, or the reply-to address
+ * if it has been overwritten.
+ * @param data ChannelMessage
+ */
+ public void messageReceived(ChannelMessage data);
+
+ /**
+ * The <code>heartbeat()</code> method gets invoked periodically
+ * to allow interceptors to clean up resources, time out object and
+ * perform actions that are unrelated to sending/receiving data.
+ */
+ public void heartbeat();
+
+ /**
+ * Intercepts the <code>Channel.hasMembers()</code> method
+ * @return boolean - if the channel has members in its membership group
+ * @see Channel#hasMembers()
+ */
+ public boolean hasMembers() ;
+
+ /**
+ * Intercepts the code>Channel.getMembers()</code> method
+ * @return Member[]
+ * @see Channel#getMembers()
+ */
+ public Member[] getMembers() ;
+
+ /**
+ * Intercepts the code>Channel.getLocalMember(boolean)</code> method
+ * @param incAliveTime boolean
+ * @return Member
+ * @see Channel#getLocalMember(boolean)
+ */
+ public Member getLocalMember(boolean incAliveTime) ;
+
+ /**
+ * Intercepts the code>Channel.getMember(Member)</code> method
+ * @param mbr Member
+ * @return Member - the actual member information, including stay alive
+ * @see Channel#getMember(Member)
+ */
+ public Member getMember(Member mbr);
+
+ /**
+ * Starts up the channel. This can be called multiple times for individual services to start
+ * The svc parameter can be the logical or value of any constants
+ * @param svc int value of <BR>
+ * Channel.DEFAULT - will start all services <BR>
+ * Channel.MBR_RX_SEQ - starts the membership receiver <BR>
+ * Channel.MBR_TX_SEQ - starts the membership broadcaster <BR>
+ * Channel.SND_TX_SEQ - starts the replication transmitter<BR>
+ * Channel.SND_RX_SEQ - starts the replication receiver<BR>
+ * @throws ChannelException if a startup error occurs or the service is already started.
+ * @see Channel
+ */
+ public void start(int svc) throws ChannelException;
+
+ /**
+ * Shuts down the channel. This can be called multiple times for individual services to shutdown
+ * The svc parameter can be the logical or value of any constants
+ * @param svc int value of <BR>
+ * Channel.DEFAULT - will shutdown all services <BR>
+ * Channel.MBR_RX_SEQ - stops the membership receiver <BR>
+ * Channel.MBR_TX_SEQ - stops the membership broadcaster <BR>
+ * Channel.SND_TX_SEQ - stops the replication transmitter<BR>
+ * Channel.SND_RX_SEQ - stops the replication receiver<BR>
+ * @throws ChannelException if a startup error occurs or the service is already started.
+ * @see Channel
+ */
+ public void stop(int svc) throws ChannelException;
+
+ public void fireInterceptorEvent(InterceptorEvent event);
+
+ interface InterceptorEvent {
+ int getEventType();
+ String getEventTypeDesc();
+ ChannelInterceptor getInterceptor();
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.io.Serializable;
+/**
+ *
+ * <p>Title: ChannelListener</p>
+ *
+ * <p>Description: An interface to listens to incoming messages from a channel </p>
+ * When a message is received, the Channel will invoke the channel listener in a conditional sequence.
+ * <code>if ( listener.accept(msg,sender) ) listener.messageReceived(msg,sender);</code><br>
+ * A ChannelListener implementation MUST NOT return true on <code>accept(Serializable, Member)</code>
+ * if it doesn't intend to process the message. The channel can this way track whether a message
+ * was processed by an above application or if it was just received and forgot about, a featuer required
+ * to support message-response(RPC) calls<br>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+
+public interface ChannelListener {
+
+ /**
+ * Receive a message from the channel
+ * @param msg Serializable
+ * @param sender - the source of the message
+ */
+ public void messageReceived(Serializable msg, Member sender);
+
+ /**
+ * Invoked by the channel to determine if the listener will process this message or not.
+ * @param msg Serializable
+ * @param sender Member
+ * @return boolean
+ */
+ public boolean accept(Serializable msg, Member sender);
+
+ /**
+ *
+ * @param listener Object
+ * @return boolean
+ * @see Object#equals(Object)
+ */
+ public boolean equals(Object listener);
+
+ /**
+ *
+ * @return int
+ * @see Object#hashCode(int)
+ */
+ public int hashCode();
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.io.Serializable;
+import org.apache.catalina.tribes.io.XByteBuffer;
+
+/**
+ * Message that is passed through the interceptor stack after the
+ * data serialized in the Channel object and then passed down to the
+ * interceptor and eventually down to the ChannelSender component
+ * @author Filip Hanik
+ *
+ */
+public interface ChannelMessage extends Serializable {
+
+
+
+
+ /**
+ * Get the address that this message originated from.
+ * Almost always <code>Channel.getLocalMember(boolean)</code><br>
+ * This would be set to a different address
+ * if the message was being relayed from a host other than the one
+ * that originally sent it.
+ * @return the source or reply-to address of this message
+ */
+ public Member getAddress();
+
+ /**
+ * Sets the source or reply-to address of this message
+ * @param member Member
+ */
+ public void setAddress(Member member);
+
+ /**
+ * Timestamp of when the message was created.
+ * @return long timestamp in milliseconds
+ */
+ public long getTimestamp();
+
+ /**
+ *
+ * Sets the timestamp of this message
+ * @param timestamp The timestamp
+ */
+ public void setTimestamp(long timestamp);
+
+ /**
+ * Each message must have a globally unique Id.
+ * interceptors heavily depend on this id for message processing
+ * @return byte
+ */
+ public byte[] getUniqueId();
+
+ /**
+ * The byte buffer that contains the actual message payload
+ * @param buf XByteBuffer
+ */
+ public void setMessage(XByteBuffer buf);
+
+ /**
+ * returns the byte buffer that contains the actual message payload
+ * @return XByteBuffer
+ */
+ public XByteBuffer getMessage();
+
+ /**
+ * The message options is a 32 bit flag set
+ * that triggers interceptors and message behavior.
+ * @see Channel#send(Member[], Serializable, int)
+ * @see ChannelInterceptor#getOptionFlag
+ * @return int - the option bits set for this message
+ */
+ public int getOptions();
+
+ /**
+ * sets the option bits for this message
+ * @param options int
+ * @see #getOptions()
+ */
+ public void setOptions(int options);
+
+ /**
+ * Shallow clone, what gets cloned depends on the implementation
+ * @return ChannelMessage
+ */
+ public Object clone();
+
+ /**
+ * Deep clone, all fields MUST get cloned
+ * @return ChannelMessage
+ */
+ public Object deepclone();
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+
+/**
+ * ChannelReceiver Interface<br>
+ * The <code>ChannelReceiver</code> interface is the data receiver component
+ * at the bottom layer, the IO layer (for layers see the javadoc for the {@link Channel} interface).
+ * This class may optionally implement a thread pool for parallel processing of incoming messages.
+ * @author Filip Hanik
+ * @version $Revision: 379904 $, $Date: 2006-02-22 15:16:25 -0600 (Wed, 22 Feb 2006) $
+ */
+public interface ChannelReceiver extends Heartbeat {
+ /**
+ * Start listening for incoming messages on the host/port
+ * @throws java.io.IOException
+ */
+ public void start() throws java.io.IOException;
+
+ /**
+ * Stop listening for messages
+ */
+ public void stop();
+
+ /**
+ * String representation of the IPv4 or IPv6 address that this host is listening
+ * to.
+ * @return the host that this receiver is listening to
+ */
+ public String getHost();
+
+
+ /**
+ * Returns the listening port
+ * @return port
+ */
+ public int getPort();
+
+ /**
+ * Sets the message listener to receive notification of incoming
+ * @param listener MessageListener
+ * @see MessageListener
+ */
+ public void setMessageListener(MessageListener listener);
+
+ /**
+ * Returns the message listener that is associated with this receiver
+ * @return MessageListener
+ * @see MessageListener
+ */
+ public MessageListener getMessageListener();
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+
+/**
+ * ChannelReceiver Interface<br>
+ * The <code>ChannelSender</code> interface is the data sender component
+ * at the bottom layer, the IO layer (for layers see the javadoc for the {@link Channel} interface).<br>
+ * The channel sender must support "silent" members, ie, be able to send a message to a member
+ * that is not in the membership, but is part of the destination parameter
+ * @author Filip Hanik
+ * @version $Revision: 379904 $, $Date: 2006-02-22 15:16:25 -0600 (Wed, 22 Feb 2006) $
+ */
+public interface ChannelSender extends Heartbeat
+{
+ /**
+ * Notify the sender of a member being added to the group.<br>
+ * Optional. This can be an empty implementation, that does nothing
+ * @param member Member
+ */
+ public void add(Member member);
+ /**
+ * Notification that a member has been removed or crashed.
+ * Can be used to clean up open connections etc
+ * @param member Member
+ */
+ public void remove(Member member);
+
+ /**
+ * Start the channel sender
+ * @throws IOException if preprocessing takes place and an error happens
+ */
+ public void start() throws java.io.IOException;
+ /**
+ * Stop the channel sender
+ */
+ public void stop();
+
+ /**
+ * A channel heartbeat, use this method to clean up resources
+ */
+ public void heartbeat() ;
+
+ /**
+ * Send a message to one or more recipients.
+ * @param message ChannelMessage - the message to be sent
+ * @param destination Member[] - the destinations
+ * @throws ChannelException - if an error happens, the ChannelSender MUST report
+ * individual send failures on a per member basis, using ChannelException.addFaultyMember
+ * @see ChannelException#addFaultyMember(Member,java.lang.Exception)
+ */
+ public void sendMessage(ChannelMessage message, Member[] destination) throws ChannelException;
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.tribes;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.tribes</code>
+ * package.
+ *
+ * @author Bip Thelin
+ * @author Filip Hanik
+ * @version $Revision: 302726 $, $Date: 2004-02-27 08:59:07 -0600 (Fri, 27 Feb 2004) $
+ */
+
+public final class Constants {
+ public static final String Package = "org.apache.catalina.tribes";
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+
+
+/**
+ * The <code>ErrorHandler</code> class is used when sending messages
+ * that are sent asynchronously and the application still needs to get
+ * confirmation when the message was sent successfully or when a message errored out.
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public interface ErrorHandler {
+
+ /**
+ * Invoked if the message is dispatched asynch, and an error occurs
+ * @param x ChannelException - the error that happened
+ * @param id - the unique id for the message
+ * @see Channel#send(Member[], Serializable, int, ErrorHandler)
+ */
+ public void handleError(ChannelException x, UniqueId id);
+
+ /**
+ * Invoked when the message has been sent successfully.
+ * @param id - the unique id for the message
+ * @see Channel#send(Member[], Serializable, int, ErrorHandler)
+ */
+ public void handleCompletion(UniqueId id);
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+/**
+ * Can be implemented by the ChannelListener and Membership listeners to receive heartbeat
+ * notifications from the Channel
+ * @author Filip Hanik
+ * @version 1.0
+ * @see Channel
+ * @see Channel#heartbeat()
+ */
+public interface Heartbeat {
+
+ /**
+ * Heartbeat invokation for resources cleanup etc
+ */
+ public void heartbeat();
+
+}
\ No newline at end of file
--- /dev/null
+cluster.mbean.register.already=MBean {0} already registered!
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.util.Iterator;
+
+/**
+ * Channel interface
+ * A managed channel interface gives you access to the components of the channels
+ * such as senders, receivers, interceptors etc for configurations purposes
+ * @author Filip Hanik
+ * @version $Revision: 304032 $, $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $
+ */
+public interface ManagedChannel extends Channel {
+
+ /**
+ * Sets the channel sender
+ * @param sender ChannelSender
+ * @see ChannelSender
+ */
+ public void setChannelSender(ChannelSender sender);
+
+ /**
+ * Sets the channel receiver
+ * @param receiver ChannelReceiver
+ * @see ChannelReceiver
+ */
+ public void setChannelReceiver(ChannelReceiver receiver);
+
+ /**
+ * Sets the membership service
+ * @param service MembershipService
+ * @see MembershipService
+ */
+ public void setMembershipService(MembershipService service);
+
+ /**
+ * returns the channel sender
+ * @return ChannelSender
+ * @see ChannelSender
+ */
+ public ChannelSender getChannelSender();
+
+ /**
+ * returns the channel receiver
+ * @return ChannelReceiver
+ * @see ChannelReceiver
+ */
+ public ChannelReceiver getChannelReceiver();
+
+ /**
+ * Returns the membership service
+ * @return MembershipService
+ * @see MembershipService
+ */
+ public MembershipService getMembershipService();
+
+ /**
+ * Returns the interceptor stack
+ * @return Iterator
+ * @see Channel#addInterceptor(ChannelInterceptor)
+ */
+ public Iterator getInterceptors();
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+/**
+ * The Member interface, defines a member in the group.
+ * Each member can carry a set of properties, defined by the actual implementation.<BR>
+ * A member is identified by the host/ip/uniqueId<br>
+ * The host is what interface the member is listening to, to receive data<br>
+ * The port is what port the member is listening to, to receive data<br>
+ * The uniqueId defines the session id for the member. This is an important feature
+ * since a member that has crashed and the starts up again on the same port/host is
+ * not guaranteed to be the same member, so no state transfers will ever be confused
+ * @author Filip Hanik
+ * @version $Revision: 303950 $, $Date: 2005-06-09 15:38:30 -0500 (Thu, 09 Jun 2005) $
+ */
+
+
+public interface Member {
+
+ /**
+ * When a member leaves the cluster, the payload of the memberDisappeared member
+ * will be the following bytes. This indicates a soft shutdown, and not a crash
+ */
+ public static final byte[] SHUTDOWN_PAYLOAD = new byte[] {66, 65, 66, 89, 45, 65, 76, 69, 88};
+
+ /**
+ * Returns the name of this node, should be unique within the group.
+ */
+ public String getName();
+
+ /**
+ * Returns the listen host for the ChannelReceiver implementation
+ * @return IPv4 or IPv6 representation of the host address this member listens to incoming data
+ * @see ChannelReceiver
+ */
+ public byte[] getHost();
+
+ /**
+ * Returns the listen port for the ChannelReceiver implementation
+ * @return IPv4 or IPv6 representation of the host address this member listens to incoming data
+ * @see ChannelReceiver
+ */
+ public int getPort();
+
+ /**
+ * Contains information on how long this member has been online.
+ * The result is the number of milli seconds this member has been
+ * broadcasting its membership to the group.
+ * @return nr of milliseconds since this member started.
+ */
+ public long getMemberAliveTime();
+
+ /**
+ * The current state of the member
+ * @return boolean - true if the member is functioning correctly
+ */
+ public boolean isReady();
+ /**
+ * The current state of the member
+ * @return boolean - true if the member is suspect, but the crash has not been confirmed
+ */
+ public boolean isSuspect();
+
+ /**
+ *
+ * @return boolean - true if the member has been confirmed to malfunction
+ */
+ public boolean isFailing();
+
+ /**
+ * returns a UUID unique for this member over all sessions.
+ * If the member crashes and restarts, the uniqueId will be different.
+ * @return byte[]
+ */
+ public byte[] getUniqueId();
+
+ /**
+ * returns the payload associated with this member
+ * @return byte[]
+ */
+ public byte[] getPayload();
+
+ /**
+ * returns the command associated with this member
+ * @return byte[]
+ */
+ public byte[] getCommand();
+
+ /**
+ * Domain for this cluster
+ * @return byte[]
+ */
+ public byte[] getDomain();
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+/**
+ * The MembershipListener interface is used as a callback to the
+ * membership service. It has two methods that will notify the listener
+ * when a member has joined the group and when a member has disappeared (crashed)
+ *
+ * @author Filip Hanik
+ * @version $Revision: 302726 $, $Date: 2004-02-27 08:59:07 -0600 (Fri, 27 Feb 2004) $
+ */
+
+
+public interface MembershipListener {
+ /**
+ * A member was added to the group
+ * @param member Member - the member that was added
+ */
+ public void memberAdded(Member member);
+
+ /**
+ * A member was removed from the group<br>
+ * If the member left voluntarily, the payload will contain the Member.SHUTDOWN_PAYLOAD data
+ * @param member Member
+ * @see Member#SHUTDOWN_PAYLOAD
+ */
+ public void memberDisappeared(Member member);
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+
+/**
+ * MembershipService Interface<br>
+ * The <code>MembershipService</code> interface is the membership component
+ * at the bottom layer, the IO layer (for layers see the javadoc for the {@link Channel} interface).<br>
+ * @author Filip Hanik
+ * @version $Revision: 378093 $, $Date: 2006-02-15 15:13:45 -0600 (Wed, 15 Feb 2006) $
+ */
+
+
+public interface MembershipService {
+
+ public static final int MBR_RX = Channel.MBR_RX_SEQ;
+ public static final int MBR_TX = Channel.MBR_TX_SEQ;
+
+ /**
+ * Sets the properties for the membership service. This must be called before
+ * the <code>start()</code> method is called.
+ * The properties are implementation specific.
+ * @param properties - to be used to configure the membership service.
+ */
+ public void setProperties(java.util.Properties properties);
+ /**
+ * Returns the properties for the configuration used.
+ */
+ public java.util.Properties getProperties();
+ /**
+ * Starts the membership service. If a membership listeners is added
+ * the listener will start to receive membership events.
+ * Performs a start level 1 and 2
+ * @throws java.lang.Exception if the service fails to start.
+ */
+ public void start() throws java.lang.Exception;
+
+ /**
+ * Starts the membership service. If a membership listeners is added
+ * the listener will start to receive membership events.
+ * @param level - level MBR_RX starts listening for members, level MBR_TX
+ * starts broad casting the server
+ * @throws java.lang.Exception if the service fails to start.
+ * @throws java.lang.IllegalArgumentException if the level is incorrect.
+ */
+ public void start(int level) throws java.lang.Exception;
+
+
+ /**
+ * Starts the membership service. If a membership listeners is added
+ * the listener will start to receive membership events.
+ * @param level - level MBR_RX stops listening for members, level MBR_TX
+ * stops broad casting the server
+ * @throws java.lang.Exception if the service fails to stop
+ * @throws java.lang.IllegalArgumentException if the level is incorrect.
+ */
+
+ public void stop(int level);
+
+ /**
+ * @return true if the the group contains members
+ */
+ public boolean hasMembers();
+
+
+ /**
+ *
+ * @param mbr Member
+ * @return Member
+ */
+ public Member getMember(Member mbr);
+ /**
+ * Returns a list of all the members in the cluster.
+ */
+
+ public Member[] getMembers();
+
+ /**
+ * Returns the member object that defines this member
+ */
+ public Member getLocalMember(boolean incAliveTime);
+
+ /**
+ * Return all members by name
+ */
+ public String[] getMembersByName() ;
+
+ /**
+ * Return the member by name
+ */
+ public Member findMemberByName(String name) ;
+
+ /**
+ * Sets the local member properties for broadcasting
+ */
+ public void setLocalMemberProperties(String listenHost, int listenPort);
+
+ /**
+ * Sets the membership listener, only one listener can be added.
+ * If you call this method twice, the last listener will be used.
+ * @param listener The listener
+ */
+ public void setMembershipListener(MembershipListener listener);
+
+ /**
+ * removes the membership listener.
+ */
+ public void removeMembershipListener();
+
+ /**
+ * Set a payload to be broadcasted with each membership
+ * broadcast.
+ * @param payload byte[]
+ */
+ public void setPayload(byte[] payload);
+
+ public void setDomain(byte[] domain);
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+/**
+ *
+ * <p>Title: MessageListener</p>
+ *
+ * <p>Description: The listener to be registered with the ChannelReceiver, internal Tribes component</p>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+
+public interface MessageListener {
+
+ /**
+ * Receive a message from the IO components in the Channel stack
+ * @param msg ChannelMessage
+ */
+ public void messageReceived(ChannelMessage msg);
+
+ public boolean accept(ChannelMessage msg);
+
+ public boolean equals(Object listener);
+
+ public int hashCode();
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.catalina.tribes;
+
+/**
+ * <p>Title: RemoteProcessException</p>
+ *
+ * <p>Description: Message thrown by a sender when USE_SYNC_ACK receives a FAIL_ACK_COMMAND.<br>
+ * This means that the message was received on the remote node but the processing of the message failed.
+ * This message will be embedded in a ChannelException.FaultyMember
+ * </p>
+ * @see ChannelException
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class RemoteProcessException extends RuntimeException {
+ public RemoteProcessException() {
+ super();
+ }
+
+ public RemoteProcessException(String message) {
+ super(message);
+ }
+
+ public RemoteProcessException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public RemoteProcessException(Throwable cause) {
+ super(cause);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import org.apache.catalina.tribes.util.Arrays;
+import java.io.Serializable;
+
+/**
+ * <p>Title: Represents a globabally unique Id</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public final class UniqueId implements Serializable{
+ protected byte[] id;
+
+ public UniqueId() {
+ }
+
+ public UniqueId(byte[] id) {
+ this.id = id;
+ }
+
+ public UniqueId(byte[] id, int offset, int length) {
+ this.id = new byte[length];
+ System.arraycopy(id,offset,this.id,0,length);
+ }
+
+ public int hashCode() {
+ if ( id == null ) return 0;
+ return Arrays.hashCode(id);
+ }
+
+ public boolean equals(Object other) {
+ boolean result = (other instanceof UniqueId);
+ if ( result ) {
+ UniqueId uid = (UniqueId)other;
+ if ( this.id == null && uid.id == null ) result = true;
+ else if ( this.id == null && uid.id != null ) result = false;
+ else if ( this.id != null && uid.id == null ) result = false;
+ else result = Arrays.equals(this.id,uid.id);
+ }//end if
+ return result;
+ }
+
+ public byte[] getBytes() {
+ return id;
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer("UniqueId");
+ buf.append(org.apache.catalina.tribes.util.Arrays.toString(id));
+ return buf.toString();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.apache.catalina.tribes.group;
+
+import org.apache.catalina.tribes.Member;
+import java.util.Comparator;
+import java.util.Arrays;
+
+/**
+ * <p>Title: Membership - Absolute Order</p>
+ *
+ * <p>Description: A simple, yet agreeable and efficient way of ordering members</p>
+ * <p>
+ * Ordering members can serve as a basis for electing a leader or coordinating efforts.<br>
+ * This is stinky simple, it works on the basis of the <code>Member</code> interface
+ * and orders members in the following format:
+ *
+ * <ol>
+ * <li>IP comparison - byte by byte, lower byte higher rank</li>
+ * <li>IPv4 addresses rank higher than IPv6, ie the lesser number of bytes, the higher rank</li>
+ * <li>Port comparison - lower port, higher rank</li>
+ * <li>UniqueId comparison- byte by byte, lower byte higher rank</li>
+ * </ol>
+ *
+ * </p>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ * @see org.apache.catalina.tribes.Member
+ */
+public class AbsoluteOrder {
+ public static final AbsoluteComparator comp = new AbsoluteComparator();
+
+ protected AbsoluteOrder() {
+ super();
+ }
+
+
+
+ public static void absoluteOrder(Member[] members) {
+ if ( members == null || members.length == 0 ) return;
+ Arrays.sort(members,comp);
+ }
+
+
+ public static class AbsoluteComparator implements Comparator {
+ public int compare(Object o1, Object o2) {
+ if ( !((o1 instanceof Member) && (o2 instanceof Member)) ) return 0;
+ return compareMembers((Member)o1,(Member)o2);
+ }
+
+ public int compareMembers(Member m1, Member m2) {
+ int result = compareIps(m1,m2);
+ if ( result == 0 ) result = comparePorts(m1,m2);
+ if ( result == 0 ) result = compareIds(m1,m2);
+ return result;
+ }
+
+ public int compareIps(Member m1, Member m2) {
+ return compareBytes(m1.getHost(),m2.getHost());
+ }
+
+ public int comparePorts(Member m1, Member m2) {
+ return compareInts(m1.getPort(),m2.getPort());
+ }
+
+ public int compareIds(Member m1, Member m2) {
+ return compareBytes(m1.getUniqueId(),m2.getUniqueId());
+ }
+
+ protected int compareBytes(byte[] d1, byte[] d2) {
+ int result = 0;
+ if ( d1.length == d2.length ) {
+ for (int i=0; (result==0) && (i<d1.length); i++) {
+ result = compareBytes(d1[i],d2[i]);
+ }
+ } else if ( d1.length < d2.length) {
+ result = -1;
+ } else {
+ result = 1;
+ }
+ return result;
+ }
+
+ protected int compareBytes(byte b1, byte b2) {
+ return compareInts((int)b1,(int)b2);
+ }
+
+ protected int compareInts(int b1, int b2) {
+ int result = 0;
+ if ( b1 == b2 ) {
+
+ } else if ( b1 < b2) {
+ result = -1;
+ } else {
+ result = 1;
+ }
+ return result;
+ }
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ChannelReceiver;
+import org.apache.catalina.tribes.ChannelSender;
+
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipService;
+import org.apache.catalina.tribes.MessageListener;
+import org.apache.catalina.tribes.transport.SenderState;
+import org.apache.catalina.tribes.transport.ReplicationTransmitter;
+import org.apache.catalina.tribes.membership.McastService;
+import org.apache.catalina.tribes.transport.nio.NioReceiver;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.util.Logs;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.util.Arrays;
+
+
+/**
+ * The channel coordinator object coordinates the membership service,
+ * the sender and the receiver.
+ * This is the last interceptor in the chain.
+ * @author Filip Hanik
+ * @version $Revision: 304032 $, $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $
+ */
+public class ChannelCoordinator extends ChannelInterceptorBase implements MessageListener {
+ private ChannelReceiver clusterReceiver = new NioReceiver();
+ private ChannelSender clusterSender = new ReplicationTransmitter();
+ private MembershipService membershipService = new McastService();
+
+ //override optionflag
+ protected int optionFlag = Channel.SEND_OPTIONS_BYTE_MESSAGE|Channel.SEND_OPTIONS_USE_ACK|Channel.SEND_OPTIONS_SYNCHRONIZED_ACK;
+ public int getOptionFlag() {return optionFlag;}
+ public void setOptionFlag(int flag) {optionFlag=flag;}
+
+ private int startLevel = 0;
+
+ public ChannelCoordinator() {
+
+ }
+
+ public ChannelCoordinator(ChannelReceiver receiver,
+ ChannelSender sender,
+ MembershipService service) {
+ this();
+ this.setClusterReceiver(receiver);
+ this.setClusterSender(sender);
+ this.setMembershipService(service);
+ }
+
+ /**
+ * Send a message to one or more members in the cluster
+ * @param destination Member[] - the destinations, null or zero length means all
+ * @param msg ClusterMessage - the message to send
+ * @param options int - sender options, see class documentation
+ * @return ClusterMessage[] - the replies from the members, if any.
+ */
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+ if ( destination == null ) destination = membershipService.getMembers();
+ clusterSender.sendMessage(msg,destination);
+ if ( Logs.MESSAGES.isTraceEnabled() ) {
+ Logs.MESSAGES.trace("ChannelCoordinator - Sent msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " to "+Arrays.toNameString(destination));
+ }
+ }
+
+
+ /**
+ * Starts up the channel. This can be called multiple times for individual services to start
+ * The svc parameter can be the logical or value of any constants
+ * @param svc int value of <BR>
+ * DEFAULT - will start all services <BR>
+ * MBR_RX_SEQ - starts the membership receiver <BR>
+ * MBR_TX_SEQ - starts the membership broadcaster <BR>
+ * SND_TX_SEQ - starts the replication transmitter<BR>
+ * SND_RX_SEQ - starts the replication receiver<BR>
+ * @throws ChannelException if a startup error occurs or the service is already started.
+ */
+ public void start(int svc) throws ChannelException {
+ this.internalStart(svc);
+ }
+
+ /**
+ * Shuts down the channel. This can be called multiple times for individual services to shutdown
+ * The svc parameter can be the logical or value of any constants
+ * @param svc int value of <BR>
+ * DEFAULT - will shutdown all services <BR>
+ * MBR_RX_SEQ - stops the membership receiver <BR>
+ * MBR_TX_SEQ - stops the membership broadcaster <BR>
+ * SND_TX_SEQ - stops the replication transmitter<BR>
+ * SND_RX_SEQ - stops the replication receiver<BR>
+ * @throws ChannelException if a startup error occurs or the service is already started.
+ */
+ public void stop(int svc) throws ChannelException {
+ this.internalStop(svc);
+ }
+
+
+ /**
+ * Starts up the channel. This can be called multiple times for individual services to start
+ * The svc parameter can be the logical or value of any constants
+ * @param svc int value of <BR>
+ * DEFAULT - will start all services <BR>
+ * MBR_RX_SEQ - starts the membership receiver <BR>
+ * MBR_TX_SEQ - starts the membership broadcaster <BR>
+ * SND_TX_SEQ - starts the replication transmitter<BR>
+ * SND_RX_SEQ - starts the replication receiver<BR>
+ * @throws ChannelException if a startup error occurs or the service is already started.
+ */
+ protected synchronized void internalStart(int svc) throws ChannelException {
+ try {
+ boolean valid = false;
+ //make sure we don't pass down any flags that are unrelated to the bottom layer
+ svc = svc & Channel.DEFAULT;
+
+ if (startLevel == Channel.DEFAULT) return; //we have already started up all components
+ if (svc == 0 ) return;//nothing to start
+
+ if (svc == (svc & startLevel)) throw new ChannelException("Channel already started for level:"+svc);
+
+ //must start the receiver first so that we can coordinate the port it
+ //listens to with the local membership settings
+ if ( Channel.SND_RX_SEQ==(svc & Channel.SND_RX_SEQ) ) {
+ clusterReceiver.setMessageListener(this);
+ clusterReceiver.start();
+ //synchronize, big time FIXME
+ membershipService.setLocalMemberProperties(getClusterReceiver().getHost(), getClusterReceiver().getPort());
+ valid = true;
+ }
+ if ( Channel.SND_TX_SEQ==(svc & Channel.SND_TX_SEQ) ) {
+ clusterSender.start();
+ valid = true;
+ }
+
+ if ( Channel.MBR_RX_SEQ==(svc & Channel.MBR_RX_SEQ) ) {
+ membershipService.setMembershipListener(this);
+ membershipService.start(MembershipService.MBR_RX);
+ valid = true;
+ }
+ if ( Channel.MBR_TX_SEQ==(svc & Channel.MBR_TX_SEQ) ) {
+ membershipService.start(MembershipService.MBR_TX);
+ valid = true;
+ }
+
+ if ( !valid) {
+ throw new IllegalArgumentException("Invalid start level, valid levels are:SND_RX_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ");
+ }
+ startLevel = (startLevel | svc);
+ }catch ( ChannelException cx ) {
+ throw cx;
+ }catch ( Exception x ) {
+ throw new ChannelException(x);
+ }
+ }
+
+ /**
+ * Shuts down the channel. This can be called multiple times for individual services to shutdown
+ * The svc parameter can be the logical or value of any constants
+ * @param svc int value of <BR>
+ * DEFAULT - will shutdown all services <BR>
+ * MBR_RX_SEQ - starts the membership receiver <BR>
+ * MBR_TX_SEQ - starts the membership broadcaster <BR>
+ * SND_TX_SEQ - starts the replication transmitter<BR>
+ * SND_RX_SEQ - starts the replication receiver<BR>
+ * @throws ChannelException if a startup error occurs or the service is already started.
+ */
+ protected synchronized void internalStop(int svc) throws ChannelException {
+ try {
+ //make sure we don't pass down any flags that are unrelated to the bottom layer
+ svc = svc & Channel.DEFAULT;
+
+ if (startLevel == 0) return; //we have already stopped up all components
+ if (svc == 0 ) return;//nothing to stop
+
+ boolean valid = false;
+ if ( Channel.SND_RX_SEQ==(svc & Channel.SND_RX_SEQ) ) {
+ clusterReceiver.stop();
+ clusterReceiver.setMessageListener(null);
+ valid = true;
+ }
+ if ( Channel.SND_TX_SEQ==(svc & Channel.SND_TX_SEQ) ) {
+ clusterSender.stop();
+ valid = true;
+ }
+
+ if ( Channel.MBR_RX_SEQ==(svc & Channel.MBR_RX_SEQ) ) {
+ membershipService.stop(MembershipService.MBR_RX);
+ membershipService.setMembershipListener(null);
+ valid = true;
+
+ }
+ if ( Channel.MBR_TX_SEQ==(svc & Channel.MBR_TX_SEQ) ) {
+ valid = true;
+ membershipService.stop(MembershipService.MBR_TX);
+ }
+ if ( !valid) {
+ throw new IllegalArgumentException("Invalid start level, valid levels are:SND_RX_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ");
+ }
+
+ startLevel = (startLevel & (~svc));
+
+ }catch ( Exception x ) {
+ throw new ChannelException(x);
+ } finally {
+
+ }
+
+ }
+
+ public void memberAdded(Member member){
+ SenderState.getSenderState(member);
+ if ( clusterSender!=null ) clusterSender.add(member);
+ super.memberAdded(member);
+ }
+
+ public void memberDisappeared(Member member){
+ SenderState.removeSenderState(member);
+ if ( clusterSender!=null ) clusterSender.remove(member);
+ super.memberDisappeared(member);
+ }
+
+ public void messageReceived(ChannelMessage msg) {
+ if ( Logs.MESSAGES.isTraceEnabled() ) {
+ Logs.MESSAGES.trace("ChannelCoordinator - Received msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " from "+msg.getAddress().getName());
+ }
+ super.messageReceived(msg);
+ }
+
+
+ public ChannelReceiver getClusterReceiver() {
+ return clusterReceiver;
+ }
+
+ public ChannelSender getClusterSender() {
+ return clusterSender;
+ }
+
+ public MembershipService getMembershipService() {
+ return membershipService;
+ }
+
+ public void setClusterReceiver(ChannelReceiver clusterReceiver) {
+ if ( clusterReceiver != null ) {
+ this.clusterReceiver = clusterReceiver;
+ this.clusterReceiver.setMessageListener(this);
+ } else {
+ if (this.clusterReceiver!=null ) this.clusterReceiver.setMessageListener(null);
+ this.clusterReceiver = null;
+ }
+ }
+
+ public void setClusterSender(ChannelSender clusterSender) {
+ this.clusterSender = clusterSender;
+ }
+
+ public void setMembershipService(MembershipService membershipService) {
+ this.membershipService = membershipService;
+ this.membershipService.setMembershipListener(this);
+ }
+
+ public void hearbeat() {
+ if ( clusterSender!=null ) clusterSender.heartbeat();
+ super.heartbeat();
+ }
+
+ /**
+ * has members
+ */
+ public boolean hasMembers() {
+ return this.getMembershipService().hasMembers();
+ }
+
+ /**
+ * Get all current cluster members
+ * @return all members or empty array
+ */
+ public Member[] getMembers() {
+ return this.getMembershipService().getMembers();
+ }
+
+ /**
+ *
+ * @param mbr Member
+ * @return Member
+ */
+ public Member getMember(Member mbr){
+ return this.getMembershipService().getMember(mbr);
+ }
+
+
+ /**
+ * Return the member that represents this node.
+ *
+ * @return Member
+ */
+ public Member getLocalMember(boolean incAlive) {
+ return this.getMembershipService().getLocalMember(incAlive);
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelInterceptor;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+
+/**
+ * Abstract class for the interceptor base class.
+ * @author Filip Hanik
+ * @version $Revision: 304032 $, $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $
+ */
+
+public abstract class ChannelInterceptorBase implements ChannelInterceptor {
+
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(
+ ChannelInterceptorBase.class);
+
+ private ChannelInterceptor next;
+ private ChannelInterceptor previous;
+ //default value, always process
+ protected int optionFlag = 0;
+
+ public ChannelInterceptorBase() {
+
+ }
+
+ public boolean okToProcess(int messageFlags) {
+ if (this.optionFlag == 0 ) return true;
+ return ((optionFlag&messageFlags) == optionFlag);
+ }
+
+ public final void setNext(ChannelInterceptor next) {
+ this.next = next;
+ }
+
+ public final ChannelInterceptor getNext() {
+ return next;
+ }
+
+ public final void setPrevious(ChannelInterceptor previous) {
+ this.previous = previous;
+ }
+
+ public void setOptionFlag(int optionFlag) {
+ this.optionFlag = optionFlag;
+ }
+
+ public final ChannelInterceptor getPrevious() {
+ return previous;
+ }
+
+ public int getOptionFlag() {
+ return optionFlag;
+ }
+
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws
+ ChannelException {
+ if (getNext() != null) getNext().sendMessage(destination, msg, payload);
+ }
+
+ public void messageReceived(ChannelMessage msg) {
+ if (getPrevious() != null) getPrevious().messageReceived(msg);
+ }
+
+ public boolean accept(ChannelMessage msg) {
+ return true;
+ }
+
+ public void memberAdded(Member member) {
+ //notify upwards
+ if (getPrevious() != null) getPrevious().memberAdded(member);
+ }
+
+ public void memberDisappeared(Member member) {
+ //notify upwards
+ if (getPrevious() != null) getPrevious().memberDisappeared(member);
+ }
+
+ public void heartbeat() {
+ if (getNext() != null) getNext().heartbeat();
+ }
+
+ /**
+ * has members
+ */
+ public boolean hasMembers() {
+ if ( getNext()!=null )return getNext().hasMembers();
+ else return false;
+ }
+
+ /**
+ * Get all current cluster members
+ * @return all members or empty array
+ */
+ public Member[] getMembers() {
+ if ( getNext()!=null ) return getNext().getMembers();
+ else return null;
+ }
+
+ /**
+ *
+ * @param mbr Member
+ * @return Member
+ */
+ public Member getMember(Member mbr) {
+ if ( getNext()!=null) return getNext().getMember(mbr);
+ else return null;
+ }
+
+ /**
+ * Return the member that represents this node.
+ *
+ * @return Member
+ */
+ public Member getLocalMember(boolean incAlive) {
+ if ( getNext()!=null ) return getNext().getLocalMember(incAlive);
+ else return null;
+ }
+
+ /**
+ * Starts up the channel. This can be called multiple times for individual services to start
+ * The svc parameter can be the logical or value of any constants
+ * @param svc int value of <BR>
+ * DEFAULT - will start all services <BR>
+ * MBR_RX_SEQ - starts the membership receiver <BR>
+ * MBR_TX_SEQ - starts the membership broadcaster <BR>
+ * SND_TX_SEQ - starts the replication transmitter<BR>
+ * SND_RX_SEQ - starts the replication receiver<BR>
+ * @throws ChannelException if a startup error occurs or the service is already started.
+ */
+ public void start(int svc) throws ChannelException {
+ if ( getNext()!=null ) getNext().start(svc);
+ }
+
+ /**
+ * Shuts down the channel. This can be called multiple times for individual services to shutdown
+ * The svc parameter can be the logical or value of any constants
+ * @param svc int value of <BR>
+ * DEFAULT - will shutdown all services <BR>
+ * MBR_RX_SEQ - stops the membership receiver <BR>
+ * MBR_TX_SEQ - stops the membership broadcaster <BR>
+ * SND_TX_SEQ - stops the replication transmitter<BR>
+ * SND_RX_SEQ - stops the replication receiver<BR>
+ * @throws ChannelException if a startup error occurs or the service is already started.
+ */
+ public void stop(int svc) throws ChannelException {
+ if (getNext() != null) getNext().stop(svc);
+ }
+
+ public void fireInterceptorEvent(InterceptorEvent event) {
+ //empty operation
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.catalina.tribes.ByteMessage;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelInterceptor;
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ChannelReceiver;
+import org.apache.catalina.tribes.ChannelSender;
+import org.apache.catalina.tribes.ErrorHandler;
+import org.apache.catalina.tribes.ManagedChannel;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.MembershipService;
+import org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.Heartbeat;
+import org.apache.catalina.tribes.io.BufferPool;
+import java.io.IOException;
+import org.apache.catalina.tribes.RemoteProcessException;
+import org.apache.catalina.tribes.util.Logs;
+import org.apache.catalina.tribes.util.Arrays;
+
+/**
+ * The default implementation of a Channel.<br>
+ * The GroupChannel manages the replication channel. It coordinates
+ * message being sent and received with membership announcements.
+ * The channel has an chain of interceptors that can modify the message or perform other logic.<br>
+ * It manages a complete group, both membership and replication.
+ * @author Filip Hanik
+ * @version $Revision: 304032 $, $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $
+ */
+public class GroupChannel extends ChannelInterceptorBase implements ManagedChannel {
+ /**
+ * Flag to determine if the channel manages its own heartbeat
+ * If set to true, the channel will start a local thread for the heart beat.
+ */
+ protected boolean heartbeat = true;
+ /**
+ * If <code>heartbeat == true</code> then how often do we want this
+ * heartbeat to run. default is one minute
+ */
+ protected long heartbeatSleeptime = 5*1000;//every 5 seconds
+
+ /**
+ * Internal heartbeat thread
+ */
+ protected HeartbeatThread hbthread = null;
+
+ /**
+ * The <code>ChannelCoordinator</code> coordinates the bottom layer components:<br>
+ * - MembershipService<br>
+ * - ChannelSender <br>
+ * - ChannelReceiver<br>
+ */
+ protected ChannelCoordinator coordinator = new ChannelCoordinator();
+
+ /**
+ * The first interceptor in the inteceptor stack.
+ * The interceptors are chained in a linked list, so we only need a reference to the
+ * first one
+ */
+ protected ChannelInterceptor interceptors = null;
+
+ /**
+ * A list of membership listeners that subscribe to membership announcements
+ */
+ protected ArrayList membershipListeners = new ArrayList();
+
+ /**
+ * A list of channel listeners that subscribe to incoming messages
+ */
+ protected ArrayList channelListeners = new ArrayList();
+
+ /**
+ * If set to true, the GroupChannel will check to make sure that
+ */
+ protected boolean optionCheck = false;
+
+ /**
+ * Creates a GroupChannel. This constructor will also
+ * add the first interceptor in the GroupChannel.<br>
+ * The first interceptor is always the channel itself.
+ */
+ public GroupChannel() {
+ addInterceptor(this);
+ }
+
+
+ /**
+ * Adds an interceptor to the stack for message processing<br>
+ * Interceptors are ordered in the way they are added.<br>
+ * <code>channel.addInterceptor(A);</code><br>
+ * <code>channel.addInterceptor(C);</code><br>
+ * <code>channel.addInterceptor(B);</code><br>
+ * Will result in a interceptor stack like this:<br>
+ * <code>A -> C -> B</code><br>
+ * The complete stack will look like this:<br>
+ * <code>Channel -> A -> C -> B -> ChannelCoordinator</code><br>
+ * @param interceptor ChannelInterceptorBase
+ */
+ public void addInterceptor(ChannelInterceptor interceptor) {
+ if ( interceptors == null ) {
+ interceptors = interceptor;
+ interceptors.setNext(coordinator);
+ interceptors.setPrevious(null);
+ coordinator.setPrevious(interceptors);
+ } else {
+ ChannelInterceptor last = interceptors;
+ while ( last.getNext() != coordinator ) {
+ last = last.getNext();
+ }
+ last.setNext(interceptor);
+ interceptor.setNext(coordinator);
+ interceptor.setPrevious(last);
+ coordinator.setPrevious(interceptor);
+ }
+ }
+
+ /**
+ * Sends a heartbeat through the interceptor stack.<br>
+ * Invoke this method from the application on a periodic basis if
+ * you have turned off internal heartbeats <code>channel.setHeartbeat(false)</code>
+ */
+ public void heartbeat() {
+ super.heartbeat();
+ Iterator i = membershipListeners.iterator();
+ while ( i.hasNext() ) {
+ Object o = i.next();
+ if ( o instanceof Heartbeat ) ((Heartbeat)o).heartbeat();
+ }
+ i = channelListeners.iterator();
+ while ( i.hasNext() ) {
+ Object o = i.next();
+ if ( o instanceof Heartbeat ) ((Heartbeat)o).heartbeat();
+ }
+
+ }
+
+
+ /**
+ * Send a message to the destinations specified
+ * @param destination Member[] - destination.length > 1
+ * @param msg Serializable - the message to send
+ * @param options int - sender options, options can trigger guarantee levels and different interceptors to
+ * react to the message see class documentation for the <code>Channel</code> object.<br>
+ * @return UniqueId - the unique Id that was assigned to this message
+ * @throws ChannelException - if an error occurs processing the message
+ * @see org.apache.catalina.tribes.Channel
+ */
+ public UniqueId send(Member[] destination, Serializable msg, int options) throws ChannelException {
+ return send(destination,msg,options,null);
+ }
+
+ /**
+ *
+ * @param destination Member[] - destination.length > 1
+ * @param msg Serializable - the message to send
+ * @param options int - sender options, options can trigger guarantee levels and different interceptors to
+ * react to the message see class documentation for the <code>Channel</code> object.<br>
+ * @param handler - callback object for error handling and completion notification, used when a message is
+ * sent asynchronously using the <code>Channel.SEND_OPTIONS_ASYNCHRONOUS</code> flag enabled.
+ * @return UniqueId - the unique Id that was assigned to this message
+ * @throws ChannelException - if an error occurs processing the message
+ * @see org.apache.catalina.tribes.Channel
+ */
+ public UniqueId send(Member[] destination, Serializable msg, int options, ErrorHandler handler) throws ChannelException {
+ if ( msg == null ) throw new ChannelException("Cant send a NULL message");
+ XByteBuffer buffer = null;
+ try {
+ if ( destination == null || destination.length == 0) throw new ChannelException("No destination given");
+ ChannelData data = new ChannelData(true);//generates a unique Id
+ data.setAddress(getLocalMember(false));
+ data.setTimestamp(System.currentTimeMillis());
+ byte[] b = null;
+ if ( msg instanceof ByteMessage ){
+ b = ((ByteMessage)msg).getMessage();
+ options = options | SEND_OPTIONS_BYTE_MESSAGE;
+ } else {
+ b = XByteBuffer.serialize(msg);
+ options = options & (~SEND_OPTIONS_BYTE_MESSAGE);
+ }
+ data.setOptions(options);
+ //XByteBuffer buffer = new XByteBuffer(b.length+128,false);
+ buffer = BufferPool.getBufferPool().getBuffer(b.length+128, false);
+ buffer.append(b,0,b.length);
+ data.setMessage(buffer);
+ InterceptorPayload payload = null;
+ if ( handler != null ) {
+ payload = new InterceptorPayload();
+ payload.setErrorHandler(handler);
+ }
+ getFirstInterceptor().sendMessage(destination, data, payload);
+ if ( Logs.MESSAGES.isTraceEnabled() ) {
+ Logs.MESSAGES.trace("GroupChannel - Sent msg:" + new UniqueId(data.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " to "+Arrays.toNameString(destination));
+ Logs.MESSAGES.trace("GroupChannel - Send Message:" + new UniqueId(data.getUniqueId()) + " is " +msg);
+ }
+
+ return new UniqueId(data.getUniqueId());
+ }catch ( Exception x ) {
+ if ( x instanceof ChannelException ) throw (ChannelException)x;
+ throw new ChannelException(x);
+ } finally {
+ if ( buffer != null ) BufferPool.getBufferPool().returnBuffer(buffer);
+ }
+ }
+
+
+ /**
+ * Callback from the interceptor stack. <br>
+ * When a message is received from a remote node, this method will be invoked by
+ * the previous interceptor.<br>
+ * This method can also be used to send a message to other components within the same application,
+ * but its an extreme case, and you're probably better off doing that logic between the applications itself.
+ * @param msg ChannelMessage
+ */
+ public void messageReceived(ChannelMessage msg) {
+ if ( msg == null ) return;
+ try {
+ if ( Logs.MESSAGES.isTraceEnabled() ) {
+ Logs.MESSAGES.trace("GroupChannel - Received msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " from "+msg.getAddress().getName());
+ }
+
+ Serializable fwd = null;
+ if ( (msg.getOptions() & SEND_OPTIONS_BYTE_MESSAGE) == SEND_OPTIONS_BYTE_MESSAGE ) {
+ fwd = new ByteMessage(msg.getMessage().getBytes());
+ } else {
+ fwd = XByteBuffer.deserialize(msg.getMessage().getBytesDirect(),0,msg.getMessage().getLength());
+ }
+ if ( Logs.MESSAGES.isTraceEnabled() ) {
+ Logs.MESSAGES.trace("GroupChannel - Receive Message:" + new UniqueId(msg.getUniqueId()) + " is " +fwd);
+ }
+
+ //get the actual member with the correct alive time
+ Member source = msg.getAddress();
+ boolean rx = false;
+ boolean delivered = false;
+ for ( int i=0; i<channelListeners.size(); i++ ) {
+ ChannelListener channelListener = (ChannelListener)channelListeners.get(i);
+ if (channelListener != null && channelListener.accept(fwd, source)) {
+ channelListener.messageReceived(fwd, source);
+ delivered = true;
+ //if the message was accepted by an RPC channel, that channel
+ //is responsible for returning the reply, otherwise we send an absence reply
+ if ( channelListener instanceof RpcChannel ) rx = true;
+ }
+ }//for
+ if ((!rx) && (fwd instanceof RpcMessage)) {
+ //if we have a message that requires a response,
+ //but none was given, send back an immediate one
+ sendNoRpcChannelReply((RpcMessage)fwd,source);
+ }
+ if ( Logs.MESSAGES.isTraceEnabled() ) {
+ Logs.MESSAGES.trace("GroupChannel delivered["+delivered+"] id:"+new UniqueId(msg.getUniqueId()));
+ }
+
+ } catch ( Exception x ) {
+ if ( log.isDebugEnabled() ) log.error("Unable to process channel:IOException.",x);
+ throw new RemoteProcessException("IOException:"+x.getMessage(),x);
+ }
+ }
+
+ /**
+ * Sends a <code>NoRpcChannelReply</code> message to a member<br>
+ * This method gets invoked by the channel if a RPC message comes in
+ * and no channel listener accepts the message. This avoids timeout
+ * @param msg RpcMessage
+ * @param destination Member - the destination for the reply
+ */
+ protected void sendNoRpcChannelReply(RpcMessage msg, Member destination) {
+ try {
+ //avoid circular loop
+ if ( msg instanceof RpcMessage.NoRpcChannelReply) return;
+ RpcMessage.NoRpcChannelReply reply = new RpcMessage.NoRpcChannelReply(msg.rpcId,msg.uuid);
+ send(new Member[]{destination},reply,Channel.SEND_OPTIONS_ASYNCHRONOUS);
+ } catch ( Exception x ) {
+ log.error("Unable to find rpc channel, failed to send NoRpcChannelReply.",x);
+ }
+ }
+
+ /**
+ * memberAdded gets invoked by the interceptor below the channel
+ * and the channel will broadcast it to the membership listeners
+ * @param member Member - the new member
+ */
+ public void memberAdded(Member member) {
+ //notify upwards
+ for (int i=0; i<membershipListeners.size(); i++ ) {
+ MembershipListener membershipListener = (MembershipListener)membershipListeners.get(i);
+ if (membershipListener != null) membershipListener.memberAdded(member);
+ }
+ }
+
+ /**
+ * memberDisappeared gets invoked by the interceptor below the channel
+ * and the channel will broadcast it to the membership listeners
+ * @param member Member - the member that left or crashed
+ */
+ public void memberDisappeared(Member member) {
+ //notify upwards
+ for (int i=0; i<membershipListeners.size(); i++ ) {
+ MembershipListener membershipListener = (MembershipListener)membershipListeners.get(i);
+ if (membershipListener != null) membershipListener.memberDisappeared(member);
+ }
+ }
+
+ /**
+ * Sets up the default implementation interceptor stack
+ * if no interceptors have been added
+ * @throws ChannelException
+ */
+ protected synchronized void setupDefaultStack() throws ChannelException {
+
+ if ( getFirstInterceptor() != null &&
+ ((getFirstInterceptor().getNext() instanceof ChannelCoordinator))) {
+ ChannelInterceptor interceptor = null;
+ Class clazz = null;
+ try {
+ clazz = Class.forName("org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor",
+ true,GroupChannel.class.getClassLoader());
+ clazz.newInstance();
+ } catch ( Throwable x ) {
+ clazz = MessageDispatchInterceptor.class;
+ }//catch
+ try {
+ interceptor = (ChannelInterceptor) clazz.newInstance();
+ } catch (Exception x) {
+ throw new ChannelException("Unable to add MessageDispatchInterceptor to interceptor chain.",x);
+ }
+ this.addInterceptor(interceptor);
+ }
+ }
+
+ /**
+ * Validates the option flags that each interceptor is using and reports
+ * an error if two interceptor share the same flag.
+ * @throws ChannelException
+ */
+ protected void checkOptionFlags() throws ChannelException {
+ StringBuffer conflicts = new StringBuffer();
+ ChannelInterceptor first = interceptors;
+ while ( first != null ) {
+ int flag = first.getOptionFlag();
+ if ( flag != 0 ) {
+ ChannelInterceptor next = first.getNext();
+ while ( next != null ) {
+ int nflag = next.getOptionFlag();
+ if (nflag!=0 && (((flag & nflag) == flag ) || ((flag & nflag) == nflag)) ) {
+ conflicts.append("[");
+ conflicts.append(first.getClass().getName());
+ conflicts.append(":");
+ conflicts.append(flag);
+ conflicts.append(" == ");
+ conflicts.append(next.getClass().getName());
+ conflicts.append(":");
+ conflicts.append(nflag);
+ conflicts.append("] ");
+ }//end if
+ next = next.getNext();
+ }//while
+ }//end if
+ first = first.getNext();
+ }//while
+ if ( conflicts.length() > 0 ) throw new ChannelException("Interceptor option flag conflict: "+conflicts.toString());
+
+ }
+
+ /**
+ * Starts the channel
+ * @param svc int - what service to start
+ * @throws ChannelException
+ * @see org.apache.catalina.tribes.Channel#start(int)
+ */
+ public synchronized void start(int svc) throws ChannelException {
+ setupDefaultStack();
+ if (optionCheck) checkOptionFlags();
+ super.start(svc);
+ if ( hbthread == null && heartbeat ) {
+ hbthread = new HeartbeatThread(this,heartbeatSleeptime);
+ hbthread.start();
+ }
+ }
+
+ /**
+ * Stops the channel
+ * @param svc int
+ * @throws ChannelException
+ * @see org.apache.catalina.tribes.Channel#stop(int)
+ */
+ public synchronized void stop(int svc) throws ChannelException {
+ if (hbthread != null) {
+ hbthread.stopHeartbeat();
+ hbthread = null;
+ }
+ super.stop(svc);
+ }
+
+ /**
+ * Returns the first interceptor of the stack. Useful for traversal.
+ * @return ChannelInterceptor
+ */
+ public ChannelInterceptor getFirstInterceptor() {
+ if (interceptors != null) return interceptors;
+ else return coordinator;
+ }
+
+ /**
+ * Returns the channel receiver component
+ * @return ChannelReceiver
+ */
+ public ChannelReceiver getChannelReceiver() {
+ return coordinator.getClusterReceiver();
+ }
+
+ /**
+ * Returns the channel sender component
+ * @return ChannelSender
+ */
+ public ChannelSender getChannelSender() {
+ return coordinator.getClusterSender();
+ }
+
+ /**
+ * Returns the membership service component
+ * @return MembershipService
+ */
+ public MembershipService getMembershipService() {
+ return coordinator.getMembershipService();
+ }
+
+ /**
+ * Sets the channel receiver component
+ * @param clusterReceiver ChannelReceiver
+ */
+ public void setChannelReceiver(ChannelReceiver clusterReceiver) {
+ coordinator.setClusterReceiver(clusterReceiver);
+ }
+
+ /**
+ * Sets the channel sender component
+ * @param clusterSender ChannelSender
+ */
+ public void setChannelSender(ChannelSender clusterSender) {
+ coordinator.setClusterSender(clusterSender);
+ }
+
+ /**
+ * Sets the membership component
+ * @param membershipService MembershipService
+ */
+ public void setMembershipService(MembershipService membershipService) {
+ coordinator.setMembershipService(membershipService);
+ }
+
+ /**
+ * Adds a membership listener to the channel.<br>
+ * Membership listeners are uniquely identified using the equals(Object) method
+ * @param membershipListener MembershipListener
+ */
+ public void addMembershipListener(MembershipListener membershipListener) {
+ if (!this.membershipListeners.contains(membershipListener) )
+ this.membershipListeners.add(membershipListener);
+ }
+
+ /**
+ * Removes a membership listener from the channel.<br>
+ * Membership listeners are uniquely identified using the equals(Object) method
+ * @param membershipListener MembershipListener
+ */
+
+ public void removeMembershipListener(MembershipListener membershipListener) {
+ membershipListeners.remove(membershipListener);
+ }
+
+ /**
+ * Adds a channel listener to the channel.<br>
+ * Channel listeners are uniquely identified using the equals(Object) method
+ * @param channelListener ChannelListener
+ */
+ public void addChannelListener(ChannelListener channelListener) {
+ if (!this.channelListeners.contains(channelListener) ) {
+ this.channelListeners.add(channelListener);
+ } else {
+ throw new IllegalArgumentException("Listener already exists:"+channelListener);
+ }
+ }
+
+ /**
+ *
+ * Removes a channel listener from the channel.<br>
+ * Channel listeners are uniquely identified using the equals(Object) method
+ * @param channelListener ChannelListener
+ */
+ public void removeChannelListener(ChannelListener channelListener) {
+ channelListeners.remove(channelListener);
+ }
+
+ /**
+ * Returns an iterator of all the interceptors in this stack
+ * @return Iterator
+ */
+ public Iterator getInterceptors() {
+ return new InterceptorIterator(this.getNext(),this.coordinator);
+ }
+
+ /**
+ * Enables/disables the option check<br>
+ * Setting this to true, will make the GroupChannel perform a conflict check
+ * on the interceptors. If two interceptors are using the same option flag
+ * and throw an error upon start.
+ * @param optionCheck boolean
+ */
+ public void setOptionCheck(boolean optionCheck) {
+ this.optionCheck = optionCheck;
+ }
+
+ /**
+ * Configure local heartbeat sleep time<br>
+ * Only used when <code>getHeartbeat()==true</code>
+ * @param heartbeatSleeptime long - time in milliseconds to sleep between heartbeats
+ */
+ public void setHeartbeatSleeptime(long heartbeatSleeptime) {
+ this.heartbeatSleeptime = heartbeatSleeptime;
+ }
+
+ /**
+ * Enables or disables local heartbeat.
+ * if <code>setHeartbeat(true)</code> is invoked then the channel will start an internal
+ * thread to invoke <code>Channel.heartbeat()</code> every <code>getHeartbeatSleeptime</code> milliseconds
+ * @param heartbeat boolean
+ */
+ public void setHeartbeat(boolean heartbeat) {
+ this.heartbeat = heartbeat;
+ }
+
+ /**
+ * @see #setOptionCheck(boolean)
+ * @return boolean
+ */
+ public boolean getOptionCheck() {
+ return optionCheck;
+ }
+
+ /**
+ * @see #setHeartbeat(boolean)
+ * @return boolean
+ */
+ public boolean getHeartbeat() {
+ return heartbeat;
+ }
+
+ /**
+ * Returns the sleep time in milliseconds that the internal heartbeat will
+ * sleep in between invokations of <code>Channel.heartbeat()</code>
+ * @return long
+ */
+ public long getHeartbeatSleeptime() {
+ return heartbeatSleeptime;
+ }
+
+ /**
+ *
+ * <p>Title: Interceptor Iterator</p>
+ *
+ * <p>Description: An iterator to loop through the interceptors in a channel</p>
+ *
+ * @version 1.0
+ */
+ public static class InterceptorIterator implements Iterator {
+ private ChannelInterceptor end;
+ private ChannelInterceptor start;
+ public InterceptorIterator(ChannelInterceptor start, ChannelInterceptor end) {
+ this.end = end;
+ this.start = start;
+ }
+
+ public boolean hasNext() {
+ return start!=null && start != end;
+ }
+
+ public Object next() {
+ Object result = null;
+ if ( hasNext() ) {
+ result = start;
+ start = start.getNext();
+ }
+ return result;
+ }
+
+ public void remove() {
+ //empty operation
+ }
+ }
+
+ /**
+ *
+ * <p>Title: Internal heartbeat thread</p>
+ *
+ * <p>Description: if <code>Channel.getHeartbeat()==true</code> then a thread of this class
+ * is created</p>
+ *
+ * @version 1.0
+ */
+ public static class HeartbeatThread extends Thread {
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(HeartbeatThread.class);
+ protected static int counter = 1;
+ protected static synchronized int inc() {
+ return counter++;
+ }
+
+ protected boolean doRun = true;
+ protected GroupChannel channel;
+ protected long sleepTime;
+ public HeartbeatThread(GroupChannel channel, long sleepTime) {
+ super();
+ this.setPriority(MIN_PRIORITY);
+ setName("GroupChannel-Heartbeat-"+inc());
+ setDaemon(true);
+ this.channel = channel;
+ this.sleepTime = sleepTime;
+ }
+ public void stopHeartbeat() {
+ doRun = false;
+ interrupt();
+ }
+
+ public void run() {
+ while (doRun) {
+ try {
+ Thread.sleep(sleepTime);
+ channel.heartbeat();
+ } catch ( InterruptedException x ) {
+ interrupted();
+ } catch ( Exception x ) {
+ log.error("Unable to send heartbeat through Tribes interceptor stack. Will try to sleep again.",x);
+ }//catch
+ }//while
+ }//run
+ }//HeartbeatThread
+
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import org.apache.catalina.tribes.ErrorHandler;
+
+/**
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class InterceptorPayload {
+ private ErrorHandler errorHandler;
+
+ public ErrorHandler getErrorHandler() {
+ return errorHandler;
+ }
+
+ public void setErrorHandler(ErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.Member;
+
+/**
+ * A response object holds a message from a responding partner.
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class Response {
+ private Member source;
+ private Serializable message;
+ public Response() {
+ }
+
+ public Response(Member source, Serializable message) {
+ this.source = source;
+ this.message = message;
+ }
+
+ public void setSource(Member source) {
+ this.source = source;
+ }
+
+ public void setMessage(Serializable message) {
+ this.message = message;
+ }
+
+ public Member getSource() {
+ return source;
+ }
+
+ public Serializable getMessage() {
+ return message;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.Member;
+
+/**
+ * The RpcCallback interface is an interface for the Tribes channel to request a
+ * response object to a request that came in.
+ * @author not attributable
+ * @version 1.0
+ */
+public interface RpcCallback {
+
+ /**
+ *
+ * @param msg Serializable
+ * @return Serializable - null if no reply should be sent
+ */
+ public Serializable replyRequest(Serializable msg, Member sender);
+
+ /**
+ * If the reply has already been sent to the requesting thread,
+ * the rpc callback can handle any data that comes in after the fact.
+ * @param msg Serializable
+ * @param sender Member
+ */
+ public void leftOver(Serializable msg, Member sender);
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+
+/**
+ * A channel to handle RPC messaging
+ * @author Filip Hanik
+ */
+public class RpcChannel implements ChannelListener{
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(RpcChannel.class);
+
+ public static final int FIRST_REPLY = 1;
+ public static final int MAJORITY_REPLY = 2;
+ public static final int ALL_REPLY = 3;
+ public static final int NO_REPLY = 4;
+
+ private Channel channel;
+ private RpcCallback callback;
+ private byte[] rpcId;
+
+ private HashMap responseMap = new HashMap();
+
+ /**
+ * Create an RPC channel. You can have several RPC channels attached to a group
+ * all separated out by the uniqueness
+ * @param rpcId - the unique Id for this RPC group
+ * @param channel Channel
+ * @param callback RpcCallback
+ */
+ public RpcChannel(byte[] rpcId, Channel channel, RpcCallback callback) {
+ this.channel = channel;
+ this.callback = callback;
+ this.rpcId = rpcId;
+ channel.addChannelListener(this);
+ }
+
+
+ /**
+ * Send a message and wait for the response.
+ * @param destination Member[] - the destination for the message, and the members you request a reply from
+ * @param message Serializable - the message you are sending out
+ * @param options int - FIRST_REPLY, MAJORITY_REPLY or ALL_REPLY
+ * @param timeout long - timeout in milliseconds, if no reply is received within this time null is returned
+ * @return Response[] - an array of response objects.
+ * @throws ChannelException
+ */
+ public Response[] send(Member[] destination,
+ Serializable message,
+ int rpcOptions,
+ int channelOptions,
+ long timeout) throws ChannelException {
+
+ if ( destination==null || destination.length == 0 ) return new Response[0];
+
+ //avoid dead lock
+ channelOptions = channelOptions & ~Channel.SEND_OPTIONS_SYNCHRONIZED_ACK;
+
+ RpcCollectorKey key = new RpcCollectorKey(UUIDGenerator.randomUUID(false));
+ RpcCollector collector = new RpcCollector(key,rpcOptions,destination.length,timeout);
+ try {
+ synchronized (collector) {
+ if ( rpcOptions != NO_REPLY ) responseMap.put(key, collector);
+ RpcMessage rmsg = new RpcMessage(rpcId, key.id, message);
+ channel.send(destination, rmsg, channelOptions);
+ if ( rpcOptions != NO_REPLY ) collector.wait(timeout);
+ }
+ } catch ( InterruptedException ix ) {
+ Thread.currentThread().interrupted();
+ //throw new ChannelException(ix);
+ }finally {
+ responseMap.remove(key);
+ }
+ return collector.getResponses();
+ }
+
+ public void messageReceived(Serializable msg, Member sender) {
+ RpcMessage rmsg = (RpcMessage)msg;
+ RpcCollectorKey key = new RpcCollectorKey(rmsg.uuid);
+ if ( rmsg.reply ) {
+ RpcCollector collector = (RpcCollector)responseMap.get(key);
+ if (collector == null) {
+ callback.leftOver(rmsg.message, sender);
+ } else {
+ synchronized (collector) {
+ //make sure it hasn't been removed
+ if ( responseMap.containsKey(key) ) {
+ if ( (rmsg instanceof RpcMessage.NoRpcChannelReply) )
+ collector.destcnt--;
+ else
+ collector.addResponse(rmsg.message, sender);
+ if (collector.isComplete()) collector.notifyAll();
+ } else {
+ if (! (rmsg instanceof RpcMessage.NoRpcChannelReply) )
+ callback.leftOver(rmsg.message, sender);
+ }
+ }//synchronized
+ }//end if
+ } else{
+ Serializable reply = callback.replyRequest(rmsg.message,sender);
+ rmsg.reply = true;
+ rmsg.message = reply;
+ try {
+ channel.send(new Member[] {sender}, rmsg,0);
+ }catch ( Exception x ) {
+ log.error("Unable to send back reply in RpcChannel.",x);
+ }
+ }//end if
+ }
+
+ public void breakdown() {
+ channel.removeChannelListener(this);
+ }
+
+ public void finalize() {
+ breakdown();
+ }
+
+ public boolean accept(Serializable msg, Member sender) {
+ if ( msg instanceof RpcMessage ) {
+ RpcMessage rmsg = (RpcMessage)msg;
+ return Arrays.equals(rmsg.rpcId,rpcId);
+ }else return false;
+ }
+
+ public Channel getChannel() {
+ return channel;
+ }
+
+ public RpcCallback getCallback() {
+ return callback;
+ }
+
+ public byte[] getRpcId() {
+ return rpcId;
+ }
+
+ public void setChannel(Channel channel) {
+ this.channel = channel;
+ }
+
+ public void setCallback(RpcCallback callback) {
+ this.callback = callback;
+ }
+
+ public void setRpcId(byte[] rpcId) {
+ this.rpcId = rpcId;
+ }
+
+
+
+ /**
+ *
+ * Class that holds all response.
+ * @author not attributable
+ * @version 1.0
+ */
+ public static class RpcCollector {
+ public ArrayList responses = new ArrayList();
+ public RpcCollectorKey key;
+ public int options;
+ public int destcnt;
+ public long timeout;
+
+ public RpcCollector(RpcCollectorKey key, int options, int destcnt, long timeout) {
+ this.key = key;
+ this.options = options;
+ this.destcnt = destcnt;
+ this.timeout = timeout;
+ }
+
+ public void addResponse(Serializable message, Member sender){
+ Response resp = new Response(sender,message);
+ responses.add(resp);
+ }
+
+ public boolean isComplete() {
+ if ( destcnt <= 0 ) return true;
+ switch (options) {
+ case ALL_REPLY:
+ return destcnt == responses.size();
+ case MAJORITY_REPLY:
+ {
+ float perc = ((float)responses.size()) / ((float)destcnt);
+ return perc >= 0.50f;
+ }
+ case FIRST_REPLY:
+ return responses.size()>0;
+ default:
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ return key.hashCode();
+ }
+
+ public boolean equals(Object o) {
+ if ( o instanceof RpcCollector ) {
+ RpcCollector r = (RpcCollector)o;
+ return r.key.equals(this.key);
+ } else return false;
+ }
+
+ public Response[] getResponses() {
+ return (Response[])responses.toArray(new Response[responses.size()]);
+ }
+ }
+
+ public static class RpcCollectorKey {
+ byte[] id;
+ public RpcCollectorKey(byte[] id) {
+ this.id = id;
+ }
+
+ public int hashCode() {
+ return id[0]+id[1]+id[2]+id[3];
+ }
+
+ public boolean equals(Object o) {
+ if ( o instanceof RpcCollectorKey ) {
+ RpcCollectorKey r = (RpcCollectorKey)o;
+ return Arrays.equals(id,r.id);
+ } else return false;
+ }
+
+ }
+
+ protected static String bToS(byte[] data) {
+ StringBuffer buf = new StringBuffer(4*16);
+ buf.append("{");
+ for (int i=0; data!=null && i<data.length; i++ ) buf.append(String.valueOf(data[i])).append(" ");
+ buf.append("}");
+ return buf.toString();
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.group;
+
+import java.io.ObjectInput;
+import java.io.Serializable;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectOutput;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class RpcMessage implements Externalizable {
+
+ protected Serializable message;
+ protected byte[] uuid;
+ protected byte[] rpcId;
+ protected boolean reply = false;
+
+ public RpcMessage() {
+ //for serialization
+ }
+
+ public RpcMessage(byte[] rpcId, byte[] uuid, Serializable message) {
+ this.rpcId = rpcId;
+ this.uuid = uuid;
+ this.message = message;
+ }
+
+ public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
+ reply = in.readBoolean();
+ int length = in.readInt();
+ uuid = new byte[length];
+ in.read(uuid, 0, length);
+ length = in.readInt();
+ rpcId = new byte[length];
+ in.read(rpcId, 0, length);
+ message = (Serializable)in.readObject();
+ }
+
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeBoolean(reply);
+ out.writeInt(uuid.length);
+ out.write(uuid, 0, uuid.length);
+ out.writeInt(rpcId.length);
+ out.write(rpcId, 0, rpcId.length);
+ out.writeObject(message);
+ }
+
+ public static class NoRpcChannelReply extends RpcMessage {
+ public NoRpcChannelReply() {
+
+ }
+
+ public NoRpcChannelReply(byte[] rpcid, byte[] uuid) {
+ super(rpcid,uuid,null);
+ reply = true;
+ }
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ reply = true;
+ int length = in.readInt();
+ uuid = new byte[length];
+ in.read(uuid, 0, length);
+ length = in.readInt();
+ rpcId = new byte[length];
+ in.read(rpcId, 0, length);
+ }
+
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeInt(uuid.length);
+ out.write(uuid, 0, uuid.length);
+ out.writeInt(rpcId.length);
+ out.write(rpcId, 0, rpcId.length);
+ }
+ }
+
+
+}
--- /dev/null
+/*\r
+ * Copyright 1999,2004 The Apache Software Foundation.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ */\r
+package org.apache.catalina.tribes.group.interceptors;\r
+\r
+import org.apache.catalina.tribes.ChannelMessage;\r
+import org.apache.catalina.tribes.Member;\r
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;\r
+import org.apache.catalina.tribes.membership.MemberImpl;\r
+import org.apache.catalina.tribes.membership.Membership;\r
+import java.util.Arrays;\r
+\r
+/**\r
+ * <p>Title: Member domain filter interceptor </p>\r
+ *\r
+ * <p>Description: Filters membership based on domain.\r
+ * </p>\r
+ *\r
+ * @author Filip Hanik\r
+ * @version 1.0\r
+ */\r
+public class DomainFilterInterceptor extends ChannelInterceptorBase {\r
+\r
+ private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog( DomainFilterInterceptor.class );\r
+\r
+ protected Membership membership = null;\r
+ \r
+ protected byte[] domain = new byte[0];\r
+\r
+ public void messageReceived(ChannelMessage msg) {\r
+ //should we filter incoming based on domain?\r
+ super.messageReceived(msg);\r
+ }//messageReceived\r
+\r
+\r
+ public void memberAdded(Member member) {\r
+ if ( membership == null ) setupMembership();\r
+ boolean notify = false;\r
+ synchronized (membership) {\r
+ notify = Arrays.equals(domain,member.getDomain());\r
+ if ( notify ) notify = membership.memberAlive((MemberImpl)member);\r
+ }\r
+ if ( notify ) super.memberAdded(member);\r
+ }\r
+\r
+ public void memberDisappeared(Member member) {\r
+ if ( membership == null ) setupMembership();\r
+ boolean notify = false;\r
+ synchronized (membership) {\r
+ notify = Arrays.equals(domain,member.getDomain());\r
+ membership.removeMember((MemberImpl)member);\r
+ }\r
+ if ( notify ) super.memberDisappeared(member);\r
+ }\r
+\r
+ public boolean hasMembers() {\r
+ if ( membership == null ) setupMembership();\r
+ return membership.hasMembers();\r
+ }\r
+\r
+ public Member[] getMembers() {\r
+ if ( membership == null ) setupMembership();\r
+ return membership.getMembers();\r
+ }\r
+\r
+ public Member getMember(Member mbr) {\r
+ if ( membership == null ) setupMembership();\r
+ return membership.getMember(mbr);\r
+ }\r
+\r
+ public Member getLocalMember(boolean incAlive) {\r
+ return super.getLocalMember(incAlive);\r
+ }\r
+\r
+\r
+ protected synchronized void setupMembership() {\r
+ if ( membership == null ) {\r
+ membership = new Membership((MemberImpl)super.getLocalMember(true));\r
+ }\r
+\r
+ }\r
+\r
+ public byte[] getDomain() {\r
+ return domain;\r
+ }\r
+\r
+ public void setDomain(byte[] domain) {\r
+ this.domain = domain;\r
+ }\r
+}\r
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ */
+
+package org.apache.catalina.tribes.group.interceptors;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Set;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.XByteBuffer;
+
+/**
+ *
+ * The fragmentation interceptor splits up large messages into smaller messages and assembles them on the other end.
+ * This is very useful when you don't want large messages hogging the sending sockets
+ * and smaller messages can make it through.
+ *
+ * <br><b>Configuration Options</b><br>
+ * OrderInteceptor.expire=<milliseconds> - how long do we keep the fragments in memory and wait for the rest to arrive<b>default=60,000ms -> 60seconds</b>
+ * This setting is useful to avoid OutOfMemoryErrors<br>
+ * OrderInteceptor.maxSize=<max message size> - message size in bytes <b>default=1024*100 (around a tenth of a MB)</b><br>
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class FragmentationInterceptor extends ChannelInterceptorBase {
+ private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog( FragmentationInterceptor.class );
+
+ protected HashMap fragpieces = new HashMap();
+ private int maxSize = 1024*100;
+ private long expire = 1000 * 60; //one minute expiration
+ protected boolean deepclone = true;
+
+
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+ int size = msg.getMessage().getLength();
+ boolean frag = (size>maxSize) && okToProcess(msg.getOptions());
+ if ( frag ) {
+ frag(destination, msg, payload);
+ } else {
+ msg.getMessage().append(frag);
+ super.sendMessage(destination, msg, payload);
+ }
+ }
+
+ public void messageReceived(ChannelMessage msg) {
+ boolean isFrag = XByteBuffer.toBoolean(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-1);
+ msg.getMessage().trim(1);
+ if ( isFrag ) {
+ defrag(msg);
+ } else {
+ super.messageReceived(msg);
+ }
+ }
+
+
+ public FragCollection getFragCollection(FragKey key, ChannelMessage msg) {
+ FragCollection coll = (FragCollection)fragpieces.get(key);
+ if ( coll == null ) {
+ synchronized (fragpieces) {
+ coll = (FragCollection)fragpieces.get(key);
+ if ( coll == null ) {
+ coll = new FragCollection(msg);
+ fragpieces.put(key, coll);
+ }
+ }
+ }
+ return coll;
+ }
+
+ public void removeFragCollection(FragKey key) {
+ fragpieces.remove(key);
+ }
+
+ public void defrag(ChannelMessage msg ) {
+ FragKey key = new FragKey(msg.getUniqueId());
+ FragCollection coll = getFragCollection(key,msg);
+ coll.addMessage((ChannelMessage)msg.deepclone());
+
+ if ( coll.complete() ) {
+ removeFragCollection(key);
+ ChannelMessage complete = coll.assemble();
+ super.messageReceived(complete);
+
+ }
+ }
+
+ public void frag(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+ int size = msg.getMessage().getLength();
+
+ int count = ((size / maxSize )+(size%maxSize==0?0:1));
+ ChannelMessage[] messages = new ChannelMessage[count];
+ int remaining = size;
+ for ( int i=0; i<count; i++ ) {
+ ChannelMessage tmp = (ChannelMessage)msg.clone();
+ int offset = (i*maxSize);
+ int length = Math.min(remaining,maxSize);
+ tmp.getMessage().clear();
+ tmp.getMessage().append(msg.getMessage().getBytesDirect(),offset,length);
+ //add the msg nr
+ //tmp.getMessage().append(XByteBuffer.toBytes(i),0,4);
+ tmp.getMessage().append(i);
+ //add the total nr of messages
+ //tmp.getMessage().append(XByteBuffer.toBytes(count),0,4);
+ tmp.getMessage().append(count);
+ //add true as the frag flag
+ //byte[] flag = XByteBuffer.toBytes(true);
+ //tmp.getMessage().append(flag,0,flag.length);
+ tmp.getMessage().append(true);
+ messages[i] = tmp;
+ remaining -= length;
+
+ }
+ for ( int i=0; i<messages.length; i++ ) {
+ super.sendMessage(destination,messages[i],payload);
+ }
+ }
+
+ public void heartbeat() {
+ try {
+ Set set = fragpieces.keySet();
+ Object[] keys = set.toArray();
+ for ( int i=0; i<keys.length; i++ ) {
+ FragKey key = (FragKey)keys[i];
+ if ( key != null && key.expired(getExpire()) )
+ removeFragCollection(key);
+ }
+ }catch ( Exception x ) {
+ if ( log.isErrorEnabled() ) {
+ log.error("Unable to perform heartbeat clean up in the frag interceptor",x);
+ }
+ }
+ super.heartbeat();
+ }
+
+
+
+ public int getMaxSize() {
+ return maxSize;
+ }
+
+ public long getExpire() {
+ return expire;
+ }
+
+ public void setMaxSize(int maxSize) {
+ this.maxSize = maxSize;
+ }
+
+ public void setExpire(long expire) {
+ this.expire = expire;
+ }
+
+ public static class FragCollection {
+ private long received = System.currentTimeMillis();
+ private ChannelMessage msg;
+ private XByteBuffer[] frags;
+ public FragCollection(ChannelMessage msg) {
+ //get the total messages
+ int count = XByteBuffer.toInt(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-4);
+ frags = new XByteBuffer[count];
+ this.msg = msg;
+ }
+
+ public void addMessage(ChannelMessage msg) {
+ //remove the total messages
+ msg.getMessage().trim(4);
+ //get the msg nr
+ int nr = XByteBuffer.toInt(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-4);
+ //remove the msg nr
+ msg.getMessage().trim(4);
+ frags[nr] = msg.getMessage();
+
+ }
+
+ public boolean complete() {
+ boolean result = true;
+ for ( int i=0; (i<frags.length) && (result); i++ ) result = (frags[i] != null);
+ return result;
+ }
+
+ public ChannelMessage assemble() {
+ if ( !complete() ) throw new IllegalStateException("Fragments are missing.");
+ int buffersize = 0;
+ for (int i=0; i<frags.length; i++ ) buffersize += frags[i].getLength();
+ XByteBuffer buf = new XByteBuffer(buffersize,false);
+ msg.setMessage(buf);
+ for ( int i=0; i<frags.length; i++ ) {
+ msg.getMessage().append(frags[i].getBytesDirect(),0,frags[i].getLength());
+ }
+ return msg;
+ }
+
+ public boolean expired(long expire) {
+ return (System.currentTimeMillis()-received)>expire;
+ }
+
+
+
+ }
+
+ public static class FragKey {
+ private byte[] uniqueId;
+ private long received = System.currentTimeMillis();
+ public FragKey(byte[] id ) {
+ this.uniqueId = id;
+ }
+ public int hashCode() {
+ return XByteBuffer.toInt(uniqueId,0);
+ }
+
+ public boolean equals(Object o ) {
+ if ( o instanceof FragKey ) {
+ return Arrays.equals(uniqueId,((FragKey)o).uniqueId);
+ } else return false;
+
+ }
+
+ public boolean expired(long expire) {
+ return (System.currentTimeMillis()-received)>expire;
+ }
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ */
+
+package org.apache.catalina.tribes.group.interceptors;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+
+
+
+/**
+ *
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class GzipInterceptor extends ChannelInterceptorBase {
+ public static final int DEFAULT_BUFFER_SIZE = 2048;
+
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+ try {
+ byte[] data = compress(msg.getMessage().getBytes());
+ msg.getMessage().trim(msg.getMessage().getLength());
+ msg.getMessage().append(data,0,data.length);
+ getNext().sendMessage(destination, msg, payload);
+ } catch ( IOException x ) {
+ log.error("Unable to compress byte contents");
+ throw new ChannelException(x);
+ }
+ }
+
+ public void messageReceived(ChannelMessage msg) {
+ try {
+ byte[] data = decompress(msg.getMessage().getBytes());
+ msg.getMessage().trim(msg.getMessage().getLength());
+ msg.getMessage().append(data,0,data.length);
+ getPrevious().messageReceived(msg);
+ } catch ( IOException x ) {
+ log.error("Unable to decompress byte contents",x);
+ }
+ }
+
+ public static byte[] compress(byte[] data) throws IOException {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ GZIPOutputStream gout = new GZIPOutputStream(bout);
+ gout.write(data);
+ gout.flush();
+ gout.close();
+ return bout.toByteArray();
+ }
+
+ /**
+ * @todo Fix to create an automatically growing buffer.
+ * @param data byte[]
+ * @return byte[]
+ * @throws IOException
+ */
+ public static byte[] decompress(byte[] data) throws IOException {
+ ByteArrayInputStream bin = new ByteArrayInputStream(data);
+ GZIPInputStream gin = new GZIPInputStream(bin);
+ byte[] tmp = new byte[DEFAULT_BUFFER_SIZE];
+ int length = gin.read(tmp);
+ byte[] result = new byte[length];
+ System.arraycopy(tmp,0,result,0,length);
+ return result;
+ }
+
+ public static void main(String[] arg) throws Exception {
+ byte[] data = new byte[1024];
+ Arrays.fill(data,(byte)1);
+ byte[] compress = compress(data);
+ byte[] decompress = decompress(compress);
+ System.out.println("Debug test");
+
+ }
+
+}
--- /dev/null
+/*\r
+ * Copyright 1999,2004 The Apache Software Foundation.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ */\r
+package org.apache.catalina.tribes.group.interceptors;\r
+\r
+import java.util.concurrent.LinkedBlockingQueue;\r
+import java.util.concurrent.ThreadPoolExecutor;\r
+import java.util.concurrent.atomic.AtomicLong;\r
+\r
+import org.apache.catalina.tribes.ChannelMessage;\r
+import org.apache.catalina.tribes.Member;\r
+import org.apache.catalina.tribes.group.InterceptorPayload;\r
+import org.apache.catalina.tribes.transport.bio.util.LinkObject;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+/**\r
+ * \r
+ * Same implementation as the MessageDispatchInterceptor\r
+ * except is ues an atomic long for the currentSize calculation\r
+ * and uses a thread pool for message sending.\r
+ * \r
+ * @author Filip Hanik\r
+ * @version 1.0\r
+ */\r
+\r
+public class MessageDispatch15Interceptor extends MessageDispatchInterceptor {\r
+\r
+ protected AtomicLong currentSize = new AtomicLong(0);\r
+ protected ThreadPoolExecutor executor = null;\r
+ protected int maxThreads = 10;\r
+ protected int maxSpareThreads = 2;\r
+ protected long keepAliveTime = 5000;\r
+ protected LinkedBlockingQueue<Runnable> runnablequeue = new LinkedBlockingQueue<Runnable>();\r
+\r
+ public long getCurrentSize() {\r
+ return currentSize.get();\r
+ }\r
+\r
+ public long addAndGetCurrentSize(long inc) {\r
+ return currentSize.addAndGet(inc);\r
+ }\r
+\r
+ public long setAndGetCurrentSize(long value) {\r
+ currentSize.set(value);\r
+ return value;\r
+ }\r
+ \r
+ public boolean addToQueue(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {\r
+ final LinkObject obj = new LinkObject(msg,destination,payload);\r
+ Runnable r = new Runnable() {\r
+ public void run() {\r
+ sendAsyncData(obj);\r
+ }\r
+ };\r
+ executor.execute(r);\r
+ return true;\r
+ }\r
+\r
+ public LinkObject removeFromQueue() {\r
+ return null; //not used, thread pool contains its own queue.\r
+ }\r
+\r
+ public void startQueue() {\r
+ if ( run ) return;\r
+ executor = new ThreadPoolExecutor(maxSpareThreads,maxThreads,keepAliveTime,TimeUnit.MILLISECONDS,runnablequeue);\r
+ run = true;\r
+ }\r
+\r
+ public void stopQueue() {\r
+ run = false;\r
+ executor.shutdownNow();\r
+ setAndGetCurrentSize(0);\r
+ runnablequeue.clear();\r
+ }\r
+\r
+ public long getKeepAliveTime() {\r
+ return keepAliveTime;\r
+ }\r
+\r
+ public int getMaxSpareThreads() {\r
+ return maxSpareThreads;\r
+ }\r
+\r
+ public int getMaxThreads() {\r
+ return maxThreads;\r
+ }\r
+\r
+ public void setKeepAliveTime(long keepAliveTime) {\r
+ this.keepAliveTime = keepAliveTime;\r
+ }\r
+\r
+ public void setMaxSpareThreads(int maxSpareThreads) {\r
+ this.maxSpareThreads = maxSpareThreads;\r
+ }\r
+\r
+ public void setMaxThreads(int maxThreads) {\r
+ this.maxThreads = maxThreads;\r
+ }\r
+\r
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ */
+
+package org.apache.catalina.tribes.group.interceptors;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.transport.bio.util.FastQueue;
+import org.apache.catalina.tribes.transport.bio.util.LinkObject;
+import org.apache.catalina.tribes.UniqueId;
+
+/**
+ *
+ * The message dispatcher is a way to enable asynchronous communication
+ * through a channel. The dispatcher will look for the <code>Channel.SEND_OPTIONS_ASYNCHRONOUS</code>
+ * flag to be set, if it is, it will queue the message for delivery and immediately return to the sender.
+ *
+ *
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class MessageDispatchInterceptor extends ChannelInterceptorBase implements Runnable {
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(MessageDispatchInterceptor.class);
+
+ protected long maxQueueSize = 1024*1024*64; //64MB
+ protected FastQueue queue = new FastQueue();
+ protected boolean run = false;
+ protected Thread msgDispatchThread = null;
+ protected long currentSize = 0;
+ protected boolean useDeepClone = true;
+ protected boolean alwaysSend = true;
+
+ public MessageDispatchInterceptor() {
+ setOptionFlag(Channel.SEND_OPTIONS_ASYNCHRONOUS);
+ }
+
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+ boolean async = (msg.getOptions() & Channel.SEND_OPTIONS_ASYNCHRONOUS) == Channel.SEND_OPTIONS_ASYNCHRONOUS;
+ if ( async && run ) {
+ if ( (getCurrentSize()+msg.getMessage().getLength()) > maxQueueSize ) {
+ if ( alwaysSend ) {
+ super.sendMessage(destination,msg,payload);
+ return;
+ } else {
+ throw new ChannelException("Asynchronous queue is full, reached its limit of " + maxQueueSize +" bytes, current:" + getCurrentSize() + " bytes.");
+ }//end if
+ }//end if
+ //add to queue
+ if ( useDeepClone ) msg = (ChannelMessage)msg.deepclone();
+ if (!addToQueue(msg, destination, payload) ) {
+ throw new ChannelException("Unable to add the message to the async queue, queue bug?");
+ }
+ addAndGetCurrentSize(msg.getMessage().getLength());
+ } else {
+ super.sendMessage(destination, msg, payload);
+ }
+ }
+
+ public boolean addToQueue(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+ return queue.add(msg,destination,payload);
+ }
+
+ public LinkObject removeFromQueue() {
+ return queue.remove();
+ }
+
+ public void startQueue() {
+ msgDispatchThread = new Thread(this);
+ msgDispatchThread.setName("MessageDispatchInterceptor.MessageDispatchThread");
+ msgDispatchThread.setDaemon(true);
+ msgDispatchThread.setPriority(Thread.MAX_PRIORITY);
+ queue.setEnabled(true);
+ run = true;
+ msgDispatchThread.start();
+ }
+
+ public void stopQueue() {
+ run = false;
+ msgDispatchThread.interrupt();
+ queue.setEnabled(false);
+ setAndGetCurrentSize(0);
+ }
+
+
+ public void setOptionFlag(int flag) {
+ if ( flag != Channel.SEND_OPTIONS_ASYNCHRONOUS ) log.warn("Warning, you are overriding the asynchronous option flag, this will disable the Channel.SEND_OPTIONS_ASYNCHRONOUS that other apps might use.");
+ super.setOptionFlag(flag);
+ }
+
+ public void setMaxQueueSize(long maxQueueSize) {
+ this.maxQueueSize = maxQueueSize;
+ }
+
+ public void setUseDeepClone(boolean useDeepClone) {
+ this.useDeepClone = useDeepClone;
+ }
+
+ public long getMaxQueueSize() {
+ return maxQueueSize;
+ }
+
+ public boolean getUseDeepClone() {
+ return useDeepClone;
+ }
+
+ public long getCurrentSize() {
+ return currentSize;
+ }
+
+ public synchronized long addAndGetCurrentSize(long inc) {
+ currentSize += inc;
+ return currentSize;
+ }
+
+ public synchronized long setAndGetCurrentSize(long value) {
+ currentSize = value;
+ return value;
+ }
+
+ public void start(int svc) throws ChannelException {
+ //start the thread
+ if (!run ) {
+ synchronized (this) {
+ if ( !run && ((svc & Channel.SND_TX_SEQ)==Channel.SND_TX_SEQ) ) {//only start with the sender
+ startQueue();
+ }//end if
+ }//sync
+ }//end if
+ super.start(svc);
+ }
+
+
+ public void stop(int svc) throws ChannelException {
+ //stop the thread
+ if ( run ) {
+ synchronized (this) {
+ if ( run && ((svc & Channel.SND_TX_SEQ)==Channel.SND_TX_SEQ)) {
+ stopQueue();
+ }//end if
+ }//sync
+ }//end if
+
+ super.stop(svc);
+ }
+
+ public void run() {
+ while ( run ) {
+ LinkObject link = removeFromQueue();
+ if ( link == null ) continue; //should not happen unless we exceed wait time
+ while ( link != null && run ) {
+ link = sendAsyncData(link);
+ }//while
+ }//while
+ }//run
+
+ protected LinkObject sendAsyncData(LinkObject link) {
+ ChannelMessage msg = link.data();
+ Member[] destination = link.getDestination();
+ try {
+ super.sendMessage(destination,msg,null);
+ try {
+ if ( link.getHandler() != null ) link.getHandler().handleCompletion(new UniqueId(msg.getUniqueId()));
+ } catch ( Exception ex ) {
+ log.error("Unable to report back completed message.",ex);
+ }
+ } catch ( Exception x ) {
+ ChannelException cx = null;
+ if ( x instanceof ChannelException ) cx = (ChannelException)x;
+ else cx = new ChannelException(x);
+ if ( log.isDebugEnabled() ) log.debug("Error while processing async message.",x);
+ try {
+ if (link.getHandler() != null) link.getHandler().handleError(cx, new UniqueId(msg.getUniqueId()));
+ } catch ( Exception ex ) {
+ log.error("Unable to report back error message.",ex);
+ }
+ } finally {
+ addAndGetCurrentSize(-msg.getMessage().getLength());
+ link = link.next();
+ }//try
+ return link;
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ */
+package org.apache.catalina.tribes.group.interceptors;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelInterceptor;
+import org.apache.catalina.tribes.ChannelInterceptor.InterceptorEvent;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.group.AbsoluteOrder;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.membership.Membership;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+
+/**
+ * <p>Title: Auto merging leader election algorithm</p>
+ *
+ * <p>Description: Implementation of a simple coordinator algorithm that not only selects a coordinator,
+ * it also merges groups automatically when members are discovered that werent part of the
+ * </p>
+ * <p>This algorithm is non blocking meaning it allows for transactions while the coordination phase is going on
+ * </p>
+ * <p>This implementation is based on a home brewed algorithm that uses the AbsoluteOrder of a membership
+ * to pass a token ring of the current membership.<br>
+ * This is not the same as just using AbsoluteOrder! Consider the following scenario:<br>
+ * Nodes, A,B,C,D,E on a network, in that priority. AbsoluteOrder will only work if all
+ * nodes are receiving pings from all the other nodes.
+ * meaning, that node{i} receives pings from node{all}-node{i}<br>
+ * but the following could happen if a multicast problem occurs.
+ * A has members {B,C,D}<br>
+ * B has members {A,C}<br>
+ * C has members {D,E}<br>
+ * D has members {A,B,C,E}<br>
+ * E has members {A,C,D}<br>
+ * Because the default Tribes membership implementation, relies on the multicast packets to
+ * arrive at all nodes correctly, there is nothing guaranteeing that it will.<br>
+ * <br>
+ * To best explain how this algorithm works, lets take the above example:
+ * For simplicity we assume that a send operation is O(1) for all nodes, although this algorithm will work
+ * where messages overlap, as they all depend on absolute order<br>
+ * Scenario 1: A,B,C,D,E all come online at the same time
+ * Eval phase, A thinks of itself as leader, B thinks of A as leader,
+ * C thinks of itself as leader, D,E think of A as leader<br>
+ * Token phase:<br>
+ * (1) A sends out a message X{A-ldr, A-src, mbrs-A,B,C,D} to B where X is the id for the message(and the view)<br>
+ * (1) C sends out a message Y{C-ldr, C-src, mbrs-C,D,E} to D where Y is the id for the message(and the view)<br>
+ * (2) B receives X{A-ldr, A-src, mbrs-A,B,C,D}, sends X{A-ldr, A-src, mbrs-A,B,C,D} to C <br>
+ * (2) D receives Y{C-ldr, C-src, mbrs-C,D,E} D is aware of A,B, sends Y{A-ldr, C-src, mbrs-A,B,C,D,E} to E<br>
+ * (3) C receives X{A-ldr, A-src, mbrs-A,B,C,D}, sends X{A-ldr, A-src, mbrs-A,B,C,D,E} to D<br>
+ * (3) E receives Y{A-ldr, C-src, mbrs-A,B,C,D,E} sends Y{A-ldr, C-src, mbrs-A,B,C,D,E} to A<br>
+ * (4) D receives X{A-ldr, A-src, mbrs-A,B,C,D,E} sends sends X{A-ldr, A-src, mbrs-A,B,C,D,E} to A<br>
+ * (4) A receives Y{A-ldr, C-src, mbrs-A,B,C,D,E}, holds the message, add E to its list of members<br>
+ * (5) A receives X{A-ldr, A-src, mbrs-A,B,C,D,E} <br>
+ * At this point, the state looks like<br>
+ * A - {A-ldr, mbrs-A,B,C,D,E, id=X}<br>
+ * B - {A-ldr, mbrs-A,B,C,D, id=X}<br>
+ * C - {A-ldr, mbrs-A,B,C,D,E, id=X}<br>
+ * D - {A-ldr, mbrs-A,B,C,D,E, id=X}<br>
+ * E - {A-ldr, mbrs-A,B,C,D,E, id=Y}<br>
+ * <br>
+ * A message doesn't stop until it reaches its original sender, unless its dropped by a higher leader.
+ * As you can see, E still thinks the viewId=Y, which is not correct. But at this point we have
+ * arrived at the same membership and all nodes are informed of each other.<br>
+ * To synchronize the rest we simply perform the following check at A when A receives X:<br>
+ * Original X{A-ldr, A-src, mbrs-A,B,C,D} == Arrived X{A-ldr, A-src, mbrs-A,B,C,D,E}<br>
+ * Since the condition is false, A, will resend the token, and A sends X{A-ldr, A-src, mbrs-A,B,C,D,E} to B
+ * When A receives X again, the token is complete. <br>
+ * Optionally, A can send a message X{A-ldr, A-src, mbrs-A,B,C,D,E confirmed} to A,B,C,D,E who then
+ * install and accept the view.
+ * </p>
+ * <p>
+ * Lets assume that C1 arrives, C1 has lower priority than C, but higher priority than D.<br>
+ * Lets also assume that C1 sees the following view {B,D,E}<br>
+ * C1 waits for a token to arrive. When the token arrives, the same scenario as above will happen.<br>
+ * In the scenario where C1 sees {D,E} and A,B,C can not see C1, no token will ever arrive.<br>
+ * In this case, C1 sends a Z{C1-ldr, C1-src, mbrs-C1,D,E} to D<br>
+ * D receives Z{C1-ldr, C1-src, mbrs-C1,D,E} and sends Z{A-ldr, C1-src, mbrs-A,B,C,C1,D,E} to E<br>
+ * E receives Z{A-ldr, C1-src, mbrs-A,B,C,C1,D,E} and sends it to A<br>
+ * A sends Z{A-ldr, A-src, mbrs-A,B,C,C1,D,E} to B and the chain continues until A receives the token again.
+ * At that time A optionally sends out Z{A-ldr, A-src, mbrs-A,B,C,C1,D,E, confirmed} to A,B,C,C1,D,E
+ * </p>
+ * <p>To ensure that the view gets implemented at all nodes at the same time,
+ * A will send out a VIEW_CONF message, this is the 'confirmed' message that is optional above.
+ * <p>Ideally, the interceptor below this one would be the TcpFailureDetector to ensure correct memberships</p>
+ *
+ * <p>The example above, of course can be simplified with a finite statemachine:<br>
+ * But I suck at writing state machines, my head gets all confused. One day I will document this algorithm though.<br>
+ * Maybe I'll do a state diagram :)
+ * </p>
+ * <h2>State Diagrams</h2>
+ * <a href="http://people.apache.org/~fhanik/tribes/docs/leader-election-initiate-election.jpg">Initiate an election</a><br><br>
+ * <a href="http://people.apache.org/~fhanik/tribes/docs/leader-election-message-arrives.jpg">Receive an election message</a><br><br>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ *
+ *
+ *
+ */
+public class NonBlockingCoordinator extends ChannelInterceptorBase {
+
+ /**
+ * header for a coordination message
+ */
+ protected static final byte[] COORD_HEADER = new byte[] {-86, 38, -34, -29, -98, 90, 65, 63, -81, -122, -6, -110, 99, -54, 13, 63};
+ /**
+ * Coordination request
+ */
+ protected static final byte[] COORD_REQUEST = new byte[] {104, -95, -92, -42, 114, -36, 71, -19, -79, 20, 122, 101, -1, -48, -49, 30};
+ /**
+ * Coordination confirmation, for blocking installations
+ */
+ protected static final byte[] COORD_CONF = new byte[] {67, 88, 107, -86, 69, 23, 76, -70, -91, -23, -87, -25, -125, 86, 75, 20};
+
+ /**
+ * Alive message
+ */
+ protected static final byte[] COORD_ALIVE = new byte[] {79, -121, -25, -15, -59, 5, 64, 94, -77, 113, -119, -88, 52, 114, -56, -46,
+ -18, 102, 10, 34, -127, -9, 71, 115, -70, 72, -101, 88, 72, -124, 127, 111,
+ 74, 76, -116, 50, 111, 103, 65, 3, -77, 51, -35, 0, 119, 117, 9, -26,
+ 119, 50, -75, -105, -102, 36, 79, 37, -68, -84, -123, 15, -22, -109, 106, -55};
+ /**
+ * Time to wait for coordination timeout
+ */
+ protected long waitForCoordMsgTimeout = 15000;
+ /**
+ * Our current view
+ */
+ protected Membership view = null;
+ /**
+ * Out current viewId
+ */
+ protected UniqueId viewId;
+
+ /**
+ * Our nonblocking membership
+ */
+ protected Membership membership = null;
+
+ /**
+ * indicates that we are running an election
+ * and this is the one we are running
+ */
+ protected UniqueId suggestedviewId;
+ protected Membership suggestedView;
+
+ protected boolean started = false;
+ protected final int startsvc = 0xFFFF;
+
+ protected Object electionMutex = new Object();
+
+ protected AtomicBoolean coordMsgReceived = new AtomicBoolean(false);
+
+ public NonBlockingCoordinator() {
+ super();
+ }
+
+//============================================================================================================
+// COORDINATION HANDLING
+//============================================================================================================
+
+ public void startElection(boolean force) throws ChannelException {
+ synchronized (electionMutex) {
+ MemberImpl local = (MemberImpl)getLocalMember(false);
+ MemberImpl[] others = (MemberImpl[])membership.getMembers();
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START_ELECT,this,"Election initated"));
+ if ( others.length == 0 ) {
+ this.viewId = new UniqueId(UUIDGenerator.randomUUID(false));
+ this.view = new Membership(local,AbsoluteOrder.comp, true);
+ this.handleViewConf(this.createElectionMsg(local,others,local),local,view);
+ return; //the only member, no need for an election
+ }
+ if ( suggestedviewId != null ) {
+
+ if ( view != null && Arrays.diff(view,suggestedView,local).length == 0 && Arrays.diff(suggestedView,view,local).length == 0) {
+ suggestedviewId = null;
+ suggestedView = null;
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, running election matches view"));
+ } else {
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, election running"));
+ }
+ return; //election already running, I'm not allowed to have two of them
+ }
+ if ( view != null && Arrays.diff(view,membership,local).length == 0 && Arrays.diff(membership,view,local).length == 0) {
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, view matches membership"));
+ return; //already have this view installed
+ }
+ int prio = AbsoluteOrder.comp.compare(local,others[0]);
+ MemberImpl leader = ( prio < 0 )?local:others[0];//am I the leader in my view?
+ if ( local.equals(leader) || force ) {
+ CoordinationMessage msg = createElectionMsg(local, others, leader);
+ suggestedviewId = msg.getId();
+ suggestedView = new Membership(local,AbsoluteOrder.comp,true);
+ Arrays.fill(suggestedView,msg.getMembers());
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_PROCESS_ELECT,this,"Election, sending request"));
+ sendElectionMsg(local,others[0],msg);
+ } else {
+ try {
+ coordMsgReceived.set(false);
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_WAIT_FOR_MSG,this,"Election, waiting for request"));
+ electionMutex.wait(waitForCoordMsgTimeout);
+ }catch ( InterruptedException x ) {
+ Thread.currentThread().interrupted();
+ }
+ if ( suggestedviewId == null && (!coordMsgReceived.get())) {
+ //no message arrived, send the coord msg
+// fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_WAIT_FOR_MSG,this,"Election, waiting timed out."));
+// startElection(true);
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, waiting timed out."));
+ } else {
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, received a message"));
+ }
+ }//end if
+
+ }
+ }
+
+ private CoordinationMessage createElectionMsg(MemberImpl local, MemberImpl[] others, MemberImpl leader) {
+ Membership m = new Membership(local,AbsoluteOrder.comp,true);
+ Arrays.fill(m,others);
+ MemberImpl[] mbrs = m.getMembers();
+ m.reset();
+ CoordinationMessage msg = new CoordinationMessage(leader, local, mbrs,new UniqueId(UUIDGenerator.randomUUID(true)), this.COORD_REQUEST);
+ return msg;
+ }
+
+ protected void sendElectionMsg(MemberImpl local, MemberImpl next, CoordinationMessage msg) throws ChannelException {
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_SEND_MSG,this,"Sending election message to("+next.getName()+")"));
+ super.sendMessage(new Member[] {next}, createData(msg, local), null);
+ }
+
+ protected void sendElectionMsgToNextInline(MemberImpl local, CoordinationMessage msg) throws ChannelException {
+ int next = Arrays.nextIndex(local,msg.getMembers());
+ int current = next;
+ msg.leader = msg.getMembers()[0];
+ boolean sent = false;
+ while ( !sent && current >= 0 ) {
+ try {
+ sendElectionMsg(local, (MemberImpl) msg.getMembers()[current], msg);
+ sent = true;
+ }catch ( ChannelException x ) {
+ log.warn("Unable to send election message to:"+msg.getMembers()[current]);
+ current = Arrays.nextIndex(msg.getMembers()[current],msg.getMembers());
+ if ( current == next ) throw x;
+ }
+ }
+ }
+
+ public Member getNextInLine(MemberImpl local, MemberImpl[] others) {
+ MemberImpl result = null;
+ for ( int i=0; i<others.length; i++ ) {
+
+ }
+ return result;
+ }
+
+ public ChannelData createData(CoordinationMessage msg, MemberImpl local) {
+ msg.write();
+ ChannelData data = new ChannelData(true);
+ data.setAddress(local);
+ data.setMessage(msg.getBuffer());
+ data.setOptions(Channel.SEND_OPTIONS_USE_ACK);
+ data.setTimestamp(System.currentTimeMillis());
+ return data;
+ }
+
+ protected void viewChange(UniqueId viewId, Member[] view) {
+ //invoke any listeners
+ }
+
+ protected boolean alive(Member mbr) {
+ return TcpFailureDetector.memberAlive(mbr,
+ COORD_ALIVE,
+ false,
+ false,
+ waitForCoordMsgTimeout,
+ waitForCoordMsgTimeout,
+ getOptionFlag());
+ }
+
+ protected Membership mergeOnArrive(CoordinationMessage msg, Member sender) {
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_PRE_MERGE,this,"Pre merge"));
+ MemberImpl local = (MemberImpl)getLocalMember(false);
+ Membership merged = new Membership(local,AbsoluteOrder.comp,true);
+ Arrays.fill(merged,msg.getMembers());
+ Arrays.fill(merged,getMembers());
+ Member[] diff = Arrays.diff(merged,membership,local);
+ for ( int i=0; i<diff.length; i++ ) {
+ if (!alive(diff[i])) merged.removeMember((MemberImpl)diff[i]);
+ else memberAdded(diff[i],false);
+ }
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_POST_MERGE,this,"Post merge"));
+ return merged;
+ }
+
+ protected void processCoordMessage(CoordinationMessage msg, Member sender) throws ChannelException {
+ if ( !coordMsgReceived.get() ) {
+ coordMsgReceived.set(true);
+ synchronized (electionMutex) { electionMutex.notifyAll();}
+ }
+ msg.timestamp = System.currentTimeMillis();
+ Membership merged = mergeOnArrive(msg, sender);
+ if (isViewConf(msg)) handleViewConf(msg, sender, merged);
+ else handleToken(msg, sender, merged);
+ ClassLoader loader;
+
+ }
+
+ protected void handleToken(CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+ MemberImpl local = (MemberImpl)getLocalMember(false);
+ if ( local.equals(msg.getSource()) ) {
+ //my message msg.src=local
+ handleMyToken(local, msg, sender,merged);
+ } else {
+ handleOtherToken(local, msg, sender,merged);
+ }
+ }
+
+ protected void handleMyToken(MemberImpl local, CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+ if ( local.equals(msg.getLeader()) ) {
+ //no leadership change
+ if ( Arrays.sameMembers(msg.getMembers(),merged.getMembers()) ) {
+ msg.type = COORD_CONF;
+ super.sendMessage(Arrays.remove(msg.getMembers(),local),createData(msg,local),null);
+ handleViewConf(msg,local,merged);
+ } else {
+ //membership change
+ suggestedView = new Membership(local,AbsoluteOrder.comp,true);
+ suggestedviewId = msg.getId();
+ Arrays.fill(suggestedView,merged.getMembers());
+ msg.view = (MemberImpl[])merged.getMembers();
+ sendElectionMsgToNextInline(local,msg);
+ }
+ } else {
+ //leadership change
+ suggestedView = null;
+ suggestedviewId = null;
+ msg.view = (MemberImpl[])merged.getMembers();
+ sendElectionMsgToNextInline(local,msg);
+ }
+ }
+
+ protected void handleOtherToken(MemberImpl local, CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+ if ( local.equals(msg.getLeader()) ) {
+ //I am the new leader
+ //startElection(false);
+ } else {
+ msg.view = (MemberImpl[])merged.getMembers();
+ sendElectionMsgToNextInline(local,msg);
+ }
+ }
+
+ protected void handleViewConf(CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+ if ( viewId != null && msg.getId().equals(viewId) ) return;//we already have this view
+ view = new Membership((MemberImpl)getLocalMember(false),AbsoluteOrder.comp,true);
+ Arrays.fill(view,msg.getMembers());
+ viewId = msg.getId();
+
+ if ( viewId.equals(suggestedviewId) ) {
+ suggestedView = null;
+ suggestedviewId = null;
+ }
+
+ if (suggestedView != null && AbsoluteOrder.comp.compare(suggestedView.getMembers()[0],merged.getMembers()[0])<0 ) {
+ suggestedView = null;
+ suggestedviewId = null;
+ }
+
+ viewChange(viewId,view.getMembers());
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_CONF_RX,this,"Accepted View"));
+
+ if ( suggestedviewId == null && hasHigherPriority(merged.getMembers(),membership.getMembers()) ) {
+ startElection(false);
+ }
+ }
+
+ protected boolean isViewConf(CoordinationMessage msg) {
+ return Arrays.contains(msg.getType(),0,COORD_CONF,0,COORD_CONF.length);
+ }
+
+ protected boolean hasHigherPriority(Member[] complete, Member[] local) {
+ if ( local == null || local.length == 0 ) return false;
+ if ( complete == null || complete.length == 0 ) return true;
+ AbsoluteOrder.absoluteOrder(complete);
+ AbsoluteOrder.absoluteOrder(local);
+ return (AbsoluteOrder.comp.compare(complete[0],local[0]) > 0);
+
+ }
+
+
+ /**
+ * Returns coordinator if one is available
+ * @return Member
+ */
+ public Member getCoordinator() {
+ return (view != null && view.hasMembers()) ? view.getMembers()[0] : null;
+ }
+
+ public Member[] getView() {
+ return (view != null && view.hasMembers()) ? view.getMembers() : new Member[0];
+ }
+
+ public UniqueId getViewId() {
+ return viewId;
+ }
+
+ /**
+ * Block in/out messages while a election is going on
+ */
+ protected void halt() {
+
+ }
+
+ /**
+ * Release lock for in/out messages election is completed
+ */
+ protected void release() {
+
+ }
+
+ /**
+ * Wait for an election to end
+ */
+ protected void waitForRelease() {
+
+ }
+
+
+//============================================================================================================
+// OVERRIDDEN METHODS FROM CHANNEL INTERCEPTOR BASE
+//============================================================================================================
+ public void start(int svc) throws ChannelException {
+ if (membership == null) setupMembership();
+ if (started)return;
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START, this, "Before start"));
+ super.start(startsvc);
+ started = true;
+ if (view == null) view = new Membership( (MemberImpl)super.getLocalMember(true), AbsoluteOrder.comp, true);
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START, this, "After start"));
+ startElection(false);
+ }
+
+ public void stop(int svc) throws ChannelException {
+ try {
+ halt();
+ synchronized (electionMutex) {
+ if (!started)return;
+ started = false;
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_STOP, this, "Before stop"));
+ super.stop(startsvc);
+ this.view = null;
+ this.viewId = null;
+ this.suggestedView = null;
+ this.suggestedviewId = null;
+ this.membership.reset();
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_STOP, this, "After stop"));
+ }
+ }finally {
+ release();
+ }
+ }
+
+
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+ waitForRelease();
+ super.sendMessage(destination, msg, payload);
+ }
+
+ public void messageReceived(ChannelMessage msg) {
+ if ( Arrays.contains(msg.getMessage().getBytesDirect(),0,COORD_ALIVE,0,COORD_ALIVE.length) ) {
+ //ignore message, its an alive message
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MSG_ARRIVE,this,"Alive Message"));
+
+ } else if ( Arrays.contains(msg.getMessage().getBytesDirect(),0,COORD_HEADER,0,COORD_HEADER.length) ) {
+ try {
+ CoordinationMessage cmsg = new CoordinationMessage(msg.getMessage());
+ Member[] cmbr = cmsg.getMembers();
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MSG_ARRIVE,this,"Coord Msg Arrived("+Arrays.toNameString(cmbr)+")"));
+ processCoordMessage(cmsg, msg.getAddress());
+ }catch ( ChannelException x ) {
+ log.error("Error processing coordination message. Could be fatal.",x);
+ }
+ } else {
+ super.messageReceived(msg);
+ }
+ }
+
+ public boolean accept(ChannelMessage msg) {
+ return super.accept(msg);
+ }
+
+ public void memberAdded(Member member) {
+ memberAdded(member,true);
+ }
+
+ public void memberAdded(Member member,boolean elect) {
+ try {
+ if ( membership == null ) setupMembership();
+ if ( membership.memberAlive((MemberImpl)member) ) super.memberAdded(member);
+ try {
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MBR_ADD,this,"Member add("+member.getName()+")"));
+ if (started && elect) startElection(false);
+ }catch ( ChannelException x ) {
+ log.error("Unable to start election when member was added.",x);
+ }
+ }finally {
+ }
+
+ }
+
+ public void memberDisappeared(Member member) {
+ try {
+
+ membership.removeMember((MemberImpl)member);
+ super.memberDisappeared(member);
+ try {
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MBR_DEL,this,"Member remove("+member.getName()+")"));
+ if ( started && (isCoordinator() || isHighest()) )
+ startElection(true); //to do, if a member disappears, only the coordinator can start
+ }catch ( ChannelException x ) {
+ log.error("Unable to start election when member was removed.",x);
+ }
+ }finally {
+ }
+ }
+
+ public boolean isHighest() {
+ Member local = getLocalMember(false);
+ if ( membership.getMembers().length == 0 ) return true;
+ else return AbsoluteOrder.comp.compare(local,membership.getMembers()[0])<=0;
+ }
+
+ public boolean isCoordinator() {
+ Member coord = getCoordinator();
+ return coord != null && getLocalMember(false).equals(coord);
+ }
+
+ public void heartbeat() {
+ try {
+ MemberImpl local = (MemberImpl)getLocalMember(false);
+ if ( view != null && (Arrays.diff(view,membership,local).length != 0 || Arrays.diff(membership,view,local).length != 0) ) {
+ if ( isHighest() ) {
+ fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START_ELECT, this,
+ "Heartbeat found inconsistency, restart election"));
+ startElection(true);
+ }
+ }
+ } catch ( Exception x ){
+ log.error("Unable to perform heartbeat.",x);
+ } finally {
+ super.heartbeat();
+ }
+ }
+
+ /**
+ * has members
+ */
+ public boolean hasMembers() {
+
+ return membership.hasMembers();
+ }
+
+ /**
+ * Get all current cluster members
+ * @return all members or empty array
+ */
+ public Member[] getMembers() {
+
+ return membership.getMembers();
+ }
+
+ /**
+ *
+ * @param mbr Member
+ * @return Member
+ */
+ public Member getMember(Member mbr) {
+
+ return membership.getMember(mbr);
+ }
+
+ /**
+ * Return the member that represents this node.
+ *
+ * @return Member
+ */
+ public Member getLocalMember(boolean incAlive) {
+ Member local = super.getLocalMember(incAlive);
+ if ( view == null && (local != null)) setupMembership();
+ return local;
+ }
+
+ protected synchronized void setupMembership() {
+ if ( membership == null ) {
+ membership = new Membership((MemberImpl)super.getLocalMember(true),AbsoluteOrder.comp,false);
+ }
+ }
+
+
+//============================================================================================================
+// HELPER CLASSES FOR COORDINATION
+//============================================================================================================
+
+
+
+
+ public static class CoordinationMessage {
+ //X{A-ldr, A-src, mbrs-A,B,C,D}
+ protected XByteBuffer buf;
+ protected MemberImpl leader;
+ protected MemberImpl source;
+ protected MemberImpl[] view;
+ protected UniqueId id;
+ protected byte[] type;
+ protected long timestamp = System.currentTimeMillis();
+
+ public CoordinationMessage(XByteBuffer buf) {
+ this.buf = buf;
+ parse();
+ }
+
+ public CoordinationMessage(MemberImpl leader,
+ MemberImpl source,
+ MemberImpl[] view,
+ UniqueId id,
+ byte[] type) {
+ this.buf = new XByteBuffer(4096,false);
+ this.leader = leader;
+ this.source = source;
+ this.view = view;
+ this.id = id;
+ this.type = type;
+ this.write();
+ }
+
+
+ public byte[] getHeader() {
+ return NonBlockingCoordinator.COORD_HEADER;
+ }
+
+ public MemberImpl getLeader() {
+ if ( leader == null ) parse();
+ return leader;
+ }
+
+ public MemberImpl getSource() {
+ if ( source == null ) parse();
+ return source;
+ }
+
+ public UniqueId getId() {
+ if ( id == null ) parse();
+ return id;
+ }
+
+ public MemberImpl[] getMembers() {
+ if ( view == null ) parse();
+ return view;
+ }
+
+ public byte[] getType() {
+ if (type == null ) parse();
+ return type;
+ }
+
+ public XByteBuffer getBuffer() {
+ return this.buf;
+ }
+
+ public void parse() {
+ //header
+ int offset = 16;
+ //leader
+ int ldrLen = buf.toInt(buf.getBytesDirect(),offset);
+ offset += 4;
+ byte[] ldr = new byte[ldrLen];
+ System.arraycopy(buf.getBytesDirect(),offset,ldr,0,ldrLen);
+ leader = MemberImpl.getMember(ldr);
+ offset += ldrLen;
+ //source
+ int srcLen = buf.toInt(buf.getBytesDirect(),offset);
+ offset += 4;
+ byte[] src = new byte[srcLen];
+ System.arraycopy(buf.getBytesDirect(),offset,src,0,srcLen);
+ source = MemberImpl.getMember(src);
+ offset += srcLen;
+ //view
+ int mbrCount = buf.toInt(buf.getBytesDirect(),offset);
+ offset += 4;
+ view = new MemberImpl[mbrCount];
+ for (int i=0; i<view.length; i++ ) {
+ int mbrLen = buf.toInt(buf.getBytesDirect(),offset);
+ offset += 4;
+ byte[] mbr = new byte[mbrLen];
+ System.arraycopy(buf.getBytesDirect(), offset, mbr, 0, mbrLen);
+ view[i] = MemberImpl.getMember(mbr);
+ offset += mbrLen;
+ }
+ //id
+ this.id = new UniqueId(buf.getBytesDirect(),offset,16);
+ offset += 16;
+ type = new byte[16];
+ System.arraycopy(buf.getBytesDirect(), offset, type, 0, type.length);
+ offset += 16;
+
+ }
+
+ public void write() {
+ buf.reset();
+ //header
+ buf.append(COORD_HEADER,0,COORD_HEADER.length);
+ //leader
+ byte[] ldr = leader.getData(false,false);
+ buf.append(ldr.length);
+ buf.append(ldr,0,ldr.length);
+ ldr = null;
+ //source
+ byte[] src = source.getData(false,false);
+ buf.append(src.length);
+ buf.append(src,0,src.length);
+ src = null;
+ //view
+ buf.append(view.length);
+ for (int i=0; i<view.length; i++ ) {
+ byte[] mbr = view[i].getData(false,false);
+ buf.append(mbr.length);
+ buf.append(mbr,0,mbr.length);
+ }
+ //id
+ buf.append(id.getBytes(),0,id.getBytes().length);
+ buf.append(type,0,type.length);
+ }
+ }
+
+ public void fireInterceptorEvent(InterceptorEvent event) {
+ if (event instanceof CoordinationEvent &&
+ ((CoordinationEvent)event).type == CoordinationEvent.EVT_CONF_RX)
+ log.info(event);
+ }
+
+ public static class CoordinationEvent implements InterceptorEvent {
+ public static final int EVT_START = 1;
+ public static final int EVT_MBR_ADD = 2;
+ public static final int EVT_MBR_DEL = 3;
+ public static final int EVT_START_ELECT = 4;
+ public static final int EVT_PROCESS_ELECT = 5;
+ public static final int EVT_MSG_ARRIVE = 6;
+ public static final int EVT_PRE_MERGE = 7;
+ public static final int EVT_POST_MERGE = 8;
+ public static final int EVT_WAIT_FOR_MSG = 9;
+ public static final int EVT_SEND_MSG = 10;
+ public static final int EVT_STOP = 11;
+ public static final int EVT_CONF_RX = 12;
+ public static final int EVT_ELECT_ABANDONED = 13;
+
+ int type;
+ ChannelInterceptor interceptor;
+ Member coord;
+ Member[] mbrs;
+ String info;
+ Membership view;
+ Membership suggestedView;
+ public CoordinationEvent(int type,ChannelInterceptor interceptor, String info) {
+ this.type = type;
+ this.interceptor = interceptor;
+ this.coord = ((NonBlockingCoordinator)interceptor).getCoordinator();
+ this.mbrs = ((NonBlockingCoordinator)interceptor).membership.getMembers();
+ this.info = info;
+ this.view = ((NonBlockingCoordinator)interceptor).view;
+ this.suggestedView = ((NonBlockingCoordinator)interceptor).suggestedView;
+ }
+
+ public int getEventType() {
+ return type;
+ }
+
+ public String getEventTypeDesc() {
+ switch (type) {
+ case EVT_START: return "EVT_START:"+info;
+ case EVT_MBR_ADD: return "EVT_MBR_ADD:"+info;
+ case EVT_MBR_DEL: return "EVT_MBR_DEL:"+info;
+ case EVT_START_ELECT: return "EVT_START_ELECT:"+info;
+ case EVT_PROCESS_ELECT: return "EVT_PROCESS_ELECT:"+info;
+ case EVT_MSG_ARRIVE: return "EVT_MSG_ARRIVE:"+info;
+ case EVT_PRE_MERGE: return "EVT_PRE_MERGE:"+info;
+ case EVT_POST_MERGE: return "EVT_POST_MERGE:"+info;
+ case EVT_WAIT_FOR_MSG: return "EVT_WAIT_FOR_MSG:"+info;
+ case EVT_SEND_MSG: return "EVT_SEND_MSG:"+info;
+ case EVT_STOP: return "EVT_STOP:"+info;
+ case EVT_CONF_RX: return "EVT_CONF_RX:"+info;
+ case EVT_ELECT_ABANDONED: return "EVT_ELECT_ABANDONED:"+info;
+ default: return "Unknown";
+ }
+ }
+
+ public ChannelInterceptor getInterceptor() {
+ return interceptor;
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer("CoordinationEvent[type=");
+ buf.append(type).append("\n\tLocal:");
+ Member local = interceptor.getLocalMember(false);
+ buf.append(local!=null?local.getName():"").append("\n\tCoord:");
+ buf.append(coord!=null?coord.getName():"").append("\n\tView:");
+ buf.append(Arrays.toNameString(view!=null?view.getMembers():null)).append("\n\tSuggested View:");
+ buf.append(Arrays.toNameString(suggestedView!=null?suggestedView.getMembers():null)).append("\n\tMembers:");
+ buf.append(Arrays.toNameString(mbrs)).append("\n\tInfo:");
+ buf.append(info).append("]");
+ return buf.toString();
+ }
+ }
+
+
+
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ */
+
+package org.apache.catalina.tribes.group.interceptors;
+
+import java.util.HashMap;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.XByteBuffer;
+
+
+
+/**
+ *
+ * The order interceptor guarantees that messages are received in the same order they were
+ * sent.
+ * This interceptor works best with the ack=true setting. <br>
+ * There is no point in
+ * using this with the replicationMode="fastasynchqueue" as this mode guarantees ordering.<BR>
+ * If you are using the mode ack=false replicationMode=pooled, and have a lot of concurrent threads,
+ * this interceptor can really slow you down, as many messages will be completely out of order
+ * and the queue might become rather large. If this is the case, then you might want to set
+ * the value OrderInterceptor.maxQueue = 25 (meaning that we will never keep more than 25 messages in our queue)
+ * <br><b>Configuration Options</b><br>
+ * OrderInteceptor.expire=<milliseconds> - if a message arrives out of order, how long before we act on it <b>default=3000ms</b><br>
+ * OrderInteceptor.maxQueue=<max queue size> - how much can the queue grow to ensure ordering.
+ * This setting is useful to avoid OutOfMemoryErrors<b>default=Integer.MAX_VALUE</b><br>
+ * OrderInterceptor.forwardExpired=<boolean> - this flag tells the interceptor what to
+ * do when a message has expired or the queue has grown larger than the maxQueue value.
+ * true means that the message is sent up the stack to the receiver that will receive and out of order message
+ * false means, forget the message and reset the message counter. <b>default=true</b>
+ *
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class OrderInterceptor extends ChannelInterceptorBase {
+ private HashMap outcounter = new HashMap();
+ private HashMap incounter = new HashMap();
+ private HashMap incoming = new HashMap();
+ private long expire = 3000;
+ private boolean forwardExpired = true;
+ private int maxQueue = Integer.MAX_VALUE;
+
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+ for ( int i=0; i<destination.length; i++ ) {
+ int nr = incCounter(destination[i]);
+ //reduce byte copy
+ msg.getMessage().append(nr);
+ try {
+ getNext().sendMessage(new Member[] {destination[i]}, msg, payload);
+ }finally {
+ msg.getMessage().trim(4);
+ }
+ }
+ }
+
+ public void messageReceived(ChannelMessage msg) {
+ int msgnr = XByteBuffer.toInt(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-4);
+ msg.getMessage().trim(4);
+ MessageOrder order = new MessageOrder(msgnr,(ChannelMessage)msg.deepclone());
+ if ( processIncoming(order) ) processLeftOvers(msg.getAddress(),false);
+ }
+
+ public synchronized void processLeftOvers(Member member, boolean force) {
+ MessageOrder tmp = (MessageOrder)incoming.get(member);
+ if ( force ) {
+ Counter cnt = getInCounter(member);
+ cnt.setCounter(Integer.MAX_VALUE);
+ }
+ if ( tmp!= null ) processIncoming(tmp);
+ }
+ /**
+ *
+ * @param order MessageOrder
+ * @return boolean - true if a message expired and was processed
+ */
+ public synchronized boolean processIncoming(MessageOrder order) {
+ boolean result = false;
+ Member member = order.getMessage().getAddress();
+ Counter cnt = getInCounter(member);
+
+ MessageOrder tmp = (MessageOrder)incoming.get(member);
+ if ( tmp != null ) {
+ order = MessageOrder.add(tmp,order);
+ }
+
+
+ while ( (order!=null) && (order.getMsgNr() <= cnt.getCounter()) ) {
+ //we are right on target. process orders
+ if ( order.getMsgNr() == cnt.getCounter() ) cnt.inc();
+ else if ( order.getMsgNr() > cnt.getCounter() ) cnt.setCounter(order.getMsgNr());
+ super.messageReceived(order.getMessage());
+ order.setMessage(null);
+ order = order.next;
+ }
+ MessageOrder head = order;
+ MessageOrder prev = null;
+ tmp = order;
+ //flag to empty out the queue when it larger than maxQueue
+ boolean empty = order!=null?order.getCount()>=maxQueue:false;
+ while ( tmp != null ) {
+ //process expired messages or empty out the queue
+ if ( tmp.isExpired(expire) || empty ) {
+ //reset the head
+ if ( tmp == head ) head = tmp.next;
+ cnt.setCounter(tmp.getMsgNr()+1);
+ if ( getForwardExpired() ) super.messageReceived(tmp.getMessage());
+ tmp.setMessage(null);
+ tmp = tmp.next;
+ if ( prev != null ) prev.next = tmp;
+ result = true;
+ } else {
+ prev = tmp;
+ tmp = tmp.next;
+ }
+ }
+ if ( head == null ) incoming.remove(member);
+ else incoming.put(member, head);
+ return result;
+ }
+
+ public void memberAdded(Member member) {
+ //notify upwards
+ getInCounter(member);
+ getOutCounter(member);
+ super.memberAdded(member);
+ }
+
+ public void memberDisappeared(Member member) {
+ //notify upwards
+ outcounter.remove(member);
+ incounter.remove(member);
+ //clear the remaining queue
+ processLeftOvers(member,true);
+ super.memberDisappeared(member);
+ }
+
+ public int incCounter(Member mbr) {
+ Counter cnt = getOutCounter(mbr);
+ return cnt.inc();
+ }
+
+ public synchronized Counter getInCounter(Member mbr) {
+ Counter cnt = (Counter)incounter.get(mbr);
+ if ( cnt == null ) {
+ cnt = new Counter();
+ cnt.inc(); //always start at 1 for incoming
+ incounter.put(mbr,cnt);
+ }
+ return cnt;
+ }
+
+ public synchronized Counter getOutCounter(Member mbr) {
+ Counter cnt = (Counter)outcounter.get(mbr);
+ if ( cnt == null ) {
+ cnt = new Counter();
+ outcounter.put(mbr,cnt);
+ }
+ return cnt;
+ }
+
+ public static class Counter {
+ private int value = 0;
+
+ public int getCounter() {
+ return value;
+ }
+
+ public synchronized void setCounter(int counter) {
+ this.value = counter;
+ }
+
+ public synchronized int inc() {
+ return ++value;
+ }
+ }
+
+ public static class MessageOrder {
+ private long received = System.currentTimeMillis();
+ private MessageOrder next;
+ private int msgNr;
+ private ChannelMessage msg = null;
+ public MessageOrder(int msgNr,ChannelMessage msg) {
+ this.msgNr = msgNr;
+ this.msg = msg;
+ }
+
+ public boolean isExpired(long expireTime) {
+ return (System.currentTimeMillis()-received) > expireTime;
+ }
+
+ public ChannelMessage getMessage() {
+ return msg;
+ }
+
+ public void setMessage(ChannelMessage msg) {
+ this.msg = msg;
+ }
+
+ public void setNext(MessageOrder order) {
+ this.next = order;
+ }
+ public MessageOrder getNext() {
+ return next;
+ }
+
+ public int getCount() {
+ int counter = 1;
+ MessageOrder tmp = next;
+ while ( tmp != null ) {
+ counter++;
+ tmp = tmp.next;
+ }
+ return counter;
+ }
+
+ public static MessageOrder add(MessageOrder head, MessageOrder add) {
+ if ( head == null ) return add;
+ if ( add == null ) return head;
+ if ( head == add ) return add;
+
+ if ( head.getMsgNr() > add.getMsgNr() ) {
+ add.next = head;
+ return add;
+ }
+
+ MessageOrder iter = head;
+ MessageOrder prev = null;
+ while ( iter.getMsgNr() < add.getMsgNr() && (iter.next !=null ) ) {
+ prev = iter;
+ iter = iter.next;
+ }
+ if ( iter.getMsgNr() < add.getMsgNr() ) {
+ //add after
+ add.next = iter.next;
+ iter.next = add;
+ } else if (iter.getMsgNr() > add.getMsgNr()) {
+ //add before
+ prev.next = add;
+ add.next = iter;
+
+ } else {
+ throw new ArithmeticException("Message added has the same counter, synchronization bug. Disable the order interceptor");
+ }
+
+ return head;
+ }
+
+ public int getMsgNr() {
+ return msgNr;
+ }
+
+
+
+ }
+
+ public void setExpire(long expire) {
+ this.expire = expire;
+ }
+
+ public void setForwardExpired(boolean forwardExpired) {
+ this.forwardExpired = forwardExpired;
+ }
+
+ public void setMaxQueue(int maxQueue) {
+ this.maxQueue = maxQueue;
+ }
+
+ public long getExpire() {
+ return expire;
+ }
+
+ public boolean getForwardExpired() {
+ return forwardExpired;
+ }
+
+ public int getMaxQueue() {
+ return maxQueue;
+ }
+
+}
--- /dev/null
+package org.apache.catalina.tribes.group.interceptors;
+
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.Member;
+import java.util.ArrayList;
+import org.apache.catalina.tribes.group.AbsoluteOrder;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.Channel;
+
+public class StaticMembershipInterceptor
+ extends ChannelInterceptorBase {
+ protected ArrayList members = new ArrayList();
+ protected Member localMember = null;
+
+ public StaticMembershipInterceptor() {
+ super();
+ }
+
+ public void addStaticMember(Member member) {
+ synchronized (members) {
+ if (!members.contains(member)) members.add(member);
+ }
+ }
+
+ public void removeStaticMember(Member member) {
+ synchronized (members) {
+ if (members.contains(member)) members.remove(member);
+ }
+ }
+
+ public void setLocalMember(Member member) {
+ this.localMember = member;
+ }
+
+ /**
+ * has members
+ */
+ public boolean hasMembers() {
+ return super.hasMembers() || (members.size()>0);
+ }
+
+ /**
+ * Get all current cluster members
+ * @return all members or empty array
+ */
+ public Member[] getMembers() {
+ if ( members.size() == 0 ) return super.getMembers();
+ else {
+ synchronized (members) {
+ Member[] others = super.getMembers();
+ Member[] result = new Member[members.size() + others.length];
+ for (int i = 0; i < others.length; i++) result[i] = others[i];
+ for (int i = 0; i < members.size(); i++) result[i + others.length] = (Member) members.get(i);
+ AbsoluteOrder.absoluteOrder(result);
+ return result;
+ }//sync
+ }//end if
+ }
+
+ /**
+ *
+ * @param mbr Member
+ * @return Member
+ */
+ public Member getMember(Member mbr) {
+ if ( members.contains(mbr) ) return (Member)members.get(members.indexOf(mbr));
+ else return super.getMember(mbr);
+ }
+
+ /**
+ * Return the member that represents this node.
+ *
+ * @return Member
+ */
+ public Member getLocalMember(boolean incAlive) {
+ if (this.localMember != null ) return localMember;
+ else return super.getLocalMember(incAlive);
+ }
+
+ /**
+ * Send notifications upwards
+ * @param svc int
+ * @throws ChannelException
+ */
+ public void start(int svc) throws ChannelException {
+ if ( (Channel.SND_RX_SEQ&svc)==Channel.SND_RX_SEQ ) super.start(Channel.SND_RX_SEQ);
+ if ( (Channel.SND_TX_SEQ&svc)==Channel.SND_TX_SEQ ) super.start(Channel.SND_TX_SEQ);
+ final Member[] mbrs = (Member[])members.toArray(new Member[members.size()]);
+ final ChannelInterceptorBase base = this;
+ Thread t = new Thread() {
+ public void run() {
+ for (int i=0; i<mbrs.length; i++ ) {
+ base.memberAdded(mbrs[i]);
+ }
+ }
+ };
+ t.start();
+ super.start(svc & (~Channel.SND_RX_SEQ) & (~Channel.SND_TX_SEQ));
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ */
+package org.apache.catalina.tribes.group.interceptors;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelException.FaultyMember;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.RemoteProcessException;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.membership.Membership;
+import java.net.ConnectException;
+
+/**
+ * <p>Title: A perfect failure detector </p>
+ *
+ * <p>Description: The TcpFailureDetector is a useful interceptor
+ * that adds reliability to the membership layer.</p>
+ * <p>
+ * If the network is busy, or the system is busy so that the membership receiver thread
+ * is not getting enough time to update its table, members can be "timed out"
+ * This failure detector will intercept the memberDisappeared message(unless its a true shutdown message)
+ * and connect to the member using TCP.
+ * </p>
+ * <p>
+ * The TcpFailureDetector works in two ways. <br>
+ * 1. It intercepts memberDisappeared events
+ * 2. It catches send errors
+ * </p>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class TcpFailureDetector extends ChannelInterceptorBase {
+
+ private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog( TcpFailureDetector.class );
+
+ protected static byte[] TCP_FAIL_DETECT = new byte[] {
+ 79, -89, 115, 72, 121, -126, 67, -55, -97, 111, -119, -128, -95, 91, 7, 20,
+ 125, -39, 82, 91, -21, -15, 67, -102, -73, 126, -66, -113, -127, 103, 30, -74,
+ 55, 21, -66, -121, 69, 126, 76, -88, -65, 10, 77, 19, 83, 56, 21, 50,
+ 85, -10, -108, -73, 58, -6, 64, 120, -111, 4, 125, -41, 114, -124, -64, -43};
+
+ protected boolean performConnectTest = true;
+
+ protected long connectTimeout = 1000;//1 second default
+
+ protected boolean performSendTest = true;
+
+ protected boolean performReadTest = false;
+
+ protected long readTestTimeout = 5000;//5 seconds
+
+ protected Membership membership = null;
+
+ protected HashMap removeSuspects = new HashMap();
+
+ protected HashMap addSuspects = new HashMap();
+
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+ try {
+ super.sendMessage(destination, msg, payload);
+ }catch ( ChannelException cx ) {
+ FaultyMember[] mbrs = cx.getFaultyMembers();
+ for ( int i=0; i<mbrs.length; i++ ) {
+ if ( mbrs[i].getCause()!=null &&
+ (!(mbrs[i].getCause() instanceof RemoteProcessException)) ) {//RemoteProcessException's are ok
+ this.memberDisappeared(mbrs[i].getMember());
+ }//end if
+ }//for
+ throw cx;
+ }
+ }
+
+ public void messageReceived(ChannelMessage msg) {
+ //catch incoming
+ boolean process = true;
+ if ( okToProcess(msg.getOptions()) ) {
+ //check to see if it is a testMessage, if so, process = false
+ process = ( (msg.getMessage().getLength() != TCP_FAIL_DETECT.length) ||
+ (!Arrays.equals(TCP_FAIL_DETECT,msg.getMessage().getBytes()) ) );
+ }//end if
+
+ //ignore the message, it doesnt have the flag set
+ if ( process ) super.messageReceived(msg);
+ else if ( log.isDebugEnabled() ) log.debug("Received a failure detector packet:"+msg);
+ }//messageReceived
+
+
+ public void memberAdded(Member member) {
+ if ( membership == null ) setupMembership();
+ boolean notify = false;
+ synchronized (membership) {
+ if (removeSuspects.containsKey(member)) {
+ //previously marked suspect, system below picked up the member again
+ removeSuspects.remove(member);
+ } else if (membership.getMember( (MemberImpl) member) == null){
+ //if we add it here, then add it upwards too
+ //check to see if it is alive
+ if (memberAlive(member)) {
+ membership.memberAlive( (MemberImpl) member);
+ notify = true;
+ } else {
+ addSuspects.put(member, new Long(System.currentTimeMillis()));
+ }
+ }
+ }
+ if ( notify ) super.memberAdded(member);
+ }
+
+ public void memberDisappeared(Member member) {
+ if ( membership == null ) setupMembership();
+ boolean notify = false;
+ boolean shutdown = Arrays.equals(member.getCommand(),Member.SHUTDOWN_PAYLOAD);
+ if ( !shutdown ) log.info("Received memberDisappeared["+member+"] message. Will verify.");
+ synchronized (membership) {
+ //check to see if the member really is gone
+ //if the payload is not a shutdown message
+ if (shutdown || !memberAlive(member)) {
+ //not correct, we need to maintain the map
+ membership.removeMember( (MemberImpl) member);
+ removeSuspects.remove(member);
+ notify = true;
+ } else {
+ //add the member as suspect
+ removeSuspects.put(member, new Long(System.currentTimeMillis()));
+ }
+ }
+ if ( notify ) {
+ log.info("Verification complete. Member disappeared["+member+"]");
+ super.memberDisappeared(member);
+ } else {
+ log.info("Verification complete. Member still alive["+member+"]");
+
+ }
+ }
+
+ public boolean hasMembers() {
+ if ( membership == null ) setupMembership();
+ return membership.hasMembers();
+ }
+
+ public Member[] getMembers() {
+ if ( membership == null ) setupMembership();
+ return membership.getMembers();
+ }
+
+ public Member getMember(Member mbr) {
+ if ( membership == null ) setupMembership();
+ return membership.getMember(mbr);
+ }
+
+ public Member getLocalMember(boolean incAlive) {
+ return super.getLocalMember(incAlive);
+ }
+
+ public void heartbeat() {
+ try {
+ if (membership == null) setupMembership();
+ synchronized (membership) {
+ //update all alive times
+ Member[] members = super.getMembers();
+ for (int i = 0; members != null && i < members.length; i++) {
+ if (membership.memberAlive( (MemberImpl) members[i])) {
+ //we don't have this one in our membership, check to see if he/she is alive
+ if (memberAlive(members[i])) {
+ log.warn("Member added, even though we werent notified:" + members[i]);
+ super.memberAdded(members[i]);
+ } else {
+ membership.removeMember( (MemberImpl) members[i]);
+ } //end if
+ } //end if
+ } //for
+
+ //check suspect members if they are still alive,
+ //if not, simply issue the memberDisappeared message
+ MemberImpl[] keys = (MemberImpl[]) removeSuspects.keySet().toArray(new MemberImpl[removeSuspects.size()]);
+ for (int i = 0; i < keys.length; i++) {
+ MemberImpl m = (MemberImpl) keys[i];
+ if (membership.getMember(m) != null && (!memberAlive(m))) {
+ membership.removeMember(m);
+ super.memberDisappeared(m);
+ removeSuspects.remove(m);
+ log.info("Suspect member, confirmed dead.["+m+"]");
+ } //end if
+ }
+
+ //check add suspects members if they are alive now,
+ //if they are, simply issue the memberAdded message
+ keys = (MemberImpl[]) addSuspects.keySet().toArray(new MemberImpl[addSuspects.size()]);
+ for (int i = 0; i < keys.length; i++) {
+ MemberImpl m = (MemberImpl) keys[i];
+ if ( membership.getMember(m) == null && (memberAlive(m))) {
+ membership.memberAlive(m);
+ super.memberAdded(m);
+ addSuspects.remove(m);
+ log.info("Suspect member, confirmed alive.["+m+"]");
+ } //end if
+ }
+ }
+ }catch ( Exception x ) {
+ log.warn("Unable to perform heartbeat on the TcpFailureDetector.",x);
+ } finally {
+ super.heartbeat();
+ }
+ }
+
+ protected synchronized void setupMembership() {
+ if ( membership == null ) {
+ membership = new Membership((MemberImpl)super.getLocalMember(true));
+ }
+
+ }
+
+ protected boolean memberAlive(Member mbr) {
+ return memberAlive(mbr,TCP_FAIL_DETECT,performSendTest,performReadTest,readTestTimeout,connectTimeout,getOptionFlag());
+ }
+
+ protected static boolean memberAlive(Member mbr, byte[] msgData,
+ boolean sendTest, boolean readTest,
+ long readTimeout, long conTimeout,
+ int optionFlag) {
+ //could be a shutdown notification
+ if ( Arrays.equals(mbr.getCommand(),Member.SHUTDOWN_PAYLOAD) ) return false;
+
+ Socket socket = new Socket();
+ try {
+ InetAddress ia = InetAddress.getByAddress(mbr.getHost());
+ InetSocketAddress addr = new InetSocketAddress(ia, mbr.getPort());
+ socket.setSoTimeout((int)readTimeout);
+ socket.connect(addr, (int) conTimeout);
+ if ( sendTest ) {
+ ChannelData data = new ChannelData(true);
+ data.setAddress(mbr);
+ data.setMessage(new XByteBuffer(msgData,false));
+ data.setTimestamp(System.currentTimeMillis());
+ int options = optionFlag | Channel.SEND_OPTIONS_BYTE_MESSAGE;
+ if ( readTest ) options = (options | Channel.SEND_OPTIONS_USE_ACK);
+ else options = (options & (~Channel.SEND_OPTIONS_USE_ACK));
+ data.setOptions(options);
+ byte[] message = XByteBuffer.createDataPackage(data);
+ socket.getOutputStream().write(message);
+ if ( readTest ) {
+ int length = socket.getInputStream().read(message);
+ return length > 0;
+ }
+ }//end if
+ return true;
+ } catch ( SocketTimeoutException sx) {
+ //do nothing, we couldn't connect
+ } catch ( ConnectException cx) {
+ //do nothing, we couldn't connect
+ }catch (Exception x ) {
+ log.error("Unable to perform failure detection check, assuming member down.",x);
+ } finally {
+ try {socket.close(); } catch ( Exception ignore ){}
+ }
+ return false;
+ }
+
+
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ */
+
+package org.apache.catalina.tribes.group.interceptors;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import java.text.DecimalFormat;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+
+
+/**
+ *
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class ThroughputInterceptor extends ChannelInterceptorBase {
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(ThroughputInterceptor.class);
+
+ double mbTx = 0;
+ double mbAppTx = 0;
+ double mbRx = 0;
+ double timeTx = 0;
+ double lastCnt = 0;
+ AtomicLong msgTxCnt = new AtomicLong(1);
+ AtomicLong msgRxCnt = new AtomicLong(0);
+ AtomicLong msgTxErr = new AtomicLong(0);
+ int interval = 10000;
+ AtomicInteger access = new AtomicInteger(0);
+ long txStart = 0;
+ long rxStart = 0;
+ DecimalFormat df = new DecimalFormat("#0.00");
+
+
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+ if ( access.addAndGet(1) == 1 ) txStart = System.currentTimeMillis();
+ long bytes = XByteBuffer.getDataPackageLength(((ChannelData)msg).getDataPackageLength());
+ try {
+ super.sendMessage(destination, msg, payload);
+ }catch ( ChannelException x ) {
+ msgTxErr.addAndGet(1);
+ access.addAndGet(-1);
+ throw x;
+ }
+ mbTx += ((double)(bytes*destination.length))/(1024d*1024d);
+ mbAppTx += ((double)(bytes))/(1024d*1024d);
+ if ( access.addAndGet(-1) == 0 ) {
+ long stop = System.currentTimeMillis();
+ timeTx += ( (double) (stop - txStart)) / 1000d;
+ if ((msgTxCnt.get() / interval) >= lastCnt) {
+ lastCnt++;
+ report(timeTx);
+ }
+ }
+ msgTxCnt.addAndGet(1);
+ }
+
+ public void messageReceived(ChannelMessage msg) {
+ if ( rxStart == 0 ) rxStart = System.currentTimeMillis();
+ long bytes = XByteBuffer.getDataPackageLength(((ChannelData)msg).getDataPackageLength());
+ mbRx += ((double)bytes)/(1024d*1024d);
+ msgRxCnt.addAndGet(1);
+ if ( msgRxCnt.get() % interval == 0 ) report(timeTx);
+ super.messageReceived(msg);
+
+ }
+
+ public void report(double timeTx) {
+ StringBuffer buf = new StringBuffer("ThroughputInterceptor Report[\n\tTx Msg:");
+ buf.append(msgTxCnt).append(" messages\n\tSent:");
+ buf.append(df.format(mbTx));
+ buf.append(" MB (total)\n\tSent:");
+ buf.append(df.format(mbAppTx));
+ buf.append(" MB (application)\n\tTime:");
+ buf.append(df.format(timeTx));
+ buf.append(" seconds\n\tTx Speed:");
+ buf.append(df.format(mbTx/timeTx));
+ buf.append(" MB/sec (total)\n\tTxSpeed:");
+ buf.append(df.format(mbAppTx/timeTx));
+ buf.append(" MB/sec (application)\n\tError Msg:");
+ buf.append(msgTxErr).append("\n\tRx Msg:");
+ buf.append(msgRxCnt);
+ buf.append(" messages\n\tRx Speed:");
+ buf.append(df.format(mbRx/((double)((System.currentTimeMillis()-rxStart)/1000))));
+ buf.append(" MB/sec (since 1st msg)\n\tReceived:");
+ buf.append(df.format(mbRx)).append(" MB]\n");
+ if ( log.isInfoEnabled() ) log.info(buf);
+ }
+
+ public void setInterval(int interval) {
+ this.interval = interval;
+ }
+
+ public int getInterval() {
+ return interval;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group.interceptors;
+
+import java.util.HashMap;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.catalina.tribes.UniqueId;
+import java.util.Map;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class TwoPhaseCommitInterceptor extends ChannelInterceptorBase {
+
+ public static final byte[] START_DATA = new byte[] {113, 1, -58, 2, -34, -60, 75, -78, -101, -12, 32, -29, 32, 111, -40, 4};
+ public static final byte[] END_DATA = new byte[] {54, -13, 90, 110, 47, -31, 75, -24, -81, -29, 36, 52, -58, 77, -110, 56};
+ private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(TwoPhaseCommitInterceptor.class);
+
+ protected HashMap messages = new HashMap();
+ protected long expire = 1000 * 60; //one minute expiration
+ protected boolean deepclone = true;
+
+ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws
+ ChannelException {
+ //todo, optimize, if destination.length==1, then we can do
+ //msg.setOptions(msg.getOptions() & (~getOptionFlag())
+ //and just send one message
+ if (okToProcess(msg.getOptions()) ) {
+ super.sendMessage(destination, msg, null);
+ ChannelMessage confirmation = null;
+ if ( deepclone ) confirmation = (ChannelMessage)msg.deepclone();
+ else confirmation = (ChannelMessage)msg.clone();
+ confirmation.getMessage().reset();
+ UUIDGenerator.randomUUID(false,confirmation.getUniqueId(),0);
+ confirmation.getMessage().append(START_DATA,0,START_DATA.length);
+ confirmation.getMessage().append(msg.getUniqueId(),0,msg.getUniqueId().length);
+ confirmation.getMessage().append(END_DATA,0,END_DATA.length);
+ super.sendMessage(destination,confirmation,payload);
+ } else {
+ //turn off two phase commit
+ //this wont work if the interceptor has 0 as a flag
+ //since there is no flag to turn off
+ //msg.setOptions(msg.getOptions() & (~getOptionFlag()));
+ super.sendMessage(destination, msg, payload);
+ }
+ }
+
+ public void messageReceived(ChannelMessage msg) {
+ if (okToProcess(msg.getOptions())) {
+ if ( msg.getMessage().getLength() == (START_DATA.length+msg.getUniqueId().length+END_DATA.length) &&
+ Arrays.contains(msg.getMessage().getBytesDirect(),0,START_DATA,0,START_DATA.length) &&
+ Arrays.contains(msg.getMessage().getBytesDirect(),START_DATA.length+msg.getUniqueId().length,END_DATA,0,END_DATA.length) ) {
+ UniqueId id = new UniqueId(msg.getMessage().getBytesDirect(),START_DATA.length,msg.getUniqueId().length);
+ MapEntry original = (MapEntry)messages.get(id);
+ if ( original != null ) {
+ super.messageReceived(original.msg);
+ messages.remove(id);
+ } else log.warn("Received a confirmation, but original message is missing. Id:"+Arrays.toString(id.getBytes()));
+ } else {
+ UniqueId id = new UniqueId(msg.getUniqueId());
+ MapEntry entry = new MapEntry((ChannelMessage)msg.deepclone(),id,System.currentTimeMillis());
+ messages.put(id,entry);
+ }
+ } else {
+ super.messageReceived(msg);
+ }
+ }
+
+ public boolean getDeepclone() {
+ return deepclone;
+ }
+
+ public long getExpire() {
+ return expire;
+ }
+
+ public void setDeepclone(boolean deepclone) {
+ this.deepclone = deepclone;
+ }
+
+ public void setExpire(long expire) {
+ this.expire = expire;
+ }
+
+ public void heartbeat() {
+ try {
+ long now = System.currentTimeMillis();
+ Map.Entry[] entries = (Map.Entry[])messages.entrySet().toArray(new Map.Entry[messages.size()]);
+ for (int i=0; i<entries.length; i++ ) {
+ MapEntry entry = (MapEntry)entries[i].getValue();
+ if ( entry.expired(now,expire) ) {
+ log.info("Message ["+entry.id+"] has expired. Removing.");
+ messages.remove(entry.id);
+ }//end if
+ }
+ } catch ( Exception x ) {
+ log.warn("Unable to perform heartbeat on the TwoPhaseCommit interceptor.",x);
+ } finally {
+ super.heartbeat();
+ }
+ }
+
+ public static class MapEntry {
+ public ChannelMessage msg;
+ public UniqueId id;
+ public long timestamp;
+
+ public MapEntry(ChannelMessage msg, UniqueId id, long timestamp) {
+ this.msg = msg;
+ this.id = id;
+ this.timestamp = timestamp;
+ }
+ public boolean expired(long now, long expiration) {
+ return (now - timestamp ) > expiration;
+ }
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.io;
+
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.commons.logging.Log;
+
+/**
+ *
+ * @author Filip Hanik
+ *
+ * @version 1.0
+ */
+public class BufferPool {
+ protected static Log log = LogFactory.getLog(BufferPool.class);
+
+ public static int DEFAULT_POOL_SIZE = 100*1024*1024; //100MB
+
+
+
+ protected static BufferPool instance = null;
+ protected BufferPoolAPI pool = null;
+
+ private BufferPool(BufferPoolAPI pool) {
+ this.pool = pool;
+ }
+
+ public XByteBuffer getBuffer(int minSize, boolean discard) {
+ if ( pool != null ) return pool.getBuffer(minSize, discard);
+ else return new XByteBuffer(minSize,discard);
+ }
+
+ public void returnBuffer(XByteBuffer buffer) {
+ if ( pool != null ) pool.returnBuffer(buffer);
+ }
+
+ public void clear() {
+ if ( pool != null ) pool.clear();
+ }
+
+
+ public static BufferPool getBufferPool() {
+ if ( (instance == null) ) {
+ synchronized (BufferPool.class) {
+ if ( instance == null ) {
+ BufferPoolAPI pool = null;
+ Class clazz = null;
+ try {
+ clazz = Class.forName("org.apache.catalina.tribes.io.BufferPool15Impl");
+ pool = (BufferPoolAPI)clazz.newInstance();
+ } catch ( Throwable x ) {
+ try {
+ clazz = Class.forName("org.apache.catalina.tribes.io.BufferPool14Impl");
+ pool = (BufferPoolAPI)clazz.newInstance();
+ } catch ( Throwable e ) {
+ log.warn("Unable to initilize BufferPool, not pooling XByteBuffer objects:"+x.getMessage());
+ if ( log.isDebugEnabled() ) log.debug("Unable to initilize BufferPool, not pooling XByteBuffer objects:",x);
+ }
+ }
+ pool.setMaxSize(DEFAULT_POOL_SIZE);
+ log.info("Created a buffer pool with max size:"+DEFAULT_POOL_SIZE+" bytes of type:"+(clazz!=null?clazz.getName():"null"));
+ instance = new BufferPool(pool);
+ }//end if
+ }//sync
+ }//end if
+ return instance;
+ }
+
+
+ public static interface BufferPoolAPI {
+ public void setMaxSize(int bytes);
+
+ public XByteBuffer getBuffer(int minSize, boolean discard);
+
+ public void returnBuffer(XByteBuffer buffer);
+
+ public void clear();
+ }
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.io;
+
+import java.util.Queue;
+import java.util.LinkedList;
+
+
+/**
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+class BufferPool14Impl implements BufferPool.BufferPoolAPI {
+ protected int maxSize;
+ protected int size = 0;
+ protected LinkedList queue = new LinkedList();
+
+ public void setMaxSize(int bytes) {
+ this.maxSize = bytes;
+ }
+
+ public synchronized int addAndGet(int val) {
+ size = size + (val);
+ return size;
+ }
+
+
+
+ public synchronized XByteBuffer getBuffer(int minSize, boolean discard) {
+ XByteBuffer buffer = (XByteBuffer)(queue.size()>0?queue.remove(0):null);
+ if ( buffer != null ) addAndGet(-buffer.getCapacity());
+ if ( buffer == null ) buffer = new XByteBuffer(minSize,discard);
+ else if ( buffer.getCapacity() <= minSize ) buffer.expand(minSize);
+ buffer.setDiscard(discard);
+ buffer.reset();
+ return buffer;
+ }
+
+ public synchronized void returnBuffer(XByteBuffer buffer) {
+ if ( (size + buffer.getCapacity()) <= maxSize ) {
+ addAndGet(buffer.getCapacity());
+ queue.add(buffer);
+ }
+ }
+
+ public synchronized void clear() {
+ queue.clear();
+ size = 0;
+ }
+
+ public int getMaxSize() {
+ return maxSize;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.io;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+class BufferPool15Impl implements BufferPool.BufferPoolAPI {
+ protected int maxSize;
+ protected AtomicInteger size = new AtomicInteger(0);
+ protected ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
+
+ public void setMaxSize(int bytes) {
+ this.maxSize = bytes;
+ }
+
+
+ public XByteBuffer getBuffer(int minSize, boolean discard) {
+ XByteBuffer buffer = (XByteBuffer)queue.poll();
+ if ( buffer != null ) size.addAndGet(-buffer.getCapacity());
+ if ( buffer == null ) buffer = new XByteBuffer(minSize,discard);
+ else if ( buffer.getCapacity() <= minSize ) buffer.expand(minSize);
+ buffer.setDiscard(discard);
+ buffer.reset();
+ return buffer;
+ }
+
+ public void returnBuffer(XByteBuffer buffer) {
+ if ( (size.get() + buffer.getCapacity()) <= maxSize ) {
+ size.addAndGet(buffer.getCapacity());
+ queue.offer(buffer);
+ }
+ }
+
+ public void clear() {
+ queue.clear();
+ size.set(0);
+ }
+
+ public int getMaxSize() {
+ return maxSize;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.io;
+
+import java.util.Arrays;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+import org.apache.catalina.tribes.Channel;
+import java.sql.Timestamp;
+
+/**
+ * The <code>ChannelData</code> object is used to transfer a message through the
+ * channel interceptor stack and eventually out on a transport to be sent
+ * to another node. While the message is being processed by the different
+ * interceptors, the message data can be manipulated as each interceptor seems appropriate.
+ * @author Peter Rossbach
+ * @author Filip Hanik
+ * @version $Revision: 377484 $ $Date: 2006-02-13 15:00:05 -0600 (Mon, 13 Feb 2006) $
+ *
+ */
+public class ChannelData implements ChannelMessage {
+ public static ChannelData[] EMPTY_DATA_ARRAY = new ChannelData[0];
+
+ public static boolean USE_SECURE_RANDOM_FOR_UUID = false;
+
+ /**
+ * The options this message was sent with
+ */
+ private int options = 0 ;
+ /**
+ * The message data, stored in a dynamic buffer
+ */
+ private XByteBuffer message ;
+ /**
+ * The timestamp that goes with this message
+ */
+ private long timestamp ;
+ /**
+ * A unique message id
+ */
+ private byte[] uniqueId ;
+ /**
+ * The source or reply-to address for this message
+ */
+ private Member address;
+
+ /**
+ * Creates an empty channel data with a new unique Id
+ * @see #ChannelData(boolean)
+ */
+ public ChannelData() {
+ this(true);
+ }
+
+ /**
+ * Create an empty channel data object
+ * @param generateUUID boolean - if true, a unique Id will be generated
+ */
+ public ChannelData(boolean generateUUID) {
+ if ( generateUUID ) generateUUID();
+ }
+
+
+
+ /**
+ * Creates a new channel data object with data
+ * @param uniqueId - unique message id
+ * @param message - message data
+ * @param timestamp - message timestamp
+ */
+ public ChannelData(byte[] uniqueId, XByteBuffer message, long timestamp) {
+ this.uniqueId = uniqueId;
+ this.message = message;
+ this.timestamp = timestamp;
+ }
+
+ /**
+ * @return Returns the message byte buffer
+ */
+ public XByteBuffer getMessage() {
+ return message;
+ }
+ /**
+ * @param message The message to send.
+ */
+ public void setMessage(XByteBuffer message) {
+ this.message = message;
+ }
+ /**
+ * @return Returns the timestamp.
+ */
+ public long getTimestamp() {
+ return timestamp;
+ }
+ /**
+ * @param timestamp The timestamp to send
+ */
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+ /**
+ * @return Returns the uniqueId.
+ */
+ public byte[] getUniqueId() {
+ return uniqueId;
+ }
+ /**
+ * @param uniqueId The uniqueId to send.
+ */
+ public void setUniqueId(byte[] uniqueId) {
+ this.uniqueId = uniqueId;
+ }
+ /**
+ * @return returns the message options
+ * see org.apache.catalina.tribes.Channel#sendMessage(org.apache.catalina.tribes.Member[], java.io.Serializable, int)
+ *
+ */
+ public int getOptions() {
+ return options;
+ }
+ /**
+ * @param sets the message options
+ */
+ public void setOptions(int options) {
+ this.options = options;
+ }
+
+ /**
+ * Returns the source or reply-to address
+ * @return Member
+ */
+ public Member getAddress() {
+ return address;
+ }
+
+ /**
+ * Sets the source or reply-to address
+ * @param address Member
+ */
+ public void setAddress(Member address) {
+ this.address = address;
+ }
+
+ /**
+ * Generates a UUID and invokes setUniqueId
+ */
+ public void generateUUID() {
+ byte[] data = new byte[16];
+ UUIDGenerator.randomUUID(USE_SECURE_RANDOM_FOR_UUID,data,0);
+ setUniqueId(data);
+ }
+
+ public int getDataPackageLength() {
+ int length =
+ 4 + //options
+ 8 + //timestamp off=4
+ 4 + //unique id length off=12
+ uniqueId.length+ //id data off=12+uniqueId.length
+ 4 + //addr length off=12+uniqueId.length+4
+ ((MemberImpl)address).getDataLength()+ //member data off=12+uniqueId.length+4+add.length
+ 4 + //message length off=12+uniqueId.length+4+add.length+4
+ message.getLength();
+ return length;
+
+ }
+
+ /**
+ * Serializes the ChannelData object into a byte[] array
+ * @return byte[]
+ */
+ public byte[] getDataPackage() {
+ int length = getDataPackageLength();
+ byte[] data = new byte[length];
+ int offset = 0;
+ return getDataPackage(data,offset);
+ }
+
+ public byte[] getDataPackage(byte[] data, int offset) {
+ byte[] addr = ((MemberImpl)address).getData(false);
+ XByteBuffer.toBytes(options,data,offset);
+ offset += 4; //options
+ XByteBuffer.toBytes(timestamp,data,offset);
+ offset += 8; //timestamp
+ XByteBuffer.toBytes(uniqueId.length,data,offset);
+ offset += 4; //uniqueId.length
+ System.arraycopy(uniqueId,0,data,offset,uniqueId.length);
+ offset += uniqueId.length; //uniqueId data
+ XByteBuffer.toBytes(addr.length,data,offset);
+ offset += 4; //addr.length
+ System.arraycopy(addr,0,data,offset,addr.length);
+ offset += addr.length; //addr data
+ XByteBuffer.toBytes(message.getLength(),data,offset);
+ offset += 4; //message.length
+ System.arraycopy(message.getBytesDirect(),0,data,offset,message.getLength());
+ offset += message.getLength(); //message data
+ return data;
+ }
+
+ /**
+ * Deserializes a ChannelData object from a byte array
+ * @param b byte[]
+ * @return ChannelData
+ */
+ public static ChannelData getDataFromPackage(XByteBuffer xbuf) {
+ ChannelData data = new ChannelData(false);
+ int offset = 0;
+ data.setOptions(XByteBuffer.toInt(xbuf.getBytesDirect(),offset));
+ offset += 4; //options
+ data.setTimestamp(XByteBuffer.toLong(xbuf.getBytesDirect(),offset));
+ offset += 8; //timestamp
+ data.uniqueId = new byte[XByteBuffer.toInt(xbuf.getBytesDirect(),offset)];
+ offset += 4; //uniqueId length
+ System.arraycopy(xbuf.getBytesDirect(),offset,data.uniqueId,0,data.uniqueId.length);
+ offset += data.uniqueId.length; //uniqueId data
+ byte[] addr = new byte[XByteBuffer.toInt(xbuf.getBytesDirect(),offset)];
+ offset += 4; //addr length
+ System.arraycopy(xbuf.getBytesDirect(),offset,addr,0,addr.length);
+ data.setAddress(MemberImpl.getMember(addr));
+ offset += addr.length; //addr data
+ int xsize = XByteBuffer.toInt(xbuf.getBytesDirect(),offset);
+ offset += 4; //xsize length
+ System.arraycopy(xbuf.getBytesDirect(),offset,xbuf.getBytesDirect(),0,xsize);
+ xbuf.setLength(xsize);
+ data.message = xbuf;
+ return data;
+
+ }
+
+ public static ChannelData getDataFromPackage(byte[] b) {
+ ChannelData data = new ChannelData(false);
+ int offset = 0;
+ data.setOptions(XByteBuffer.toInt(b,offset));
+ offset += 4; //options
+ data.setTimestamp(XByteBuffer.toLong(b,offset));
+ offset += 8; //timestamp
+ data.uniqueId = new byte[XByteBuffer.toInt(b,offset)];
+ offset += 4; //uniqueId length
+ System.arraycopy(b,offset,data.uniqueId,0,data.uniqueId.length);
+ offset += data.uniqueId.length; //uniqueId data
+ byte[] addr = new byte[XByteBuffer.toInt(b,offset)];
+ offset += 4; //addr length
+ System.arraycopy(b,offset,addr,0,addr.length);
+ data.setAddress(MemberImpl.getMember(addr));
+ offset += addr.length; //addr data
+ int xsize = XByteBuffer.toInt(b,offset);
+ //data.message = new XByteBuffer(new byte[xsize],false);
+ data.message = BufferPool.getBufferPool().getBuffer(xsize,false);
+ offset += 4; //message length
+ System.arraycopy(b,offset,data.message.getBytesDirect(),0,xsize);
+ data.message.append(b,offset,xsize);
+ offset += xsize; //message data
+ return data;
+ }
+
+ public int hashCode() {
+ return XByteBuffer.toInt(getUniqueId(),0);
+ }
+
+ /**
+ * Compares to ChannelData objects, only compares on getUniqueId().equals(o.getUniqueId())
+ * @param o Object
+ * @return boolean
+ */
+ public boolean equals(Object o) {
+ if ( o instanceof ChannelData ) {
+ return Arrays.equals(getUniqueId(),((ChannelData)o).getUniqueId());
+ } else return false;
+ }
+
+ /**
+ * Create a shallow clone, only the data gets recreated
+ * @return ClusterData
+ */
+ public Object clone() {
+// byte[] d = this.getDataPackage();
+// return ClusterData.getDataFromPackage(d);
+ ChannelData clone = new ChannelData(false);
+ clone.options = this.options;
+ clone.message = new XByteBuffer(this.message.getBytesDirect(),false);
+ clone.timestamp = this.timestamp;
+ clone.uniqueId = this.uniqueId;
+ clone.address = this.address;
+ return clone;
+ }
+
+ /**
+ * Complete clone
+ * @return ClusterData
+ */
+ public Object deepclone() {
+ byte[] d = this.getDataPackage();
+ return ChannelData.getDataFromPackage(d);
+ }
+
+ /**
+ * Utility method, returns true if the options flag indicates that an ack
+ * is to be sent after the message has been received and processed
+ * @param options int - the options for the message
+ * @return boolean
+ * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_USE_ACK
+ * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_SYNCHRONIZED_ACK
+ */
+ public static boolean sendAckSync(int options) {
+ return ( (Channel.SEND_OPTIONS_USE_ACK & options) == Channel.SEND_OPTIONS_USE_ACK) &&
+ ( (Channel.SEND_OPTIONS_SYNCHRONIZED_ACK & options) == Channel.SEND_OPTIONS_SYNCHRONIZED_ACK);
+ }
+
+
+ /**
+ * Utility method, returns true if the options flag indicates that an ack
+ * is to be sent after the message has been received but not yet processed
+ * @param options int - the options for the message
+ * @return boolean
+ * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_USE_ACK
+ * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_SYNCHRONIZED_ACK
+ */
+ public static boolean sendAckAsync(int options) {
+ return ( (Channel.SEND_OPTIONS_USE_ACK & options) == Channel.SEND_OPTIONS_USE_ACK) &&
+ ( (Channel.SEND_OPTIONS_SYNCHRONIZED_ACK & options) != Channel.SEND_OPTIONS_SYNCHRONIZED_ACK);
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("ClusterData[src=");
+ buf.append(getAddress()).append("; id=");
+ buf.append(bToS(getUniqueId())).append("; sent=");
+ buf.append(new Timestamp(this.getTimestamp()).toString()).append("]");
+ return buf.toString();
+ }
+
+ public static String bToS(byte[] data) {
+ StringBuffer buf = new StringBuffer(4*16);
+ buf.append("{");
+ for (int i=0; data!=null && i<data.length; i++ ) buf.append(String.valueOf(data[i])).append(" ");
+ buf.append("}");
+ return buf.toString();
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Byte array output stream that exposes the byte array directly
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class DirectByteArrayOutputStream extends OutputStream {
+
+ private XByteBuffer buffer;
+
+ public DirectByteArrayOutputStream(int size) {
+ buffer = new XByteBuffer(size,false);
+ }
+
+ /**
+ * Writes the specified byte to this output stream.
+ *
+ * @param b the <code>byte</code>.
+ * @throws IOException if an I/O error occurs. In particular, an
+ * <code>IOException</code> may be thrown if the output stream has
+ * been closed.
+ * @todo Implement this java.io.OutputStream method
+ */
+ public void write(int b) throws IOException {
+ buffer.append((byte)b);
+ }
+
+ public int size() {
+ return buffer.getLength();
+ }
+
+ public byte[] getArrayDirect() {
+ return buffer.getBytesDirect();
+ }
+
+ public byte[] getArray() {
+ return buffer.getBytes();
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.io;
+
+import org.apache.catalina.tribes.ChannelMessage;
+
+
+
+/**
+ * Internal interface, similar to the MessageListener but used
+ * at the IO base
+ * The listen callback interface is used by the replication system
+ * when data has been received. The interface does not care about
+ * objects and marshalling and just passes the bytes straight through.
+ * @author Filip Hanik
+ * @version $Revision: 303987 $, $Date: 2005-07-08 15:50:30 -0500 (Fri, 08 Jul 2005) $
+ */
+public interface ListenCallback
+{
+ /**
+ * This method is invoked on the callback object to notify it that new data has
+ * been received from one of the cluster nodes.
+ * @param data - the message bytes received from the cluster/replication system
+ */
+ public void messageDataReceived(ChannelMessage data);
+
+}
\ No newline at end of file
--- /dev/null
+/*\r
+ * Copyright 1999,2004-2005 The Apache Software Foundation.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package org.apache.catalina.tribes.io;\r
+\r
+import java.io.IOException;\r
+import java.net.Socket;\r
+import java.nio.ByteBuffer;\r
+import java.nio.channels.SocketChannel;\r
+\r
+import org.apache.catalina.tribes.ChannelMessage;\r
+\r
+\r
+\r
+/**\r
+ * The object reader object is an object used in conjunction with\r
+ * java.nio TCP messages. This object stores the message bytes in a\r
+ * <code>XByteBuffer</code> until a full package has been received.\r
+ * This object uses an XByteBuffer which is an extendable object buffer that also allows\r
+ * for message encoding and decoding.\r
+ *\r
+ * @author Filip Hanik\r
+ * @version $Revision: 377484 $, $Date: 2006-02-13 15:00:05 -0600 (Mon, 13 Feb 2006) $\r
+ */\r
+public class ObjectReader {\r
+\r
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(ObjectReader.class);\r
+\r
+ private XByteBuffer buffer;\r
+ \r
+ protected long lastAccess = System.currentTimeMillis();\r
+ \r
+ protected boolean accessed = false;\r
+ private boolean cancelled;\r
+\r
+ /**\r
+ * Creates an <code>ObjectReader</code> for a TCP NIO socket channel\r
+ * @param channel - the channel to be read.\r
+ */\r
+ public ObjectReader(SocketChannel channel) {\r
+ this(channel.socket());\r
+ }\r
+ \r
+ /**\r
+ * Creates an <code>ObjectReader</code> for a TCP socket\r
+ * @param socket Socket\r
+ */\r
+ public ObjectReader(Socket socket) {\r
+ try{\r
+ this.buffer = new XByteBuffer(socket.getReceiveBufferSize(), true);\r
+ }catch ( IOException x ) {\r
+ //unable to get buffer size\r
+ log.warn("Unable to retrieve the socket receiver buffer size, setting to default 43800 bytes.");\r
+ this.buffer = new XByteBuffer(43800,true);\r
+ }\r
+ }\r
+ \r
+ public synchronized void access() {\r
+ this.accessed = true;\r
+ this.lastAccess = System.currentTimeMillis();\r
+ }\r
+ \r
+ public synchronized void finish() {\r
+ this.accessed = false;\r
+ this.lastAccess = System.currentTimeMillis();\r
+ }\r
+ \r
+ public boolean isAccessed() {\r
+ return this.accessed;\r
+ }\r
+\r
+ /**\r
+ * Append new bytes to buffer. \r
+ * @see XByteBuffer#countPackages()\r
+ * @param data new transfer buffer\r
+ * @param off offset\r
+ * @param len length in buffer\r
+ * @return number of messages that sended to callback\r
+ * @throws java.io.IOException\r
+ */\r
+ public int append(ByteBuffer data, int len, boolean count) throws java.io.IOException {\r
+ buffer.append(data,len);\r
+ int pkgCnt = -1;\r
+ if ( count ) pkgCnt = buffer.countPackages();\r
+ return pkgCnt;\r
+ }\r
+\r
+ public int append(byte[] data,int off,int len, boolean count) throws java.io.IOException {\r
+ buffer.append(data,off,len);\r
+ int pkgCnt = -1;\r
+ if ( count ) pkgCnt = buffer.countPackages();\r
+ return pkgCnt;\r
+ }\r
+\r
+ /**\r
+ * Send buffer to cluster listener (callback).\r
+ * Is message complete receiver send message to callback?\r
+ *\r
+ * @see org.apache.catalina.tribes.transport.ClusterReceiverBase#messageDataReceived(ChannelMessage)\r
+ * @see XByteBuffer#doesPackageExist()\r
+ * @see XByteBuffer#extractPackage(boolean)\r
+ *\r
+ * @return number of received packages/messages\r
+ * @throws java.io.IOException\r
+ */\r
+ public ChannelMessage[] execute() throws java.io.IOException {\r
+ int pkgCnt = buffer.countPackages();\r
+ ChannelMessage[] result = new ChannelMessage[pkgCnt];\r
+ for (int i=0; i<pkgCnt; i++) {\r
+ ChannelMessage data = buffer.extractPackage(true);\r
+ result[i] = data;\r
+ }\r
+ return result;\r
+ }\r
+ \r
+ public int bufferSize() {\r
+ return buffer.getLength();\r
+ }\r
+ \r
+\r
+ public boolean hasPackage() {\r
+ return buffer.countPackages(true)>0;\r
+ }\r
+ /**\r
+ * Returns the number of packages that the reader has read\r
+ * @return int\r
+ */\r
+ public int count() {\r
+ return buffer.countPackages();\r
+ }\r
+ \r
+ public void close() {\r
+ this.buffer = null;\r
+ }\r
+\r
+ public long getLastAccess() {\r
+ return lastAccess;\r
+ }\r
+\r
+ public boolean isCancelled() {\r
+ return cancelled;\r
+ }\r
+\r
+ public void setLastAccess(long lastAccess) {\r
+ this.lastAccess = lastAccess;\r
+ }\r
+\r
+ public void setCancelled(boolean cancelled) {\r
+ this.cancelled = cancelled;\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.tribes.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+
+/**
+ * Custom subclass of <code>ObjectInputStream</code> that loads from the
+ * class loader for this web application. This allows classes defined only
+ * with the web application to be found correctly.
+ *
+ * @author Craig R. McClanahan
+ * @author Bip Thelin
+ * @author Filip Hanik
+ * @version $Revision: 377484 $, $Date: 2006-02-13 15:00:05 -0600 (Mon, 13 Feb 2006) $
+ */
+
+public final class ReplicationStream extends ObjectInputStream {
+
+
+ /**
+ * The class loader we will use to resolve classes.
+ */
+ private ClassLoader[] classLoaders = null;
+
+
+ /**
+ * Construct a new instance of CustomObjectInputStream
+ *
+ * @param stream The input stream we will read from
+ * @param classLoader The class loader used to instantiate objects
+ *
+ * @exception IOException if an input/output error occurs
+ */
+ public ReplicationStream(InputStream stream,
+ ClassLoader[] classLoaders)
+ throws IOException {
+
+ super(stream);
+ this.classLoaders = classLoaders;
+ }
+
+ /**
+ * Load the local class equivalent of the specified stream class
+ * description, by using the class loader assigned to this Context.
+ *
+ * @param classDesc Class description from the input stream
+ *
+ * @exception ClassNotFoundException if this class cannot be found
+ * @exception IOException if an input/output error occurs
+ */
+ public Class resolveClass(ObjectStreamClass classDesc)
+ throws ClassNotFoundException, IOException {
+ String name = classDesc.getName();
+ boolean tryRepFirst = name.startsWith("org.apache.catalina.tribes");
+ try {
+ try
+ {
+ if ( tryRepFirst ) return findReplicationClass(name);
+ else return findExternalClass(name);
+ }
+ catch ( Exception x )
+ {
+ if ( tryRepFirst ) return findExternalClass(name);
+ else return findReplicationClass(name);
+ }
+ } catch (ClassNotFoundException e) {
+ return super.resolveClass(classDesc);
+ }
+ }
+
+ public Class findReplicationClass(String name)
+ throws ClassNotFoundException, IOException {
+ Class clazz = Class.forName(name, false, getClass().getClassLoader());
+ return clazz;
+ }
+
+ public Class findExternalClass(String name) throws ClassNotFoundException {
+ ClassNotFoundException cnfe = null;
+ for (int i=0; i<classLoaders.length; i++ ) {
+ try {
+ Class clazz = Class.forName(name, false, classLoaders[i]);
+ return clazz;
+ } catch ( ClassNotFoundException x ) {
+ cnfe = x;
+ }
+ }
+ if ( cnfe != null ) throw cnfe;
+ else throw new ClassNotFoundException(name);
+ }
+
+ public void close() throws IOException {
+ this.classLoaders = null;
+ super.close();
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.io;
+
+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.Serializable;
+import java.nio.ByteBuffer;
+
+/**
+ * The XByteBuffer provides a dual functionality.
+ * One, it stores message bytes and automatically extends the byte buffer if needed.<BR>
+ * Two, it can encode and decode packages so that they can be defined and identified
+ * as they come in on a socket.
+ * <br>
+ * <b>THIS CLASS IS NOT THREAD SAFE</B><BR>
+ * <br/>
+ * Transfer package:
+ * <ul>
+ * <li><b>START_DATA/b> - 7 bytes - <i>FLT2002</i></li>
+ * <li><b>SIZE</b> - 4 bytes - size of the data package</li>
+ * <li><b>DATA</b> - should be as many bytes as the prev SIZE</li>
+ * <li><b>END_DATA</b> - 7 bytes - <i>TLF2003</i></lI>
+ * </ul>
+ * @author Filip Hanik
+ * @version $Revision: 377484 $, $Date: 2006-02-13 15:00:05 -0600 (Mon, 13 Feb 2006) $
+ */
+public class XByteBuffer
+{
+
+ public static org.apache.commons.logging.Log log =
+ org.apache.commons.logging.LogFactory.getLog( XByteBuffer.class );
+
+ /**
+ * This is a package header, 7 bytes (FLT2002)
+ */
+ public static final byte[] START_DATA = {70,76,84,50,48,48,50};
+
+ /**
+ * This is the package footer, 7 bytes (TLF2003)
+ */
+ public static final byte[] END_DATA = {84,76,70,50,48,48,51};
+
+ /**
+ * Default size on the initial byte buffer
+ */
+ private static final int DEF_SIZE = 2048;
+
+ /**
+ * Default size to extend the buffer with
+ */
+ private static final int DEF_EXT = 1024;
+
+ /**
+ * Variable to hold the data
+ */
+ protected byte[] buf = null;
+
+ /**
+ * Current length of data in the buffer
+ */
+ protected int bufSize = 0;
+
+ /**
+ * Flag for discarding invalid packages
+ * If this flag is set to true, and append(byte[],...) is called,
+ * the data added will be inspected, and if it doesn't start with
+ * <code>START_DATA</code> it will be thrown away.
+ *
+ */
+ protected boolean discard = true;
+
+ /**
+ * Constructs a new XByteBuffer
+ * @param size - the initial size of the byte buffer
+ * @todo use a pool of byte[] for performance
+ */
+ public XByteBuffer(int size, boolean discard) {
+ buf = new byte[size];
+ this.discard = discard;
+ }
+
+ public XByteBuffer(byte[] data,boolean discard) {
+ this(data,data.length+128,discard);
+ }
+
+ public XByteBuffer(byte[] data, int size,boolean discard) {
+ int length = Math.max(data.length,size);
+ buf = new byte[length];
+ System.arraycopy(data,0,buf,0,data.length);
+ bufSize = data.length;
+ this.discard = discard;
+ }
+
+ public int getLength() {
+ return bufSize;
+ }
+
+ public void setLength(int size) {
+ if ( size > buf.length ) throw new ArrayIndexOutOfBoundsException("Size is larger than existing buffer.");
+ bufSize = size;
+ }
+
+ public void trim(int length) {
+ if ( (bufSize - length) < 0 )
+ throw new ArrayIndexOutOfBoundsException("Can't trim more bytes than are available. length:"+bufSize+" trim:"+length);
+ bufSize -= length;
+ }
+
+ public void reset() {
+ bufSize = 0;
+ }
+
+ public byte[] getBytesDirect() {
+ return this.buf;
+ }
+
+ /**
+ * Returns the bytes in the buffer, in its exact length
+ */
+ public byte[] getBytes() {
+ byte[] b = new byte[bufSize];
+ System.arraycopy(buf,0,b,0,bufSize);
+ return b;
+ }
+
+ /**
+ * Resets the buffer
+ */
+ public void clear() {
+ bufSize = 0;
+ }
+
+ /**
+ * Appends the data to the buffer. If the data is incorrectly formatted, ie, the data should always start with the
+ * header, false will be returned and the data will be discarded.
+ * @param b - bytes to be appended
+ * @param off - the offset to extract data from
+ * @param len - the number of bytes to append.
+ * @return true if the data was appended correctly. Returns false if the package is incorrect, ie missing header or something, or the length of data is 0
+ */
+ public boolean append(ByteBuffer b, int len) {
+ int newcount = bufSize + len;
+ if (newcount > buf.length) {
+ expand(newcount);
+ }
+ b.get(buf,bufSize,len);
+
+ bufSize = newcount;
+
+ if ( discard ) {
+ if (bufSize > START_DATA.length && (firstIndexOf(buf, 0, START_DATA) == -1)) {
+ bufSize = 0;
+ log.error("Discarded the package, invalid header");
+ return false;
+ }
+ }
+ return true;
+
+ }
+
+ public boolean append(byte i) {
+ int newcount = bufSize + 1;
+ if (newcount > buf.length) {
+ expand(newcount);
+ }
+ buf[bufSize] = i;
+ bufSize = newcount;
+ return true;
+ }
+
+
+ public boolean append(boolean i) {
+ int newcount = bufSize + 1;
+ if (newcount > buf.length) {
+ expand(newcount);
+ }
+ XByteBuffer.toBytes(i,buf,bufSize);
+ bufSize = newcount;
+ return true;
+ }
+
+ public boolean append(long i) {
+ int newcount = bufSize + 8;
+ if (newcount > buf.length) {
+ expand(newcount);
+ }
+ XByteBuffer.toBytes(i,buf,bufSize);
+ bufSize = newcount;
+ return true;
+ }
+
+ public boolean append(int i) {
+ int newcount = bufSize + 4;
+ if (newcount > buf.length) {
+ expand(newcount);
+ }
+ XByteBuffer.toBytes(i,buf,bufSize);
+ bufSize = newcount;
+ return true;
+ }
+
+ public boolean append(byte[] b, int off, int len) {
+ if ((off < 0) || (off > b.length) || (len < 0) ||
+ ((off + len) > b.length) || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return false;
+ }
+
+ int newcount = bufSize + len;
+ if (newcount > buf.length) {
+ expand(newcount);
+ }
+ System.arraycopy(b, off, buf, bufSize, len);
+ bufSize = newcount;
+
+ if ( discard ) {
+ if (bufSize > START_DATA.length && (firstIndexOf(buf, 0, START_DATA) == -1)) {
+ bufSize = 0;
+ log.error("Discarded the package, invalid header");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void expand(int newcount) {
+ //don't change the allocation strategy
+ byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
+ System.arraycopy(buf, 0, newbuf, 0, bufSize);
+ buf = newbuf;
+ }
+
+ public int getCapacity() {
+ return buf.length;
+ }
+
+
+ /**
+ * Internal mechanism to make a check if a complete package exists
+ * within the buffer
+ * @return - true if a complete package (header,compress,size,data,footer) exists within the buffer
+ */
+ public int countPackages() {
+ return countPackages(false);
+ }
+
+ public int countPackages(boolean first)
+ {
+ int cnt = 0;
+ int pos = START_DATA.length;
+ int start = 0;
+
+ while ( start < bufSize ) {
+ //first check start header
+ int index = XByteBuffer.firstIndexOf(buf,start,START_DATA);
+ //if the header (START_DATA) isn't the first thing or
+ //the buffer isn't even 14 bytes
+ if ( index != start || ((bufSize-start)<14) ) break;
+ //next 4 bytes are compress flag not needed for count packages
+ //then get the size 4 bytes
+ int size = toInt(buf, pos);
+ //now the total buffer has to be long enough to hold
+ //START_DATA.length+4+size+END_DATA.length
+ pos = start + START_DATA.length + 4 + size;
+ if ( (pos + END_DATA.length) > bufSize) break;
+ //and finally check the footer of the package END_DATA
+ int newpos = firstIndexOf(buf, pos, END_DATA);
+ //mismatch, there is no package
+ if (newpos != pos) break;
+ //increase the packet count
+ cnt++;
+ //reset the values
+ start = pos + END_DATA.length;
+ pos = start + START_DATA.length;
+ //we only want to verify that we have at least one package
+ if ( first ) break;
+ }
+ return cnt;
+ }
+
+ /**
+ * Method to check if a package exists in this byte buffer.
+ * @return - true if a complete package (header,options,size,data,footer) exists within the buffer
+ */
+ public boolean doesPackageExist() {
+ return (countPackages(true)>0);
+ }
+
+ /**
+ * Extracts the message bytes from a package.
+ * If no package exists, a IllegalStateException will be thrown.
+ * @param clearFromBuffer - if true, the package will be removed from the byte buffer
+ * @return - returns the actual message bytes (header, compress,size and footer not included).
+ */
+ public XByteBuffer extractDataPackage(boolean clearFromBuffer) {
+ int psize = countPackages(true);
+ if (psize == 0) {
+ throw new java.lang.IllegalStateException("No package exists in XByteBuffer");
+ }
+ int size = toInt(buf, START_DATA.length);
+ XByteBuffer xbuf = BufferPool.getBufferPool().getBuffer(size,false);
+ xbuf.setLength(size);
+ System.arraycopy(buf, START_DATA.length + 4, xbuf.getBytesDirect(), 0, size);
+ if (clearFromBuffer) {
+ int totalsize = START_DATA.length + 4 + size + END_DATA.length;
+ bufSize = bufSize - totalsize;
+ System.arraycopy(buf, totalsize, buf, 0, bufSize);
+ }
+ return xbuf;
+
+ }
+
+ public ChannelData extractPackage(boolean clearFromBuffer) throws java.io.IOException {
+ XByteBuffer xbuf = extractDataPackage(clearFromBuffer);
+ ChannelData cdata = ChannelData.getDataFromPackage(xbuf);
+ return cdata;
+ }
+
+ /**
+ * Creates a complete data package
+ * @param indata - the message data to be contained within the package
+ * @param compressed - compression flag for the indata buffer
+ * @return - a full package (header,size,data,footer)
+ *
+ */
+ public static byte[] createDataPackage(ChannelData cdata) {
+// return createDataPackage(cdata.getDataPackage());
+ //avoid one extra byte array creation
+ int dlength = cdata.getDataPackageLength();
+ int length = getDataPackageLength(dlength);
+ byte[] data = new byte[length];
+ int offset = 0;
+ System.arraycopy(START_DATA, 0, data, offset, START_DATA.length);
+ offset += START_DATA.length;
+ toBytes(dlength,data, START_DATA.length);
+ offset += 4;
+ cdata.getDataPackage(data,offset);
+ offset += dlength;
+ System.arraycopy(END_DATA, 0, data, offset, END_DATA.length);
+ offset += END_DATA.length;
+ return data;
+ }
+
+ public static byte[] createDataPackage(byte[] data, int doff, int dlength, byte[] buffer, int bufoff) {
+ if ( (buffer.length-bufoff) > getDataPackageLength(dlength) ) {
+ throw new ArrayIndexOutOfBoundsException("Unable to create data package, buffer is too small.");
+ }
+ System.arraycopy(START_DATA, 0, buffer, bufoff, START_DATA.length);
+ toBytes(data.length,buffer, bufoff+START_DATA.length);
+ System.arraycopy(data, doff, buffer, bufoff+START_DATA.length + 4, dlength);
+ System.arraycopy(END_DATA, 0, buffer, bufoff+START_DATA.length + 4 + data.length, END_DATA.length);
+ return buffer;
+ }
+
+
+ public static int getDataPackageLength(int datalength) {
+ int length =
+ START_DATA.length + //header length
+ 4 + //data length indicator
+ datalength + //actual data length
+ END_DATA.length; //footer length
+ return length;
+
+ }
+
+ public static byte[] createDataPackage(byte[] data) {
+ int length = getDataPackageLength(data.length);
+ byte[] result = new byte[length];
+ return createDataPackage(data,0,data.length,result,0);
+ }
+
+
+
+// public static void fillDataPackage(byte[] data, int doff, int dlength, XByteBuffer buf) {
+// int pkglen = getDataPackageLength(dlength);
+// if ( buf.getCapacity() < pkglen ) buf.expand(pkglen);
+// createDataPackage(data,doff,dlength,buf.getBytesDirect(),buf.getLength());
+// }
+
+ /**
+ * Convert four bytes to an int
+ * @param b - the byte array containing the four bytes
+ * @param off - the offset
+ * @return the integer value constructed from the four bytes
+ * @exception java.lang.ArrayIndexOutOfBoundsException
+ */
+ public static int toInt(byte[] b,int off){
+ return ( ( (int) b[off+3]) & 0xFF) +
+ ( ( ( (int) b[off+2]) & 0xFF) << 8) +
+ ( ( ( (int) b[off+1]) & 0xFF) << 16) +
+ ( ( ( (int) b[off+0]) & 0xFF) << 24);
+ }
+
+ /**
+ * Convert eight bytes to a long
+ * @param b - the byte array containing the four bytes
+ * @param off - the offset
+ * @return the long value constructed from the eight bytes
+ * @exception java.lang.ArrayIndexOutOfBoundsException
+ */
+ public static long toLong(byte[] b,int off){
+ return ( ( (long) b[off+7]) & 0xFF) +
+ ( ( ( (long) b[off+6]) & 0xFF) << 8) +
+ ( ( ( (long) b[off+5]) & 0xFF) << 16) +
+ ( ( ( (long) b[off+4]) & 0xFF) << 24) +
+ ( ( ( (long) b[off+3]) & 0xFF) << 32) +
+ ( ( ( (long) b[off+2]) & 0xFF) << 40) +
+ ( ( ( (long) b[off+1]) & 0xFF) << 48) +
+ ( ( ( (long) b[off+0]) & 0xFF) << 56);
+ }
+
+
+ /**
+ * Converts an integer to four bytes
+ * @param n - the integer
+ * @return - four bytes in an array
+ * @deprecated use toBytes(boolean,byte[],int)
+ */
+ public static byte[] toBytes(boolean bool) {
+ byte[] b = new byte[1] ;
+ return toBytes(bool,b,0);
+
+ }
+
+ public static byte[] toBytes(boolean bool, byte[] data, int offset) {
+ data[offset] = (byte)(bool?1:0);
+ return data;
+ }
+
+ /**
+ *
+ * @param <any> long
+ * @return use
+ */
+ public static boolean toBoolean(byte[] b, int offset) {
+ return b[offset] != 0;
+ }
+
+
+ /**
+ * Converts an integer to four bytes
+ * @param n - the integer
+ * @return - four bytes in an array
+ * @deprecated use toBytes(int,byte[],int)
+ */
+ public static byte[] toBytes(int n) {
+ return toBytes(n,new byte[4],0);
+ }
+
+ public static byte[] toBytes(int n,byte[] b, int offset) {
+ b[offset+3] = (byte) (n);
+ n >>>= 8;
+ b[offset+2] = (byte) (n);
+ n >>>= 8;
+ b[offset+1] = (byte) (n);
+ n >>>= 8;
+ b[offset+0] = (byte) (n);
+ return b;
+ }
+
+ /**
+ * Converts an long to eight bytes
+ * @param n - the long
+ * @return - eight bytes in an array
+ * @deprecated use toBytes(long,byte[],int)
+ */
+ public static byte[] toBytes(long n) {
+ return toBytes(n,new byte[8],0);
+ }
+ public static byte[] toBytes(long n, byte[] b, int offset) {
+ b[offset+7] = (byte) (n);
+ n >>>= 8;
+ b[offset+6] = (byte) (n);
+ n >>>= 8;
+ b[offset+5] = (byte) (n);
+ n >>>= 8;
+ b[offset+4] = (byte) (n);
+ n >>>= 8;
+ b[offset+3] = (byte) (n);
+ n >>>= 8;
+ b[offset+2] = (byte) (n);
+ n >>>= 8;
+ b[offset+1] = (byte) (n);
+ n >>>= 8;
+ b[offset+0] = (byte) (n);
+ return b;
+ }
+
+ /**
+ * Similar to a String.IndexOf, but uses pure bytes
+ * @param src - the source bytes to be searched
+ * @param srcOff - offset on the source buffer
+ * @param find - the string to be found within src
+ * @return - the index of the first matching byte. -1 if the find array is not found
+ */
+ public static int firstIndexOf(byte[] src, int srcOff, byte[] find){
+ int result = -1;
+ if (find.length > src.length) return result;
+ if (find.length == 0 || src.length == 0) return result;
+ if (srcOff >= src.length ) throw new java.lang.ArrayIndexOutOfBoundsException();
+ boolean found = false;
+ int srclen = src.length;
+ int findlen = find.length;
+ byte first = find[0];
+ int pos = srcOff;
+ while (!found) {
+ //find the first byte
+ while (pos < srclen){
+ if (first == src[pos])
+ break;
+ pos++;
+ }
+ if (pos >= srclen)
+ return -1;
+
+ //we found the first character
+ //match the rest of the bytes - they have to match
+ if ( (srclen - pos) < findlen)
+ return -1;
+ //assume it does exist
+ found = true;
+ for (int i = 1; ( (i < findlen) && found); i++)
+ found = found && (find[i] == src[pos + i]);
+ if (found)
+ result = pos;
+ else if ( (srclen - pos) < findlen)
+ return -1; //no more matches possible
+ else
+ pos++;
+ }
+ return result;
+ }
+
+
+ public static Serializable deserialize(byte[] data)
+ throws IOException, ClassNotFoundException, ClassCastException {
+ return deserialize(data,0,data.length);
+ }
+
+ public static Serializable deserialize(byte[] data, int offset, int length)
+ throws IOException, ClassNotFoundException, ClassCastException {
+ return deserialize(data,offset,length,null);
+ }
+ public static int invokecount = 0;
+ public static Serializable deserialize(byte[] data, int offset, int length, ClassLoader[] cls)
+ throws IOException, ClassNotFoundException, ClassCastException {
+ synchronized (XByteBuffer.class) { invokecount++;}
+ Object message = null;
+ if ( cls == null ) cls = new ClassLoader[0];
+ if (data != null) {
+ InputStream instream = new ByteArrayInputStream(data,offset,length);
+ ObjectInputStream stream = null;
+ stream = (cls.length>0)? new ReplicationStream(instream,cls):new ObjectInputStream(instream);
+ message = stream.readObject();
+ instream.close();
+ stream.close();
+ }
+ if ( message == null ) {
+ return null;
+ } else if (message instanceof Serializable)
+ return (Serializable) message;
+ else {
+ throw new ClassCastException("Message has the wrong class. It should implement Serializable, instead it is:"+message.getClass().getName());
+ }
+ }
+
+ /**
+ * Serializes a message into cluster data
+ * @param msg ClusterMessage
+ * @param compress boolean
+ * @return
+ * @throws IOException
+ */
+ public static byte[] serialize(Serializable msg) throws IOException {
+ ByteArrayOutputStream outs = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(outs);
+ out.writeObject(msg);
+ out.flush();
+ byte[] data = outs.toByteArray();
+ return data;
+ }
+
+ public void setDiscard(boolean discard) {
+ this.discard = discard;
+ }
+
+ public boolean getDiscard() {
+ return discard;
+ }
+
+}
--- /dev/null
+<?xml version="1.0"?>
+<mbeans-descriptors>
+
+ <mbean name="SimpleTcpCluster"
+ className="org.apache.catalina.mbeans.ClassNameMBean"
+ description="Tcp Cluster implementation"
+ domain="Catalina"
+ group="Cluster"
+ type="org.apache.catalina.cluster.tcp.SimpleTcpCluster">
+
+ <attribute name="protocolStack"
+ description="JavaGroups protocol stack selection"
+ type="java.lang.String"/>
+
+ </mbean>
+
+
+ <mbean name="SimpleTcpReplicationManager"
+ className="org.apache.catalina.mbeans.ClassNameMBean"
+ description="Clustered implementation of the Manager interface"
+ domain="Catalina"
+ group="Manager"
+ type="org.apache.catalina.cluster.tcp.SimpleTcpReplicationManager">
+
+ <attribute name="algorithm"
+ description="The message digest algorithm to be used when generating
+ session identifiers"
+ type="java.lang.String"/>
+
+ <attribute name="checkInterval"
+ description="The interval (in seconds) between checks for expired
+ sessions"
+ type="int"/>
+
+ <attribute name="className"
+ description="Fully qualified class name of the managed object"
+ type="java.lang.String"
+ writeable="false"/>
+
+ <attribute name="distributable"
+ description="The distributable flag for Sessions created by this
+ Manager"
+ type="boolean"/>
+
+ <attribute name="entropy"
+ description="A String initialization parameter used to increase the
+ entropy of the initialization of our random number
+ generator"
+ type="java.lang.String"/>
+
+ <attribute name="managedResource"
+ description="The managed resource this MBean is associated with"
+ type="java.lang.Object"/>
+
+ <attribute name="maxActiveSessions"
+ description="The maximum number of active Sessions allowed, or -1
+ for no limit"
+ type="int"/>
+
+ <attribute name="maxInactiveInterval"
+ description="The default maximum inactive interval for Sessions
+ created by this Manager"
+ type="int"/>
+
+ <attribute name="name"
+ description="The descriptive name of this Manager implementation
+ (for logging)"
+ type="java.lang.String"
+ writeable="false"/>
+
+ </mbean>
+
+
+
+<mbean name="ReplicationValve"
+ className="org.apache.catalina.mbeans.ClassNameMBean"
+ description="Valve for simple tcp replication"
+ domain="Catalina"
+ group="Valve"
+ type="org.apache.catalina.cluster.tcp.ReplicationValve">
+
+ <attribute name="className"
+ description="Fully qualified class name of the managed object"
+ type="java.lang.String"
+ writeable="false"/>
+
+ <attribute name="debug"
+ description="The debugging detail level for this component"
+ type="int"/>
+
+ </mbean>
+
+
+</mbeans-descriptors>
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.tribes.membership;
+
+import org.apache.catalina.tribes.util.Arrays;
+
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.tribes.membership</code>
+ * package.
+ *
+ * @author Peter Rossbach
+ * @version $Revision: 303950 $ $Date: 2005-06-09 15:38:30 -0500 (Thu, 09 Jun 2005) $
+ * @author Filip Hanik
+ */
+
+public class Constants {
+
+ public static final String Package = "org.apache.catalina.tribes.membership";
+ public static void main(String[] args) throws Exception {
+ System.out.println(Arrays.toString("TRIBES-B".getBytes()));
+ System.out.println(Arrays.toString("TRIBES-E".getBytes()));
+ }
+}
--- /dev/null
+cluster.mbean.register.already=MBean {0} already registered!
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership;
+
+import java.util.Properties;
+
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.MembershipService;
+import org.apache.catalina.tribes.util.StringManager;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+import java.io.IOException;
+
+/**
+ * A <b>membership</b> implementation using simple multicast.
+ * This is the representation of a multicast membership service.
+ * This class is responsible for maintaining a list of active cluster nodes in the cluster.
+ * If a node fails to send out a heartbeat, the node will be dismissed.
+ *
+ * @author Filip Hanik
+ * @version $Revision: 378093 $, $Date: 2006-02-15 15:13:45 -0600 (Wed, 15 Feb 2006) $
+ */
+
+
+public class McastService implements MembershipService,MembershipListener {
+
+ private static org.apache.commons.logging.Log log =
+ org.apache.commons.logging.LogFactory.getLog( McastService.class );
+
+ /**
+ * The string manager for this package.
+ */
+ protected StringManager sm = StringManager.getManager(Constants.Package);
+
+ /**
+ * The descriptive information about this implementation.
+ */
+ private static final String info = "McastService/2.1";
+
+ /**
+ * The implementation specific properties
+ */
+ protected Properties properties = new Properties();
+ /**
+ * A handle to the actual low level implementation
+ */
+ protected McastServiceImpl impl;
+ /**
+ * A membership listener delegate (should be the cluster :)
+ */
+ protected MembershipListener listener;
+ /**
+ * The local member
+ */
+ protected MemberImpl localMember ;
+ private int mcastSoTimeout;
+ private int mcastTTL;
+
+ protected byte[] payload;
+
+ protected byte[] domain;
+
+ /**
+ * Create a membership service.
+ */
+ public McastService() {
+ //default values
+ properties.setProperty("mcastPort","45564");
+ properties.setProperty("mcastAddress","228.0.0.4");
+ properties.setProperty("memberDropTime","3000");
+ properties.setProperty("mcastFrequency","500");
+
+ }
+
+ /**
+ * Return descriptive information about this implementation and the
+ * corresponding version number, in the format
+ * <code><description>/<version></code>.
+ */
+ public String getInfo() {
+ return (info);
+ }
+
+ /**
+ *
+ * @param properties
+ * <BR/>All are required<BR />
+ * 1. mcastPort - the port to listen to<BR>
+ * 2. mcastAddress - the mcast group address<BR>
+ * 4. bindAddress - the bind address if any - only one that can be null<BR>
+ * 5. memberDropTime - the time a member is gone before it is considered gone.<BR>
+ * 6. mcastFrequency - the frequency of sending messages<BR>
+ * 7. tcpListenPort - the port this member listens to<BR>
+ * 8. tcpListenHost - the bind address of this member<BR>
+ * @exception java.lang.IllegalArgumentException if a property is missing.
+ */
+ public void setProperties(Properties properties) {
+ hasProperty(properties,"mcastPort");
+ hasProperty(properties,"mcastAddress");
+ hasProperty(properties,"memberDropTime");
+ hasProperty(properties,"mcastFrequency");
+ hasProperty(properties,"tcpListenPort");
+ hasProperty(properties,"tcpListenHost");
+ this.properties = properties;
+ }
+
+ /**
+ * Return the properties, see setProperties
+ */
+ public Properties getProperties() {
+ return properties;
+ }
+
+ /**
+ * Return the local member name
+ */
+ public String getLocalMemberName() {
+ return localMember.toString() ;
+ }
+
+ /**
+ * Return the local member
+ */
+ public Member getLocalMember(boolean alive) {
+ if ( alive && localMember != null && impl != null) localMember.setMemberAliveTime(System.currentTimeMillis()-impl.getServiceStartTime());
+ return localMember;
+ }
+
+ /**
+ * Sets the local member properties for broadcasting
+ */
+ public void setLocalMemberProperties(String listenHost, int listenPort) {
+ properties.setProperty("tcpListenHost",listenHost);
+ properties.setProperty("tcpListenPort",String.valueOf(listenPort));
+ try {
+ if (localMember != null) {
+ localMember.setHostname(listenHost);
+ localMember.setPort(listenPort);
+ } else {
+ localMember = new MemberImpl(listenHost, listenPort, 0);
+ localMember.setUniqueId(UUIDGenerator.randomUUID(true));
+ localMember.setPayload(getPayload());
+ localMember.setDomain(getDomain());
+ }
+ localMember.getData(true, true);
+ }catch ( IOException x ) {
+ throw new IllegalArgumentException(x);
+ }
+ }
+
+ public void setMcastAddr(String addr) {
+ properties.setProperty("mcastAddress", addr);
+ }
+
+ public String getMcastAddr() {
+ return properties.getProperty("mcastAddress");
+ }
+
+ public void setMcastBindAddress(String bindaddr) {
+ properties.setProperty("mcastBindAddress", bindaddr);
+ }
+
+ public String getMcastBindAddress() {
+ return properties.getProperty("mcastBindAddress");
+ }
+
+ public void setMcastPort(int port) {
+ properties.setProperty("mcastPort", String.valueOf(port));
+ }
+
+ public int getMcastPort() {
+ String p = properties.getProperty("mcastPort");
+ return new Integer(p).intValue();
+ }
+
+ public void setMcastFrequency(long time) {
+ properties.setProperty("mcastFrequency", String.valueOf(time));
+ }
+
+ public long getMcastFrequency() {
+ String p = properties.getProperty("mcastFrequency");
+ return new Long(p).longValue();
+ }
+
+ public void setMcastDropTime(long time) {
+ properties.setProperty("memberDropTime", String.valueOf(time));
+ }
+
+ public long getMcastDropTime() {
+ String p = properties.getProperty("memberDropTime");
+ return new Long(p).longValue();
+ }
+
+ /**
+ * Check if a required property is available.
+ * @param properties The set of properties
+ * @param name The property to check for
+ */
+ protected void hasProperty(Properties properties, String name){
+ if ( properties.getProperty(name)==null) throw new IllegalArgumentException("McastService:Required property \""+name+"\" is missing.");
+ }
+
+ /**
+ * Start broadcasting and listening to membership pings
+ * @throws java.lang.Exception if a IO error occurs
+ */
+ public void start() throws java.lang.Exception {
+ start(MembershipService.MBR_RX);
+ start(MembershipService.MBR_TX);
+ }
+
+ public void start(int level) throws java.lang.Exception {
+ hasProperty(properties,"mcastPort");
+ hasProperty(properties,"mcastAddress");
+ hasProperty(properties,"memberDropTime");
+ hasProperty(properties,"mcastFrequency");
+ hasProperty(properties,"tcpListenPort");
+ hasProperty(properties,"tcpListenHost");
+
+ if ( impl != null ) {
+ impl.start(level);
+ return;
+ }
+ String host = getProperties().getProperty("tcpListenHost");
+ int port = Integer.parseInt(getProperties().getProperty("tcpListenPort"));
+
+ if ( localMember == null ) {
+ localMember = new MemberImpl(host, port, 100);
+ localMember.setUniqueId(UUIDGenerator.randomUUID(true));
+ } else {
+ localMember.setHostname(host);
+ localMember.setPort(port);
+ localMember.setMemberAliveTime(100);
+ }
+ if ( this.payload != null ) localMember.setPayload(payload);
+ if ( this.domain != null ) localMember.setDomain(domain);
+ localMember.setServiceStartTime(System.currentTimeMillis());
+ java.net.InetAddress bind = null;
+ if ( properties.getProperty("mcastBindAddress")!= null ) {
+ bind = java.net.InetAddress.getByName(properties.getProperty("mcastBindAddress"));
+ }
+ int ttl = -1;
+ int soTimeout = -1;
+ if ( properties.getProperty("mcastTTL") != null ) {
+ try {
+ ttl = Integer.parseInt(properties.getProperty("mcastTTL"));
+ } catch ( Exception x ) {
+ log.error("Unable to parse mcastTTL="+properties.getProperty("mcastTTL"),x);
+ }
+ }
+ if ( properties.getProperty("mcastSoTimeout") != null ) {
+ try {
+ soTimeout = Integer.parseInt(properties.getProperty("mcastSoTimeout"));
+ } catch ( Exception x ) {
+ log.error("Unable to parse mcastSoTimeout="+properties.getProperty("mcastSoTimeout"),x);
+ }
+ }
+
+ impl = new McastServiceImpl((MemberImpl)localMember,Long.parseLong(properties.getProperty("mcastFrequency")),
+ Long.parseLong(properties.getProperty("memberDropTime")),
+ Integer.parseInt(properties.getProperty("mcastPort")),
+ bind,
+ java.net.InetAddress.getByName(properties.getProperty("mcastAddress")),
+ ttl,
+ soTimeout,
+ this);
+
+ impl.start(level);
+
+
+ }
+
+
+ /**
+ * Stop broadcasting and listening to membership pings
+ */
+ public void stop(int svc) {
+ try {
+ if ( impl != null && impl.stop(svc) ) impl = null;
+ } catch ( Exception x) {
+ log.error("Unable to stop the mcast service, level:"+svc+".",x);
+ }
+ }
+
+
+ /**
+ * Return all the members by name
+ */
+ public String[] getMembersByName() {
+ Member[] currentMembers = getMembers();
+ String [] membernames ;
+ if(currentMembers != null) {
+ membernames = new String[currentMembers.length];
+ for (int i = 0; i < currentMembers.length; i++) {
+ membernames[i] = currentMembers[i].toString() ;
+ }
+ } else
+ membernames = new String[0] ;
+ return membernames ;
+ }
+
+ /**
+ * Return the member by name
+ */
+ public Member findMemberByName(String name) {
+ Member[] currentMembers = getMembers();
+ for (int i = 0; i < currentMembers.length; i++) {
+ if (name.equals(currentMembers[i].toString()))
+ return currentMembers[i];
+ }
+ return null;
+ }
+
+ /**
+ * has members?
+ */
+ public boolean hasMembers() {
+ if ( impl == null || impl.membership == null ) return false;
+ return impl.membership.hasMembers();
+ }
+
+ public Member getMember(Member mbr) {
+ if ( impl == null || impl.membership == null ) return null;
+ return impl.membership.getMember(mbr);
+ }
+
+ /**
+ * Return all the members
+ */
+ public Member[] getMembers() {
+ if ( impl == null || impl.membership == null ) return null;
+ return impl.membership.getMembers();
+ }
+ /**
+ * Add a membership listener, this version only supports one listener per service,
+ * so calling this method twice will result in only the second listener being active.
+ * @param listener The listener
+ */
+ public void setMembershipListener(MembershipListener listener) {
+ this.listener = listener;
+ }
+ /**
+ * Remove the membership listener
+ */
+ public void removeMembershipListener(){
+ listener = null;
+ }
+
+ public void memberAdded(Member member) {
+ if ( listener!=null ) listener.memberAdded(member);
+ }
+
+ /**
+ * Callback from the impl when a new member has been received
+ * @param member The member
+ */
+ public void memberDisappeared(Member member)
+ {
+ if ( listener!=null ) listener.memberDisappeared(member);
+ }
+
+ public int getMcastSoTimeout() {
+ return mcastSoTimeout;
+ }
+ public void setMcastSoTimeout(int mcastSoTimeout) {
+ this.mcastSoTimeout = mcastSoTimeout;
+ properties.setProperty("mcastSoTimeout", String.valueOf(mcastSoTimeout));
+ }
+ public int getMcastTTL() {
+ return mcastTTL;
+ }
+
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ public byte[] getDomain() {
+ return domain;
+ }
+
+ public void setMcastTTL(int mcastTTL) {
+ this.mcastTTL = mcastTTL;
+ properties.setProperty("mcastTTL", String.valueOf(mcastTTL));
+ }
+
+ public void setPayload(byte[] payload) {
+ this.payload = payload;
+ if ( localMember != null ) {
+ localMember.setPayload(payload);
+ localMember.getData(true,true);
+ try {
+ if (impl != null) impl.send(false);
+ }catch ( Exception x ) {
+ log.error("Unable to send payload update.",x);
+ }
+ }
+ }
+
+ public void setDomain(byte[] domain) {
+ this.domain = domain;
+ if ( localMember != null ) {
+ localMember.setDomain(domain);
+ localMember.getData(true,true);
+ try {
+ if (impl != null) impl.send(false);
+ }catch ( Exception x ) {
+ log.error("Unable to send domain update.",x);
+ }
+ }
+ }
+
+ /**
+ * Simple test program
+ * @param args Command-line arguments
+ * @throws Exception If an error occurs
+ */
+ public static void main(String args[]) throws Exception {
+ if(log.isInfoEnabled())
+ log.info("Usage McastService hostname tcpport");
+ McastService service = new McastService();
+ java.util.Properties p = new java.util.Properties();
+ p.setProperty("mcastPort","5555");
+ p.setProperty("mcastAddress","224.10.10.10");
+ p.setProperty("mcastClusterDomain","catalina");
+ p.setProperty("bindAddress","localhost");
+ p.setProperty("memberDropTime","3000");
+ p.setProperty("mcastFrequency","500");
+ p.setProperty("tcpListenPort","4000");
+ p.setProperty("tcpListenHost","127.0.0.1");
+ service.setProperties(p);
+ service.start();
+ Thread.sleep(60*1000*60);
+ }
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership;
+
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+
+import org.apache.catalina.tribes.MembershipListener;
+import java.util.Arrays;
+import java.net.SocketTimeoutException;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.Channel;
+import java.net.InetSocketAddress;
+
+/**
+ * A <b>membership</b> implementation using simple multicast.
+ * This is the representation of a multicast membership service.
+ * This class is responsible for maintaining a list of active cluster nodes in the cluster.
+ * If a node fails to send out a heartbeat, the node will be dismissed.
+ * This is the low level implementation that handles the multicasting sockets.
+ * Need to fix this, could use java.nio and only need one thread to send and receive, or
+ * just use a timeout on the receive
+ * @author Filip Hanik
+ * @version $Revision: 356540 $, $Date: 2005-12-13 10:53:40 -0600 (Tue, 13 Dec 2005) $
+ */
+public class McastServiceImpl
+{
+ private static org.apache.commons.logging.Log log =
+ org.apache.commons.logging.LogFactory.getLog( McastService.class );
+
+ protected static int MAX_PACKET_SIZE = 65535;
+ /**
+ * Internal flag used for the listen thread that listens to the multicasting socket.
+ */
+ protected boolean doRunSender = false;
+ protected boolean doRunReceiver = false;
+ protected int startLevel = 0;
+ /**
+ * Socket that we intend to listen to
+ */
+ protected MulticastSocket socket;
+ /**
+ * The local member that we intend to broad cast over and over again
+ */
+ protected MemberImpl member;
+ /**
+ * The multicast address
+ */
+ protected InetAddress address;
+ /**
+ * The multicast port
+ */
+ protected int port;
+ /**
+ * The time it takes for a member to expire.
+ */
+ protected long timeToExpiration;
+ /**
+ * How often to we send out a broadcast saying we are alive, must be smaller than timeToExpiration
+ */
+ protected long sendFrequency;
+ /**
+ * Reuse the sendPacket, no need to create a new one everytime
+ */
+ protected DatagramPacket sendPacket;
+ /**
+ * Reuse the receivePacket, no need to create a new one everytime
+ */
+ protected DatagramPacket receivePacket;
+ /**
+ * The membership, used so that we calculate memberships when they arrive or don't arrive
+ */
+ protected Membership membership;
+ /**
+ * The actual listener, for callback when shits goes down
+ */
+ protected MembershipListener service;
+ /**
+ * Thread to listen for pings
+ */
+ protected ReceiverThread receiver;
+ /**
+ * Thread to send pings
+ */
+ protected SenderThread sender;
+
+ /**
+ * When was the service started
+ */
+ protected long serviceStartTime = System.currentTimeMillis();
+
+ /**
+ * Time to live for the multicast packets that are being sent out
+ */
+ protected int mcastTTL = -1;
+ /**
+ * Read timeout on the mcast socket
+ */
+ protected int mcastSoTimeout = -1;
+ /**
+ * bind address
+ */
+ protected InetAddress mcastBindAddress = null;
+
+ /**
+ * Create a new mcast service impl
+ * @param member - the local member
+ * @param sendFrequency - the time (ms) in between pings sent out
+ * @param expireTime - the time (ms) for a member to expire
+ * @param port - the mcast port
+ * @param bind - the bind address (not sure this is used yet)
+ * @param mcastAddress - the mcast address
+ * @param service - the callback service
+ * @throws IOException
+ */
+ public McastServiceImpl(
+ MemberImpl member,
+ long sendFrequency,
+ long expireTime,
+ int port,
+ InetAddress bind,
+ InetAddress mcastAddress,
+ int ttl,
+ int soTimeout,
+ MembershipListener service)
+ throws IOException {
+ this.member = member;
+ this.address = mcastAddress;
+ this.port = port;
+ this.mcastSoTimeout = soTimeout;
+ this.mcastTTL = ttl;
+ this.mcastBindAddress = bind;
+ this.timeToExpiration = expireTime;
+ this.service = service;
+ this.sendFrequency = sendFrequency;
+ setupSocket();
+ sendPacket = new DatagramPacket(new byte[MAX_PACKET_SIZE],MAX_PACKET_SIZE);
+ sendPacket.setAddress(address);
+ sendPacket.setPort(port);
+ receivePacket = new DatagramPacket(new byte[MAX_PACKET_SIZE],MAX_PACKET_SIZE);
+ receivePacket.setAddress(address);
+ receivePacket.setPort(port);
+ membership = new Membership(member);
+ }
+
+ protected void setupSocket() throws IOException {
+ if (mcastBindAddress != null) socket = new MulticastSocket(new InetSocketAddress(mcastBindAddress, port));
+ else socket = new MulticastSocket(port);
+ if (mcastBindAddress != null) {
+ if(log.isInfoEnabled())
+ log.info("Setting multihome multicast interface to:" +mcastBindAddress);
+ socket.setInterface(mcastBindAddress);
+ } //end if
+ //force a so timeout so that we don't block forever
+ if ( mcastSoTimeout <= 0 ) mcastSoTimeout = (int)sendFrequency;
+ if(log.isInfoEnabled())
+ log.info("Setting cluster mcast soTimeout to "+mcastSoTimeout);
+ socket.setSoTimeout(mcastSoTimeout);
+
+ if ( mcastTTL >= 0 ) {
+ if(log.isInfoEnabled())
+ log.info("Setting cluster mcast TTL to " + mcastTTL);
+ socket.setTimeToLive(mcastTTL);
+ }
+ }
+
+
+
+ /**
+ * Start the service
+ * @param level 1 starts the receiver, level 2 starts the sender
+ * @throws IOException if the service fails to start
+ * @throws IllegalStateException if the service is already started
+ */
+ public synchronized void start(int level) throws IOException {
+ boolean valid = false;
+ if ( (level & Channel.MBR_RX_SEQ)==Channel.MBR_RX_SEQ ) {
+ if ( receiver != null ) throw new IllegalStateException("McastService.receive already running.");
+ if ( sender == null ) socket.joinGroup(address);
+ doRunReceiver = true;
+ receiver = new ReceiverThread();
+ receiver.setDaemon(true);
+ receiver.start();
+ valid = true;
+ }
+ if ( (level & Channel.MBR_TX_SEQ)==Channel.MBR_TX_SEQ ) {
+ if ( sender != null ) throw new IllegalStateException("McastService.send already running.");
+ if ( receiver == null ) socket.joinGroup(address);
+ //make sure at least one packet gets out there
+ send(false);
+ doRunSender = true;
+ serviceStartTime = System.currentTimeMillis();
+ sender = new SenderThread(sendFrequency);
+ sender.setDaemon(true);
+ sender.start();
+ //we have started the receiver, but not yet waited for membership to establish
+ valid = true;
+ }
+ if (!valid) {
+ throw new IllegalArgumentException("Invalid start level. Only acceptable levels are Channel.MBR_RX_SEQ and Channel.MBR_TX_SEQ");
+ }
+ //pause, once or twice
+ waitForMembers(level);
+ startLevel = (startLevel | level);
+ }
+
+ private void waitForMembers(int level) {
+ long memberwait = sendFrequency*2;
+ if(log.isInfoEnabled())
+ log.info("Sleeping for "+memberwait+" milliseconds to establish cluster membership, start level:"+level);
+ try {Thread.sleep(memberwait);}catch (InterruptedException ignore){}
+ if(log.isInfoEnabled())
+ log.info("Done sleeping, membership established, start level:"+level);
+ }
+
+ /**
+ * Stops the service
+ * @throws IOException if the service fails to disconnect from the sockets
+ */
+ public synchronized boolean stop(int level) throws IOException {
+ boolean valid = false;
+
+ if ( (level & Channel.MBR_RX_SEQ)==Channel.MBR_RX_SEQ ) {
+ valid = true;
+ doRunReceiver = false;
+ if ( receiver !=null ) receiver.interrupt();
+ receiver = null;
+ }
+ if ( (level & Channel.MBR_TX_SEQ)==Channel.MBR_TX_SEQ ) {
+ valid = true;
+ doRunSender = false;
+ if ( sender != null )sender.interrupt();
+ sender = null;
+ }
+
+ if (!valid) {
+ throw new IllegalArgumentException("Invalid stop level. Only acceptable levels are Channel.MBR_RX_SEQ and Channel.MBR_TX_SEQ");
+ }
+ startLevel = (startLevel & (~level));
+ //we're shutting down, send a shutdown message and close the socket
+ if ( startLevel == 0 ) {
+ //send a stop message
+ member.setCommand(Member.SHUTDOWN_PAYLOAD);
+ member.getData(true, true);
+ send(false);
+ //leave mcast group
+ try {socket.leaveGroup(address);}catch ( Exception ignore){}
+ serviceStartTime = Long.MAX_VALUE;
+ }
+ return (startLevel == 0);
+ }
+
+ /**
+ * Receive a datagram packet, locking wait
+ * @throws IOException
+ */
+ public void receive() throws IOException {
+ try {
+ socket.receive(receivePacket);
+ if(receivePacket.getLength() > MAX_PACKET_SIZE) {
+ log.error("Multicast packet received was too long, dropping package:"+receivePacket.getLength());
+ } else {
+ byte[] data = new byte[receivePacket.getLength()];
+ System.arraycopy(receivePacket.getData(), receivePacket.getOffset(), data, 0, data.length);
+ final MemberImpl m = MemberImpl.getMember(data);
+ if (log.isTraceEnabled()) log.trace("Mcast receive ping from member " + m);
+ Thread t = null;
+ if (Arrays.equals(m.getCommand(), Member.SHUTDOWN_PAYLOAD)) {
+ if (log.isDebugEnabled()) log.debug("Member has shutdown:" + m);
+ membership.removeMember(m);
+ t = new Thread() {
+ public void run() {
+ service.memberDisappeared(m);
+ }
+ };
+ } else if (membership.memberAlive(m)) {
+ if (log.isDebugEnabled()) log.debug("Mcast add member " + m);
+ t = new Thread() {
+ public void run() {
+ service.memberAdded(m);
+ }
+ };
+ } //end if
+ if ( t != null ) t.start();
+ }
+ } catch (SocketTimeoutException x ) {
+ //do nothing, this is normal, we don't want to block forever
+ //since the receive thread is the same thread
+ //that does membership expiration
+ }
+ checkExpired();
+ }
+
+ protected Object expiredMutex = new Object();
+ protected void checkExpired() {
+ synchronized (expiredMutex) {
+ MemberImpl[] expired = membership.expire(timeToExpiration);
+ for (int i = 0; i < expired.length; i++) {
+ final MemberImpl member = expired[i];
+ if (log.isDebugEnabled())
+ log.debug("Mcast exipre member " + expired[i]);
+ try {
+ Thread t = new Thread() {
+ public void run() {
+ service.memberDisappeared(member);
+ }
+ };
+ t.start();
+ } catch (Exception x) {
+ log.error("Unable to process member disappeared message.", x);
+ }
+ }
+ }
+ }
+
+ /**
+ * Send a ping
+ * @throws Exception
+ */
+ public void send(boolean checkexpired) throws IOException{
+ //ignore if we haven't started the sender
+ //if ( (startLevel&Channel.MBR_TX_SEQ) != Channel.MBR_TX_SEQ ) return;
+ member.inc();
+ if(log.isTraceEnabled())
+ log.trace("Mcast send ping from member " + member);
+ byte[] data = member.getData();
+ DatagramPacket p = new DatagramPacket(data,data.length);
+ p.setAddress(address);
+ p.setPort(port);
+ socket.send(p);
+ if ( checkexpired ) checkExpired();
+ }
+
+ public long getServiceStartTime() {
+ return this.serviceStartTime;
+ }
+
+
+ public class ReceiverThread extends Thread {
+ public ReceiverThread() {
+ super();
+ setName("Cluster-MembershipReceiver");
+ }
+ public void run() {
+ while ( doRunReceiver ) {
+ try {
+ receive();
+ } catch ( ArrayIndexOutOfBoundsException ax ) {
+ //we can ignore this, as it means we have an invalid package
+ //but we will log it to debug
+ if ( log.isDebugEnabled() )
+ log.debug("Invalid member mcast package.",ax);
+ } catch ( Exception x ) {
+ log.warn("Error receiving mcast package. Sleeping 500ms",x);
+ try { Thread.sleep(500); } catch ( Exception ignore ){}
+
+ }
+ }
+ }
+ }//class ReceiverThread
+
+ public class SenderThread extends Thread {
+ long time;
+ public SenderThread(long time) {
+ this.time = time;
+ setName("Cluster-MembershipSender");
+
+ }
+ public void run() {
+ while ( doRunSender ) {
+ try {
+ send(true);
+ } catch ( Exception x ) {
+ log.warn("Unable to send mcast message.",x);
+ }
+ try { Thread.sleep(time); } catch ( Exception ignore ) {}
+ }
+ }
+ }//class SenderThread
+}
--- /dev/null
+/*\r
+ * Copyright 1999,2004-2005 The Apache Software Foundation.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.apache.catalina.tribes.membership;\r
+\r
+import java.io.IOException;\r
+import java.io.ObjectInput;\r
+import java.io.ObjectOutput;\r
+import java.util.Arrays;\r
+\r
+import org.apache.catalina.tribes.Member;\r
+import org.apache.catalina.tribes.io.XByteBuffer;\r
+import org.apache.catalina.tribes.transport.SenderState;\r
+\r
+/**\r
+ * A <b>membership</b> implementation using simple multicast.\r
+ * This is the representation of a multicast member.\r
+ * Carries the host, and port of the this or other cluster nodes.\r
+ *\r
+ * @author Filip Hanik\r
+ * @version $Revision: 304032 $, $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $\r
+ */\r
+public class MemberImpl implements Member, java.io.Externalizable {\r
+\r
+ /**\r
+ * Public properties specific to this implementation\r
+ */\r
+ public static final transient String TCP_LISTEN_PORT = "tcpListenPort";\r
+ public static final transient String TCP_LISTEN_HOST = "tcpListenHost";\r
+ public static final transient String MEMBER_NAME = "memberName";\r
+ \r
+ public static final transient byte[] TRIBES_MBR_BEGIN = new byte[] {84, 82, 73, 66, 69, 83, 45, 66};\r
+ public static final transient byte[] TRIBES_MBR_END = new byte[] {84, 82, 73, 66, 69, 83, 45, 69};\r
+ \r
+ /**\r
+ * The listen host for this member\r
+ */\r
+ protected byte[] host;\r
+ protected transient String hostname;\r
+ /**\r
+ * The tcp listen port for this member\r
+ */\r
+ protected int port;\r
+\r
+ /**\r
+ * Counter for how many broadcast messages have been sent from this member\r
+ */\r
+ protected int msgCount = 0;\r
+ /**\r
+ * The number of milliseconds since this members was\r
+ * created, is kept track of using the start time\r
+ */\r
+ protected long memberAliveTime = 0;\r
+ \r
+ /**\r
+ * For the local member only\r
+ */\r
+ protected transient long serviceStartTime;\r
+ \r
+ /**\r
+ * To avoid serialization over and over again, once the local dataPkg\r
+ * has been set, we use that to transmit data\r
+ */\r
+ protected transient byte[] dataPkg = null;\r
+\r
+ /**\r
+ * Unique session Id for this member\r
+ */\r
+ protected byte[] uniqueId = new byte[16];\r
+ \r
+ /**\r
+ * Custom payload that an app framework can broadcast\r
+ * Also used to transport stop command.\r
+ */\r
+ protected byte[] payload = new byte[0];\r
+ \r
+ /**\r
+ * Command, so that the custom payload doesn't have to be used\r
+ * This is for internal tribes use, such as SHUTDOWN_COMMAND\r
+ */\r
+ protected byte[] command = new byte[0];\r
+\r
+ /**\r
+ * Domain if we want to filter based on domain.\r
+ */\r
+ protected byte[] domain = new byte[0];\r
+ \r
+ /**\r
+ * Empty constructor for serialization\r
+ */\r
+ public MemberImpl() {\r
+ \r
+ }\r
+\r
+ /**\r
+ * Construct a new member object\r
+ * @param name - the name of this member, cluster unique\r
+ * @param domain - the cluster domain name of this member\r
+ * @param host - the tcp listen host\r
+ * @param port - the tcp listen port\r
+ */\r
+ public MemberImpl(String host,\r
+ int port,\r
+ long aliveTime) throws IOException {\r
+ setHostname(host);\r
+ this.port = port;\r
+ this.memberAliveTime=aliveTime;\r
+ }\r
+ \r
+ public MemberImpl(String host,\r
+ int port,\r
+ long aliveTime,\r
+ byte[] payload) throws IOException {\r
+ this(host,port,aliveTime);\r
+ setPayload(payload);\r
+ }\r
+ \r
+ public boolean isReady() {\r
+ return SenderState.getSenderState(this).isReady();\r
+ }\r
+ public boolean isSuspect() {\r
+ return SenderState.getSenderState(this).isSuspect();\r
+ }\r
+ public boolean isFailing() {\r
+ return SenderState.getSenderState(this).isFailing();\r
+ }\r
+\r
+ /**\r
+ * Increment the message count.\r
+ */\r
+ protected void inc() {\r
+ msgCount++;\r
+ }\r
+\r
+ /**\r
+ * Create a data package to send over the wire representing this member.\r
+ * This is faster than serialization.\r
+ * @return - the bytes for this member deserialized\r
+ * @throws Exception\r
+ */\r
+ public byte[] getData() {\r
+ return getData(true);\r
+ }\r
+ /**\r
+ * Highly optimized version of serializing a member into a byte array\r
+ * Returns a cached byte[] reference, do not modify this data\r
+ * @param getalive boolean\r
+ * @return byte[]\r
+ */\r
+ public byte[] getData(boolean getalive) {\r
+ return getData(getalive,false);\r
+ }\r
+ \r
+ \r
+ public int getDataLength() {\r
+ return TRIBES_MBR_BEGIN.length+ //start pkg\r
+ 4+ //data length\r
+ 8+ //alive time\r
+ 4+ //port\r
+ 1+ //host length\r
+ host.length+ //host\r
+ 4+ //command length\r
+ command.length+ //command\r
+ 4+ //domain length\r
+ domain.length+ //domain\r
+ 16+ //unique id\r
+ 4+ //payload length\r
+ payload.length+ //payload\r
+ TRIBES_MBR_END.length; //end pkg\r
+ }\r
+ \r
+ /**\r
+ * \r
+ * @param getalive boolean - calculate memberAlive time\r
+ * @param reset boolean - reset the cached data package, and create a new one\r
+ * @return byte[]\r
+ */\r
+ public byte[] getData(boolean getalive, boolean reset) {\r
+ if ( reset ) dataPkg = null;\r
+ //look in cache first\r
+ if ( dataPkg!=null ) {\r
+ if ( getalive ) {\r
+ //you'd be surprised, but System.currentTimeMillis\r
+ //shows up on the profiler\r
+ long alive=System.currentTimeMillis()-getServiceStartTime();\r
+ XByteBuffer.toBytes( (long) alive, dataPkg, TRIBES_MBR_BEGIN.length+4);\r
+ }\r
+ return dataPkg;\r
+ }\r
+ \r
+ //package looks like\r
+ //start package TRIBES_MBR_BEGIN.length\r
+ //package length - 4 bytes\r
+ //alive - 8 bytes\r
+ //port - 4 bytes\r
+ //host length - 1 byte\r
+ //host - hl bytes\r
+ //clen - 4 bytes\r
+ //command - clen bytes\r
+ //dlen - 4 bytes\r
+ //domain - dlen bytes\r
+ //uniqueId - 16 bytes\r
+ //payload length - 4 bytes\r
+ //payload plen bytes\r
+ //end package TRIBES_MBR_END.length\r
+ byte[] addr = host;\r
+ long alive=System.currentTimeMillis()-getServiceStartTime();\r
+ byte hl = (byte)addr.length;\r
+ byte[] data = new byte[getDataLength()];\r
+ \r
+ int bodylength = (getDataLength() - TRIBES_MBR_BEGIN.length - TRIBES_MBR_END.length - 4);\r
+ \r
+ int pos = 0;\r
+ \r
+ //TRIBES_MBR_BEGIN\r
+ System.arraycopy(TRIBES_MBR_BEGIN,0,data,pos,TRIBES_MBR_BEGIN.length);\r
+ pos += TRIBES_MBR_BEGIN.length;\r
+ \r
+ //body length\r
+ XByteBuffer.toBytes(bodylength,data,pos);\r
+ pos += 4;\r
+ \r
+ //alive data\r
+ XByteBuffer.toBytes((long)alive,data,pos);\r
+ pos += 8;\r
+ //port\r
+ XByteBuffer.toBytes(port,data,pos);\r
+ pos += 4;\r
+ //host length\r
+ data[pos++] = hl;\r
+ //host\r
+ System.arraycopy(addr,0,data,pos,addr.length);\r
+ pos+=addr.length;\r
+ //clen - 4 bytes\r
+ XByteBuffer.toBytes(command.length,data,pos);\r
+ pos+=4;\r
+ //command - clen bytes\r
+ System.arraycopy(command,0,data,pos,command.length);\r
+ pos+=command.length;\r
+ //dlen - 4 bytes\r
+ XByteBuffer.toBytes(domain.length,data,pos);\r
+ pos+=4;\r
+ //domain - dlen bytes\r
+ System.arraycopy(domain,0,data,pos,domain.length);\r
+ pos+=domain.length;\r
+ //unique Id\r
+ System.arraycopy(uniqueId,0,data,pos,uniqueId.length);\r
+ pos+=uniqueId.length;\r
+ //payload\r
+ XByteBuffer.toBytes(payload.length,data,pos);\r
+ pos+=4;\r
+ System.arraycopy(payload,0,data,pos,payload.length);\r
+ pos+=payload.length;\r
+ \r
+ //TRIBES_MBR_END\r
+ System.arraycopy(TRIBES_MBR_END,0,data,pos,TRIBES_MBR_END.length);\r
+ pos += TRIBES_MBR_END.length;\r
+\r
+ //create local data\r
+ dataPkg = data;\r
+ return data;\r
+ }\r
+ /**\r
+ * Deserializes a member from data sent over the wire\r
+ * @param data - the bytes received\r
+ * @return a member object.\r
+ */\r
+ public static MemberImpl getMember(byte[] data, MemberImpl member) {\r
+ return getMember(data,0,data.length,member);\r
+ }\r
+\r
+ public static MemberImpl getMember(byte[] data, int offset, int length, MemberImpl member) {\r
+ //package looks like\r
+ //start package TRIBES_MBR_BEGIN.length\r
+ //package length - 4 bytes\r
+ //alive - 8 bytes\r
+ //port - 4 bytes\r
+ //host length - 1 byte\r
+ //host - hl bytes\r
+ //clen - 4 bytes\r
+ //command - clen bytes\r
+ //dlen - 4 bytes\r
+ //domain - dlen bytes\r
+ //uniqueId - 16 bytes\r
+ //payload length - 4 bytes\r
+ //payload plen bytes\r
+ //end package TRIBES_MBR_END.length\r
+\r
+ int pos = offset;\r
+ \r
+ if (XByteBuffer.firstIndexOf(data,offset,TRIBES_MBR_BEGIN)!=pos) {\r
+ throw new IllegalArgumentException("Invalid package, should start with:"+org.apache.catalina.tribes.util.Arrays.toString(TRIBES_MBR_BEGIN));\r
+ }\r
+\r
+ if ( length < (TRIBES_MBR_BEGIN.length+4) ) {\r
+ throw new ArrayIndexOutOfBoundsException("Member package to small to validate.");\r
+ }\r
+ \r
+ pos += TRIBES_MBR_BEGIN.length;\r
+ \r
+ int bodylength = XByteBuffer.toInt(data,pos);\r
+ pos += 4;\r
+ \r
+ if ( length < (bodylength+4+TRIBES_MBR_BEGIN.length+TRIBES_MBR_END.length) ) {\r
+ throw new ArrayIndexOutOfBoundsException("Not enough bytes in member package.");\r
+ }\r
+ \r
+ int endpos = pos+bodylength;\r
+ if (XByteBuffer.firstIndexOf(data,endpos,TRIBES_MBR_END)!=endpos) {\r
+ throw new IllegalArgumentException("Invalid package, should end with:"+org.apache.catalina.tribes.util.Arrays.toString(TRIBES_MBR_END));\r
+ }\r
+\r
+\r
+ byte[] alived = new byte[8];\r
+ System.arraycopy(data, pos, alived, 0, 8);\r
+ pos += 8;\r
+ byte[] portd = new byte[4];\r
+ System.arraycopy(data, pos, portd, 0, 4);\r
+ pos += 4;\r
+ \r
+ byte hl = data[pos++];\r
+ byte[] addr = new byte[hl];\r
+ System.arraycopy(data, pos, addr, 0, hl);\r
+ pos += hl;\r
+ \r
+ int cl = XByteBuffer.toInt(data, pos);\r
+ pos += 4;\r
+ \r
+ byte[] command = new byte[cl];\r
+ System.arraycopy(data, pos, command, 0, command.length);\r
+ pos += command.length;\r
+ \r
+ int dl = XByteBuffer.toInt(data, pos);\r
+ pos += 4;\r
+ \r
+ byte[] domain = new byte[dl];\r
+ System.arraycopy(data, pos, domain, 0, domain.length);\r
+ pos += domain.length;\r
+ \r
+ byte[] uniqueId = new byte[16];\r
+ System.arraycopy(data, pos, uniqueId, 0, 16);\r
+ pos += 16;\r
+ \r
+ int pl = XByteBuffer.toInt(data, pos);\r
+ pos += 4;\r
+ \r
+ byte[] payload = new byte[pl];\r
+ System.arraycopy(data, pos, payload, 0, payload.length);\r
+ pos += payload.length;\r
+ \r
+ member.setHost(addr);\r
+ member.setPort(XByteBuffer.toInt(portd, 0));\r
+ member.setMemberAliveTime(XByteBuffer.toLong(alived, 0));\r
+ member.setUniqueId(uniqueId);\r
+ member.payload = payload;\r
+ member.domain = domain;\r
+ member.command = command;\r
+ \r
+ member.dataPkg = new byte[length];\r
+ System.arraycopy(data, offset, member.dataPkg, 0, length);\r
+ \r
+ return member;\r
+ }\r
+\r
+ public static MemberImpl getMember(byte[] data) {\r
+ return getMember(data,new MemberImpl());\r
+ }\r
+\r
+ /**\r
+ * Return the name of this object\r
+ * @return a unique name to the cluster\r
+ */\r
+ public String getName() {\r
+ return "tcp://"+getHostname()+":"+getPort();\r
+ }\r
+ \r
+ /**\r
+ * Return the listen port of this member\r
+ * @return - tcp listen port\r
+ */\r
+ public int getPort() {\r
+ return this.port;\r
+ }\r
+\r
+ /**\r
+ * Return the TCP listen host for this member\r
+ * @return IP address or host name\r
+ */\r
+ public byte[] getHost() {\r
+ return host;\r
+ }\r
+ \r
+ public String getHostname() {\r
+ if ( this.hostname != null ) return hostname;\r
+ else {\r
+ try {\r
+ this.hostname = java.net.InetAddress.getByAddress(host).getHostName();\r
+ return this.hostname;\r
+ }catch ( IOException x ) {\r
+ throw new RuntimeException("Unable to parse hostname.",x);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Contains information on how long this member has been online.\r
+ * The result is the number of milli seconds this member has been\r
+ * broadcasting its membership to the cluster.\r
+ * @return nr of milliseconds since this member started.\r
+ */\r
+ public long getMemberAliveTime() {\r
+ return memberAliveTime;\r
+ }\r
+\r
+ public long getServiceStartTime() {\r
+ return serviceStartTime;\r
+ }\r
+\r
+ public byte[] getUniqueId() {\r
+ return uniqueId;\r
+ }\r
+\r
+ public byte[] getPayload() {\r
+ return payload;\r
+ }\r
+\r
+ public byte[] getCommand() {\r
+ return command;\r
+ }\r
+\r
+ public byte[] getDomain() {\r
+ return domain;\r
+ }\r
+\r
+ public void setMemberAliveTime(long time) {\r
+ memberAliveTime=time;\r
+ }\r
+\r
+\r
+\r
+ /**\r
+ * String representation of this object\r
+ */\r
+ public String toString() {\r
+ StringBuffer buf = new StringBuffer("org.apache.catalina.tribes.membership.MemberImpl[");\r
+ buf.append(getName()).append(",");\r
+ buf.append(getHostname()).append(",");\r
+ buf.append(port).append(", alive=");\r
+ buf.append(memberAliveTime).append(",");\r
+ buf.append("id=").append(bToS(this.uniqueId)).append(", ");\r
+ buf.append("payload=").append(bToS(this.payload,8)).append(", ");\r
+ buf.append("command=").append(bToS(this.command,8)).append(", ");\r
+ buf.append("domain=").append(bToS(this.domain,8)).append(", ");\r
+ buf.append("]");\r
+ return buf.toString();\r
+ }\r
+ public static String bToS(byte[] data) {\r
+ return bToS(data,data.length);\r
+ }\r
+ public static String bToS(byte[] data, int max) {\r
+ StringBuffer buf = new StringBuffer(4*16);\r
+ buf.append("{");\r
+ for (int i=0; data!=null && i<data.length; i++ ) {\r
+ buf.append(String.valueOf(data[i])).append(" ");\r
+ if ( i==max ) {\r
+ buf.append("...("+data.length+")");\r
+ break;\r
+ }\r
+ }\r
+ buf.append("}");\r
+ return buf.toString();\r
+ }\r
+\r
+ /**\r
+ * @see java.lang.Object#hashCode()\r
+ * @return The hash code\r
+ */\r
+ public int hashCode() {\r
+ return getHost()[0]+getHost()[1]+getHost()[2]+getHost()[3];\r
+ }\r
+\r
+ /**\r
+ * Returns true if the param o is a McastMember with the same name\r
+ * @param o\r
+ */\r
+ public boolean equals(Object o) {\r
+ if ( o instanceof MemberImpl ) {\r
+ return Arrays.equals(this.getHost(),((MemberImpl)o).getHost()) &&\r
+ this.getPort() == ((MemberImpl)o).getPort() &&\r
+ Arrays.equals(this.getUniqueId(),((MemberImpl)o).getUniqueId());\r
+ }\r
+ else\r
+ return false;\r
+ }\r
+ \r
+ public void setHost(byte[] host) {\r
+ this.host = host;\r
+ }\r
+ \r
+ public void setHostname(String host) throws IOException {\r
+ hostname = host;\r
+ this.host = java.net.InetAddress.getByName(host).getAddress();\r
+ }\r
+ \r
+ public void setMsgCount(int msgCount) {\r
+ this.msgCount = msgCount;\r
+ }\r
+\r
+ public void setPort(int port) {\r
+ this.port = port;\r
+ this.dataPkg = null;\r
+ }\r
+\r
+ public void setServiceStartTime(long serviceStartTime) {\r
+ this.serviceStartTime = serviceStartTime;\r
+ }\r
+\r
+ public void setUniqueId(byte[] uniqueId) {\r
+ this.uniqueId = uniqueId!=null?uniqueId:new byte[16];\r
+ getData(true,true);\r
+ }\r
+\r
+ public void setPayload(byte[] payload) {\r
+ byte[] oldpayload = this.payload;\r
+ this.payload = payload!=null?payload:new byte[0];\r
+ if ( this.getData(true,true).length > McastServiceImpl.MAX_PACKET_SIZE ) {\r
+ this.payload = oldpayload;\r
+ throw new IllegalArgumentException("Payload is to large for tribes to handle.");\r
+ }\r
+ \r
+ }\r
+\r
+ public void setCommand(byte[] command) {\r
+ this.command = command!=null?command:new byte[0];\r
+ getData(true,true);\r
+ }\r
+\r
+ public void setDomain(byte[] domain) {\r
+ this.domain = domain!=null?domain:new byte[0];\r
+ getData(true,true);\r
+ }\r
+\r
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {\r
+ int length = in.readInt();\r
+ byte[] message = new byte[length];\r
+ in.read(message);\r
+ getMember(message,this);\r
+ \r
+ }\r
+\r
+ public void writeExternal(ObjectOutput out) throws IOException {\r
+ byte[] data = this.getData();\r
+ out.writeInt(data.length);\r
+ out.write(data);\r
+ }\r
+ \r
+}\r
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership;
+
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.catalina.tribes.Member;
+import java.util.Comparator;
+
+/**
+ * A <b>membership</b> implementation using simple multicast.
+ * This is the representation of a multicast membership.
+ * This class is responsible for maintaining a list of active cluster nodes in the cluster.
+ * If a node fails to send out a heartbeat, the node will be dismissed.
+ *
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Revision: 356540 $, $Date: 2005-12-13 10:53:40 -0600 (Tue, 13 Dec 2005) $
+ */
+public class Membership
+{
+ protected static final MemberImpl[] EMPTY_MEMBERS = new MemberImpl[0];
+
+ /**
+ * The name of this membership, has to be the same as the name for the local
+ * member
+ */
+ protected MemberImpl local;
+
+ /**
+ * A map of all the members in the cluster.
+ */
+ protected HashMap map = new HashMap();
+
+ /**
+ * A list of all the members in the cluster.
+ */
+ protected MemberImpl[] members = EMPTY_MEMBERS;
+
+ /**
+ * sort members by alive time
+ */
+ protected Comparator memberComparator = new MemberComparator();
+
+ public Object clone() {
+ synchronized (members) {
+ Membership clone = new Membership(local, memberComparator);
+ clone.map = (HashMap) map.clone();
+ clone.members = new MemberImpl[members.length];
+ System.arraycopy(members,0,clone.members,0,members.length);
+ return clone;
+ }
+ }
+
+ /**
+ * Constructs a new membership
+ * @param name - has to be the name of the local member. Used to filter the local member from the cluster membership
+ */
+ public Membership(MemberImpl local, boolean includeLocal) {
+ this.local = local;
+ if ( includeLocal ) addMember(local);
+ }
+
+ public Membership(MemberImpl local) {
+ this(local,false);
+ }
+
+ public Membership(MemberImpl local, Comparator comp) {
+ this(local,comp,false);
+ }
+
+ public Membership(MemberImpl local, Comparator comp, boolean includeLocal) {
+ this(local,includeLocal);
+ this.memberComparator = comp;
+ }
+ /**
+ * Reset the membership and start over fresh.
+ * Ie, delete all the members and wait for them to ping again and join this membership
+ */
+ public synchronized void reset() {
+ map.clear();
+ members = EMPTY_MEMBERS ;
+ }
+
+ /**
+ * Notify the membership that this member has announced itself.
+ *
+ * @param member - the member that just pinged us
+ * @return - true if this member is new to the cluster, false otherwise.
+ * @return - false if this member is the local member or updated.
+ */
+ public synchronized boolean memberAlive(MemberImpl member) {
+ boolean result = false;
+ //ignore ourselves
+ if ( member.equals(local) ) return result;
+
+ //return true if the membership has changed
+ MbrEntry entry = (MbrEntry)map.get(member);
+ if ( entry == null ) {
+ entry = addMember(member);
+ result = true;
+ } else {
+ //update the member alive time
+ MemberImpl updateMember = entry.getMember() ;
+ if(updateMember.getMemberAliveTime() != member.getMemberAliveTime()) {
+ //update fields that can change
+ updateMember.setMemberAliveTime(member.getMemberAliveTime());
+ updateMember.setPayload(member.getPayload());
+ updateMember.setCommand(member.getCommand());
+ Arrays.sort(members, memberComparator);
+ }
+ }
+ entry.accessed();
+ return result;
+ }
+
+ /**
+ * Add a member to this component and sort array with memberComparator
+ * @param member The member to add
+ */
+ public synchronized MbrEntry addMember(MemberImpl member) {
+ synchronized (members) {
+ MbrEntry entry = new MbrEntry(member);
+ if (!map.containsKey(member) ) {
+ map.put(member, entry);
+ MemberImpl results[] = new MemberImpl[members.length + 1];
+ for (int i = 0; i < members.length; i++) results[i] = members[i];
+ results[members.length] = member;
+ members = results;
+ Arrays.sort(members, memberComparator);
+ }
+ return entry;
+ }
+ }
+
+ /**
+ * Remove a member from this component.
+ *
+ * @param member The member to remove
+ */
+ public void removeMember(MemberImpl member) {
+ map.remove(member);
+ synchronized (members) {
+ int n = -1;
+ for (int i = 0; i < members.length; i++) {
+ if (members[i] == member || members[i].equals(member)) {
+ n = i;
+ break;
+ }
+ }
+ if (n < 0) return;
+ MemberImpl results[] = new MemberImpl[members.length - 1];
+ int j = 0;
+ for (int i = 0; i < members.length; i++) {
+ if (i != n)
+ results[j++] = members[i];
+ }
+ members = results;
+ }
+ }
+
+ /**
+ * Runs a refresh cycle and returns a list of members that has expired.
+ * This also removes the members from the membership, in such a way that
+ * getMembers() = getMembers() - expire()
+ * @param maxtime - the max time a member can remain unannounced before it is considered dead.
+ * @return the list of expired members
+ */
+ public synchronized MemberImpl[] expire(long maxtime) {
+ if(!hasMembers() )
+ return EMPTY_MEMBERS;
+
+ ArrayList list = null;
+ Iterator i = map.values().iterator();
+ while(i.hasNext()) {
+ MbrEntry entry = (MbrEntry)i.next();
+ if( entry.hasExpired(maxtime) ) {
+ if(list == null) // only need a list when members are expired (smaller gc)
+ list = new java.util.ArrayList();
+ list.add(entry.getMember());
+ }
+ }
+
+ if(list != null) {
+ MemberImpl[] result = new MemberImpl[list.size()];
+ list.toArray(result);
+ for( int j=0; j<result.length; j++) {
+ removeMember(result[j]);
+ }
+ return result;
+ } else {
+ return EMPTY_MEMBERS ;
+ }
+ }
+
+ /**
+ * Returning that service has members or not
+ */
+ public boolean hasMembers() {
+ return members.length > 0 ;
+ }
+
+
+ public MemberImpl getMember(Member mbr) {
+ if(hasMembers()) {
+ MemberImpl result = null;
+ for ( int i=0; i<this.members.length && result==null; i++ ) {
+ if ( members[i].equals(mbr) ) result = members[i];
+ }//for
+ return result;
+ } else {
+ return null;
+ }
+ }
+
+ public boolean contains(Member mbr) {
+ return getMember(mbr)!=null;
+ }
+
+ /**
+ * Returning a list of all the members in the membership
+ * We not need a copy: add and remove generate new arrays.
+ */
+ public MemberImpl[] getMembers() {
+ if(hasMembers()) {
+ return members;
+ } else {
+ return EMPTY_MEMBERS;
+ }
+ }
+
+ /**
+ * get a copy from all member entries
+ */
+ protected synchronized MbrEntry[] getMemberEntries()
+ {
+ MbrEntry[] result = new MbrEntry[map.size()];
+ java.util.Iterator i = map.entrySet().iterator();
+ int pos = 0;
+ while ( i.hasNext() )
+ result[pos++] = ((MbrEntry)((java.util.Map.Entry)i.next()).getValue());
+ return result;
+ }
+
+ // --------------------------------------------- Inner Class
+
+ private class MemberComparator implements java.util.Comparator {
+
+ public int compare(Object o1, Object o2) {
+ try {
+ return compare((MemberImpl) o1, (MemberImpl) o2);
+ } catch (ClassCastException x) {
+ return 0;
+ }
+ }
+
+ public int compare(MemberImpl m1, MemberImpl m2) {
+ //longer alive time, means sort first
+ long result = m2.getMemberAliveTime() - m1.getMemberAliveTime();
+ if (result < 0)
+ return -1;
+ else if (result == 0)
+ return 0;
+ else
+ return 1;
+ }
+ }
+
+ /**
+ * Inner class that represents a member entry
+ */
+ protected static class MbrEntry
+ {
+
+ protected MemberImpl mbr;
+ protected long lastHeardFrom;
+
+ public MbrEntry(MemberImpl mbr) {
+ this.mbr = mbr;
+ }
+
+ /**
+ * Indicate that this member has been accessed.
+ */
+ public void accessed(){
+ lastHeardFrom = System.currentTimeMillis();
+ }
+
+ /**
+ * Return the actual Member object
+ */
+ public MemberImpl getMember() {
+ return mbr;
+ }
+
+ /**
+ * Check if this dude has expired
+ * @param maxtime The time threshold
+ */
+ public boolean hasExpired(long maxtime) {
+ long delta = System.currentTimeMillis() - lastHeardFrom;
+ return delta > maxtime;
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mbeans-descriptors PUBLIC
+ "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+ "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+<mbeans-descriptors>
+
+ <mbean name="McastService"
+ description="Cluster Membership service implementation"
+ domain="Catalina"
+ group="Cluster"
+ type="org.apache.catalina.cluster.mcast.McastService">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="mcastAddr"
+ description="Multicast IP Address"
+ type="java.lang.String"/>
+ <attribute name="mcastBindAddress"
+ description="Multicast IP Interface address (default auto)"
+ type="java.lang.String"/>
+ <attribute name="mcastPort"
+ description="Multicast UDP Port"
+ type="int"/>
+ <attribute name="mcastFrequency"
+ description="Ping Frequency at msec"
+ type="long"/>
+ <attribute name="mcastClusterDomain"
+ description="Cluster Domain of this member"
+ type="java.lang.String"/>
+ <attribute name="mcastDropTime"
+ description="Timeout from frequency ping after member disapper notify"
+ type="long"/>
+ <attribute name="mcastSoTimeout"
+ description="Multicast Socket Timeout"
+ type="int"/>
+ <attribute name="mcastTTL"
+ description=""
+ type="int"/>
+ <attribute name="localMemberName"
+ description="Complete local receiver information"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="membersByName"
+ description="Complete remote sender information"
+ type="[Ljava.lang.String;"
+ writeable="false"/>
+
+ <operation name="start"
+ description="Start the cluster membership"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="stop"
+ description="Stop the cluster membership"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+</mbeans-descriptors>
--- /dev/null
+<body>
+<head><title>Apache Tribes - The Tomcat Cluster Communication Module</title>
+<h3>QuickStart</h3>
+ <pre><code>
+ //create a channel
+ Channel myChannel = new GroupChannel();
+
+ //create my listeners
+ MyMessageListener msgListener = new MyMessageListener();
+ MyMemberListener mbrListener = new MyMemberListener();
+
+ //attach the listeners to the channel
+ myChannel.addMembershipListener(mbrListener);
+ myChannel.addChannelListener(msgListener);
+
+ //start the channel
+ myChannel.start(Channel.DEFAULT);
+
+ //create a message to be sent, message must implement java.io.Serializable
+ //for performance reasons you probably want them to implement java.io.Externalizable
+ Serializable myMsg = new MyMessage();
+
+ //retrieve my current members
+ Member[] group = myChannel.getMembers();
+
+ //send the message
+ channel.send(group,myMsg,Channel.SEND_OPTIONS_DEFAULT);
+
+ </code></pre>
+<h3>Interfaces for the Application Developer</h3>
+ <ol>
+ <li><code>org.apache.catalina.tribes.Channel</code>
+ Main component to interact with to send messages
+ </li>
+ <li><code>org.apache.catalina.tribes.MembershipListener</code>
+ Listen to membership changes
+ </li>
+ <li><code>org.apache.catalina.tribes.ChannelListener</code>
+ Listen to data messages
+ </li>
+ <li><code>org.apache.catalina.tribes.Member</code>
+ Identifies a node, implementation specific, default is org.apache.catalina.tribes.membership.MemberImpl
+ </li>
+ </ol>
+ <h3>Interfaces for the Tribes Component Developer</h3>
+ <ol>
+ <li><code>org.apache.catalina.tribes.Channel</code>
+ Main component to that the application interacts with
+ </li>
+ <li><code>org.apache.catalina.tribes.ChannelReceiver</code>
+ IO Component to receive messages over some network transport
+ </li>
+ <li><code>org.apache.catalina.tribes.ChannelSender</code>
+ IO Component to send messages over some network transport
+ </li>
+ <li><code>org.apache.catalina.tribes.MembershipService</code>
+ IO Component that handles membership discovery and
+ </li>
+ <li><code>org.apache.catalina.tribes.ChannelInterceptor</code>
+ interceptors between the Channel and the IO layer
+ </li>
+ <li><code>org.apache.catalina.tribes.ChannelMessage</code>
+ The message that is sent through the interceptor stack down to the IO layer
+ </li>
+
+ <li><code>org.apache.catalina.tribes.Member</code>
+ Identifies a node, implementation specific to the underlying IO logic
+ </li>
+ </ol>
+</body>
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.tipis;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.Heartbeat;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.group.Response;
+import org.apache.catalina.tribes.group.RpcCallback;
+import org.apache.catalina.tribes.group.RpcChannel;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.catalina.tribes.util.Arrays;
+import java.util.ConcurrentModificationException;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public abstract class AbstractReplicatedMap extends LinkedHashMap implements RpcCallback, ChannelListener, MembershipListener, Heartbeat {
+ protected static Log log = LogFactory.getLog(AbstractReplicatedMap.class);
+
+ /**
+ * The default initial capacity - MUST be a power of two.
+ */
+ public static final int DEFAULT_INITIAL_CAPACITY = 16;
+
+ /**
+ * The load factor used when none specified in constructor.
+ **/
+ public static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+ /**
+ * Used to identify the map
+ */
+ final String chset = "ISO-8859-1";
+
+//------------------------------------------------------------------------------
+// INSTANCE VARIABLES
+//------------------------------------------------------------------------------
+ private transient long rpcTimeout = 5000;
+ private transient Channel channel;
+ private transient RpcChannel rpcChannel;
+ private transient byte[] mapContextName;
+ private transient boolean stateTransferred = false;
+ private transient Object stateMutex = new Object();
+ private transient HashMap mapMembers = new HashMap();
+ private transient int channelSendOptions = Channel.SEND_OPTIONS_DEFAULT;
+ private transient Object mapOwner;
+ private transient ClassLoader[] externalLoaders;
+ protected transient int currentNode = 0;
+ private transient long accessTimeout = 5000;
+ private transient String mapname = "";
+
+
+//------------------------------------------------------------------------------
+// CONSTRUCTORS
+//------------------------------------------------------------------------------
+
+ /**
+ * Creates a new map
+ * @param channel The channel to use for communication
+ * @param timeout long - timeout for RPC messags
+ * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+ * @param initialCapacity int - the size of this map, see HashMap
+ * @param loadFactor float - load factor, see HashMap
+ * @param cls - a list of classloaders to be used for deserialization of objects.
+ */
+ public AbstractReplicatedMap(Object owner,
+ Channel channel,
+ long timeout,
+ String mapContextName,
+ int initialCapacity,
+ float loadFactor,
+ int channelSendOptions,
+ ClassLoader[] cls) {
+ super(initialCapacity, loadFactor);
+ init(owner, channel, mapContextName, timeout, channelSendOptions, cls);
+
+ }
+
+ protected Member[] wrap(Member m) {
+ if ( m == null ) return new Member[0];
+ else return new Member[] {m};
+ }
+
+ private void init(Object owner, Channel channel, String mapContextName, long timeout, int channelSendOptions,ClassLoader[] cls) {
+ log.info("Initializing AbstractReplicatedMap with context name:"+mapContextName);
+ this.mapOwner = owner;
+ this.externalLoaders = cls;
+ this.channelSendOptions = channelSendOptions;
+ this.channel = channel;
+ this.rpcTimeout = timeout;
+
+ try {
+ this.mapname = mapContextName;
+ //unique context is more efficient if it is stored as bytes
+ this.mapContextName = mapContextName.getBytes(chset);
+ } catch (UnsupportedEncodingException x) {
+ log.warn("Unable to encode mapContextName[" + mapContextName + "] using getBytes(" + chset +") using default getBytes()", x);
+ this.mapContextName = mapContextName.getBytes();
+ }
+ if ( log.isTraceEnabled() ) log.trace("Created Lazy Map with name:"+mapContextName+", bytes:"+Arrays.toString(this.mapContextName));
+
+ //create an rpc channel and add the map as a listener
+ this.rpcChannel = new RpcChannel(this.mapContextName, channel, this);
+ this.channel.addChannelListener(this);
+ this.channel.addMembershipListener(this);
+
+
+ try {
+ broadcast(MapMessage.MSG_INIT, true);
+ //transfer state from another map
+ transferState();
+ broadcast(MapMessage.MSG_START, true);
+ } catch (ChannelException x) {
+ log.warn("Unable to send map start message.");
+ throw new RuntimeException("Unable to start replicated map.",x);
+ }
+
+ }
+
+
+ private void ping(long timeout) throws ChannelException {
+ //send out a map membership message, only wait for the first reply
+ MapMessage msg = new MapMessage(this.mapContextName, MapMessage.MSG_INIT,
+ false, null, null, null, wrap(channel.getLocalMember(false)));
+ Response[] resp = rpcChannel.send(channel.getMembers(), msg, rpcChannel.ALL_REPLY, (channelSendOptions), (int)accessTimeout);
+ for (int i = 0; i < resp.length; i++) {
+ memberAlive(resp[i].getSource());
+ }//for
+
+ synchronized (mapMembers) {
+ Iterator it = mapMembers.entrySet().iterator();
+ long now = System.currentTimeMillis();
+ while ( it.hasNext() ) {
+ Map.Entry entry = (Map.Entry)it.next();
+ long access = ((Long)entry.getValue()).longValue();
+ if ( (now - access) > timeout ) memberDisappeared((Member)entry.getKey());
+ }
+ }//synch
+ }
+
+ private void memberAlive(Member member) {
+ synchronized (mapMembers) {
+ if (!mapMembers.containsKey(member)) {
+ mapMemberAdded(member);
+ } //end if
+ mapMembers.put(member, new Long(System.currentTimeMillis()));
+ }
+ }
+
+ private void broadcast(int msgtype, boolean rpc) throws ChannelException {
+ //send out a map membership message, only wait for the first reply
+ MapMessage msg = new MapMessage(this.mapContextName, msgtype,
+ false, null, null, null, wrap(channel.getLocalMember(false)));
+ if ( rpc) {
+ Response[] resp = rpcChannel.send(channel.getMembers(), msg, rpcChannel.FIRST_REPLY, (channelSendOptions),rpcTimeout);
+ for (int i = 0; i < resp.length; i++) {
+ mapMemberAdded(resp[i].getSource());
+ messageReceived(resp[i].getMessage(), resp[i].getSource());
+ }
+ } else {
+ channel.send(channel.getMembers(),msg,channelSendOptions);
+ }
+ }
+
+ public void breakdown() {
+ finalize();
+ }
+
+ public void finalize() {
+ try {broadcast(MapMessage.MSG_STOP,false); }catch ( Exception ignore){}
+ //cleanup
+ if (this.rpcChannel != null) {
+ this.rpcChannel.breakdown();
+ }
+ if (this.channel != null) {
+ this.channel.removeChannelListener(this);
+ this.channel.removeMembershipListener(this);
+ }
+ this.rpcChannel = null;
+ this.channel = null;
+ this.mapMembers.clear();
+ super.clear();
+ this.stateTransferred = false;
+ this.externalLoaders = null;
+ }
+
+ public int hashCode() {
+ return Arrays.hashCode(this.mapContextName);
+ }
+
+ public boolean equals(Object o) {
+ if ( o == null ) return false;
+ if ( !(o instanceof AbstractReplicatedMap)) return false;
+ if ( !(o.getClass().equals(this.getClass())) ) return false;
+ AbstractReplicatedMap other = (AbstractReplicatedMap)o;
+ return Arrays.equals(mapContextName,other.mapContextName);
+ }
+
+//------------------------------------------------------------------------------
+// GROUP COM INTERFACES
+//------------------------------------------------------------------------------
+ public Member[] getMapMembers(HashMap members) {
+ synchronized (members) {
+ Member[] result = new Member[members.size()];
+ members.keySet().toArray(result);
+ return result;
+ }
+ }
+ public Member[] getMapMembers() {
+ return getMapMembers(this.mapMembers);
+ }
+
+ public Member[] getMapMembersExcl(Member[] exclude) {
+ synchronized (mapMembers) {
+ HashMap list = (HashMap)mapMembers.clone();
+ for (int i=0; i<exclude.length;i++) list.remove(exclude[i]);
+ return getMapMembers(list);
+ }
+ }
+
+
+ /**
+ * Replicates any changes to the object since the last time
+ * The object has to be primary, ie, if the object is a proxy or a backup, it will not be replicated<br>
+ * @param complete - if set to true, the object is replicated to its backup
+ * if set to false, only objects that implement ReplicatedMapEntry and the isDirty() returns true will
+ * be replicated
+ */
+ public void replicate(Object key, boolean complete) {
+ if ( log.isTraceEnabled() )
+ log.trace("Replicate invoked on key:"+key);
+ MapEntry entry = (MapEntry)super.get(key);
+ if ( !entry.isSerializable() ) return;
+ if (entry != null && entry.isPrimary() && entry.getBackupNodes()!= null && entry.getBackupNodes().length > 0) {
+ Object value = entry.getValue();
+ //check to see if we need to replicate this object isDirty()||complete
+ boolean repl = complete || ( (value instanceof ReplicatedMapEntry) && ( (ReplicatedMapEntry) value).isDirty());
+
+ if (!repl) {
+ if ( log.isTraceEnabled() )
+ log.trace("Not replicating:"+key+", no change made");
+
+ return;
+ }
+ //check to see if the message is diffable
+ boolean diff = ( (value instanceof ReplicatedMapEntry) && ( (ReplicatedMapEntry) value).isDiffable());
+ MapMessage msg = null;
+ if (diff) {
+ ReplicatedMapEntry rentry = (ReplicatedMapEntry)entry.getValue();
+ try {
+ rentry.lock();
+ //construct a diff message
+ msg = new MapMessage(mapContextName, MapMessage.MSG_BACKUP,
+ true, (Serializable) entry.getKey(), null,
+ rentry.getDiff(),
+ entry.getBackupNodes());
+ } catch (IOException x) {
+ log.error("Unable to diff object. Will replicate the entire object instead.", x);
+ } finally {
+ rentry.unlock();
+ }
+
+ }
+ if (msg == null) {
+ //construct a complete
+ msg = new MapMessage(mapContextName, MapMessage.MSG_BACKUP,
+ false, (Serializable) entry.getKey(),
+ (Serializable) entry.getValue(),
+ null, entry.getBackupNodes());
+
+ }
+ try {
+ if ( channel!=null && entry.getBackupNodes()!= null && entry.getBackupNodes().length > 0 ) {
+ channel.send(entry.getBackupNodes(), msg, channelSendOptions);
+ }
+ } catch (ChannelException x) {
+ log.error("Unable to replicate data.", x);
+ }
+ } //end if
+
+ }
+
+ /**
+ * This can be invoked by a periodic thread to replicate out any changes.
+ * For maps that don't store objects that implement ReplicatedMapEntry, this
+ * method should be used infrequently to avoid large amounts of data transfer
+ * @param complete boolean
+ */
+ public void replicate(boolean complete) {
+ Iterator i = super.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry e = (Map.Entry) i.next();
+ replicate(e.getKey(), complete);
+ } //while
+
+ }
+
+ public void transferState() {
+ try {
+ Member[] members = getMapMembers();
+ Member backup = members.length > 0 ? (Member) members[0] : null;
+ if (backup != null) {
+ MapMessage msg = new MapMessage(mapContextName, MapMessage.MSG_STATE, false,
+ null, null, null, null);
+ Response[] resp = rpcChannel.send(new Member[] {backup}, msg, rpcChannel.FIRST_REPLY, channelSendOptions, rpcTimeout);
+ if (resp.length > 0) {
+ synchronized (stateMutex) {
+ msg = (MapMessage) resp[0].getMessage();
+ msg.deserialize(getExternalLoaders());
+ ArrayList list = (ArrayList) msg.getValue();
+ for (int i = 0; i < list.size(); i++) {
+ messageReceived( (Serializable) list.get(i), resp[0].getSource());
+ } //for
+ }
+ } else {
+ log.warn("Transfer state, 0 replies, probably a timeout.");
+ }
+ }
+ } catch (ChannelException x) {
+ log.error("Unable to transfer LazyReplicatedMap state.", x);
+ } catch (IOException x) {
+ log.error("Unable to transfer LazyReplicatedMap state.", x);
+ } catch (ClassNotFoundException x) {
+ log.error("Unable to transfer LazyReplicatedMap state.", x);
+ }
+ stateTransferred = true;
+ }
+
+ /**
+ * @todo implement state transfer
+ * @param msg Serializable
+ * @return Serializable - null if no reply should be sent
+ */
+ public Serializable replyRequest(Serializable msg, final Member sender) {
+ if (! (msg instanceof MapMessage))return null;
+ MapMessage mapmsg = (MapMessage) msg;
+
+ //map init request
+ if (mapmsg.getMsgType() == mapmsg.MSG_INIT) {
+ mapmsg.setBackUpNodes(wrap(channel.getLocalMember(false)));
+ return mapmsg;
+ }
+
+ //map start request
+ if (mapmsg.getMsgType() == mapmsg.MSG_START) {
+ mapmsg.setBackUpNodes(wrap(channel.getLocalMember(false)));
+ mapMemberAdded(sender);
+ return mapmsg;
+ }
+
+ //backup request
+ if (mapmsg.getMsgType() == mapmsg.MSG_RETRIEVE_BACKUP) {
+ MapEntry entry = (MapEntry)super.get(mapmsg.getKey());
+ if (entry == null || (!entry.isSerializable()) )return null;
+ mapmsg.setValue( (Serializable) entry.getValue());
+ return mapmsg;
+ }
+
+ //state transfer request
+ if (mapmsg.getMsgType() == mapmsg.MSG_STATE) {
+ synchronized (stateMutex) { //make sure we dont do two things at the same time
+ ArrayList list = new ArrayList();
+ Iterator i = super.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry e = (Map.Entry) i.next();
+ MapEntry entry = (MapEntry) e.getValue();
+ if ( entry.isSerializable() ) {
+ MapMessage me = new MapMessage(mapContextName, MapMessage.MSG_PROXY,
+ false, (Serializable) entry.getKey(), null, null, entry.getBackupNodes());
+ list.add(me);
+ }
+ }
+ mapmsg.setValue(list);
+ return mapmsg;
+
+ } //synchronized
+ }
+
+ return null;
+
+ }
+
+ /**
+ * If the reply has already been sent to the requesting thread,
+ * the rpc callback can handle any data that comes in after the fact.
+ * @param msg Serializable
+ * @param sender Member
+ */
+ public void leftOver(Serializable msg, Member sender) {
+ //left over membership messages
+ if (! (msg instanceof MapMessage))return;
+
+ MapMessage mapmsg = (MapMessage) msg;
+ try {
+ mapmsg.deserialize(getExternalLoaders());
+ if (mapmsg.getMsgType() == MapMessage.MSG_START) {
+ mapMemberAdded(mapmsg.getBackupNodes()[0]);
+ } else if (mapmsg.getMsgType() == MapMessage.MSG_INIT) {
+ memberAlive(mapmsg.getBackupNodes()[0]);
+ }
+ } catch (IOException x ) {
+ log.error("Unable to deserialize MapMessage.",x);
+ } catch (ClassNotFoundException x ) {
+ log.error("Unable to deserialize MapMessage.",x);
+ }
+ }
+
+ public void messageReceived(Serializable msg, Member sender) {
+ if (! (msg instanceof MapMessage)) return;
+
+ MapMessage mapmsg = (MapMessage) msg;
+ if ( log.isTraceEnabled() ) {
+ log.trace("Map["+mapname+"] received message:"+mapmsg);
+ }
+
+ try {
+ mapmsg.deserialize(getExternalLoaders());
+ } catch (IOException x) {
+ log.error("Unable to deserialize MapMessage.", x);
+ return;
+ } catch (ClassNotFoundException x) {
+ log.error("Unable to deserialize MapMessage.", x);
+ return;
+ }
+ if ( log.isTraceEnabled() )
+ log.trace("Map message received from:"+sender.getName()+" msg:"+mapmsg);
+ if (mapmsg.getMsgType() == MapMessage.MSG_START) {
+ mapMemberAdded(mapmsg.getBackupNodes()[0]);
+ }
+
+ if (mapmsg.getMsgType() == MapMessage.MSG_STOP) {
+ memberDisappeared(mapmsg.getBackupNodes()[0]);
+ }
+
+ if (mapmsg.getMsgType() == MapMessage.MSG_PROXY) {
+ MapEntry entry = (MapEntry)super.get(mapmsg.getKey());
+ if ( entry==null ) {
+ entry = new MapEntry(mapmsg.getKey(), mapmsg.getValue());
+ entry.setBackup(false);
+ entry.setProxy(true);
+ entry.setBackupNodes(mapmsg.getBackupNodes());
+ super.put(entry.getKey(), entry);
+ } else {
+ entry.setProxy(true);
+ entry.setBackup(false);
+ entry.setBackupNodes(mapmsg.getBackupNodes());
+ }
+ }
+
+ if (mapmsg.getMsgType() == MapMessage.MSG_REMOVE) {
+ super.remove(mapmsg.getKey());
+ }
+
+ if (mapmsg.getMsgType() == MapMessage.MSG_BACKUP) {
+ MapEntry entry = (MapEntry)super.get(mapmsg.getKey());
+ if (entry == null) {
+ entry = new MapEntry(mapmsg.getKey(), mapmsg.getValue());
+ entry.setBackup(true);
+ entry.setProxy(false);
+ entry.setBackupNodes(mapmsg.getBackupNodes());
+ if (mapmsg.getValue()!=null && mapmsg.getValue() instanceof ReplicatedMapEntry ) {
+ ((ReplicatedMapEntry)mapmsg.getValue()).setOwner(getMapOwner());
+ }
+ } else {
+ entry.setBackup(true);
+ entry.setProxy(false);
+ entry.setBackupNodes(mapmsg.getBackupNodes());
+ if (entry.getValue() instanceof ReplicatedMapEntry) {
+ ReplicatedMapEntry diff = (ReplicatedMapEntry) entry.getValue();
+ if (mapmsg.isDiff()) {
+ try {
+ diff.lock();
+ diff.applyDiff(mapmsg.getDiffValue(), 0, mapmsg.getDiffValue().length);
+ } catch (Exception x) {
+ log.error("Unable to apply diff to key:" + entry.getKey(), x);
+ } finally {
+ diff.unlock();
+ }
+ } else {
+ if ( mapmsg.getValue()!=null ) entry.setValue(mapmsg.getValue());
+ ((ReplicatedMapEntry)entry.getValue()).setOwner(getMapOwner());
+ } //end if
+ } else if (mapmsg.getValue() instanceof ReplicatedMapEntry) {
+ ReplicatedMapEntry re = (ReplicatedMapEntry)mapmsg.getValue();
+ re.setOwner(getMapOwner());
+ entry.setValue(re);
+ } else {
+ if ( mapmsg.getValue()!=null ) entry.setValue(mapmsg.getValue());
+ } //end if
+ } //end if
+ super.put(entry.getKey(), entry);
+ } //end if
+ }
+
+ public boolean accept(Serializable msg, Member sender) {
+ boolean result = false;
+ if (msg instanceof MapMessage) {
+ if ( log.isTraceEnabled() ) log.trace("Map["+mapname+"] accepting...."+msg);
+ result = Arrays.equals(mapContextName, ( (MapMessage) msg).getMapId());
+ if ( log.isTraceEnabled() ) log.trace("Msg["+mapname+"] accepted["+result+"]...."+msg);
+ }
+ return result;
+ }
+
+ public void mapMemberAdded(Member member) {
+ if ( member.equals(getChannel().getLocalMember(false)) ) return;
+ boolean memberAdded = false;
+ //select a backup node if we don't have one
+ synchronized (mapMembers) {
+ if (!mapMembers.containsKey(member) ) {
+ mapMembers.put(member, new Long(System.currentTimeMillis()));
+ memberAdded = true;
+ }
+ }
+ if ( memberAdded ) {
+ synchronized (stateMutex) {
+ Iterator i = super.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry e = (Map.Entry) i.next();
+ MapEntry entry = (MapEntry) e.getValue();
+ if ( entry == null ) continue;
+ if (entry.isPrimary() && (entry.getBackupNodes() == null || entry.getBackupNodes().length == 0)) {
+ try {
+ Member[] backup = publishEntryInfo(entry.getKey(), entry.getValue());
+ entry.setBackupNodes(backup);
+ } catch (ChannelException x) {
+ log.error("Unable to select backup node.", x);
+ } //catch
+ } //end if
+ } //while
+ } //synchronized
+ }//end if
+ }
+
+ public boolean inSet(Member m, Member[] set) {
+ if ( set == null ) return false;
+ boolean result = false;
+ for (int i=0; i<set.length && (!result); i++ )
+ if ( m.equals(set[i]) ) result = true;
+ return result;
+ }
+
+ public Member[] excludeFromSet(Member[] mbrs, Member[] set) {
+ ArrayList result = new ArrayList();
+ for (int i=0; i<set.length; i++ ) {
+ boolean include = true;
+ for (int j=0; j<mbrs.length; j++ )
+ if ( mbrs[j].equals(set[i]) ) include = false;
+ if ( include ) result.add(set[i]);
+ }
+ return (Member[])result.toArray(new Member[result.size()]);
+ }
+
+ public void memberAdded(Member member) {
+ //do nothing
+ }
+
+ public void memberDisappeared(Member member) {
+ boolean removed = false;
+ synchronized (mapMembers) {
+ removed = (mapMembers.remove(member) != null );
+ }
+ Iterator i = super.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry e = (Map.Entry) i.next();
+ MapEntry entry = (MapEntry) e.getValue();
+ if (entry.isPrimary() && inSet(member,entry.getBackupNodes())) {
+ try {
+ Member[] backup = publishEntryInfo(entry.getKey(), entry.getValue());
+ entry.setBackupNodes(backup);
+ } catch (ChannelException x) {
+ log.error("Unable to relocate[" + entry.getKey() + "] to a new backup node", x);
+ }
+ } //end if
+ } //while
+ }
+
+ public int getNextBackupIndex() {
+ int size = mapMembers.size();
+ if (mapMembers.size() == 0)return -1;
+ int node = currentNode++;
+ if (node >= size) {
+ node = 0;
+ currentNode = 0;
+ }
+ return node;
+ }
+ public Member getNextBackupNode() {
+ Member[] members = getMapMembers();
+ int node = getNextBackupIndex();
+ if ( members.length == 0 || node==-1) return null;
+ if ( node >= members.length ) node = 0;
+ return members[node];
+ }
+
+ protected abstract Member[] publishEntryInfo(Object key, Object value) throws ChannelException;
+
+ public void heartbeat() {
+ try {
+ ping(accessTimeout);
+ }catch ( Exception x ) {
+ log.error("Unable to send AbstractReplicatedMap.ping message",x);
+ }
+ }
+
+//------------------------------------------------------------------------------
+// METHODS TO OVERRIDE
+//------------------------------------------------------------------------------
+
+ /**
+ * Removes an object from this map, it will also remove it from
+ *
+ * @param key Object
+ * @return Object
+ */
+ public Object remove(Object key) {
+ MapEntry entry = (MapEntry)super.remove(key);
+
+ try {
+ MapMessage msg = new MapMessage(getMapContextName(),MapMessage.MSG_REMOVE,false,(Serializable)key,null,null,null);
+ getChannel().send(getMapMembers(), msg,getChannelSendOptions());
+ } catch ( ChannelException x ) {
+ log.error("Unable to replicate out data for a LazyReplicatedMap.remove operation",x);
+ }
+ return entry!=null?entry.getValue():null;
+ }
+
+ public Object get(Object key) {
+ MapEntry entry = (MapEntry)super.get(key);
+ if (log.isTraceEnabled()) log.trace("Requesting id:"+key+" entry:"+entry);
+ if ( entry == null ) return null;
+ if ( !entry.isPrimary() ) {
+ //if the message is not primary, we need to retrieve the latest value
+ try {
+ Member[] backup = null;
+ MapMessage msg = null;
+ if ( !entry.isBackup() ) {
+ //make sure we don't retrieve from ourselves
+ msg = new MapMessage(getMapContextName(), MapMessage.MSG_RETRIEVE_BACKUP, false,
+ (Serializable) key, null, null, null);
+ Response[] resp = getRpcChannel().send(entry.getBackupNodes(),msg, this.getRpcChannel().FIRST_REPLY, Channel.SEND_OPTIONS_DEFAULT, getRpcTimeout());
+ if (resp == null || resp.length == 0) {
+ //no responses
+ log.warn("Unable to retrieve remote object for key:" + key);
+ return null;
+ }
+ msg = (MapMessage) resp[0].getMessage();
+ msg.deserialize(getExternalLoaders());
+ backup = entry.getBackupNodes();
+ if ( entry.getValue() instanceof ReplicatedMapEntry ) {
+ ReplicatedMapEntry val = (ReplicatedMapEntry)entry.getValue();
+ val.setOwner(getMapOwner());
+ }
+ if ( msg.getValue()!=null ) entry.setValue(msg.getValue());
+ }
+ if (entry.isBackup()) {
+ //select a new backup node
+ backup = publishEntryInfo(key, entry.getValue());
+ } else if ( entry.isProxy() ) {
+ //invalidate the previous primary
+ msg = new MapMessage(getMapContextName(),MapMessage.MSG_PROXY,false,(Serializable)key,null,null,backup);
+ Member[] dest = getMapMembersExcl(backup);
+ if ( dest!=null && dest.length >0) {
+ getChannel().send(dest, msg, getChannelSendOptions());
+ }
+ }
+
+ entry.setBackupNodes(backup);
+ entry.setBackup(false);
+ entry.setProxy(false);
+
+
+ } catch (Exception x) {
+ log.error("Unable to replicate out data for a LazyReplicatedMap.get operation", x);
+ return null;
+ }
+ }
+ if (log.isTraceEnabled()) log.trace("Requesting id:"+key+" result:"+entry.getValue());
+ return entry.getValue();
+ }
+
+
+ protected void printMap(String header) {
+ try {
+ System.out.println("\nDEBUG MAP:"+header);
+ System.out.println("Map["+ new String(mapContextName, chset) + ", Map Size:" + super.size());
+ Member[] mbrs = getMapMembers();
+ for ( int i=0; i<mbrs.length;i++ ) {
+ System.out.println("Mbr["+(i+1)+"="+mbrs[i].getName());
+ }
+ Iterator i = super.entrySet().iterator();
+ int cnt = 0;
+
+ while (i.hasNext()) {
+ Map.Entry e = (Map.Entry) i.next();
+ System.out.println( (++cnt) + ". " + e.getValue());
+ }
+ System.out.println("EndMap]\n\n");
+ }catch ( Exception ignore) {
+ ignore.printStackTrace();
+ }
+ }
+
+ /**
+ * Returns true if the key has an entry in the map.
+ * The entry can be a proxy or a backup entry, invoking <code>get(key)</code>
+ * will make this entry primary for the group
+ * @param key Object
+ * @return boolean
+ */
+ public boolean containsKey(Object key) {
+ return super.containsKey(key);
+ }
+
+
+ public Object put(Object key, Object value) {
+ MapEntry entry = new MapEntry(key,value);
+ entry.setBackup(false);
+ entry.setProxy(false);
+
+ Object old = null;
+
+ //make sure that any old values get removed
+ if ( containsKey(key) ) old = remove(key);
+ try {
+ Member[] backup = publishEntryInfo(key, value);
+ entry.setBackupNodes(backup);
+ } catch (ChannelException x) {
+ log.error("Unable to replicate out data for a LazyReplicatedMap.put operation", x);
+ }
+ super.put(key,entry);
+ return old;
+ }
+
+
+ /**
+ * Copies all values from one map to this instance
+ * @param m Map
+ */
+ public void putAll(Map m) {
+ Iterator i = m.entrySet().iterator();
+ while ( i.hasNext() ) {
+ Map.Entry entry = (Map.Entry)i.next();
+ put(entry.getKey(),entry.getValue());
+ }
+ }
+
+ public void clear() {
+ //only delete active keys
+ Iterator keys = keySet().iterator();
+ while ( keys.hasNext() ) remove(keys.next());
+ }
+
+ public boolean containsValue(Object value) {
+ if ( value == null ) {
+ return super.containsValue(value);
+ } else {
+ Iterator i = super.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry e = (Map.Entry) i.next();
+ MapEntry entry = (MapEntry) e.getValue();
+ if (entry.isPrimary() && value.equals(entry.getValue())) return true;
+ }//while
+ return false;
+ }//end if
+ }
+
+ public Object clone() {
+ throw new UnsupportedOperationException("This operation is not valid on a replicated map");
+ }
+
+ /**
+ * Returns the entire contents of the map
+ * Map.Entry.getValue() will return a LazyReplicatedMap.MapEntry object containing all the information
+ * about the object.
+ * @return Set
+ */
+ public Set entrySetFull() {
+ return super.entrySet();
+ }
+
+ public Set keySetFull() {
+ return super.keySet();
+ }
+
+ public int sizeFull() {
+ return super.size();
+ }
+
+ public Set entrySet() {
+ LinkedHashSet set = new LinkedHashSet(super.size());
+ Iterator i = super.entrySet().iterator();
+ while ( i.hasNext() ) {
+ Map.Entry e = (Map.Entry)i.next();
+ MapEntry entry = (MapEntry)e.getValue();
+ if ( entry.isPrimary() ) set.add(entry);
+ }
+ return Collections.unmodifiableSet(set);
+ }
+
+ public Set keySet() {
+ //todo implement
+ //should only return keys where this is active.
+ LinkedHashSet set = new LinkedHashSet(super.size());
+ Iterator i = super.entrySet().iterator();
+ while ( i.hasNext() ) {
+ Map.Entry e = (Map.Entry)i.next();
+ MapEntry entry = (MapEntry)e.getValue();
+ if ( entry.isPrimary() ) set.add(entry.getKey());
+ }
+ return Collections.unmodifiableSet(set);
+ }
+
+
+ public int size() {
+ //todo, implement a counter variable instead
+ //only count active members in this node
+ int counter = 0;
+ Object[] items = super.entrySet().toArray();
+ for (int i=0; i<items.length; i++ ) {
+ Map.Entry e = (Map.Entry) items[i];
+ if ( e != null ) {
+ MapEntry entry = (MapEntry) e.getValue();
+ if (entry.isPrimary() && entry.getValue() != null) counter++;
+ }
+ }
+ return counter;
+ }
+
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return false;
+ }
+
+ public boolean isEmpty() {
+ return size()==0;
+ }
+
+ public Collection values() {
+ ArrayList values = new ArrayList(super.size());
+ Iterator i = super.entrySet().iterator();
+ while ( i.hasNext() ) {
+ Map.Entry e = (Map.Entry)i.next();
+ MapEntry entry = (MapEntry)e.getValue();
+ if ( entry.isPrimary() && entry.getValue()!=null) values.add(entry.getValue());
+ }
+ return Collections.unmodifiableCollection(values);
+ }
+
+
+//------------------------------------------------------------------------------
+// Map Entry class
+//------------------------------------------------------------------------------
+ public static class MapEntry implements Map.Entry {
+ private boolean backup;
+ private boolean proxy;
+ private Member[] backupNodes;
+
+ private Object key;
+ private Object value;
+
+ public MapEntry(Object key, Object value) {
+ setKey(key);
+ setValue(value);
+
+ }
+
+ public boolean isKeySerializable() {
+ return (key == null) || (key instanceof Serializable);
+ }
+
+ public boolean isValueSerializable() {
+ return (value==null) || (value instanceof Serializable);
+ }
+
+ public boolean isSerializable() {
+ return isKeySerializable() && isValueSerializable();
+ }
+
+ public boolean isBackup() {
+ return backup;
+ }
+
+ public void setBackup(boolean backup) {
+ this.backup = backup;
+ }
+
+ public boolean isProxy() {
+ return proxy;
+ }
+
+ public boolean isPrimary() {
+ return ( (!proxy) && (!backup));
+ }
+
+ public void setProxy(boolean proxy) {
+ this.proxy = proxy;
+ }
+
+ public boolean isDiffable() {
+ return (value instanceof ReplicatedMapEntry) &&
+ ((ReplicatedMapEntry)value).isDiffable();
+ }
+
+ public void setBackupNodes(Member[] nodes) {
+ this.backupNodes = nodes;
+ }
+
+ public Member[] getBackupNodes() {
+ return backupNodes;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public Object setValue(Object value) {
+ Object old = this.value;
+ this.value = (Serializable) value;
+ return old;
+ }
+
+ public Object getKey() {
+ return key;
+ }
+
+ public Object setKey(Object key) {
+ Object old = this.key;
+ this.key = (Serializable)key;
+ return old;
+ }
+
+ public int hashCode() {
+ return key.hashCode();
+ }
+
+ public boolean equals(Object o) {
+ return key.equals(o);
+ }
+
+ /**
+ * apply a diff, or an entire object
+ * @param data byte[]
+ * @param offset int
+ * @param length int
+ * @param diff boolean
+ * @throws IOException
+ * @throws ClassNotFoundException
+ */
+ public void apply(byte[] data, int offset, int length, boolean diff) throws IOException, ClassNotFoundException {
+ if (isDiffable() && diff) {
+ ReplicatedMapEntry rentry = (ReplicatedMapEntry) value;
+ try {
+ rentry.lock();
+ rentry.applyDiff(data, offset, length);
+ } finally {
+ rentry.unlock();
+ }
+ } else if (length == 0) {
+ value = null;
+ proxy = true;
+ } else {
+ value = XByteBuffer.deserialize(data, offset, length);
+ }
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer("MapEntry[key:");
+ buf.append(getKey()).append("; ");
+ buf.append("value:").append(getValue()).append("; ");
+ buf.append("primary:").append(isPrimary()).append("; ");
+ buf.append("backup:").append(isBackup()).append("; ");
+ buf.append("proxy:").append(isProxy()).append(";]");
+ return buf.toString();
+ }
+
+ }
+
+//------------------------------------------------------------------------------
+// map message to send to and from other maps
+//------------------------------------------------------------------------------
+
+ public static class MapMessage implements Serializable {
+ public static final int MSG_BACKUP = 1;
+ public static final int MSG_RETRIEVE_BACKUP = 2;
+ public static final int MSG_PROXY = 3;
+ public static final int MSG_REMOVE = 4;
+ public static final int MSG_STATE = 5;
+ public static final int MSG_START = 6;
+ public static final int MSG_STOP = 7;
+ public static final int MSG_INIT = 8;
+
+ private byte[] mapId;
+ private int msgtype;
+ private boolean diff;
+ private transient Serializable key;
+ private transient Serializable value;
+ private byte[] valuedata;
+ private byte[] keydata;
+ private byte[] diffvalue;
+ private Member[] nodes;
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer("MapMessage[context=");
+ buf.append(new String(mapId));
+ buf.append("; type=");
+ buf.append(getTypeDesc());
+ buf.append("; key=");
+ buf.append(key);
+ buf.append("; value=");
+ buf.append(value);
+ return buf.toString();
+ }
+
+ public String getTypeDesc() {
+ switch (msgtype) {
+ case MSG_BACKUP: return "MSG_BACKUP";
+ case MSG_RETRIEVE_BACKUP: return "MSG_RETRIEVE_BACKUP";
+ case MSG_PROXY: return "MSG_PROXY";
+ case MSG_REMOVE: return "MSG_REMOVE";
+ case MSG_STATE: return "MSG_STATE";
+ case MSG_START: return "MSG_START";
+ case MSG_STOP: return "MSG_STOP";
+ case MSG_INIT: return "MSG_INIT";
+ default : return "UNKNOWN";
+ }
+ }
+
+ public MapMessage() {}
+
+ public MapMessage(byte[] mapId,int msgtype, boolean diff,
+ Serializable key, Serializable value,
+ byte[] diffvalue, Member[] nodes) {
+ this.mapId = mapId;
+ this.msgtype = msgtype;
+ this.diff = diff;
+ this.key = key;
+ this.value = value;
+ this.diffvalue = diffvalue;
+ this.nodes = nodes;
+ setValue(value);
+ setKey(key);
+ }
+
+ public void deserialize(ClassLoader[] cls) throws IOException, ClassNotFoundException {
+ key(cls);
+ value(cls);
+ }
+
+ public int getMsgType() {
+ return msgtype;
+ }
+
+ public boolean isDiff() {
+ return diff;
+ }
+
+ public Serializable getKey() {
+ try {
+ return key(null);
+ } catch ( Exception x ) {
+ log.error("Deserialization error of the MapMessage.key",x);
+ return null;
+ }
+ }
+
+ public Serializable key(ClassLoader[] cls) throws IOException, ClassNotFoundException {
+ if ( key!=null ) return key;
+ if ( keydata == null || keydata.length == 0 ) return null;
+ key = XByteBuffer.deserialize(keydata,0,keydata.length,cls);
+ keydata = null;
+ return key;
+ }
+
+ public byte[] getKeyData() {
+ return keydata;
+ }
+
+ public Serializable getValue() {
+ try {
+ return value(null);
+ } catch ( Exception x ) {
+ log.error("Deserialization error of the MapMessage.value",x);
+ return null;
+ }
+ }
+
+ public Serializable value(ClassLoader[] cls) throws IOException, ClassNotFoundException {
+ if ( value!=null ) return value;
+ if ( valuedata == null || valuedata.length == 0 ) return null;
+ value = XByteBuffer.deserialize(valuedata,0,valuedata.length,cls);
+ valuedata = null;;
+ return value;
+ }
+
+ public byte[] getValueData() {
+ return valuedata;
+ }
+
+ public byte[] getDiffValue() {
+ return diffvalue;
+ }
+
+ public Member[] getBackupNodes() {
+ return nodes;
+ }
+
+ private void setBackUpNodes(Member[] nodes) {
+ this.nodes = nodes;
+ }
+
+ public byte[] getMapId() {
+ return mapId;
+ }
+
+ public void setValue(Serializable value) {
+ try {
+ if ( value != null ) valuedata = XByteBuffer.serialize(value);
+ this.value = value;
+ }catch ( IOException x ) {
+ throw new RuntimeException(x);
+ }
+ }
+
+ public void setKey(Serializable key) {
+ try {
+ if (key != null) keydata = XByteBuffer.serialize(key);
+ this.key = key;
+ } catch (IOException x) {
+ throw new RuntimeException(x);
+ }
+ }
+
+ protected Member[] readMembers(ObjectInput in) throws IOException, ClassNotFoundException {
+ int nodecount = in.readInt();
+ Member[] members = new Member[nodecount];
+ for ( int i=0; i<members.length; i++ ) {
+ byte[] d = new byte[in.readInt()];
+ in.read(d);
+ if (d.length > 0) members[i] = MemberImpl.getMember(d);
+ }
+ return members;
+ }
+
+ protected void writeMembers(ObjectOutput out,Member[] members) throws IOException {
+ if ( members == null ) members = new Member[0];
+ out.writeInt(members.length);
+ for (int i=0; i<members.length; i++ ) {
+ if ( members[i] != null ) {
+ byte[] d = members[i] != null ? ( (MemberImpl)members[i]).getData(false) : new byte[0];
+ out.writeInt(d.length);
+ out.write(d);
+ }
+ }
+ }
+
+
+ /**
+ * shallow clone
+ * @return Object
+ */
+ public Object clone() {
+ MapMessage msg = new MapMessage(this.mapId, this.msgtype, this.diff, this.key, this.value, this.diffvalue, this.nodes);
+ msg.keydata = this.keydata;
+ msg.valuedata = this.valuedata;
+ return msg;
+ }
+ } //MapMessage
+
+
+ public Channel getChannel() {
+ return channel;
+ }
+
+ public byte[] getMapContextName() {
+ return mapContextName;
+ }
+
+ public RpcChannel getRpcChannel() {
+ return rpcChannel;
+ }
+
+ public long getRpcTimeout() {
+ return rpcTimeout;
+ }
+
+ public Object getStateMutex() {
+ return stateMutex;
+ }
+
+ public boolean isStateTransferred() {
+ return stateTransferred;
+ }
+
+ public Object getMapOwner() {
+ return mapOwner;
+ }
+
+ public ClassLoader[] getExternalLoaders() {
+ return externalLoaders;
+ }
+
+ public int getChannelSendOptions() {
+ return channelSendOptions;
+ }
+
+ public long getAccessTimeout() {
+ return accessTimeout;
+ }
+
+ public void setMapOwner(Object mapOwner) {
+ this.mapOwner = mapOwner;
+ }
+
+ public void setExternalLoaders(ClassLoader[] externalLoaders) {
+ this.externalLoaders = externalLoaders;
+ }
+
+ public void setChannelSendOptions(int channelSendOptions) {
+ this.channelSendOptions = channelSendOptions;
+ }
+
+ public void setAccessTimeout(long accessTimeout) {
+ this.accessTimeout = accessTimeout;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.tipis;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.group.RpcCallback;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.catalina.tribes.UniqueId;
+
+/**
+ * A smart implementation of a stateful replicated map. uses primary/secondary backup strategy.
+ * One node is always the primary and one node is always the backup.
+ * This map is synchronized across a cluster, and only has one backup member.<br/>
+ * A perfect usage for this map would be a session map for a session manager in a clustered environment.<br/>
+ * The only way to modify this list is to use the <code>put, putAll, remove</code> methods.
+ * entrySet, entrySetFull, keySet, keySetFull, returns all non modifiable sets.<br><br>
+ * If objects (values) in the map change without invoking <code>put()</code> or <code>remove()</code>
+ * the data can be distributed using two different methods:<br>
+ * <code>replicate(boolean)</code> and <code>replicate(Object, boolean)</code><br>
+ * These two methods are very important two understand. The map can work with two set of value objects:<br>
+ * 1. Serializable - the entire object gets serialized each time it is replicated<br>
+ * 2. ReplicatedMapEntry - this interface allows for a isDirty() flag and to replicate diffs if desired.<br>
+ * Implementing the <code>ReplicatedMapEntry</code> interface allows you to decide what objects
+ * get replicated and how much data gets replicated each time.<br>
+ * If you implement a smart AOP mechanism to detect changes in underlying objects, you can replicate
+ * only those changes by implementing the ReplicatedMapEntry interface, and return true when isDiffable()
+ * is invoked.<br><br>
+ *
+ * This map implementation doesn't have a background thread running to replicate changes.
+ * If you do have changes without invoking put/remove then you need to invoke one of the following methods:
+ * <ul>
+ * <li><code>replicate(Object,boolean)</code> - replicates only the object that belongs to the key</li>
+ * <li><code>replicate(boolean)</code> - Scans the entire map for changes and replicates data</li>
+ * </ul>
+ * the <code>boolean</code> value in the <code>replicate</code> method used to decide
+ * whether to only replicate objects that implement the <code>ReplicatedMapEntry</code> interface
+ * or to replicate all objects. If an object doesn't implement the <code>ReplicatedMapEntry</code> interface
+ * each time the object gets replicated the entire object gets serialized, hence a call to <code>replicate(true)</code>
+ * will replicate all objects in this map that are using this node as primary.
+ *
+ * <br><br><b>REMBER TO CALL <code>breakdown()</code> or <code>finalize()</code> when you are done with the map to
+ * avoid memory leaks.<br><br>
+ * @todo implement periodic sync/transfer thread
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class LazyReplicatedMap extends AbstractReplicatedMap
+ implements RpcCallback, ChannelListener, MembershipListener {
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LazyReplicatedMap.class);
+
+
+
+//------------------------------------------------------------------------------
+// CONSTRUCTORS / DESTRUCTORS
+//------------------------------------------------------------------------------
+ /**
+ * Creates a new map
+ * @param channel The channel to use for communication
+ * @param timeout long - timeout for RPC messags
+ * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+ * @param initialCapacity int - the size of this map, see HashMap
+ * @param loadFactor float - load factor, see HashMap
+ */
+ public LazyReplicatedMap(Object owner, Channel channel, long timeout, String mapContextName, int initialCapacity, float loadFactor, ClassLoader[] cls) {
+ super(owner,channel,timeout,mapContextName,initialCapacity,loadFactor, Channel.SEND_OPTIONS_DEFAULT,cls);
+ }
+
+ /**
+ * Creates a new map
+ * @param channel The channel to use for communication
+ * @param timeout long - timeout for RPC messags
+ * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+ * @param initialCapacity int - the size of this map, see HashMap
+ */
+ public LazyReplicatedMap(Object owner, Channel channel, long timeout, String mapContextName, int initialCapacity, ClassLoader[] cls) {
+ super(owner, channel,timeout,mapContextName,initialCapacity, LazyReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls);
+ }
+
+ /**
+ * Creates a new map
+ * @param channel The channel to use for communication
+ * @param timeout long - timeout for RPC messags
+ * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+ */
+ public LazyReplicatedMap(Object owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls) {
+ super(owner, channel,timeout,mapContextName, LazyReplicatedMap.DEFAULT_INITIAL_CAPACITY,LazyReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls);
+ }
+
+
+
+
+
+//------------------------------------------------------------------------------
+// METHODS TO OVERRIDE
+//------------------------------------------------------------------------------
+ /**
+ * publish info about a map pair (key/value) to other nodes in the cluster
+ * @param key Object
+ * @param value Object
+ * @return Member - the backup node
+ * @throws ChannelException
+ */
+ protected Member[] publishEntryInfo(Object key, Object value) throws ChannelException {
+ if (! (key instanceof Serializable && value instanceof Serializable) ) return new Member[0];
+ Member[] members = getMapMembers();
+ int firstIdx = getNextBackupIndex();
+ int nextIdx = firstIdx;
+ Member[] backup = new Member[0];
+
+ //there are no backups
+ if ( members.length == 0 || firstIdx == -1 ) return backup;
+
+ boolean success = false;
+ do {
+ //select a backup node
+ Member next = members[firstIdx];
+
+ //increment for the next round of back up selection
+ nextIdx = firstIdx + 1;
+ if ( nextIdx >= members.length ) nextIdx = 0;
+
+ if (next == null) {
+ continue;
+ }
+ MapMessage msg = null;
+ try {
+ backup = wrap(next);
+ //publish the backup data to one node
+ msg = new MapMessage(getMapContextName(), MapMessage.MSG_BACKUP, false,
+ (Serializable) key, (Serializable) value, null, backup);
+ if ( log.isTraceEnabled() )
+ log.trace("Publishing backup data:"+msg+" to: "+next.getName());
+ UniqueId id = getChannel().send(backup, msg, getChannelSendOptions());
+ if ( log.isTraceEnabled() )
+ log.trace("Data published:"+msg+" msg Id:"+id);
+ //we published out to a backup, mark the test success
+ success = true;
+ }catch ( ChannelException x ) {
+ log.error("Unable to replicate backup key:"+key+" to backup:"+next+". Reason:"+x.getMessage(),x);
+ }
+ try {
+ //publish the data out to all nodes
+ Member[] proxies = excludeFromSet(backup, getMapMembers());
+ if (success && proxies.length > 0 ) {
+ msg = new MapMessage(getMapContextName(), MapMessage.MSG_PROXY, false,
+ (Serializable) key, null, null, backup);
+ if ( log.isTraceEnabled() )
+ log.trace("Publishing proxy data:"+msg+" to: "+Arrays.toNameString(proxies));
+ getChannel().send(proxies, msg, getChannelSendOptions());
+ }
+ }catch ( ChannelException x ) {
+ //log the error, but proceed, this should only happen if a node went down,
+ //and if the node went down, then it can't receive the message, the others
+ //should still get it.
+ log.error("Unable to replicate proxy key:"+key+" to backup:"+next+". Reason:"+x.getMessage(),x);
+ }
+ } while ( !success && (firstIdx!=nextIdx));
+ return backup;
+ }
+
+
+
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.tipis;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.group.RpcCallback;
+
+/**
+ * All-to-all replication for a hash map implementation. Each node in the cluster will carry an identical
+ * copy of the map.<br><br>
+ * This map implementation doesn't have a background thread running to replicate changes.
+ * If you do have changes without invoking put/remove then you need to invoke one of the following methods:
+ * <ul>
+ * <li><code>replicate(Object,boolean)</code> - replicates only the object that belongs to the key</li>
+ * <li><code>replicate(boolean)</code> - Scans the entire map for changes and replicates data</li>
+ * </ul>
+ * the <code>boolean</code> value in the <code>replicate</code> method used to decide
+ * whether to only replicate objects that implement the <code>ReplicatedMapEntry</code> interface
+ * or to replicate all objects. If an object doesn't implement the <code>ReplicatedMapEntry</code> interface
+ * each time the object gets replicated the entire object gets serialized, hence a call to <code>replicate(true)</code>
+ * will replicate all objects in this map that are using this node as primary.
+ *
+ * <br><br><b>REMBER TO CALL <code>breakdown()</code> or <code>finalize()</code> when you are done with the map to
+ * avoid memory leaks.<br><br>
+ * @todo implement periodic sync/transfer thread
+ * @author Filip Hanik
+ * @version 1.0
+ *
+ * @todo memberDisappeared, should do nothing except change map membership
+ * by default it relocates the primary objects
+ */
+public class ReplicatedMap extends AbstractReplicatedMap implements RpcCallback, ChannelListener, MembershipListener {
+
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(ReplicatedMap.class);
+
+//------------------------------------------------------------------------------
+// CONSTRUCTORS / DESTRUCTORS
+//------------------------------------------------------------------------------
+ /**
+ * Creates a new map
+ * @param channel The channel to use for communication
+ * @param timeout long - timeout for RPC messags
+ * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+ * @param initialCapacity int - the size of this map, see HashMap
+ * @param loadFactor float - load factor, see HashMap
+ */
+ public ReplicatedMap(Object owner, Channel channel, long timeout, String mapContextName, int initialCapacity,float loadFactor, ClassLoader[] cls) {
+ super(owner,channel, timeout, mapContextName, initialCapacity, loadFactor, Channel.SEND_OPTIONS_DEFAULT, cls);
+ }
+
+ /**
+ * Creates a new map
+ * @param channel The channel to use for communication
+ * @param timeout long - timeout for RPC messags
+ * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+ * @param initialCapacity int - the size of this map, see HashMap
+ */
+ public ReplicatedMap(Object owner, Channel channel, long timeout, String mapContextName, int initialCapacity, ClassLoader[] cls) {
+ super(owner,channel, timeout, mapContextName, initialCapacity, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls);
+ }
+
+ /**
+ * Creates a new map
+ * @param channel The channel to use for communication
+ * @param timeout long - timeout for RPC messags
+ * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+ */
+ public ReplicatedMap(Object owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls) {
+ super(owner, channel, timeout, mapContextName,AbstractReplicatedMap.DEFAULT_INITIAL_CAPACITY, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls);
+ }
+
+//------------------------------------------------------------------------------
+// METHODS TO OVERRIDE
+//------------------------------------------------------------------------------
+ /**
+ * publish info about a map pair (key/value) to other nodes in the cluster
+ * @param key Object
+ * @param value Object
+ * @return Member - the backup node
+ * @throws ChannelException
+ */
+ protected Member[] publishEntryInfo(Object key, Object value) throws ChannelException {
+ if (! (key instanceof Serializable && value instanceof Serializable) ) return new Member[0];
+ //select a backup node
+ Member[] backup = getMapMembers();
+
+ if (backup == null || backup.length == 0) return null;
+
+ //publish the data out to all nodes
+ MapMessage msg = new MapMessage(getMapContextName(), MapMessage.MSG_BACKUP, false,
+ (Serializable) key, null, null, backup);
+
+ getChannel().send(getMapMembers(), msg, getChannelSendOptions());
+
+ return backup;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.tipis;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ *
+ * For smarter replication, an object can implement this interface to replicate diffs<br>
+ * The replication logic will call the methods in the following order:<br>
+ * <code>
+ * 1. if ( entry.isDirty() ) <br>
+ * try {
+ * 2. entry.lock();<br>
+ * 3. byte[] diff = entry.getDiff();<br>
+ * 4. entry.reset();<br>
+ * } finally {<br>
+ * 5. entry.unlock();<br>
+ * }<br>
+ * }<br>
+ * </code>
+ * <br>
+ * <br>
+ * When the data is deserialized the logic is called in the following order<br>
+ * <code>
+ * 1. ReplicatedMapEntry entry = (ReplicatedMapEntry)objectIn.readObject();<br>
+ * 2. if ( isBackup(entry)||isPrimary(entry) ) entry.setOwner(owner); <br>
+ * </code>
+ * <br>
+ *
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public interface ReplicatedMapEntry extends Serializable {
+
+ /**
+ * Has the object changed since last replication
+ * and is not in a locked state
+ * @return boolean
+ */
+ public boolean isDirty();
+
+ /**
+ * If this returns true, the map will extract the diff using getDiff()
+ * Otherwise it will serialize the entire object.
+ * @return boolean
+ */
+ public boolean isDiffable();
+
+ /**
+ * Returns a diff and sets the dirty map to false
+ * @return byte[]
+ * @throws IOException
+ */
+ public byte[] getDiff() throws IOException;
+
+
+ /**
+ * Applies a diff to an existing object.
+ * @param diff byte[]
+ * @param offset int
+ * @param length int
+ * @throws IOException
+ */
+ public void applyDiff(byte[] diff, int offset, int length) throws IOException, ClassNotFoundException;
+
+ /**
+ * Resets the current diff state and resets the dirty flag
+ */
+ public void resetDiff();
+
+ /**
+ * Lock during serialization
+ */
+ public void lock();
+
+ /**
+ * Unlock after serialization
+ */
+ public void unlock();
+
+ /**
+ * This method is called after the object has been
+ * created on a remote map. On this method,
+ * the object can initialize itself for any data that wasn't
+ *
+ * @param owner Object
+ */
+ public void setOwner(Object owner);
+
+ /**
+ * For accuracy checking, a serialized attribute can contain a version number
+ * This number increases as modifications are made to the data.
+ * The replicated map can use this to ensure accuracy on a periodic basis
+ * @return long - the version number or -1 if the data is not versioned
+ */
+ public long getVersion();
+
+ /**
+ * Forces a certain version to a replicated map entry<br>
+ * @param version long
+ */
+ public void setVersion(long version);
+
+
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.tipis;
+
+import java.io.IOException;
+
+/**
+ * Example usage:
+ * <code><pre>
+ * byte[] data = new byte[1024];
+ * Streamable st = ....;
+ * while ( !st.eof() ) {
+ * int length = st.read(data,0,data.length);
+ * String s = new String(data,0,length);
+ * System.out.println(s);
+ * }
+ * </pre></code>
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public interface Streamable {
+
+ /**
+ * returns true if the stream has reached its end
+ * @return boolean
+ */
+ public boolean eof();
+
+ /**
+ * write data into the byte array starting at offset, maximum bytes read are (data.length-offset)
+ * @param data byte[] - the array to read data into
+ * @param offset int - start position for writing data
+ * @return int - the number of bytes written into the data buffer
+ */
+ public int write(byte[] data, int offset, int length) throws IOException;
+
+ /**
+ * read data into the byte array starting at offset
+ * @param data byte[] - the array to read data into
+ * @param offset int - start position for writing data
+ * @param length - the desired read length
+ * @return int - the number of bytes read from the data buffer
+ */
+ public int read(byte[] data, int offset, int length) throws IOException;
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.catalina.tribes.transport;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.apache.catalina.tribes.Member;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public abstract class AbstractSender implements DataSender {
+
+ private boolean connected = false;
+ private int rxBufSize = 25188;
+ private int txBufSize = 43800;
+ private boolean directBuffer = false;
+ private int keepAliveCount = -1;
+ private int requestCount = 0;
+ private long connectTime;
+ private long keepAliveTime = -1;
+ private long timeout = 3000;
+ private Member destination;
+ private InetAddress address;
+ private int port;
+ private int maxRetryAttempts = 1;//1 resends
+ private int attempt;
+ private boolean tcpNoDelay = true;
+ private boolean soKeepAlive = false;
+ private boolean ooBInline = true;
+ private boolean soReuseAddress = true;
+ private boolean soLingerOn = false;
+ private int soLingerTime = 3;
+ private int soTrafficClass = 0x04 | 0x08 | 0x010;
+ private boolean throwOnFailedAck = false;
+
+ /**
+ * transfers sender properties from one sender to another
+ * @param from AbstractSender
+ * @param to AbstractSender
+ */
+ public static void transferProperties(AbstractSender from, AbstractSender to) {
+ to.rxBufSize = from.rxBufSize;
+ to.txBufSize = from.txBufSize;
+ to.directBuffer = from.directBuffer;
+ to.keepAliveCount = from.keepAliveCount;
+ to.keepAliveTime = from.keepAliveTime;
+ to.timeout = from.timeout;
+ to.destination = from.destination;
+ to.address = from.address;
+ to.port = from.port;
+ to.maxRetryAttempts = from.maxRetryAttempts;
+ to.tcpNoDelay = from.tcpNoDelay;
+ to.soKeepAlive = from.soKeepAlive;
+ to.ooBInline = from.ooBInline;
+ to.soReuseAddress = from.soReuseAddress;
+ to.soLingerOn = from.soLingerOn;
+ to.soLingerTime = from.soLingerTime;
+ to.soTrafficClass = from.soTrafficClass;
+ to.throwOnFailedAck = from.throwOnFailedAck;
+ }
+
+
+ public AbstractSender() {
+
+ }
+
+ /**
+ * connect
+ *
+ * @throws IOException
+ * @todo Implement this org.apache.catalina.tribes.transport.DataSender method
+ */
+ public abstract void connect() throws IOException;
+
+ /**
+ * disconnect
+ *
+ * @todo Implement this org.apache.catalina.tribes.transport.DataSender method
+ */
+ public abstract void disconnect();
+
+ /**
+ * keepalive
+ *
+ * @return boolean
+ * @todo Implement this org.apache.catalina.tribes.transport.DataSender method
+ */
+ public boolean keepalive() {
+ boolean disconnect = false;
+ if ( keepAliveCount >= 0 && requestCount>keepAliveCount ) disconnect = true;
+ else if ( keepAliveTime >= 0 && keepAliveTime> (System.currentTimeMillis()-connectTime) ) disconnect = true;
+ if ( disconnect ) disconnect();
+ return disconnect;
+ }
+
+ protected void setConnected(boolean connected){
+ this.connected = connected;
+ }
+
+ public boolean isConnected() {
+ return connected;
+ }
+
+ public long getConnectTime() {
+ return connectTime;
+ }
+
+ public Member getDestination() {
+ return destination;
+ }
+
+
+ public int getKeepAliveCount() {
+ return keepAliveCount;
+ }
+
+ public long getKeepAliveTime() {
+ return keepAliveTime;
+ }
+
+ public int getRequestCount() {
+ return requestCount;
+ }
+
+ public int getRxBufSize() {
+ return rxBufSize;
+ }
+
+ public long getTimeout() {
+ return timeout;
+ }
+
+ public int getTxBufSize() {
+ return txBufSize;
+ }
+
+ public InetAddress getAddress() {
+ return address;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public int getMaxRetryAttempts() {
+ return maxRetryAttempts;
+ }
+
+ public void setDirectBuffer(boolean directBuffer) {
+ this.directBuffer = directBuffer;
+ }
+
+ public boolean getDirectBuffer() {
+ return this.directBuffer;
+ }
+
+ public int getAttempt() {
+ return attempt;
+ }
+
+ public boolean getTcpNoDelay() {
+ return tcpNoDelay;
+ }
+
+ public boolean getSoKeepAlive() {
+ return soKeepAlive;
+ }
+
+ public boolean getOoBInline() {
+ return ooBInline;
+ }
+
+ public boolean getSoReuseAddress() {
+ return soReuseAddress;
+ }
+
+ public boolean getSoLingerOn() {
+ return soLingerOn;
+ }
+
+ public int getSoLingerTime() {
+ return soLingerTime;
+ }
+
+ public int getSoTrafficClass() {
+ return soTrafficClass;
+ }
+
+ public boolean getThrowOnFailedAck() {
+ return throwOnFailedAck;
+ }
+
+ public void setKeepAliveCount(int keepAliveCount) {
+ this.keepAliveCount = keepAliveCount;
+ }
+
+ public void setKeepAliveTime(long keepAliveTime) {
+ this.keepAliveTime = keepAliveTime;
+ }
+
+ public void setRequestCount(int requestCount) {
+ this.requestCount = requestCount;
+ }
+
+ public void setRxBufSize(int rxBufSize) {
+ this.rxBufSize = rxBufSize;
+ }
+
+ public void setTimeout(long timeout) {
+ this.timeout = timeout;
+ }
+
+ public void setTxBufSize(int txBufSize) {
+ this.txBufSize = txBufSize;
+ }
+
+ public void setConnectTime(long connectTime) {
+ this.connectTime = connectTime;
+ }
+
+ public void setMaxRetryAttempts(int maxRetryAttempts) {
+ this.maxRetryAttempts = maxRetryAttempts;
+ }
+
+ public void setAttempt(int attempt) {
+ this.attempt = attempt;
+ }
+
+ public void setTcpNoDelay(boolean tcpNoDelay) {
+ this.tcpNoDelay = tcpNoDelay;
+ }
+
+ public void setSoKeepAlive(boolean soKeepAlive) {
+ this.soKeepAlive = soKeepAlive;
+ }
+
+ public void setOoBInline(boolean ooBInline) {
+ this.ooBInline = ooBInline;
+ }
+
+ public void setSoReuseAddress(boolean soReuseAddress) {
+ this.soReuseAddress = soReuseAddress;
+ }
+
+ public void setSoLingerOn(boolean soLingerOn) {
+ this.soLingerOn = soLingerOn;
+ }
+
+ public void setSoLingerTime(int soLingerTime) {
+ this.soLingerTime = soLingerTime;
+ }
+
+ public void setSoTrafficClass(int soTrafficClass) {
+ this.soTrafficClass = soTrafficClass;
+ }
+
+ public void setThrowOnFailedAck(boolean throwOnFailedAck) {
+ this.throwOnFailedAck = throwOnFailedAck;
+ }
+
+ public void setDestination(Member destination) throws UnknownHostException {
+ this.destination = destination;
+ this.address = InetAddress.getByAddress(destination.getHost());
+ this.port = destination.getPort();
+
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public void setAddress(InetAddress address) {
+ this.address = address;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.tribes.transport;
+
+import org.apache.catalina.tribes.io.XByteBuffer;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.tribes.transport</code>
+ * package.
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Revision: 303753 $ $Date: 2005-03-14 15:24:30 -0600 (Mon, 14 Mar 2005) $
+ */
+
+public class Constants {
+
+ public static final String Package = "org.apache.catalina.tribes.transport";
+
+ /*
+ * Do not change any of these values!
+ */
+ public static final byte[] ACK_DATA = new byte[] {6, 2, 3};
+ public static final byte[] FAIL_ACK_DATA = new byte[] {11, 0, 5};
+ public static final byte[] ACK_COMMAND = XByteBuffer.createDataPackage(ACK_DATA);
+ public static final byte[] FAIL_ACK_COMMAND = XByteBuffer.createDataPackage(FAIL_ACK_DATA);
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.transport;
+
+import java.io.IOException;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public interface DataSender {
+ public void connect() throws IOException;
+ public void disconnect();
+ public boolean isConnected();
+ public void setRxBufSize(int size);
+ public void setTxBufSize(int size);
+ public boolean keepalive();
+ public void setTimeout(long timeout);
+ public void setKeepAliveCount(int maxRequests);
+ public void setKeepAliveTime(long keepAliveTimeInMs);
+ public int getRequestCount();
+ public long getConnectTime();
+
+
+}
\ No newline at end of file
--- /dev/null
+AsyncSocketSender.create.thread=Create sender [{0}:{1,number,integer}] queue thread to tcp background replication
+AsyncSocketSender.queue.message=Queue message to [{0}:{1,number,integer}] id=[{2}] size={3}
+AsyncSocketSender.send.error=Unable to asynchronously send session with id=[{0}] - message will be ignored.
+AsyncSocketSender.queue.empty=Queue in sender [{0}:{1,number,integer}] returned null element!
+cluster.mbean.register.already=MBean {0} already registered!
+IDataSender.ack.eof=EOF reached at local port [{0}:{1,number,integer}]
+IDataSender.ack.receive=Got ACK at local port [{0}:{1,number,integer}]
+IDataSender.ack.missing=Unable to read acknowledgement from [{0}:{1,number,integer}] in {2,number,integer} ms. Disconnecting socket, and trying again.
+IDataSender.ack.read=Read wait ack char '{2}' [{0}:{1,number,integer}]
+IDataSender.ack.start=Waiting for ACK message [{0}:{1,number,integer}]
+IDataSender.ack.wrong=Missing correct ACK after 10 bytes read at local port [{0}:{1,number,integer}]
+IDataSender.closeSocket=Sender close socket to [{0}:{1,number,integer}] (close count {2,number,integer})
+IDataSender.connect=Sender connect to [{0}:{1,number,integer}] (connect count {2,number,integer})
+IDataSender.create=Create sender [{0}:{1,number,integer}]
+IDataSender.disconnect=Sender disconnect from [{0}:{1,number,integer}] (disconnect count {2,number,integer})
+IDataSender.message.disconnect=Message transfered: Sender can't disconnect from [{0}:{1,number,integer}]
+IDataSender.message.create=Message transfered: Sender can't create current socket [{0}:{1,number,integer}]
+IDataSender.openSocket=Sender open socket to [{0}:{1,number,integer}] (open count {2,number,integer})
+IDataSender.openSocket.failure=Open sender socket [{0}:{1,number,integer}] failure! (open failure count {2,number,integer})
+IDataSender.send.again=Send data again to [{0}:{1,number,integer}]
+IDataSender.send.crash=Send message crashed [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+IDataSender.send.message=Send message to [{0}:{1,number,integer}] id=[{2}] size={3,number,integer}
+IDataSender.send.lost=Message lost: [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+IDataSender.senderModes.Configured=Configured a data replication sender for mode {0}
+IDataSender.senderModes.Instantiate=Can't instantiate a data replication sender of class {0}
+IDataSender.senderModes.Missing=Can't configure a data replication sender for mode {0}
+IDataSender.senderModes.Resources=Can't load data replication sender mapping list
+IDataSender.stats=Send stats from [{0}:{1,number,integer}], Nr of bytes sent={2,number,integer} over {3} = {4,number,integer} bytes/request, processing time {5,number,integer} msec, avg processing time {6,number,integer} msec
+PoolSocketSender.senderQueue.sender.failed=PoolSocketSender create new sender to [{0}:{1,number,integer}] failed
+PoolSocketSender.noMoreSender=No socket sender available for client [{0}:{1,number,integer}] did it disappeared?
+ReplicationTransmitter.getProperty=get property {0}
+ReplicationTransmitter.setProperty=set property {0}: {1} old value {2}
+ReplicationTransmitter.started=Start ClusterSender at cluster {0} with name {1}
+ReplicationTransmitter.stopped=Stopped ClusterSender at cluster {0} with name {1}
+ReplicationValve.crossContext.add=add Cross Context session replication container to replicationValve threadlocal
+ReplicationValve.crossContext.registerSession=register Cross context session id={0} from context {1}
+ReplicationValve.crossContext.remove=remove Cross Context session replication container from replicationValve threadlocal
+ReplicationValve.crossContext.sendDelta=send Cross Context session delta from context {0}.
+ReplicationValve.filter.loading=Loading request filters={0}
+ReplicationValve.filter.token=Request filter={0}
+ReplicationValve.filter.token.failure=Unable to compile filter={0}
+ReplicationValve.invoke.uri=Invoking replication request on {0}
+ReplicationValve.nocluster=No cluster configured for this request.
+ReplicationValve.resetDeltaRequest=Cluster is standalone: reset Session Request Delta at context {0}
+ReplicationValve.send.failure=Unable to perform replication request.
+ReplicationValve.send.invalid.failure=Unable to send session [id={0}] invalid message over cluster.
+ReplicationValve.session.found=Context {0}: Found session {1} but it isn't a ClusterSession.
+ReplicationValve.session.indicator=Context {0}: Primarity of session {0} in request attribute {1} is {2}.
+ReplicationValve.session.invalid=Context {0}: Requested session {1} is invalid, removed or not replicated at this node.
+ReplicationValve.stats=Average request time= {0} ms for Cluster overhead time={1} ms for {2} requests {3} filter requests {4} send requests {5} cross context requests (Request={6} ms Cluster={7} ms).
+SimpleTcpCluster.event.log=Cluster receive listener event {0} with data {1}
+SimpleTcpCluster.getProperty=get property {0}
+SimpleTcpCluster.setProperty=set property {0}: {1} old value {2}
+SimpleTcpCluster.default.addClusterListener=Add Default ClusterListener at cluster {0}
+SimpleTcpCluster.default.addClusterValves=Add Default ClusterValves at cluster {0}
+SimpleTcpCluster.default.addClusterReceiver=Add Default ClusterReceiver at cluster {0}
+SimpleTcpCluster.default.addClusterSender=Add Default ClusterSender at cluster {0}
+SimpleTcpCluster.default.addMembershipService=Add Default Membership Service at cluster {0}
+SimpleTcpCluster.log.receive=RECEIVE {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4} {5}
+SimpleTcpCluster.log.send=SEND {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4}
+SimpleTcpCluster.log.send.all=SEND {0,date}:{0,time} {1,number} - {2}
+SocketReplictionListener.allreadyExists=ServerSocket [{0}:{1}] allready started!
+SocketReplictionListener.accept.failure=ServerSocket [{0}:{1}] - Exception to start thread or accept server socket
+SocketReplictionListener.open=Open Socket at [{0}:{1}]
+SocketReplictionListener.openclose.failure=ServerSocket [{0}:{1}] - Exception to open or close server socket
+SocketReplictionListener.portbusy=Port busy at [{0}:{i}] - reason [{2}]
+SocketReplictionListener.serverSocket.notExists=Fatal error: Receiver socket not bound address={0} port={1} maxport={2}
+SocketReplictionListener.timeout=Receiver ServerSocket no started [{0}:{1}] - reason: timeout={2} or listen={3}
+SocketReplictionListener.unlockSocket.failure=UnLocksocket failure at ServerSocket [{0:{1}]
--- /dev/null
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.Member;
+
+/**
+ * @author Filip Hanik
+ * @version $Revision: 303993 $ $Date: 2005-07-16 16:05:54 -0500 (Sat, 16 Jul 2005) $
+ * @since 5.5.16
+ */
+
+public interface MultiPointSender extends DataSender
+{
+ public void sendMessage(Member[] destination, ChannelMessage data) throws ChannelException;
+ public void setRxBufSize(int size);
+ public void setTxBufSize(int size);
+ public void setMaxRetryAttempts(int attempts);
+ public void setDirectBuffer(boolean directBuf);
+ public void memberAdded(Member member);
+ public void memberDisappeared(Member member);
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.transport;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public abstract class PooledSender extends AbstractSender implements MultiPointSender {
+
+ private SenderQueue queue = null;
+ private int poolSize = 25;
+ public PooledSender() {
+ queue = new SenderQueue(this,poolSize);
+ }
+
+ public abstract DataSender getNewDataSender();
+
+ public DataSender getSender() {
+ return queue.getSender(getTimeout());
+ }
+
+ public void returnSender(DataSender sender) {
+ sender.keepalive();
+ queue.returnSender(sender);
+ }
+
+ public synchronized void connect() throws IOException {
+ //do nothing, happens in the socket sender itself
+ queue.open();
+ setConnected(true);
+ }
+
+ public synchronized void disconnect() {
+ queue.close();
+ setConnected(false);
+ }
+
+
+ public int getInPoolSize() {
+ return queue.getInPoolSize();
+ }
+
+ public int getInUsePoolSize() {
+ return queue.getInUsePoolSize();
+ }
+
+
+ public void setPoolSize(int poolSize) {
+ this.poolSize = poolSize;
+ queue.setLimit(poolSize);
+ }
+
+ public int getPoolSize() {
+ return poolSize;
+ }
+
+ public boolean keepalive() {
+ //do nothing, the pool checks on every return
+ return false;
+ }
+
+
+
+ // ----------------------------------------------------- Inner Class
+
+ private class SenderQueue {
+ private int limit = 25;
+
+ PooledSender parent = null;
+
+ private List notinuse = null;
+
+ private List inuse = null;
+
+ private boolean isOpen = true;
+
+ public SenderQueue(PooledSender parent, int limit) {
+ this.limit = limit;
+ this.parent = parent;
+ notinuse = new java.util.LinkedList();
+ inuse = new java.util.LinkedList();
+ }
+
+ /**
+ * @return Returns the limit.
+ */
+ public int getLimit() {
+ return limit;
+ }
+ /**
+ * @param limit The limit to set.
+ */
+ public void setLimit(int limit) {
+ this.limit = limit;
+ }
+ /**
+ * @return
+ */
+ public int getInUsePoolSize() {
+ return inuse.size();
+ }
+
+ /**
+ * @return
+ */
+ public int getInPoolSize() {
+ return notinuse.size();
+ }
+
+ public synchronized DataSender getSender(long timeout) {
+ long start = System.currentTimeMillis();
+ while ( true ) {
+ if (!isOpen)throw new IllegalStateException("Queue is closed");
+ DataSender sender = null;
+ if (notinuse.size() == 0 && inuse.size() < limit) {
+ sender = parent.getNewDataSender();
+ } else if (notinuse.size() > 0) {
+ sender = (DataSender) notinuse.remove(0);
+ }
+ if (sender != null) {
+ inuse.add(sender);
+ return sender;
+ }//end if
+ long delta = System.currentTimeMillis() - start;
+ if ( delta > timeout && timeout>0) return null;
+ else {
+ try {
+ wait(Math.max(timeout - delta,1));
+ }catch (InterruptedException x){}
+ }//end if
+ }
+ }
+
+ public synchronized void returnSender(DataSender sender) {
+ if ( !isOpen) {
+ sender.disconnect();
+ return;
+ }
+ //to do
+ inuse.remove(sender);
+ //just in case the limit has changed
+ if ( notinuse.size() < this.getLimit() ) notinuse.add(sender);
+ else try {sender.disconnect(); } catch ( Exception ignore){}
+ notify();
+ }
+
+ public synchronized void close() {
+ isOpen = false;
+ Object[] unused = notinuse.toArray();
+ Object[] used = inuse.toArray();
+ for (int i = 0; i < unused.length; i++) {
+ DataSender sender = (DataSender) unused[i];
+ sender.disconnect();
+ }//for
+ for (int i = 0; i < used.length; i++) {
+ DataSender sender = (DataSender) used[i];
+ sender.disconnect();
+ }//for
+ notinuse.clear();
+ inuse.clear();
+ notify();
+
+
+
+ }
+
+ public synchronized void open() {
+ isOpen = true;
+ notify();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.catalina.tribes.transport;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ChannelReceiver;
+import org.apache.catalina.tribes.MessageListener;
+import org.apache.catalina.tribes.io.ListenCallback;
+import org.apache.commons.logging.Log;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public abstract class ReceiverBase implements ChannelReceiver, ListenCallback, ThreadPool.ThreadCreator {
+
+ public static final int OPTION_DIRECT_BUFFER = 0x0004;
+
+
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(ReceiverBase.class);
+
+ private MessageListener listener;
+ private String host = "auto";
+ private InetAddress bind;
+ private int port = 4000;
+ private int rxBufSize = 43800;
+ private int txBufSize = 25188;
+ private boolean listen = false;
+ private ThreadPool pool;
+ private boolean direct = true;
+ private long tcpSelectorTimeout = 5000;
+ //how many times to search for an available socket
+ private int autoBind = 10;
+ private int maxThreads = 15;
+ private int minThreads = 6;
+ private boolean tcpNoDelay = true;
+ private boolean soKeepAlive = false;
+ private boolean ooBInline = true;
+ private boolean soReuseAddress = true;
+ private boolean soLingerOn = true;
+ private int soLingerTime = 3;
+ private int soTrafficClass = 0x04 | 0x08 | 0x010;
+ private int timeout = 3000; //3 seconds
+ private boolean useBufferPool = true;
+
+
+ public ReceiverBase() {
+ }
+
+ /**
+ * getMessageListener
+ *
+ * @return MessageListener
+ * @todo Implement this org.apache.catalina.tribes.ChannelReceiver method
+ */
+ public MessageListener getMessageListener() {
+ return listener;
+ }
+
+ /**
+ *
+ * @return The port
+ * @todo Implement this org.apache.catalina.tribes.ChannelReceiver method
+ */
+ public int getPort() {
+ return port;
+ }
+
+ public int getRxBufSize() {
+ return rxBufSize;
+ }
+
+ public int getTxBufSize() {
+ return txBufSize;
+ }
+
+ /**
+ * @deprecated use getMinThreads()/getMaxThreads()
+ * @return int
+ */
+ public int getTcpThreadCount() {
+ return getMinThreads();
+ }
+
+ /**
+ * setMessageListener
+ *
+ * @param listener MessageListener
+ * @todo Implement this org.apache.catalina.tribes.ChannelReceiver method
+ */
+ public void setMessageListener(MessageListener listener) {
+ this.listener = listener;
+ }
+
+ public void setTcpListenPort(int tcpListenPort) {
+ this.port = tcpListenPort;
+ }
+
+ public void setTcpListenAddress(String tcpListenHost) {
+ this.host = tcpListenHost;
+ }
+
+ public void setRxBufSize(int rxBufSize) {
+ this.rxBufSize = rxBufSize;
+ }
+
+ public void setTxBufSize(int txBufSize) {
+ this.txBufSize = txBufSize;
+ }
+
+ public void setTcpThreadCount(int tcpThreadCount) {
+ setMinThreads(tcpThreadCount);
+ }
+
+ /**
+ * @return Returns the bind.
+ */
+ public InetAddress getBind() {
+ if (bind == null) {
+ try {
+ if ("auto".equals(host)) {
+ host = java.net.InetAddress.getLocalHost().getHostAddress();
+ }
+ if (log.isDebugEnabled())
+ log.debug("Starting replication listener on address:"+ host);
+ bind = java.net.InetAddress.getByName(host);
+ } catch (IOException ioe) {
+ log.error("Failed bind replication listener on address:"+ host, ioe);
+ }
+ }
+ return bind;
+ }
+
+ /**
+ * recursive bind to find the next available port
+ * @param socket ServerSocket
+ * @param portstart int
+ * @param retries int
+ * @return int
+ * @throws IOException
+ */
+ protected int bind(ServerSocket socket, int portstart, int retries) throws IOException {
+ InetSocketAddress addr = null;
+ while ( retries > 0 ) {
+ try {
+ addr = new InetSocketAddress(getBind(), portstart);
+ socket.bind(addr);
+ setTcpListenPort(portstart);
+ log.info("Receiver Server Socket bound to:"+addr);
+ return 0;
+ }catch ( IOException x) {
+ retries--;
+ if ( retries <= 0 ) {
+ log.info("Unable to bind server socket to:"+addr+" throwing error.");
+ throw x;
+ }
+ portstart++;
+ try {Thread.sleep(25);}catch( InterruptedException ti){Thread.currentThread().interrupted();}
+ retries = bind(socket,portstart,retries);
+ }
+ }
+ return retries;
+ }
+
+ public void messageDataReceived(ChannelMessage data) {
+ if ( this.listener != null ) {
+ if ( listener.accept(data) ) listener.messageReceived(data);
+ }
+ }
+
+ public int getWorkerThreadOptions() {
+ int options = 0;
+ if ( getDirect() ) options = options | OPTION_DIRECT_BUFFER;
+ return options;
+ }
+
+
+ /**
+ * @param bind The bind to set.
+ */
+ public void setBind(java.net.InetAddress bind) {
+ this.bind = bind;
+ }
+
+
+ public int getTcpListenPort() {
+ return this.port;
+ }
+
+ public boolean getDirect() {
+ return direct;
+ }
+
+
+
+ public void setDirect(boolean direct) {
+ this.direct = direct;
+ }
+
+
+
+ public String getHost() {
+ getBind();
+ return this.host;
+ }
+
+ public long getTcpSelectorTimeout() {
+ return tcpSelectorTimeout;
+ }
+
+ public boolean doListen() {
+ return listen;
+ }
+
+ public MessageListener getListener() {
+ return listener;
+ }
+
+ public ThreadPool getPool() {
+ return pool;
+ }
+
+ public String getTcpListenAddress() {
+ return getHost();
+ }
+
+ public int getAutoBind() {
+ return autoBind;
+ }
+
+ public int getMaxThreads() {
+ return maxThreads;
+ }
+
+ public int getMinThreads() {
+ return minThreads;
+ }
+
+ public boolean getTcpNoDelay() {
+ return tcpNoDelay;
+ }
+
+ public boolean getSoKeepAlive() {
+ return soKeepAlive;
+ }
+
+ public boolean getOoBInline() {
+ return ooBInline;
+ }
+
+
+ public boolean getSoLingerOn() {
+ return soLingerOn;
+ }
+
+ public int getSoLingerTime() {
+ return soLingerTime;
+ }
+
+ public boolean getSoReuseAddress() {
+ return soReuseAddress;
+ }
+
+ public int getSoTrafficClass() {
+ return soTrafficClass;
+ }
+
+ public int getTimeout() {
+ return timeout;
+ }
+
+ public boolean getUseBufferPool() {
+ return useBufferPool;
+ }
+
+ public void setTcpSelectorTimeout(long selTimeout) {
+ tcpSelectorTimeout = selTimeout;
+ }
+
+ public void setListen(boolean doListen) {
+ this.listen = doListen;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public void setListener(MessageListener listener) {
+ this.listener = listener;
+ }
+
+ public void setLog(Log log) {
+ this.log = log;
+ }
+
+ public void setPool(ThreadPool pool) {
+ this.pool = pool;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public void setAutoBind(int autoBind) {
+ this.autoBind = autoBind;
+ if ( this.autoBind <= 0 ) this.autoBind = 1;
+ }
+
+ public void setMaxThreads(int maxThreads) {
+ this.maxThreads = maxThreads;
+ }
+
+ public void setMinThreads(int minThreads) {
+ this.minThreads = minThreads;
+ }
+
+ public void setTcpNoDelay(boolean tcpNoDelay) {
+ this.tcpNoDelay = tcpNoDelay;
+ }
+
+ public void setSoKeepAlive(boolean soKeepAlive) {
+ this.soKeepAlive = soKeepAlive;
+ }
+
+ public void setOoBInline(boolean ooBInline) {
+ this.ooBInline = ooBInline;
+ }
+
+
+ public void setSoLingerOn(boolean soLingerOn) {
+ this.soLingerOn = soLingerOn;
+ }
+
+ public void setSoLingerTime(int soLingerTime) {
+ this.soLingerTime = soLingerTime;
+ }
+
+ public void setSoReuseAddress(boolean soReuseAddress) {
+ this.soReuseAddress = soReuseAddress;
+ }
+
+ public void setSoTrafficClass(int soTrafficClass) {
+ this.soTrafficClass = soTrafficClass;
+ }
+
+ public void setTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+
+ public void setUseBufferPool(boolean useBufferPool) {
+ this.useBufferPool = useBufferPool;
+ }
+
+ public void heartbeat() {
+ //empty operation
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.catalina.tribes.transport;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ChannelSender;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.util.StringManager;
+import org.apache.catalina.tribes.transport.nio.PooledParallelSender;
+
+/**
+ * Transmit message to other cluster members
+ * Actual senders are created based on the replicationMode
+ * type
+ *
+ * @author Filip Hanik
+ * @version $Revision: 379956 $ $Date: 2006-02-22 16:57:35 -0600 (Wed, 22 Feb 2006) $
+ */
+public class ReplicationTransmitter implements ChannelSender {
+ private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(ReplicationTransmitter.class);
+
+ /**
+ * The descriptive information about this implementation.
+ */
+ private static final String info = "ReplicationTransmitter/3.0";
+
+ /**
+ * The string manager for this package.
+ */
+ protected StringManager sm = StringManager.getManager(Constants.Package);
+
+
+
+ public ReplicationTransmitter() {
+ }
+
+ private MultiPointSender transport = new PooledParallelSender();
+
+ /**
+ * Return descriptive information about this implementation and the
+ * corresponding version number, in the format
+ * <code><description>/<version></code>.
+ */
+ public String getInfo() {
+ return (info);
+ }
+
+ public MultiPointSender getTransport() {
+ return transport;
+ }
+
+ public void setTransport(MultiPointSender transport) {
+ this.transport = transport;
+ }
+
+ // ------------------------------------------------------------- public
+
+ /**
+ * Send data to one member
+ * @see org.apache.catalina.tribes.ClusterSender#sendMessage(org.apache.catalina.tribes.ClusterMessage, org.apache.catalina.tribes.Member)
+ */
+ public void sendMessage(ChannelMessage message, Member[] destination) throws ChannelException {
+ MultiPointSender sender = getTransport();
+ sender.sendMessage(destination,message);
+ }
+
+
+ /**
+ * start the sender and register transmitter mbean
+ *
+ * @see org.apache.catalina.tribes.ClusterSender#start()
+ */
+ public void start() throws java.io.IOException {
+ getTransport().connect();
+ }
+
+ /*
+ * stop the sender and deregister mbeans (transmitter, senders)
+ *
+ * @see org.apache.catalina.tribes.ClusterSender#stop()
+ */
+ public synchronized void stop() {
+ getTransport().disconnect();
+ }
+
+ /**
+ * Call transmitter to check for sender socket status
+ *
+ * @see SimpleTcpCluster#backgroundProcess()
+ */
+
+ public void heartbeat() {
+
+ }
+
+ /**
+ * add new cluster member and create sender ( s. replicationMode) transfer
+ * current properties to sender
+ *
+ * @see org.apache.catalina.tribes.ClusterSender#add(org.apache.catalina.tribes.Member)
+ */
+ public synchronized void add(Member member) {
+ getTransport().memberAdded(member);
+ }
+
+ /**
+ * remove sender from transmitter. ( deregister mbean and disconnect sender )
+ *
+ * @see org.apache.catalina.tribes.ClusterSender#remove(org.apache.catalina.tribes.Member)
+ */
+ public synchronized void remove(Member member) {
+ getTransport().memberDisappeared(member);
+ }
+
+ // ------------------------------------------------------------- protected
+
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport;
+
+import org.apache.catalina.tribes.Member;
+import java.util.HashMap;
+
+
+/**
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ * @since 5.5.16
+ */
+
+public class SenderState {
+
+ public static final int READY = 0;
+ public static final int SUSPECT = 1;
+ public static final int FAILING = 2;
+ /**
+ * The descriptive information about this implementation.
+ */
+ private static final String info = "SenderState/1.0";
+
+
+ protected static HashMap memberStates = new HashMap();
+
+ public static SenderState getSenderState(Member member) {
+ return getSenderState(member,true);
+ }
+
+ public static SenderState getSenderState(Member member, boolean create) {
+ SenderState state = (SenderState)memberStates.get(member);
+ if ( state == null && create) {
+ synchronized ( memberStates ) {
+ state = (SenderState)memberStates.get(member);
+ if ( state == null ) {
+ state = new SenderState();
+ memberStates.put(member,state);
+ }
+ }
+ }
+ return state;
+ }
+
+ public static void removeSenderState(Member member) {
+ synchronized ( memberStates ) {
+ memberStates.remove(member);
+ }
+ }
+
+
+ // ----------------------------------------------------- Instance Variables
+
+ private int state = READY;
+
+ // ----------------------------------------------------- Constructor
+
+
+ private SenderState() {
+ this(READY);
+ }
+
+ private SenderState(int state) {
+ this.state = state;
+ }
+
+ /**
+ *
+ * @return boolean
+ */
+ public boolean isSuspect() {
+ return (state == SUSPECT) || (state == FAILING);
+ }
+
+ public void setSuspect() {
+ state = SUSPECT;
+ }
+
+ public boolean isReady() {
+ return state == READY;
+ }
+
+ public void setReady() {
+ state = READY;
+ }
+
+ public boolean isFailing() {
+ return state == FAILING;
+ }
+
+ public void setFailing() {
+ state = FAILING;
+ }
+
+
+ // ----------------------------------------------------- Public Properties
+
+}
--- /dev/null
+/*\r
+ * Copyright 1999,2004 The Apache Software Foundation.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.apache.catalina.tribes.transport;\r
+import java.util.Iterator;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+\r
+/**\r
+ * @author not attributable\r
+ * @version 1.0\r
+ */\r
+\r
+public class ThreadPool\r
+{\r
+ /**\r
+ * A very simple thread pool class. The pool size is set at\r
+ * construction time and remains fixed. Threads are cycled\r
+ * through a FIFO idle queue.\r
+ */\r
+\r
+ List idle = new LinkedList();\r
+ List used = new LinkedList();\r
+ \r
+ Object mutex = new Object();\r
+ boolean running = true;\r
+ \r
+ private static int counter = 1;\r
+ private int maxThreads;\r
+ private int minThreads;\r
+ \r
+ private ThreadCreator creator = null;\r
+\r
+ private static synchronized int inc() {\r
+ return counter++;\r
+ }\r
+\r
+ \r
+ public ThreadPool (int maxThreads, int minThreads, ThreadCreator creator) throws Exception {\r
+ // fill up the pool with worker threads\r
+ this.maxThreads = maxThreads;\r
+ this.minThreads = minThreads;\r
+ this.creator = creator;\r
+ //for (int i = 0; i < minThreads; i++) {\r
+ for (int i = 0; i < maxThreads; i++) { //temporary fix for thread hand off problem\r
+ WorkerThread thread = creator.getWorkerThread();\r
+ setupThread(thread);\r
+ idle.add (thread);\r
+ }\r
+ }\r
+ \r
+ protected void setupThread(WorkerThread thread) {\r
+ synchronized (thread) {\r
+ thread.setPool(this);\r
+ thread.setName(thread.getClass().getName() + "[" + inc() + "]");\r
+ thread.setDaemon(true);\r
+ thread.setPriority(Thread.MAX_PRIORITY);\r
+ thread.start();\r
+ try {thread.wait(500); }catch ( InterruptedException x ) {}\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Find an idle worker thread, if any. Could return null.\r
+ */\r
+ public WorkerThread getWorker()\r
+ {\r
+ WorkerThread worker = null;\r
+ synchronized (mutex) {\r
+ while ( worker == null && running ) {\r
+ if (idle.size() > 0) {\r
+ try {\r
+ worker = (WorkerThread) idle.remove(0);\r
+ } catch (java.util.NoSuchElementException x) {\r
+ //this means that there are no available workers\r
+ worker = null;\r
+ }\r
+ } else if ( used.size() < this.maxThreads && creator != null) {\r
+ worker = creator.getWorkerThread();\r
+ setupThread(worker);\r
+ } else {\r
+ try { mutex.wait(); } catch ( java.lang.InterruptedException x ) {Thread.currentThread().interrupted();}\r
+ }\r
+ }//while\r
+ if ( worker != null ) used.add(worker);\r
+ }\r
+ return (worker);\r
+ }\r
+ \r
+ public int available() {\r
+ return idle.size();\r
+ }\r
+\r
+ /**\r
+ * Called by the worker thread to return itself to the\r
+ * idle pool.\r
+ */\r
+ public void returnWorker (WorkerThread worker) {\r
+ if ( running ) {\r
+ synchronized (mutex) {\r
+ used.remove(worker);\r
+ //if ( idle.size() < minThreads && !idle.contains(worker)) idle.add(worker);\r
+ if ( idle.size() < maxThreads && !idle.contains(worker)) idle.add(worker); //let max be the upper limit\r
+ else {\r
+ worker.setDoRun(false);\r
+ synchronized (worker){worker.notify();}\r
+ }\r
+ mutex.notify();\r
+ }\r
+ }else {\r
+ worker.setDoRun(false);\r
+ synchronized (worker){worker.notify();}\r
+ }\r
+ }\r
+\r
+ public int getMaxThreads() {\r
+ return maxThreads;\r
+ }\r
+\r
+ public int getMinThreads() {\r
+ return minThreads;\r
+ }\r
+\r
+ public void stop() {\r
+ running = false;\r
+ synchronized (mutex) {\r
+ Iterator i = idle.iterator();\r
+ while ( i.hasNext() ) {\r
+ WorkerThread worker = (WorkerThread)i.next();\r
+ returnWorker(worker);\r
+ i.remove();\r
+ }\r
+ }\r
+ }\r
+\r
+ public void setMaxThreads(int maxThreads) {\r
+ this.maxThreads = maxThreads;\r
+ }\r
+\r
+ public void setMinThreads(int minThreads) {\r
+ this.minThreads = minThreads;\r
+ }\r
+\r
+ public ThreadCreator getThreadCreator() {\r
+ return this.creator;\r
+ }\r
+ \r
+ public static interface ThreadCreator {\r
+ public WorkerThread getWorkerThread();\r
+ }\r
+}\r
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport;
+
+import org.apache.catalina.tribes.io.ListenCallback;
+
+
+
+
+/**
+ * @author Filip Hanik
+ * @version $Revision: 366253 $ $Date: 2006-01-05 13:30:42 -0600 (Thu, 05 Jan 2006) $
+ */
+public abstract class WorkerThread extends Thread
+{
+
+ public static final int OPTION_DIRECT_BUFFER = ReceiverBase.OPTION_DIRECT_BUFFER;
+
+ private ListenCallback callback;
+ private ThreadPool pool;
+ private boolean doRun = true;
+ private int options;
+ protected boolean useBufferPool = true;
+
+ public WorkerThread(ListenCallback callback) {
+ this.callback = callback;
+ }
+
+ public void setPool(ThreadPool pool) {
+ this.pool = pool;
+ }
+
+ public void setOptions(int options) {
+ this.options = options;
+ }
+
+ public void setCallback(ListenCallback callback) {
+ this.callback = callback;
+ }
+
+ public void setDoRun(boolean doRun) {
+ this.doRun = doRun;
+ }
+
+ public ThreadPool getPool() {
+ return pool;
+ }
+
+ public int getOptions() {
+ return options;
+ }
+
+ public ListenCallback getCallback() {
+ return callback;
+ }
+
+ public boolean isDoRun() {
+ return doRun;
+ }
+
+ public void close()
+ {
+ doRun = false;
+ notify();
+ }
+
+ public void setUseBufferPool(boolean usebufpool) {
+ useBufferPool = usebufpool;
+ }
+
+ public boolean getUseBufferPool() {
+ return useBufferPool;
+ }
+}
--- /dev/null
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.catalina.tribes.transport.bio;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.apache.catalina.tribes.ChannelReceiver;
+import org.apache.catalina.tribes.io.ListenCallback;
+import org.apache.catalina.tribes.io.ObjectReader;
+import org.apache.catalina.tribes.transport.ReceiverBase;
+import org.apache.catalina.tribes.transport.ThreadPool;
+import org.apache.catalina.tribes.transport.WorkerThread;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class BioReceiver extends ReceiverBase implements Runnable, ChannelReceiver, ListenCallback {
+
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(BioReceiver.class);
+
+ protected ServerSocket serverSocket;
+
+ public BioReceiver() {
+ }
+
+ /**
+ *
+ * @throws IOException
+ * @todo Implement this org.apache.catalina.tribes.ChannelReceiver method
+ */
+ public void start() throws IOException {
+ try {
+ setPool(new ThreadPool(getMaxThreads(),getMinThreads(),this));
+ } catch (Exception x) {
+ log.fatal("ThreadPool can initilzed. Listener not started", x);
+ if ( x instanceof IOException ) throw (IOException)x;
+ else throw new IOException(x.getMessage());
+ }
+ try {
+ getBind();
+ bind();
+ Thread t = new Thread(this, "BioReceiver");
+ t.setDaemon(true);
+ t.start();
+ } catch (Exception x) {
+ log.fatal("Unable to start cluster receiver", x);
+ if ( x instanceof IOException ) throw (IOException)x;
+ else throw new IOException(x.getMessage());
+ }
+ }
+
+ public WorkerThread getWorkerThread() {
+ return getReplicationThread();
+ }
+
+ protected BioReplicationThread getReplicationThread() {
+ BioReplicationThread result = new BioReplicationThread(this);
+ result.setOptions(getWorkerThreadOptions());
+ result.setUseBufferPool(this.getUseBufferPool());
+ return result;
+ }
+
+ /**
+ *
+ * @todo Implement this org.apache.catalina.tribes.ChannelReceiver method
+ */
+ public void stop() {
+ setListen(false);
+ try {
+ this.serverSocket.close();
+ }catch ( Exception x ) {}
+ }
+
+
+
+
+ protected void bind() throws IOException {
+ // allocate an unbound server socket channel
+ serverSocket = new ServerSocket();
+ // set the port the server channel will listen to
+ //serverSocket.bind(new InetSocketAddress(getBind(), getTcpListenPort()));
+ bind(serverSocket,getPort(),getAutoBind());
+ }
+
+
+
+ public void run() {
+ try {
+ listen();
+ } catch (Exception x) {
+ log.error("Unable to run replication listener.", x);
+ }
+ }
+
+ public void listen() throws Exception {
+ if (doListen()) {
+ log.warn("ServerSocket already started");
+ return;
+ }
+ setListen(true);
+
+ while ( doListen() ) {
+ Socket socket = null;
+ if ( getPool().available() < 1 ) {
+ if ( log.isWarnEnabled() )
+ log.warn("All BIO server replication threads are busy, unable to handle more requests until a thread is freed up.");
+ }
+ BioReplicationThread thread = (BioReplicationThread)getPool().getWorker();
+ if ( thread == null ) continue; //should never happen
+ try {
+ socket = serverSocket.accept();
+ }catch ( Exception x ) {
+ if ( doListen() ) throw x;
+ }
+ if ( !doListen() ) {
+ thread.setDoRun(false);
+ thread.serviceSocket(null,null);
+ break; //regular shutdown
+ }
+ if ( socket == null ) continue;
+ socket.setReceiveBufferSize(getRxBufSize());
+ socket.setSendBufferSize(getRxBufSize());
+ socket.setTcpNoDelay(getTcpNoDelay());
+ socket.setKeepAlive(getSoKeepAlive());
+ socket.setOOBInline(getOoBInline());
+ socket.setReuseAddress(getSoReuseAddress());
+ socket.setSoLinger(getSoLingerOn(),getSoLingerTime());
+ socket.setTrafficClass(getSoTrafficClass());
+ socket.setSoTimeout(getTimeout());
+ ObjectReader reader = new ObjectReader(socket);
+ thread.serviceSocket(socket,reader);
+ }//while
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.bio;
+import java.io.IOException;
+
+import org.apache.catalina.tribes.io.ObjectReader;
+import org.apache.catalina.tribes.transport.Constants;
+import org.apache.catalina.tribes.transport.WorkerThread;
+import java.net.Socket;
+import java.io.InputStream;
+import org.apache.catalina.tribes.transport.ReceiverBase;
+import java.io.OutputStream;
+import org.apache.catalina.tribes.io.ListenCallback;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.BufferPool;
+
+/**
+ * A worker thread class which can drain channels and echo-back the input. Each
+ * instance is constructed with a reference to the owning thread pool object.
+ * When started, the thread loops forever waiting to be awakened to service the
+ * channel associated with a SelectionKey object. The worker is tasked by
+ * calling its serviceChannel() method with a SelectionKey object. The
+ * serviceChannel() method stores the key reference in the thread object then
+ * calls notify() to wake it up. When the channel has been drained, the worker
+ * thread returns itself to its parent pool.
+ *
+ * @author Filip Hanik
+ *
+ * @version $Revision: 378050 $, $Date: 2006-02-15 12:30:02 -0600 (Wed, 15 Feb 2006) $
+ */
+public class BioReplicationThread extends WorkerThread {
+
+
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog( BioReplicationThread.class );
+
+ protected Socket socket;
+ protected ObjectReader reader;
+
+ public BioReplicationThread (ListenCallback callback) {
+ super(callback);
+ }
+
+ // loop forever waiting for work to do
+ public synchronized void run()
+ {
+ this.notify();
+ while (isDoRun()) {
+ try {
+ // sleep and release object lock
+ this.wait();
+ } catch (InterruptedException e) {
+ if(log.isInfoEnabled())
+ log.info("TCP worker thread interrupted in cluster",e);
+ // clear interrupt status
+ Thread.interrupted();
+ }
+ if ( socket == null ) continue;
+ try {
+ drainSocket();
+ } catch ( Exception x ) {
+ log.error("Unable to service bio socket");
+ }finally {
+ try {socket.close();}catch ( Exception ignore){}
+ try {reader.close();}catch ( Exception ignore){}
+ reader = null;
+ socket = null;
+ }
+ // done, ready for more, return to pool
+ if ( getPool() != null ) getPool().returnWorker (this);
+ else setDoRun(false);
+ }
+ }
+
+
+ public synchronized void serviceSocket(Socket socket, ObjectReader reader) {
+ this.socket = socket;
+ this.reader = reader;
+ this.notify(); // awaken the thread
+ }
+
+ protected void execute(ObjectReader reader) throws Exception{
+ int pkgcnt = reader.count();
+
+ if ( pkgcnt > 0 ) {
+ ChannelMessage[] msgs = reader.execute();
+ for ( int i=0; i<msgs.length; i++ ) {
+ /**
+ * Use send ack here if you want to ack the request to the remote
+ * server before completing the request
+ * This is considered an asynchronized request
+ */
+ if (ChannelData.sendAckAsync(msgs[i].getOptions())) sendAck(Constants.ACK_COMMAND);
+ try {
+ //process the message
+ getCallback().messageDataReceived(msgs[i]);
+ /**
+ * Use send ack here if you want the request to complete on this
+ * server before sending the ack to the remote server
+ * This is considered a synchronized request
+ */
+ if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(Constants.ACK_COMMAND);
+ }catch ( Exception x ) {
+ if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(Constants.FAIL_ACK_COMMAND);
+ log.error("Error thrown from messageDataReceived.",x);
+ }
+ if ( getUseBufferPool() ) {
+ BufferPool.getBufferPool().returnBuffer(msgs[i].getMessage());
+ msgs[i].setMessage(null);
+ }
+ }
+ }
+
+
+ }
+
+ /**
+ * The actual code which drains the channel associated with
+ * the given key. This method assumes the key has been
+ * modified prior to invocation to turn off selection
+ * interest in OP_READ. When this method completes it
+ * re-enables OP_READ and calls wakeup() on the selector
+ * so the selector will resume watching this channel.
+ */
+ protected void drainSocket () throws Exception {
+ InputStream in = socket.getInputStream();
+ // loop while data available, channel is non-blocking
+ byte[] buf = new byte[1024];
+ int length = in.read(buf);
+ while ( length >= 0 ) {
+ int count = reader.append(buf,0,length,true);
+ if ( count > 0 ) execute(reader);
+ length = in.read(buf);
+ }
+ }
+
+
+
+
+ /**
+ * send a reply-acknowledgement (6,2,3)
+ * @param key
+ * @param channel
+ */
+ protected void sendAck(byte[] command) {
+ try {
+ OutputStream out = socket.getOutputStream();
+ out.write(command);
+ out.flush();
+ if (log.isTraceEnabled()) {
+ log.trace("ACK sent to " + socket.getPort());
+ }
+ } catch ( java.io.IOException x ) {
+ log.warn("Unable to send ACK back through channel, channel disconnected?: "+x.getMessage());
+ }
+ }
+
+ public void close() {
+ setDoRun(false);
+ try {socket.close();}catch ( Exception ignore){}
+ try {reader.close();}catch ( Exception ignore){}
+ reader = null;
+ socket = null;
+ super.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.catalina.tribes.transport.bio;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.RemoteProcessException;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.transport.AbstractSender;
+import org.apache.catalina.tribes.transport.Constants;
+import org.apache.catalina.tribes.transport.DataSender;
+import org.apache.catalina.tribes.transport.SenderState;
+import org.apache.catalina.tribes.util.StringManager;
+
+/**
+ * Send cluster messages with only one socket. Ack and keep Alive Handling is
+ * supported
+ *
+ * @author Peter Rossbach
+ * @author Filip Hanik
+ * @version $Revision: 377484 $ $Date: 2006-02-13 15:00:05 -0600 (Mon, 13 Feb 2006) $
+ * @since 5.5.16
+ */
+public class BioSender extends AbstractSender implements DataSender {
+
+ private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(BioSender.class);
+
+ /**
+ * The string manager for this package.
+ */
+ protected static StringManager sm = StringManager.getManager(Constants.Package);
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * The descriptive information about this implementation.
+ */
+ private static final String info = "DataSender/3.0";
+
+
+ /**
+ * current sender socket
+ */
+ private Socket socket = null;
+ private OutputStream soOut = null;
+ private InputStream soIn = null;
+
+ protected XByteBuffer ackbuf = new XByteBuffer(Constants.ACK_COMMAND.length,true);
+
+
+ // ------------------------------------------------------------- Constructor
+
+ public BioSender() {
+ }
+
+
+ // ------------------------------------------------------------- Properties
+
+ /**
+ * Return descriptive information about this implementation and the
+ * corresponding version number, in the format
+ * <code><description>/<version></code>.
+ */
+ public String getInfo() {
+ return (info);
+ }
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * Connect other cluster member receiver
+ * @see org.apache.catalina.tribes.transport.IDataSender#connect()
+ */
+ public void connect() throws IOException {
+ openSocket();
+ }
+
+
+ /**
+ * disconnect and close socket
+ *
+ * @see IDataSender#disconnect()
+ */
+ public void disconnect() {
+ boolean connect = isConnected();
+ closeSocket();
+ if (connect) {
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("IDataSender.disconnect", getAddress().getHostAddress(), new Integer(getPort()), new Long(0)));
+ }
+
+ }
+
+ /**
+ * Send message
+ *
+ * @see org.apache.catalina.tribes.transport.IDataSender#sendMessage(,
+ * ChannelMessage)
+ */
+ public void sendMessage(byte[] data, boolean waitForAck) throws IOException {
+ boolean messageTransfered = false ;
+ IOException exception = null;
+ setAttempt(0);
+ try {
+ // first try with existing connection
+ pushMessage(data,false,waitForAck);
+ messageTransfered = true ;
+ } catch (IOException x) {
+ SenderState.getSenderState(getDestination()).setSuspect();
+ exception = x;
+ if (log.isTraceEnabled()) log.trace(sm.getString("IDataSender.send.again", getAddress().getHostAddress(),new Integer(getPort())),x);
+ while ( getAttempt()<getMaxRetryAttempts() ) {
+ try {
+ setAttempt(getAttempt()+1);
+ // second try with fresh connection
+ pushMessage(data, true,waitForAck);
+ messageTransfered = true;
+ exception = null;
+ } catch (IOException xx) {
+ exception = xx;
+ closeSocket();
+ }
+ }
+ } finally {
+ setRequestCount(getRequestCount()+1);
+ keepalive();
+ if(messageTransfered) {
+
+ } else {
+ if ( exception != null ) throw exception;
+ }
+ }
+
+ }
+
+
+ /**
+ * Name of this SockerSender
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer("DataSender[(");
+ buf.append(super.toString()).append(")");
+ buf.append(getAddress()).append(":").append(getPort()).append("]");
+ return buf.toString();
+ }
+
+ // --------------------------------------------------------- Protected Methods
+
+ /**
+ * open real socket and set time out when waitForAck is enabled
+ * is socket open return directly
+ */
+ protected void openSocket() throws IOException {
+ if(isConnected()) return ;
+ try {
+ socket = new Socket();
+ InetSocketAddress sockaddr = new InetSocketAddress(getAddress(), getPort());
+ socket.connect(sockaddr,(int)getTimeout());
+ socket.setSendBufferSize(getTxBufSize());
+ socket.setReceiveBufferSize(getRxBufSize());
+ socket.setSoTimeout( (int) getTimeout());
+ socket.setTcpNoDelay(getTcpNoDelay());
+ socket.setKeepAlive(getSoKeepAlive());
+ socket.setReuseAddress(getSoReuseAddress());
+ socket.setOOBInline(getOoBInline());
+ socket.setSoLinger(getSoLingerOn(),getSoLingerTime());
+ socket.setTrafficClass(getSoTrafficClass());
+ setConnected(true);
+ soOut = socket.getOutputStream();
+ soIn = socket.getInputStream();
+ setRequestCount(0);
+ setConnectTime(System.currentTimeMillis());
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("IDataSender.openSocket", getAddress().getHostAddress(), new Integer(getPort()), new Long(0)));
+ } catch (IOException ex1) {
+ SenderState.getSenderState(getDestination()).setSuspect();
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("IDataSender.openSocket.failure",getAddress().getHostAddress(), new Integer(getPort()),new Long(0)), ex1);
+ throw (ex1);
+ }
+
+ }
+
+ /**
+ * close socket
+ *
+ * @see DataSender#disconnect()
+ * @see DataSender#closeSocket()
+ */
+ protected void closeSocket() {
+ if(isConnected()) {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException x) {
+ } finally {
+ socket = null;
+ soOut = null;
+ soIn = null;
+ }
+ }
+ setRequestCount(0);
+ setConnected(false);
+ if (log.isDebugEnabled())
+ log.debug(sm.getString("IDataSender.closeSocket",getAddress().getHostAddress(), new Integer(getPort()),new Long(0)));
+ }
+ }
+
+ /**
+ * Push messages with only one socket at a time
+ * Wait for ack is needed and make auto retry when write message is failed.
+ * After sending error close and reopen socket again.
+ *
+ * After successfull sending update stats
+ *
+ * WARNING: Subclasses must be very carefull that only one thread call this pushMessage at once!!!
+ *
+ * @see #closeSocket()
+ * @see #openSocket()
+ * @see #writeData(ChannelMessage)
+ *
+ * @param data
+ * data to send
+ * @since 5.5.10
+ */
+
+ protected void pushMessage(byte[] data, boolean reconnect, boolean waitForAck) throws IOException {
+ keepalive();
+ if ( reconnect ) closeSocket();
+ if (!isConnected()) openSocket();
+ soOut.write(data);
+ soOut.flush();
+ if (waitForAck) waitForAck();
+ SenderState.getSenderState(getDestination()).setReady();
+
+ }
+
+ /**
+ * Wait for Acknowledgement from other server
+ * FIXME Please, not wait only for three charcters, better control that the wait ack message is correct.
+ * @param timeout
+ * @throws java.io.IOException
+ * @throws java.net.SocketTimeoutException
+ */
+ protected void waitForAck() throws java.io.IOException {
+ try {
+ boolean ackReceived = false;
+ boolean failAckReceived = false;
+ ackbuf.clear();
+ int bytesRead = 0;
+ int i = soIn.read();
+ while ((i != -1) && (bytesRead < Constants.ACK_COMMAND.length)) {
+ bytesRead++;
+ byte d = (byte)i;
+ ackbuf.append(d);
+ if (ackbuf.doesPackageExist() ) {
+ byte[] ackcmd = ackbuf.extractDataPackage(true).getBytes();
+ ackReceived = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.ACK_DATA);
+ failAckReceived = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA);
+ ackReceived = ackReceived || failAckReceived;
+ break;
+ }
+ i = soIn.read();
+ }
+ if (!ackReceived) {
+ if (i == -1) throw new IOException(sm.getString("IDataSender.ack.eof",getAddress(), new Integer(socket.getLocalPort())));
+ else throw new IOException(sm.getString("IDataSender.ack.wrong",getAddress(), new Integer(socket.getLocalPort())));
+ } else if ( failAckReceived && getThrowOnFailedAck()) {
+ throw new RemoteProcessException("Received a failed ack:org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA");
+ }
+ } catch (IOException x) {
+ String errmsg = sm.getString("IDataSender.ack.missing", getAddress(),new Integer(socket.getLocalPort()), new Long(getTimeout()));
+ if ( SenderState.getSenderState(getDestination()).isReady() ) {
+ SenderState.getSenderState(getDestination()).setSuspect();
+ if ( log.isWarnEnabled() ) log.warn(errmsg, x);
+ } else {
+ if ( log.isDebugEnabled() )log.debug(errmsg, x);
+ }
+ throw x;
+ } finally {
+ ackbuf.clear();
+ }
+ }
+}
--- /dev/null
+package org.apache.catalina.tribes.transport.bio;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.transport.MultiPointSender;
+import org.apache.catalina.tribes.transport.AbstractSender;
+import org.apache.catalina.tribes.Channel;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class MultipointBioSender extends AbstractSender implements MultiPointSender {
+ public MultipointBioSender() {
+ }
+
+ protected long selectTimeout = 1000;
+ protected HashMap bioSenders = new HashMap();
+ private boolean autoConnect;
+
+ public synchronized void sendMessage(Member[] destination, ChannelMessage msg) throws ChannelException {
+ byte[] data = XByteBuffer.createDataPackage((ChannelData)msg);
+ BioSender[] senders = setupForSend(destination);
+ ChannelException cx = null;
+ for ( int i=0; i<senders.length; i++ ) {
+ try {
+ senders[i].sendMessage(data,(msg.getOptions()&Channel.SEND_OPTIONS_USE_ACK)==Channel.SEND_OPTIONS_USE_ACK);
+ } catch (Exception x) {
+ if (cx == null) cx = new ChannelException(x);
+ cx.addFaultyMember(destination[i],x);
+ }
+ }
+ if (cx!=null ) throw cx;
+ }
+
+
+
+ protected BioSender[] setupForSend(Member[] destination) throws ChannelException {
+ ChannelException cx = null;
+ BioSender[] result = new BioSender[destination.length];
+ for ( int i=0; i<destination.length; i++ ) {
+ try {
+ BioSender sender = (BioSender) bioSenders.get(destination[i]);
+ if (sender == null) {
+ sender = new BioSender();
+ sender.transferProperties(this,sender);
+ sender.setDestination(destination[i]);
+ bioSenders.put(destination[i], sender);
+ }
+ result[i] = sender;
+ if (!result[i].isConnected() ) result[i].connect();
+ result[i].keepalive();
+ }catch (Exception x ) {
+ if ( cx== null ) cx = new ChannelException(x);
+ cx.addFaultyMember(destination[i],x);
+ }
+ }
+ if ( cx!=null ) throw cx;
+ else return result;
+ }
+
+ public void connect() throws IOException {
+ //do nothing, we connect on demand
+ setConnected(true);
+ }
+
+
+ private synchronized void close() throws ChannelException {
+ ChannelException x = null;
+ Object[] members = bioSenders.keySet().toArray();
+ for (int i=0; i<members.length; i++ ) {
+ Member mbr = (Member)members[i];
+ try {
+ BioSender sender = (BioSender)bioSenders.get(mbr);
+ sender.disconnect();
+ }catch ( Exception e ) {
+ if ( x == null ) x = new ChannelException(e);
+ x.addFaultyMember(mbr,e);
+ }
+ bioSenders.remove(mbr);
+ }
+ if ( x != null ) throw x;
+ }
+
+ public void memberAdded(Member member) {
+
+ }
+
+ public void memberDisappeared(Member member) {
+ //disconnect senders
+ BioSender sender = (BioSender)bioSenders.remove(member);
+ if ( sender != null ) sender.disconnect();
+ }
+
+
+ public synchronized void disconnect() {
+ try {close(); }catch (Exception x){}
+ setConnected(false);
+ }
+
+ public void finalize() {
+ try {disconnect(); }catch ( Exception ignore){}
+ }
+
+
+ public boolean keepalive() {
+ //throw new UnsupportedOperationException("Method ParallelBioSender.checkKeepAlive() not implemented");
+ boolean result = false;
+ Map.Entry[] entries = (Map.Entry[])bioSenders.entrySet().toArray(new Map.Entry[bioSenders.size()]);
+ for ( int i=0; i<entries.length; i++ ) {
+ BioSender sender = (BioSender)entries[i].getValue();
+ if ( sender.keepalive() ) {
+ bioSenders.remove(entries[i].getKey());
+ }
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.apache.catalina.tribes.transport.bio;
+
+import org.apache.catalina.tribes.transport.DataSender;
+import org.apache.catalina.tribes.transport.PooledSender;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.transport.MultiPointSender;
+import org.apache.catalina.tribes.ChannelMessage;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class PooledMultiSender extends PooledSender {
+
+
+ public PooledMultiSender() {
+ }
+
+ public void sendMessage(Member[] destination, ChannelMessage msg) throws ChannelException {
+ MultiPointSender sender = null;
+ try {
+ sender = (MultiPointSender)getSender();
+ if (sender == null) {
+ ChannelException cx = new ChannelException("Unable to retrieve a data sender, time out error.");
+ for (int i = 0; i < destination.length; i++) cx.addFaultyMember(destination[i], new NullPointerException("Unable to retrieve a sender from the sender pool"));
+ throw cx;
+ } else {
+ sender.sendMessage(destination, msg);
+ }
+ sender.keepalive();
+ }finally {
+ if ( sender != null ) returnSender(sender);
+ }
+ }
+
+ /**
+ * getNewDataSender
+ *
+ * @return DataSender
+ * @todo Implement this org.apache.catalina.tribes.transport.PooledSender
+ * method
+ */
+ public DataSender getNewDataSender() {
+ MultipointBioSender sender = new MultipointBioSender();
+ sender.transferProperties(this,sender);
+ return sender;
+ }
+
+
+ public void memberAdded(Member member) {
+
+ }
+
+ public void memberDisappeared(Member member) {
+ //disconnect senders
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.bio.util;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ErrorHandler;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+
+
+
+/**
+ * A fast queue that remover thread lock the adder thread. <br/>Limit the queue
+ * length when you have strange producer thread problemes.
+ *
+ * FIXME add i18n support to log messages
+ * @author Rainer Jung
+ * @author Peter Rossbach
+ * @version $Revision: 345567 $ $Date: 2005-11-18 15:07:23 -0600 (Fri, 18 Nov 2005) $
+ */
+public class FastQueue {
+
+ private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(FastQueue.class);
+
+ /**
+ * This is the actual queue
+ */
+ private SingleRemoveSynchronizedAddLock lock = null;
+
+ /**
+ * First Object at queue (consumer message)
+ */
+ private LinkObject first = null;
+
+ /**
+ * Last object in queue (producer Object)
+ */
+ private LinkObject last = null;
+
+ /**
+ * Current Queue elements size
+ */
+ private int size = 0;
+
+ /**
+ * check lock to detect strange threadings things
+ */
+ private boolean checkLock = false;
+
+ /**
+ * protocol the thread wait times
+ */
+ private boolean timeWait = false;
+
+ private boolean inAdd = false;
+
+ private boolean inRemove = false;
+
+ private boolean inMutex = false;
+
+ /**
+ * limit the queue legnth ( default is unlimited)
+ */
+ private int maxQueueLength = 0;
+
+ /**
+ * addWaitTimeout for producer
+ */
+ private long addWaitTimeout = 10000L;
+
+
+ /**
+ * removeWaitTimeout for consumer
+ */
+ private long removeWaitTimeout = 30000L;
+
+ /**
+ * enabled the queue
+ */
+ private boolean enabled = true;
+
+ /**
+ * max queue size
+ */
+ private int maxSize = 0;
+
+ /**
+ * avg size sample interval
+ */
+ private int sampleInterval = 100;
+
+ /**
+ * Generate Queue SingleRemoveSynchronizedAddLock and set add and wait
+ * Timeouts
+ */
+ public FastQueue() {
+ lock = new SingleRemoveSynchronizedAddLock();
+ lock.setAddWaitTimeout(addWaitTimeout);
+ lock.setRemoveWaitTimeout(removeWaitTimeout);
+ }
+
+ /**
+ * get current add wait timeout
+ *
+ * @return current wait timeout
+ */
+ public long getAddWaitTimeout() {
+ addWaitTimeout = lock.getAddWaitTimeout();
+ return addWaitTimeout;
+ }
+
+ /**
+ * Set add wait timeout (default 10000 msec)
+ *
+ * @param timeout
+ */
+ public void setAddWaitTimeout(long timeout) {
+ addWaitTimeout = timeout;
+ lock.setAddWaitTimeout(addWaitTimeout);
+ }
+
+ /**
+ * get current remove wait timeout
+ *
+ * @return The timeout
+ */
+ public long getRemoveWaitTimeout() {
+ removeWaitTimeout = lock.getRemoveWaitTimeout();
+ return removeWaitTimeout;
+ }
+
+ /**
+ * set remove wait timeout ( default 30000 msec)
+ *
+ * @param timeout
+ */
+ public void setRemoveWaitTimeout(long timeout) {
+ removeWaitTimeout = timeout;
+ lock.setRemoveWaitTimeout(removeWaitTimeout);
+ }
+
+ /**
+ * get Max Queue length
+ *
+ * @see org.apache.catalina.tribes.util.IQueue#getMaxQueueLength()
+ */
+ public int getMaxQueueLength() {
+ return maxQueueLength;
+ }
+
+ public void setMaxQueueLength(int length) {
+ maxQueueLength = length;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enable) {
+ enabled = enable;
+ if (!enabled) {
+ lock.abortRemove();
+ last = first = null;
+ }
+ }
+
+ /**
+ * @return Returns the checkLock.
+ */
+ public boolean isCheckLock() {
+ return checkLock;
+ }
+
+ /**
+ * @param checkLock The checkLock to set.
+ */
+ public void setCheckLock(boolean checkLock) {
+ this.checkLock = checkLock;
+ }
+
+
+ /**
+ * @return The max size
+ */
+ public int getMaxSize() {
+ return maxSize;
+ }
+
+ /**
+ * @param size
+ */
+ public void setMaxSize(int size) {
+ maxSize = size;
+ }
+
+
+ /**
+ * unlock queue for next add
+ */
+ public void unlockAdd() {
+ lock.unlockAdd(size > 0 ? true : false);
+ }
+
+ /**
+ * unlock queue for next remove
+ */
+ public void unlockRemove() {
+ lock.unlockRemove();
+ }
+
+ /**
+ * start queuing
+ */
+ public void start() {
+ setEnabled(true);
+ }
+
+ /**
+ * start queuing
+ */
+ public void stop() {
+ setEnabled(false);
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public SingleRemoveSynchronizedAddLock getLock() {
+ return lock;
+ }
+
+ /**
+ * Add new data to the queue
+ * @see org.apache.catalina.tribes.util.IQueue#add(java.lang.String, java.lang.Object)
+ * FIXME extract some method
+ */
+ public boolean add(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+ boolean ok = true;
+ long time = 0;
+
+ if (!enabled) {
+ if (log.isInfoEnabled())
+ log.info("FastQueue.add: queue disabled, add aborted");
+ return false;
+ }
+
+ if (timeWait) {
+ time = System.currentTimeMillis();
+ }
+ lock.lockAdd();
+ try {
+ if (log.isTraceEnabled()) {
+ log.trace("FastQueue.add: starting with size " + size);
+ }
+ if (checkLock) {
+ if (inAdd)
+ log.warn("FastQueue.add: Detected other add");
+ inAdd = true;
+ if (inMutex)
+ log.warn("FastQueue.add: Detected other mutex in add");
+ inMutex = true;
+ }
+
+ if ((maxQueueLength > 0) && (size >= maxQueueLength)) {
+ ok = false;
+ if (log.isTraceEnabled()) {
+ log.trace("FastQueue.add: Could not add, since queue is full (" + size + ">=" + maxQueueLength + ")");
+ }
+ } else {
+ LinkObject element = new LinkObject(msg,destination, payload);
+ if (size == 0) {
+ first = last = element;
+ size = 1;
+ } else {
+ if (last == null) {
+ ok = false;
+ log.error("FastQueue.add: Could not add, since last is null although size is "+ size + " (>0)");
+ } else {
+ last.append(element);
+ last = element;
+ size++;
+ }
+ }
+ }
+
+ if (first == null) {
+ log.error("FastQueue.add: first is null, size is " + size + " at end of add");
+ }
+ if (last == null) {
+ log.error("FastQueue.add: last is null, size is " + size+ " at end of add");
+ }
+
+ if (checkLock) {
+ if (!inMutex) log.warn("FastQueue.add: Cancelled by other mutex in add");
+ inMutex = false;
+ if (!inAdd) log.warn("FastQueue.add: Cancelled by other add");
+ inAdd = false;
+ }
+ if (log.isTraceEnabled()) log.trace("FastQueue.add: add ending with size " + size);
+
+ } finally {
+ lock.unlockAdd(true);
+ }
+ return ok;
+ }
+
+ /**
+ * remove the complete queued object list
+ * @see org.apache.catalina.tribes.util.IQueue#remove()
+ * FIXME extract some method
+ */
+ public LinkObject remove() {
+ LinkObject element;
+ boolean gotLock;
+ long time = 0;
+
+ if (!enabled) {
+ if (log.isInfoEnabled())
+ log.info("FastQueue.remove: queue disabled, remove aborted");
+ return null;
+ }
+
+ if (timeWait) {
+ time = System.currentTimeMillis();
+ }
+ gotLock = lock.lockRemove();
+ try {
+
+ if (!gotLock) {
+ if (enabled) {
+ if (log.isInfoEnabled())
+ log.info("FastQueue.remove: Remove aborted although queue enabled");
+ } else {
+ if (log.isInfoEnabled())
+ log.info("FastQueue.remove: queue disabled, remove aborted");
+ }
+ return null;
+ }
+
+ if (log.isTraceEnabled()) {
+ log.trace("FastQueue.remove: remove starting with size " + size);
+ }
+ if (checkLock) {
+ if (inRemove)
+ log.warn("FastQueue.remove: Detected other remove");
+ inRemove = true;
+ if (inMutex)
+ log.warn("FastQueue.remove: Detected other mutex in remove");
+ inMutex = true;
+ }
+
+ element = first;
+
+ first = last = null;
+ size = 0;
+
+ if (checkLock) {
+ if (!inMutex)
+ log.warn("FastQueue.remove: Cancelled by other mutex in remove");
+ inMutex = false;
+ if (!inRemove)
+ log.warn("FastQueue.remove: Cancelled by other remove");
+ inRemove = false;
+ }
+ if (log.isTraceEnabled()) {
+ log.trace("FastQueue.remove: remove ending with size " + size);
+ }
+
+ if (timeWait) {
+ time = System.currentTimeMillis();
+ }
+ } finally {
+ lock.unlockRemove();
+ }
+ return element;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.bio.util;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ErrorHandler;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+
+/**
+ * The class <b>LinkObject</b> implements an element
+ * for a linked list, consisting of a general
+ * data object and a pointer to the next element.
+ *
+ * @author Rainer Jung
+ * @author Peter Rossbach
+ * @author Filip Hanik
+ * @version $Revision: 304032 $ $Date: 2005-07-27 10:11:55 -0500 (Wed, 27 Jul 2005) $
+
+ */
+
+public class LinkObject {
+
+ private ChannelMessage msg;
+ private LinkObject next;
+ private byte[] key ;
+ private Member[] destination;
+ private InterceptorPayload payload;
+
+ /**
+ * Construct a new element from the data object.
+ * Sets the pointer to null.
+ *
+ * @param key The key
+ * @param payload The data object.
+ */
+ public LinkObject(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+ this.msg = msg;
+ this.next = null;
+ this.key = msg.getUniqueId();
+ this.payload = payload;
+ this.destination = destination;
+ }
+
+ /**
+ * Set the next element.
+ * @param next The next element.
+ */
+ public void append(LinkObject next) {
+ this.next = next;
+ }
+
+ /**
+ * Get the next element.
+ * @return The next element.
+ */
+ public LinkObject next() {
+ return next;
+ }
+
+ public void setNext(LinkObject next) {
+ this.next = next;
+ }
+
+ /**
+ * Get the data object from the element.
+ * @return The data object from the element.
+ */
+ public ChannelMessage data() {
+ return msg;
+ }
+
+ /**
+ * Get the unique message id
+ * @return the unique message id
+ */
+ public byte[] getKey() {
+ return key;
+ }
+
+ public ErrorHandler getHandler() {
+ return payload!=null?payload.getErrorHandler():null;
+ }
+
+ public InterceptorPayload getPayload() {
+ return payload;
+ }
+
+ public Member[] getDestination() {
+ return destination;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.bio.util;
+
+/**
+ * The class <b>SingleRemoveSynchronizedAddLock</b> implement locking for accessing the queue
+ * by a single remove thread and multiple add threads.
+ *
+ * A thread is only allowed to be either the remove or
+ * an add thread.
+ *
+ * The lock can either be owned by the remove thread
+ * or by a single add thread.
+ *
+ * If the remove thread tries to get the lock,
+ * but the queue is empty, it will block (poll)
+ * until an add threads adds an entry to the queue and
+ * releases the lock.
+ *
+ * If the remove thread and add threads compete for
+ * the lock and an add thread releases the lock, then
+ * the remove thread will get the lock first.
+ *
+ * The remove thread removes all entries in the queue
+ * at once and proceeses them without further
+ * polling the queue.
+ *
+ * The lock is not reentrant, in the sense, that all
+ * threads must release an owned lock before competing
+ * for the lock again!
+ *
+ * @author Rainer Jung
+ * @author Peter Rossbach
+ * @version 1.1
+ */
+
+public class SingleRemoveSynchronizedAddLock {
+
+ public SingleRemoveSynchronizedAddLock() {
+ }
+
+ public SingleRemoveSynchronizedAddLock(boolean dataAvailable) {
+ this.dataAvailable=dataAvailable;
+ }
+
+ /**
+ * Time in milliseconds after which threads
+ * waiting for an add lock are woken up.
+ * This is used as a safety measure in case
+ * thread notification via the unlock methods
+ * has a bug.
+ */
+ private long addWaitTimeout = 10000L;
+
+ /**
+ * Time in milliseconds after which threads
+ * waiting for a remove lock are woken up.
+ * This is used as a safety measure in case
+ * thread notification via the unlock methods
+ * has a bug.
+ */
+ private long removeWaitTimeout = 30000L;
+
+ /**
+ * The current remove thread.
+ * It is set to the remove thread polling for entries.
+ * It is reset to null when the remove thread
+ * releases the lock and proceeds processing
+ * the removed entries.
+ */
+ private Thread remover = null;
+
+ /**
+ * A flag indicating, if an add thread owns the lock.
+ */
+ private boolean addLocked = false;
+
+ /**
+ * A flag indicating, if the remove thread owns the lock.
+ */
+ private boolean removeLocked = false;
+
+ /**
+ * A flag indicating, if the remove thread is allowed
+ * to wait for the lock. The flag is set to false, when aborting.
+ */
+ private boolean removeEnabled = true;
+
+ /**
+ * A flag indicating, if the remover needs polling.
+ * It indicates, if the locked object has data available
+ * to be removed.
+ */
+ private boolean dataAvailable = false;
+
+ /**
+ * @return Value of addWaitTimeout
+ */
+ public synchronized long getAddWaitTimeout() {
+ return addWaitTimeout;
+ }
+
+ /**
+ * Set value of addWaitTimeout
+ */
+ public synchronized void setAddWaitTimeout(long timeout) {
+ addWaitTimeout = timeout;
+ }
+
+ /**
+ * @return Value of removeWaitTimeout
+ */
+ public synchronized long getRemoveWaitTimeout() {
+ return removeWaitTimeout;
+ }
+
+ /**
+ * Set value of removeWaitTimeout
+ */
+ public synchronized void setRemoveWaitTimeout(long timeout) {
+ removeWaitTimeout = timeout;
+ }
+
+ /**
+ * Check if the locked object has data available
+ * i.e. the remover can stop poling and get the lock.
+ * @return True iff the lock Object has data available.
+ */
+ public synchronized boolean isDataAvailable() {
+ return dataAvailable;
+ }
+
+ /**
+ * Check if an add thread owns the lock.
+ * @return True iff an add thread owns the lock.
+ */
+ public synchronized boolean isAddLocked() {
+ return addLocked;
+ }
+
+ /**
+ * Check if the remove thread owns the lock.
+ * @return True iff the remove thread owns the lock.
+ */
+ public synchronized boolean isRemoveLocked() {
+ return removeLocked;
+ }
+
+ /**
+ * Check if the remove thread is polling.
+ * @return True iff the remove thread is polling.
+ */
+ public synchronized boolean isRemovePolling() {
+ if ( remover != null ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Acquires the lock by an add thread and sets the add flag.
+ * If any add thread or the remove thread already acquired the lock
+ * this add thread will block until the lock is released.
+ */
+ public synchronized void lockAdd() {
+ if ( addLocked || removeLocked ) {
+ do {
+ try {
+ wait(addWaitTimeout);
+ } catch ( InterruptedException e ) {
+ Thread.currentThread().interrupted();
+ }
+ } while ( addLocked || removeLocked );
+ }
+ addLocked=true;
+ }
+
+ /**
+ * Acquires the lock by the remove thread and sets the remove flag.
+ * If any add thread already acquired the lock or the queue is
+ * empty, the remove thread will block until the lock is released
+ * and the queue is not empty.
+ */
+ public synchronized boolean lockRemove() {
+ removeLocked=false;
+ removeEnabled=true;
+ if ( ( addLocked || ! dataAvailable ) && removeEnabled ) {
+ remover=Thread.currentThread();
+ do {
+ try {
+ wait(removeWaitTimeout);
+ } catch ( InterruptedException e ) {
+ Thread.currentThread().interrupted();
+ }
+ } while ( ( addLocked || ! dataAvailable ) && removeEnabled );
+ remover=null;
+ }
+ if ( removeEnabled ) {
+ removeLocked=true;
+ }
+ return removeLocked;
+ }
+
+ /**
+ * Releases the lock by an add thread and reset the remove flag.
+ * If the reader thread is polling, notify it.
+ */
+ public synchronized void unlockAdd(boolean dataAvailable) {
+ addLocked=false;
+ this.dataAvailable=dataAvailable;
+ if ( ( remover != null ) && ( dataAvailable || ! removeEnabled ) ) {
+ remover.interrupt();
+ } else {
+ notifyAll();
+ }
+ }
+
+ /**
+ * Releases the lock by the remove thread and reset the add flag.
+ * Notify all waiting add threads,
+ * that the lock has been released by the remove thread.
+ */
+ public synchronized void unlockRemove() {
+ removeLocked=false;
+ dataAvailable=false;
+ notifyAll();
+ }
+
+ /**
+ * Abort any polling remover thread
+ */
+ public synchronized void abortRemove() {
+ removeEnabled=false;
+ if ( remover != null ) {
+ remover.interrupt();
+ }
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mbeans-descriptors PUBLIC
+ "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+ "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+<mbeans-descriptors>
+
+ <mbean name="SimpleTcpCluster"
+ description="Tcp Cluster implementation"
+ domain="Catalina"
+ group="Cluster"
+ type="org.apache.catalina.cluster.tcp.SimpleTcpCluster">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="notifyLifecycleListenerOnFailure"
+ description="notify lifecycleListener from message transfer failure"
+ is="true"
+ type="boolean"/>
+ <attribute name="clusterName"
+ description="name of cluster"
+ type="java.lang.String"/>
+ <attribute name="managerClassName"
+ description="session mananager classname"
+ type="java.lang.String"/>
+ <attribute name="clusterLogName"
+ description="Name of cluster transfer log device"
+ type="java.lang.String"/>
+ <attribute name="doClusterLog"
+ is="true"
+ description="enable cluster log transfer logging"
+ type="boolean"/>
+ <operation name="setProperty"
+ description="set a property to all cluster managers (with prefix 'manager.')"
+ impact="ACTION"
+ returnType="void">
+ <parameter name="key"
+ description="Property name"
+ type="java.lang.String"/>
+ <parameter name="value"
+ description="Property value"
+ type="java.lang.String"/>
+ </operation>
+
+ <operation name="send"
+ description="send message to all cluster members"
+ impact="ACTION"
+ returnType="void">
+ <parameter name="message"
+ description="replication message"
+ type="org.apache.catalina.cluster.ClusterMessage"/>
+ </operation>
+
+ <operation name="sendClusterDomain"
+ description="send message to all cluster members with same domain"
+ impact="ACTION"
+ returnType="void">
+ <parameter name="message"
+ description="replication message"
+ type="org.apache.catalina.cluster.ClusterMessage"/>
+ </operation>
+
+ <operation name="sendToMember"
+ description="send message to one cluster member"
+ impact="ACTION"
+ returnType="void">
+ <parameter name="message"
+ description="replication message"
+ type="org.apache.catalina..cluster.ClusterMessage"/>
+ <parameter name="member"
+ description="cluster member"
+ type="java.lang.String"/>
+ </operation>
+
+ <operation name="start"
+ description="Start the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="stop"
+ description="Stop the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="ClusterReceiverBase"
+ description="Tcp Cluster NioReceiver implementation"
+ domain="Catalina"
+ group="Cluster"
+ type="org.apache.catalina.cluster.tcp.ClusterReceiverBase">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="tcpListenAddress"
+ description="tcp listener address"
+ type="java.lang.String"/>
+ <attribute name="tcpListenPort"
+ description="tcp listener port"
+ type="int"/>
+ <attribute name="tcpThreadCount"
+ description="number of tcp listener worker threads"
+ type="int"/>
+ <attribute name="tcpSelectorTimeout"
+ description="tcp listener Selector timeout"
+ type="long"/>
+ <attribute name="nrOfMsgsReceived"
+ description="number of messages received from other nodes"
+ type="long"
+ writeable="false"/>
+ <attribute name="receivedTime"
+ description="total time message send time"
+ type="long"
+ writeable="false"/>
+ <attribute name="receivedProcessingTime"
+ description="received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minReceivedProcessingTime"
+ description="minimal received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgReceivedProcessingTime"
+ description="received processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxReceivedProcessingTime"
+ description="maximal received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doReceivedProcessingStats"
+ description="create received processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="avgTotalReceivedBytes"
+ description="received totalReceivedBytes / nrOfMsgsReceived"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalReceivedBytes"
+ description="number of bytes received"
+ type="long"
+ writeable="false"/>
+ <attribute name="sendAck"
+ description="send ack after data received"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="compress"
+ description="data received compressed"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="doListen"
+ description="is port really started"
+ is="true"
+ type="boolean"
+ writeable="false" />
+
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="start"
+ description="Start the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="stop"
+ description="Stop the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="SocketReplicationListener"
+ description="Tcp Cluster SocketReplicationListener implementation"
+ domain="Catalina"
+ group="Cluster"
+ type="org.apache.catalina.cluster.tcp.SocketReplicationListener">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="tcpListenAddress"
+ description="tcp listener address"
+ type="java.lang.String"/>
+ <attribute name="tcpListenPort"
+ description="tcp listener port"
+ type="int"/>
+ <attribute name="tcpListenMaxPort"
+ description="max tcp listen used port"
+ type="int"/>
+ <attribute name="tcpListenTimeout"
+ description="max tcp listen timeout (sec) wait for ServerSocket start"
+ type="int"/>
+ <attribute name="nrOfMsgsReceived"
+ description="number of messages received from other nodes"
+ type="long"
+ writeable="false"/>
+ <attribute name="receivedTime"
+ description="total time message send time"
+ type="long"
+ writeable="false"/>
+ <attribute name="receivedProcessingTime"
+ description="received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minReceivedProcessingTime"
+ description="minimal received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgReceivedProcessingTime"
+ description="received processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxReceivedProcessingTime"
+ description="maximal received processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doReceivedProcessingStats"
+ description="create received processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="avgTotalReceivedBytes"
+ description="received totalReceivedBytes / nrOfMsgsReceived"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalReceivedBytes"
+ description="number of bytes received"
+ type="long"
+ writeable="false"/>
+ <attribute name="sendAck"
+ description="send ack after data received"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="compress"
+ description="data received compressed"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="doListen"
+ description="is port really started"
+ is="true"
+ type="boolean"
+ writeable="false" />
+
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="start"
+ description="Start the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ <operation name="stop"
+ description="Stop the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="ReplicationTransmitter"
+ description="Tcp replication transmitter"
+ domain="Catalina"
+ group="ClusterSender"
+ type="org.apache.catalina.cluster.tcp.ReplicationTransmitter">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="replicationMode"
+ description="replication mode (synchnous,pooled.asynchnous,fastasyncqueue)"
+ type="java.lang.String"/>
+ <attribute name="ackTimeout"
+ description="acknowledge timeout"
+ type="long"/>
+ <attribute name="autoConnect"
+ description="is sender disabled, fork a new socket"
+ is="true"
+ type="boolean" />
+ <attribute name="waitForAck"
+ description="Wait for ack after data send"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="processingTime"
+ description="sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minProcessingTime"
+ description="minimal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgProcessingTime"
+ description="processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxProcessingTime"
+ description="maximal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doTransmitterProcessingStats"
+ description="create processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="nrOfRequests"
+ description="number of send messages to other members"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalBytes"
+ description="number of bytes transfered"
+ type="long"
+ writeable="false"/>
+ <attribute name="failureCounter"
+ description="number of wrong transfers"
+ type="long"
+ writeable="false"/>
+ <attribute name="senderObjectNames"
+ description="get all sender object names"
+ type="[Ljavax.management.ObjectName;"
+ writeable="false"/>
+ <operation name="start"
+ description="Start the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="stop"
+ description="Stop the cluster"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="checkKeepAlive"
+ description="Check all sender connection for close socket (keepalive)"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ </mbean>
+
+ <mbean name="AsyncSocketSender"
+ description="Async Cluster Sender"
+ domain="Catalina"
+ group="IDataSender"
+ type="org.apache.catalina.cluster.tcp.AsyncSocketSender">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="address"
+ description="sender ip address"
+ type="java.net.InetAddress"
+ writeable="false"/>
+ <attribute name="port"
+ description="sender port"
+ type="int"
+ writeable="false" />
+ <attribute name="suspect"
+ description="Socket is gone"
+ type="boolean"/>
+ <attribute name="waitForAck"
+ description="Wait for ack after data send"
+ is="true"
+ type="boolean"
+ writeable="false"/>
+ <attribute name="ackTimeout"
+ description="acknowledge timeout"
+ type="long"/>
+ <attribute name="avgMessageSize"
+ writeable="false"
+ description="avg message size (totalbytes/nrOfRequests"
+ type="long"/>
+ <attribute name="queueSize"
+ writeable="false"
+ description="queue size"
+ type="int"/>
+ <attribute name="queuedNrOfBytes"
+ writeable="false"
+ description="number of bytes over all queued messages"
+ type="long"/>
+ <attribute name="messageTransferStarted"
+ description="message is in transfer"
+ type="boolean"
+ is="true"
+ writeable="false"/>
+ <attribute name="keepAliveTimeout"
+ description="active socket keep alive timeout"
+ type="long"/>
+ <attribute name="keepAliveMaxRequestCount"
+ description="max request over this socket"
+ type="int"/>
+ <attribute name="keepAliveCount"
+ description="keep Alive request count"
+ type="int"
+ writeable="false"/>
+ <attribute name="keepAliveConnectTime"
+ description="Connect time for keep alive"
+ type="long"
+ writeable="false"/>
+ <attribute name="resend"
+ description="after send failure make a resend"
+ is="true"
+ type="boolean" />
+ <attribute name="connected"
+ is="true"
+ description="socket connected"
+ type="boolean"
+ writeable="false"/>
+ <attribute name="nrOfRequests"
+ description="number of send messages to other members"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalBytes"
+ description="number of bytes transfered"
+ type="long"
+ writeable="false"/>
+ <attribute name="processingTime"
+ description="sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minProcessingTime"
+ description="minimal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgProcessingTime"
+ description="processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxProcessingTime"
+ description="maximal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doProcessingStats"
+ description="create processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="waitAckTime"
+ description="sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minWaitAckTime"
+ description="minimal sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgWaitAckTime"
+ description="waitAck time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxWaitAckTime"
+ description="maximal sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doWaitAckStats"
+ description="create waitAck time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="connectCounter"
+ description="counts connects"
+ type="long"
+ writeable="false"/>
+ <attribute name="disconnectCounter"
+ description="counts disconnects"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketOpenCounter"
+ description="counts open socket (KeepAlive and connects)"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketOpenFailureCounter"
+ description="counts open socket failures"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketCloseCounter"
+ description="counts closed socket (KeepAlive and disconnects)"
+ type="long"
+ writeable="false"/>
+ <attribute name="missingAckCounter"
+ description="counts missing ack"
+ type="long"
+ writeable="false"/>
+ <attribute name="dataResendCounter"
+ description="counts data resends"
+ type="long"
+ writeable="false"/>
+ <attribute name="dataFailureCounter"
+ description="counts data send failures"
+ type="long"
+ writeable="false"/>
+ <attribute name="inQueueCounter"
+ description="counts all queued messages"
+ type="long"
+ writeable="false"/>
+ <attribute name="outQueueCounter"
+ description="counts all successfully sended messages"
+ type="long"
+ writeable="false"/>
+ <operation name="connect"
+ description="connect to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="disconnect"
+ description="disconnect to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="checkKeepAlive"
+ description="Check connection for close socket"
+ impact="ACTION"
+ returnType="boolean">
+ </operation>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="MultiSocketSender"
+ description="Multi Socket Sender, more than one socket per member"
+ domain="Catalina"
+ group="IDataSender"
+ type="org.apache.catalina.cluster.tcp.PooledSocketSender">
+ <attribute name="address"
+ description="sender ip address"
+ type="java.net.InetAddress"
+ writeable="false"/>
+ <attribute name="port"
+ description="sender port"
+ type="int"
+ writeable="false" />
+ <attribute name="suspect"
+ description="Socket is gone"
+ type="boolean"/>
+ <attribute name="ackTimeout"
+ description="acknowledge timeout"
+ type="long"/>
+ <attribute name="waitForAck"
+ description="Wait for ack after data send"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="maxPoolSocketLimit"
+ description="Max parallel sockets"
+ type="int"/>
+ <attribute name="keepAliveTimeout"
+ description="active socket keep alive timeout"
+ type="long"/>
+ <attribute name="keepAliveMaxRequestCount"
+ description="max request over this socket"
+ type="int"/>
+ <attribute name="resend"
+ description="after send failure make a resend"
+ is="true"
+ type="boolean" />
+ <attribute name="connected"
+ is="true"
+ description="socket connected"
+ type="boolean"
+ writeable="false"/>
+ <attribute name="avgMessageSize"
+ writeable="false"
+ description="avg message size (totalbytes/nrOfRequests"
+ type="long"/>
+ <attribute name="nrOfRequests"
+ description="number of send messages to other members"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalBytes"
+ description="number of bytes transfered"
+ type="long"
+ writeable="false"/>
+ <attribute name="connectCounter"
+ description="counts connects"
+ type="long"
+ writeable="false"/>
+ <attribute name="disconnectCounter"
+ description="counts disconnects"
+ type="long"
+ writeable="false"/>
+ <operation name="connect"
+ description="start Queue to connect to ohter replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="disconnect"
+ description="stop Queue to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="SocketSender"
+ description="Sync Cluster Sender"
+ domain="Catalina"
+ group="IDataSender"
+ type="org.apache.catalina.cluster.tcp.SocketSender">
+ <attribute name="address"
+ description="sender ip address"
+ type="java.net.InetAddress"
+ writeable="false"/>
+ <attribute name="port"
+ description="sender port"
+ type="int"
+ writeable="false" />
+ <attribute name="suspect"
+ description="Socket is gone"
+ type="boolean"/>
+ <attribute name="ackTimeout"
+ description="acknowledge timeout"
+ type="long"/>
+ <attribute name="waitForAck"
+ description="Wait for ack after data send"
+ is="true"
+ type="boolean"
+ writeable="false" />
+ <attribute name="keepAliveTimeout"
+ description="active socket keep alive timeout"
+ type="long"/>
+ <attribute name="keepAliveMaxRequestCount"
+ description="max request over this socket"
+ type="int"/>
+ <attribute name="messageTransferStarted"
+ description="message is in transfer"
+ type="boolean"
+ is="true"
+ writeable="false"/>
+ <attribute name="keepAliveCount"
+ description="keep Alive request count"
+ type="int"
+ writeable="false"/>
+ <attribute name="keepAliveConnectTime"
+ description="Connect time for keep alive"
+ type="long"
+ writeable="false"/>
+ <attribute name="resend"
+ description="after send failure make a resend"
+ is="true"
+ type="boolean" />
+ <attribute name="connected"
+ is="true"
+ description="socket connected"
+ type="boolean"
+ writeable="false"/>
+ <attribute name="avgMessageSize"
+ writeable="false"
+ description="avg message size (totalbytes/nrOfRequests"
+ type="long"/>
+ <attribute name="nrOfRequests"
+ description="number of send messages to other members"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalBytes"
+ description="number of bytes transfered"
+ type="long"
+ writeable="false"/>
+ <attribute name="processingTime"
+ description="sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minProcessingTime"
+ description="minimal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgProcessingTime"
+ description="processing time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxProcessingTime"
+ description="maximal sending processing time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doProcessingStats"
+ description="create Processing time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="waitAckTime"
+ description="sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="minWaitAckTime"
+ description="minimal sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="avgWaitAckTime"
+ description="waitAck time / nrOfRequests"
+ type="double"
+ writeable="false"/>
+ <attribute name="maxWaitAckTime"
+ description="maximal sending waitAck time"
+ type="long"
+ writeable="false"/>
+ <attribute name="doWaitAckStats"
+ description="create waitAck time stats"
+ is="true"
+ type="boolean" />
+ <attribute name="connectCounter"
+ description="counts connects"
+ type="long"
+ writeable="false"/>
+ <attribute name="disconnectCounter"
+ description="counts disconnects"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketCloseCounter"
+ description="counts closed socket (KeepAlive and disconnects)"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketOpenFailureCounter"
+ description="counts open socket failures"
+ type="long"
+ writeable="false"/>
+ <attribute name="socketOpenCounter"
+ description="counts open socket (KeepAlive and connects)"
+ type="long"
+ writeable="false"/>
+ <attribute name="missingAckCounter"
+ description="counts missing ack"
+ type="long"
+ writeable="false"/>
+ <attribute name="dataResendCounter"
+ description="counts data resends"
+ type="long"
+ writeable="false"/>
+ <attribute name="dataFailureCounter"
+ description="counts data send failures"
+ type="long"
+ writeable="false"/>
+ <operation name="connect"
+ description="connect to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="disconnect"
+ description="disconnect to other replication node"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+ <operation name="checkKeepAlive"
+ description="Check connection for close socket"
+ impact="ACTION"
+ returnType="boolean">
+ </operation>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+ <mbean name="ReplicationValve"
+ description="Valve for simple tcp replication"
+ domain="Catalina"
+ group="Valve"
+ type="org.apache.catalina.cluster.tcp.ReplicationValve">
+ <attribute name="info"
+ description="Class version info"
+ type="java.lang.String"
+ writeable="false"/>
+ <attribute name="filter"
+ description="resource filter to disable session replication check"
+ type="java.lang.String"/>
+ <attribute name="primaryIndicator"
+ is="true"
+ description="set indicator that request processing is at primary session node"
+ type="boolean"/>
+ <attribute name="primaryIndicatorName"
+ description="Request attribute name to indicate that request processing is at primary session node"
+ type="java.lang.String"/>
+ <attribute name="doProcessingStats"
+ is="true"
+ description="active statistics counting"
+ type="boolean"/>
+ <attribute name="nrOfRequests"
+ description="number of replicated requests"
+ type="long"
+ writeable="false"/>
+ <attribute name="nrOfFilterRequests"
+ description="number of filtered requests"
+ type="long"
+ writeable="false"/>
+ <attribute name="nrOfSendRequests"
+ description="number of send requests"
+ type="long"
+ writeable="false"/>
+ <attribute name="nrOfCrossContextSendRequests"
+ description="number of send cross context session requests"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalRequestTime"
+ description="total replicated request time"
+ type="long"
+ writeable="false"/>
+ <attribute name="totalSendTime"
+ description="total replicated send time"
+ type="long"
+ writeable="false"/>
+ <attribute name="lastSendTime"
+ description="last replicated request time"
+ type="long"
+ writeable="false"/>
+ <operation name="resetStatistics"
+ description="Reset all statistics"
+ impact="ACTION"
+ returnType="void">
+ </operation>
+
+ </mbean>
+
+
+</mbeans-descriptors>
--- /dev/null
+/*\r
+ * Copyright 1999,2004-2005 The Apache Software Foundation.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.apache.catalina.tribes.transport.nio;\r
+\r
+import java.io.IOException;\r
+import java.net.ServerSocket;\r
+import java.nio.channels.SelectableChannel;\r
+import java.nio.channels.SelectionKey;\r
+import java.nio.channels.Selector;\r
+import java.nio.channels.ServerSocketChannel;\r
+import java.nio.channels.SocketChannel;\r
+import java.util.Iterator;\r
+\r
+import org.apache.catalina.tribes.ChannelReceiver;\r
+import org.apache.catalina.tribes.io.ListenCallback;\r
+import org.apache.catalina.tribes.io.ObjectReader;\r
+import org.apache.catalina.tribes.transport.Constants;\r
+import org.apache.catalina.tribes.transport.ReceiverBase;\r
+import org.apache.catalina.tribes.transport.ThreadPool;\r
+import org.apache.catalina.tribes.transport.WorkerThread;\r
+import org.apache.catalina.tribes.util.StringManager;\r
+import java.util.LinkedList;\r
+import java.util.Set;\r
+import java.nio.channels.CancelledKeyException;\r
+\r
+/**\r
+ * @author Filip Hanik\r
+ * @version $Revision: 379904 $ $Date: 2006-02-22 15:16:25 -0600 (Wed, 22 Feb 2006) $\r
+ */\r
+public class NioReceiver extends ReceiverBase implements Runnable, ChannelReceiver, ListenCallback {\r
+\r
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(NioReceiver.class);\r
+\r
+ /**\r
+ * The string manager for this package.\r
+ */\r
+ protected StringManager sm = StringManager.getManager(Constants.Package);\r
+\r
+ /**\r
+ * The descriptive information about this implementation.\r
+ */\r
+ private static final String info = "NioReceiver/1.0";\r
+\r
+ private Selector selector = null;\r
+ private ServerSocketChannel serverChannel = null;\r
+\r
+ protected LinkedList events = new LinkedList();\r
+// private Object interestOpsMutex = new Object();\r
+\r
+ public NioReceiver() {\r
+ }\r
+\r
+ /**\r
+ * Return descriptive information about this implementation and the\r
+ * corresponding version number, in the format\r
+ * <code><description>/<version></code>.\r
+ */\r
+ public String getInfo() {\r
+ return (info);\r
+ }\r
+\r
+// public Object getInterestOpsMutex() {\r
+// return interestOpsMutex;\r
+// }\r
+\r
+ public void stop() {\r
+ this.stopListening();\r
+ }\r
+\r
+ /**\r
+ * start cluster receiver\r
+ * @throws Exception\r
+ * @see org.apache.catalina.tribes.ClusterReceiver#start()\r
+ */\r
+ public void start() throws IOException {\r
+ try {\r
+// setPool(new ThreadPool(interestOpsMutex, getMaxThreads(),getMinThreads(),this));\r
+ setPool(new ThreadPool(getMaxThreads(),getMinThreads(),this));\r
+ } catch (Exception x) {\r
+ log.fatal("ThreadPool can initilzed. Listener not started", x);\r
+ if ( x instanceof IOException ) throw (IOException)x;\r
+ else throw new IOException(x.getMessage());\r
+ }\r
+ try {\r
+ getBind();\r
+ bind();\r
+ Thread t = new Thread(this, "NioReceiver");\r
+ t.setDaemon(true);\r
+ t.start();\r
+ } catch (Exception x) {\r
+ log.fatal("Unable to start cluster receiver", x);\r
+ if ( x instanceof IOException ) throw (IOException)x;\r
+ else throw new IOException(x.getMessage());\r
+ }\r
+ }\r
+ \r
+ public WorkerThread getWorkerThread() {\r
+ NioReplicationThread thread = new NioReplicationThread(this,this);\r
+ thread.setUseBufferPool(this.getUseBufferPool());\r
+ thread.setRxBufSize(getRxBufSize());\r
+ thread.setOptions(getWorkerThreadOptions());\r
+ return thread;\r
+ }\r
+ \r
+ \r
+ \r
+ protected void bind() throws IOException {\r
+ // allocate an unbound server socket channel\r
+ serverChannel = ServerSocketChannel.open();\r
+ // Get the associated ServerSocket to bind it with\r
+ ServerSocket serverSocket = serverChannel.socket();\r
+ // create a new Selector for use below\r
+ selector = Selector.open();\r
+ // set the port the server channel will listen to\r
+ //serverSocket.bind(new InetSocketAddress(getBind(), getTcpListenPort()));\r
+ bind(serverSocket,getTcpListenPort(),getAutoBind());\r
+ // set non-blocking mode for the listening socket\r
+ serverChannel.configureBlocking(false);\r
+ // register the ServerSocketChannel with the Selector\r
+ serverChannel.register(selector, SelectionKey.OP_ACCEPT);\r
+ \r
+ }\r
+ \r
+ public void addEvent(Runnable event) {\r
+ if ( selector != null ) {\r
+ synchronized (events) {\r
+ events.add(event);\r
+ }\r
+ if ( log.isTraceEnabled() ) log.trace("Adding event to selector:"+event);\r
+ selector.wakeup();\r
+ }\r
+ }\r
+\r
+ public void events() {\r
+ if ( events.size() == 0 ) return;\r
+ synchronized (events) {\r
+ Runnable r = null;\r
+ while ( (events.size() > 0) && (r = (Runnable)events.removeFirst()) != null ) {\r
+ try {\r
+ if ( log.isTraceEnabled() ) log.trace("Processing event in selector:"+r);\r
+ r.run();\r
+ } catch ( Exception x ) {\r
+ log.error("",x);\r
+ }\r
+ }\r
+ events.clear();\r
+ }\r
+ }\r
+ \r
+ public static void cancelledKey(SelectionKey key) {\r
+ ObjectReader reader = (ObjectReader)key.attachment();\r
+ if ( reader != null ) {\r
+ reader.setCancelled(true);\r
+ reader.finish();\r
+ }\r
+ key.cancel(); \r
+ key.attach(null);\r
+ try { ((SocketChannel)key.channel()).socket().close(); } catch (IOException e) { if (log.isDebugEnabled()) log.debug("", e); }\r
+ try { key.channel().close(); } catch (IOException e) { if (log.isDebugEnabled()) log.debug("", e); }\r
+ \r
+ }\r
+ \r
+ protected void socketTimeouts() {\r
+ //timeout\r
+ Set keys = selector.keys();\r
+ long now = System.currentTimeMillis();\r
+ for (Iterator iter = keys.iterator(); iter.hasNext(); ) {\r
+ SelectionKey key = (SelectionKey) iter.next();\r
+ try {\r
+// if (key.interestOps() == SelectionKey.OP_READ) {\r
+// //only timeout sockets that we are waiting for a read from\r
+// ObjectReader ka = (ObjectReader) key.attachment();\r
+// long delta = now - ka.getLastAccess();\r
+// if (delta > (long) getTimeout()) {\r
+// cancelledKey(key);\r
+// }\r
+// }\r
+// else\r
+ if ( key.interestOps() == 0 ) {\r
+ //check for keys that didn't make it in.\r
+ ObjectReader ka = (ObjectReader) key.attachment();\r
+ if ( ka != null ) {\r
+ long delta = now - ka.getLastAccess();\r
+ if (delta > (long) getTimeout() && (!ka.isAccessed())) {\r
+ log.warn("Channel key is registered, but has had no interest ops for the last "+getTimeout()+" ms. (cancelled:"+ka.isCancelled()+"):"+key+" last access:"+new java.sql.Timestamp(ka.getLastAccess()));\r
+// System.out.println("Interest:"+key.interestOps());\r
+// System.out.println("Ready Ops:"+key.readyOps());\r
+// System.out.println("Valid:"+key.isValid());\r
+ ka.setLastAccess(now);\r
+ //key.interestOps(SelectionKey.OP_READ);\r
+ }//end if\r
+ } else {\r
+ cancelledKey(key);\r
+ }//end if\r
+ }//end if\r
+ }catch ( CancelledKeyException ckx ) {\r
+ cancelledKey(key);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * get data from channel and store in byte array\r
+ * send it to cluster\r
+ * @throws IOException\r
+ * @throws java.nio.channels.ClosedChannelException\r
+ */\r
+ protected void listen() throws Exception {\r
+ if (doListen()) {\r
+ log.warn("ServerSocketChannel already started");\r
+ return;\r
+ }\r
+ \r
+ setListen(true);\r
+\r
+ while (doListen() && selector != null) {\r
+ // this may block for a long time, upon return the\r
+ // selected set contains keys of the ready channels\r
+ try {\r
+ events();\r
+ socketTimeouts();\r
+ int n = selector.select(getTcpSelectorTimeout());\r
+ if (n == 0) {\r
+ //there is a good chance that we got here\r
+ //because the TcpReplicationThread called\r
+ //selector wakeup().\r
+ //if that happens, we must ensure that that\r
+ //thread has enough time to call interestOps\r
+// synchronized (interestOpsMutex) {\r
+ //if we got the lock, means there are no\r
+ //keys trying to register for the\r
+ //interestOps method\r
+// }\r
+ continue; // nothing to do\r
+ }\r
+ // get an iterator over the set of selected keys\r
+ Iterator it = selector.selectedKeys().iterator();\r
+ // look at each key in the selected set\r
+ while (it.hasNext()) {\r
+ SelectionKey key = (SelectionKey) it.next();\r
+ // Is a new connection coming in?\r
+ if (key.isAcceptable()) {\r
+ ServerSocketChannel server = (ServerSocketChannel) key.channel();\r
+ SocketChannel channel = server.accept();\r
+ channel.socket().setReceiveBufferSize(getRxBufSize());\r
+ channel.socket().setSendBufferSize(getTxBufSize());\r
+ channel.socket().setTcpNoDelay(getTcpNoDelay());\r
+ channel.socket().setKeepAlive(getSoKeepAlive());\r
+ channel.socket().setOOBInline(getOoBInline());\r
+ channel.socket().setReuseAddress(getSoReuseAddress());\r
+ channel.socket().setSoLinger(getSoLingerOn(),getSoLingerTime());\r
+ channel.socket().setTrafficClass(getSoTrafficClass());\r
+ channel.socket().setSoTimeout(getTimeout());\r
+ Object attach = new ObjectReader(channel);\r
+ registerChannel(selector,\r
+ channel,\r
+ SelectionKey.OP_READ,\r
+ attach);\r
+ }\r
+ // is there data to read on this channel?\r
+ if (key.isReadable()) {\r
+ readDataFromSocket(key);\r
+ } else {\r
+ key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));\r
+ }\r
+\r
+ // remove key from selected set, it's been handled\r
+ it.remove();\r
+ }\r
+ } catch (java.nio.channels.ClosedSelectorException cse) {\r
+ // ignore is normal at shutdown or stop listen socket\r
+ } catch (java.nio.channels.CancelledKeyException nx) {\r
+ log.warn("Replication client disconnected, error when polling key. Ignoring client.");\r
+ } catch (Throwable x) {\r
+ try {\r
+ log.error("Unable to process request in NioReceiver", x);\r
+ }catch ( Throwable tx ) {\r
+ //in case an out of memory error, will affect the logging framework as well\r
+ tx.printStackTrace();\r
+ }\r
+ }\r
+\r
+ }\r
+ serverChannel.close();\r
+ if (selector != null)\r
+ selector.close();\r
+ }\r
+\r
+ \r
+\r
+ /**\r
+ * Close Selector.\r
+ *\r
+ * @see org.apache.catalina.tribes.transport.ClusterReceiverBase#stopListening()\r
+ */\r
+ protected void stopListening() {\r
+ // Bugzilla 37529: http://issues.apache.org/bugzilla/show_bug.cgi?id=37529\r
+ setListen(false);\r
+ if (selector != null) {\r
+ try {\r
+ for (int i = 0; i < getMaxThreads(); i++) {\r
+ selector.wakeup();\r
+ }\r
+ selector.close();\r
+ } catch (Exception x) {\r
+ log.error("Unable to close cluster receiver selector.", x);\r
+ } finally {\r
+ selector = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ // ----------------------------------------------------------\r
+\r
+ /**\r
+ * Register the given channel with the given selector for\r
+ * the given operations of interest\r
+ */\r
+ protected void registerChannel(Selector selector,\r
+ SelectableChannel channel,\r
+ int ops,\r
+ Object attach) throws Exception {\r
+ if (channel == null)return; // could happen\r
+ // set the new channel non-blocking\r
+ channel.configureBlocking(false);\r
+ // register it with the selector\r
+ channel.register(selector, ops, attach);\r
+ }\r
+\r
+ /**\r
+ * Start thread and listen\r
+ */\r
+ public void run() {\r
+ try {\r
+ listen();\r
+ } catch (Exception x) {\r
+ log.error("Unable to run replication listener.", x);\r
+ }\r
+ }\r
+\r
+ // ----------------------------------------------------------\r
+\r
+ /**\r
+ * Sample data handler method for a channel with data ready to read.\r
+ * @param key A SelectionKey object associated with a channel\r
+ * determined by the selector to be ready for reading. If the\r
+ * channel returns an EOF condition, it is closed here, which\r
+ * automatically invalidates the associated key. The selector\r
+ * will then de-register the channel on the next select call.\r
+ */\r
+ protected void readDataFromSocket(SelectionKey key) throws Exception {\r
+ NioReplicationThread worker = (NioReplicationThread) getPool().getWorker();\r
+ if (worker == null) {\r
+ // No threads available, do nothing, the selection\r
+ // loop will keep calling this method until a\r
+ // thread becomes available, the thread pool itself has a waiting mechanism\r
+ // so we will not wait here.\r
+ if (log.isDebugEnabled())\r
+ log.debug("No TcpReplicationThread available");\r
+ } else {\r
+ // invoking this wakes up the worker thread then returns\r
+ worker.serviceChannel(key);\r
+ }\r
+ }\r
+\r
+\r
+}\r
--- /dev/null
+/*\r
+ * Copyright 1999,2004 The Apache Software Foundation.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.apache.catalina.tribes.transport.nio;\r
+import java.io.IOException;\r
+import java.nio.ByteBuffer;\r
+import java.nio.channels.SelectionKey;\r
+import java.nio.channels.SocketChannel;\r
+\r
+import org.apache.catalina.tribes.io.ObjectReader;\r
+import org.apache.catalina.tribes.transport.Constants;\r
+import org.apache.catalina.tribes.transport.WorkerThread;\r
+import org.apache.catalina.tribes.ChannelMessage;\r
+import org.apache.catalina.tribes.io.ListenCallback;\r
+import org.apache.catalina.tribes.io.ChannelData;\r
+import org.apache.catalina.tribes.io.BufferPool;\r
+import java.nio.channels.CancelledKeyException;\r
+import org.apache.catalina.tribes.UniqueId;\r
+import org.apache.catalina.tribes.RemoteProcessException;\r
+import org.apache.catalina.tribes.util.Logs;\r
+\r
+/**\r
+ * A worker thread class which can drain channels and echo-back the input. Each\r
+ * instance is constructed with a reference to the owning thread pool object.\r
+ * When started, the thread loops forever waiting to be awakened to service the\r
+ * channel associated with a SelectionKey object. The worker is tasked by\r
+ * calling its serviceChannel() method with a SelectionKey object. The\r
+ * serviceChannel() method stores the key reference in the thread object then\r
+ * calls notify() to wake it up. When the channel has been drained, the worker\r
+ * thread returns itself to its parent pool.\r
+ * \r
+ * @author Filip Hanik\r
+ * \r
+ * @version $Revision: 378050 $, $Date: 2006-02-15 12:30:02 -0600 (Wed, 15 Feb 2006) $\r
+ */\r
+public class NioReplicationThread extends WorkerThread {\r
+ \r
+ private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog( NioReplicationThread.class );\r
+ \r
+ private ByteBuffer buffer = null;\r
+ private SelectionKey key;\r
+ private int rxBufSize;\r
+ private NioReceiver receiver;\r
+ public NioReplicationThread (ListenCallback callback, NioReceiver receiver)\r
+ {\r
+ super(callback);\r
+ this.receiver = receiver;\r
+ }\r
+\r
+ // loop forever waiting for work to do\r
+ public synchronized void run() { \r
+ this.notify();\r
+ if ( (getOptions() & OPTION_DIRECT_BUFFER) == OPTION_DIRECT_BUFFER ) {\r
+ buffer = ByteBuffer.allocateDirect(getRxBufSize());\r
+ }else {\r
+ buffer = ByteBuffer.allocate (getRxBufSize());\r
+ }\r
+ while (isDoRun()) {\r
+ try {\r
+ // sleep and release object lock\r
+ this.wait();\r
+ } catch (InterruptedException e) {\r
+ if(log.isInfoEnabled()) log.info("TCP worker thread interrupted in cluster",e);\r
+ // clear interrupt status\r
+ Thread.interrupted();\r
+ }\r
+ if (key == null) {\r
+ continue; // just in case\r
+ }\r
+ if ( log.isTraceEnabled() ) \r
+ log.trace("Servicing key:"+key);\r
+\r
+ try {\r
+ ObjectReader reader = (ObjectReader)key.attachment();\r
+ if ( reader == null ) {\r
+ if ( log.isTraceEnabled() ) \r
+ log.trace("No object reader, cancelling:"+key);\r
+ cancelKey(key);\r
+ } else {\r
+ if ( log.isTraceEnabled() ) \r
+ log.trace("Draining channel:"+key);\r
+\r
+ drainChannel(key, reader);\r
+ }\r
+ } catch (Exception e) {\r
+ //this is common, since the sockets on the other\r
+ //end expire after a certain time.\r
+ if ( e instanceof CancelledKeyException ) {\r
+ //do nothing\r
+ } else if ( e instanceof IOException ) {\r
+ //dont spew out stack traces for IO exceptions unless debug is enabled.\r
+ if (log.isDebugEnabled()) log.debug ("IOException in replication worker, unable to drain channel. Probable cause: Keep alive socket closed["+e.getMessage()+"].", e);\r
+ else log.warn ("IOException in replication worker, unable to drain channel. Probable cause: Keep alive socket closed["+e.getMessage()+"].");\r
+ } else if ( log.isErrorEnabled() ) {\r
+ //this is a real error, log it.\r
+ log.error("Exception caught in TcpReplicationThread.drainChannel.",e);\r
+ } \r
+ cancelKey(key);\r
+ } finally {\r
+ \r
+ }\r
+ key = null;\r
+ // done, ready for more, return to pool\r
+ getPool().returnWorker (this);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Called to initiate a unit of work by this worker thread\r
+ * on the provided SelectionKey object. This method is\r
+ * synchronized, as is the run() method, so only one key\r
+ * can be serviced at a given time.\r
+ * Before waking the worker thread, and before returning\r
+ * to the main selection loop, this key's interest set is\r
+ * updated to remove OP_READ. This will cause the selector\r
+ * to ignore read-readiness for this channel while the\r
+ * worker thread is servicing it.\r
+ */\r
+ public synchronized void serviceChannel (SelectionKey key) {\r
+ if ( log.isTraceEnabled() ) \r
+ log.trace("About to service key:"+key);\r
+ ObjectReader reader = (ObjectReader)key.attachment();\r
+ if ( reader != null ) reader.setLastAccess(System.currentTimeMillis());\r
+ this.key = key;\r
+ key.interestOps (key.interestOps() & (~SelectionKey.OP_READ));\r
+ key.interestOps (key.interestOps() & (~SelectionKey.OP_WRITE));\r
+ this.notify(); // awaken the thread\r
+ }\r
+\r
+ /**\r
+ * The actual code which drains the channel associated with\r
+ * the given key. This method assumes the key has been\r
+ * modified prior to invocation to turn off selection\r
+ * interest in OP_READ. When this method completes it\r
+ * re-enables OP_READ and calls wakeup() on the selector\r
+ * so the selector will resume watching this channel.\r
+ */\r
+ protected void drainChannel (final SelectionKey key, ObjectReader reader) throws Exception {\r
+ reader.setLastAccess(System.currentTimeMillis());\r
+ reader.access();\r
+ SocketChannel channel = (SocketChannel) key.channel();\r
+ int count;\r
+ buffer.clear(); // make buffer empty\r
+\r
+ // loop while data available, channel is non-blocking\r
+ while ((count = channel.read (buffer)) > 0) {\r
+ buffer.flip(); // make buffer readable\r
+ if ( buffer.hasArray() ) \r
+ reader.append(buffer.array(),0,count,false);\r
+ else \r
+ reader.append(buffer,count,false);\r
+ buffer.clear(); // make buffer empty\r
+ //do we have at least one package?\r
+ if ( reader.hasPackage() ) break;\r
+ }\r
+\r
+ int pkgcnt = reader.count();\r
+ \r
+ if (count < 0 && pkgcnt == 0 ) {\r
+ //end of stream, and no more packages to process\r
+ remoteEof(key);\r
+ return;\r
+ }\r
+ \r
+ ChannelMessage[] msgs = pkgcnt == 0? ChannelData.EMPTY_DATA_ARRAY : reader.execute();\r
+ \r
+ registerForRead(key,reader);//register to read new data, before we send it off to avoid dead locks\r
+ \r
+ for ( int i=0; i<msgs.length; i++ ) {\r
+ /**\r
+ * Use send ack here if you want to ack the request to the remote \r
+ * server before completing the request\r
+ * This is considered an asynchronized request\r
+ */\r
+ if (ChannelData.sendAckAsync(msgs[i].getOptions())) sendAck(key,channel,Constants.ACK_COMMAND);\r
+ try {\r
+ if ( Logs.MESSAGES.isTraceEnabled() ) {\r
+ try {\r
+ Logs.MESSAGES.trace("NioReplicationThread - Received msg:" + new UniqueId(msgs[i].getUniqueId()) + " at " + new java.sql.Timestamp(System.currentTimeMillis()));\r
+ }catch ( Throwable t ) {}\r
+ }\r
+ //process the message\r
+ getCallback().messageDataReceived(msgs[i]);\r
+ /**\r
+ * Use send ack here if you want the request to complete on this \r
+ * server before sending the ack to the remote server\r
+ * This is considered a synchronized request\r
+ */\r
+ if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(key,channel,Constants.ACK_COMMAND);\r
+ }catch ( RemoteProcessException e ) {\r
+ if ( log.isDebugEnabled() ) log.error("Processing of cluster message failed.",e);\r
+ if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(key,channel,Constants.FAIL_ACK_COMMAND);\r
+ }catch ( Exception e ) {\r
+ log.error("Processing of cluster message failed.",e);\r
+ if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(key,channel,Constants.FAIL_ACK_COMMAND);\r
+ }\r
+ if ( getUseBufferPool() ) {\r
+ BufferPool.getBufferPool().returnBuffer(msgs[i].getMessage());\r
+ msgs[i].setMessage(null);\r
+ }\r
+ } \r
+ \r
+ if (count < 0) {\r
+ remoteEof(key);\r
+ return;\r
+ }\r
+ }\r
+\r
+ private void remoteEof(SelectionKey key) {\r
+ // close channel on EOF, invalidates the key\r
+ if ( log.isDebugEnabled() ) log.debug("Channel closed on the remote end, disconnecting");\r
+ cancelKey(key);\r
+ }\r
+\r
+ protected void registerForRead(final SelectionKey key, ObjectReader reader) {\r
+ if ( log.isTraceEnabled() ) \r
+ log.trace("Adding key for read event:"+key);\r
+ reader.finish();\r
+ //register our OP_READ interest\r
+ Runnable r = new Runnable() {\r
+ public void run() {\r
+ try {\r
+ if (key.isValid()) {\r
+ // cycle the selector so this key is active again\r
+ key.selector().wakeup();\r
+ // resume interest in OP_READ, OP_WRITE\r
+ int resumeOps = key.interestOps() | SelectionKey.OP_READ;\r
+ key.interestOps(resumeOps);\r
+ if ( log.isTraceEnabled() ) \r
+ log.trace("Registering key for read:"+key);\r
+ }\r
+ } catch (CancelledKeyException ckx ) {\r
+ NioReceiver.cancelledKey(key);\r
+ if ( log.isTraceEnabled() ) \r
+ log.trace("CKX Cancelling key:"+key);\r
+\r
+ } catch (Exception x) {\r
+ log.error("Error registering key for read:"+key,x);\r
+ }\r
+ }\r
+ };\r
+ receiver.addEvent(r);\r
+ }\r
+\r
+ private void cancelKey(final SelectionKey key) {\r
+ if ( log.isTraceEnabled() ) \r
+ log.trace("Adding key for cancel event:"+key);\r
+\r
+ ObjectReader reader = (ObjectReader)key.attachment();\r
+ if ( reader != null ) {\r
+ reader.setCancelled(true);\r
+ reader.finish();\r
+ }\r
+ Runnable cx = new Runnable() {\r
+ public void run() {\r
+ if ( log.isTraceEnabled() ) \r
+ log.trace("Cancelling key:"+key);\r
+\r
+ NioReceiver.cancelledKey(key);\r
+ }\r
+ };\r
+ receiver.addEvent(cx);\r
+ }\r
+ \r
+ \r
+\r
+\r
+\r
+ /**\r
+ * send a reply-acknowledgement (6,2,3)\r
+ * @param key\r
+ * @param channel\r
+ */\r
+ protected void sendAck(SelectionKey key, SocketChannel channel, byte[] command) {\r
+ \r
+ try {\r
+ ByteBuffer buf = ByteBuffer.wrap(command);\r
+ int total = 0;\r
+ while ( total < command.length ) {\r
+ total += channel.write(buf);\r
+ }\r
+ if (log.isTraceEnabled()) {\r
+ log.trace("ACK sent to " + channel.socket().getPort());\r
+ }\r
+ } catch ( java.io.IOException x ) {\r
+ log.warn("Unable to send ACK back through channel, channel disconnected?: "+x.getMessage());\r
+ }\r
+ }\r
+\r
+ public void setRxBufSize(int rxBufSize) {\r
+ this.rxBufSize = rxBufSize;\r
+ }\r
+\r
+ public int getRxBufSize() {\r
+ return rxBufSize;\r
+ }\r
+}\r
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.nio;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.util.Arrays;
+
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.transport.AbstractSender;
+import org.apache.catalina.tribes.transport.DataSender;
+import org.apache.catalina.tribes.RemoteProcessException;
+
+/**
+ * This class is NOT thread safe and should never be used with more than one thread at a time
+ *
+ * This is a state machine, handled by the process method
+ * States are:
+ * - NOT_CONNECTED -> connect() -> CONNECTED
+ * - CONNECTED -> setMessage() -> READY TO WRITE
+ * - READY_TO_WRITE -> write() -> READY TO WRITE | READY TO READ
+ * - READY_TO_READ -> read() -> READY_TO_READ | TRANSFER_COMPLETE
+ * - TRANSFER_COMPLETE -> CONNECTED
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class NioSender extends AbstractSender implements DataSender{
+
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(NioSender.class);
+
+
+
+ protected Selector selector;
+ protected SocketChannel socketChannel;
+
+ /*
+ * STATE VARIABLES *
+ */
+ protected ByteBuffer readbuf = null;
+ protected ByteBuffer writebuf = null;
+ protected byte[] current = null;
+ protected XByteBuffer ackbuf = new XByteBuffer(128,true);
+ protected int remaining = 0;
+ protected boolean complete;
+
+ protected boolean connecting = false;
+
+ public NioSender() {
+ super();
+
+ }
+
+ /**
+ * State machine to send data
+ * @param key SelectionKey
+ * @return boolean
+ * @throws IOException
+ */
+ public boolean process(SelectionKey key, boolean waitForAck) throws IOException {
+ int ops = key.readyOps();
+ key.interestOps(key.interestOps() & ~ops);
+ //in case disconnect has been called
+ if ((!isConnected()) && (!connecting)) throw new IOException("Sender has been disconnected, can't selection key.");
+ if ( !key.isValid() ) throw new IOException("Key is not valid, it must have been cancelled.");
+ if ( key.isConnectable() ) {
+ if ( socketChannel.finishConnect() ) {
+ //we connected, register ourselves for writing
+ setConnected(true);
+ connecting = false;
+ setRequestCount(0);
+ setConnectTime(System.currentTimeMillis());
+ socketChannel.socket().setSendBufferSize(getTxBufSize());
+ socketChannel.socket().setReceiveBufferSize(getRxBufSize());
+ socketChannel.socket().setSoTimeout((int)getTimeout());
+ socketChannel.socket().setSoLinger(false,0);
+ socketChannel.socket().setTcpNoDelay(getTcpNoDelay());
+ socketChannel.socket().setKeepAlive(getSoKeepAlive());
+ socketChannel.socket().setReuseAddress(getSoReuseAddress());
+ socketChannel.socket().setOOBInline(getOoBInline());
+ socketChannel.socket().setSoLinger(getSoLingerOn(),getSoLingerTime());
+ socketChannel.socket().setTrafficClass(getSoTrafficClass());
+ if ( current != null ) key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
+ return false;
+ } else {
+ //wait for the connection to finish
+ key.interestOps(key.interestOps() | SelectionKey.OP_CONNECT);
+ return false;
+ }//end if
+ } else if ( key.isWritable() ) {
+ boolean writecomplete = write(key);
+ if ( writecomplete ) {
+ //we are completed, should we read an ack?
+ if ( waitForAck ) {
+ //register to read the ack
+ key.interestOps(key.interestOps() | SelectionKey.OP_READ);
+ } else {
+ //if not, we are ready, setMessage will reregister us for another write interest
+ //do a health check, we have no way of verify a disconnected
+ //socket since we don't register for OP_READ on waitForAck=false
+ read(key);//this causes overhead
+ setRequestCount(getRequestCount()+1);
+ return true;
+ }
+ } else {
+ //we are not complete, lets write some more
+ key.interestOps(key.interestOps()|SelectionKey.OP_WRITE);
+ }//end if
+ } else if ( key.isReadable() ) {
+ boolean readcomplete = read(key);
+ if ( readcomplete ) {
+ setRequestCount(getRequestCount()+1);
+ return true;
+ } else {
+ key.interestOps(key.interestOps() | SelectionKey.OP_READ);
+ }//end if
+ } else {
+ //unknown state, should never happen
+ log.warn("Data is in unknown state. readyOps="+ops);
+ throw new IOException("Data is in unknown state. readyOps="+ops);
+ }//end if
+ return false;
+ }
+
+
+
+ protected boolean read(SelectionKey key) throws IOException {
+ //if there is no message here, we are done
+ if ( current == null ) return true;
+ int read = socketChannel.read(readbuf);
+ //end of stream
+ if ( read == -1 ) throw new IOException("Unable to receive an ack message. EOF on socket channel has been reached.");
+ //no data read
+ else if ( read == 0 ) return false;
+ readbuf.flip();
+ ackbuf.append(readbuf,read);
+ readbuf.clear();
+ if (ackbuf.doesPackageExist() ) {
+ byte[] ackcmd = ackbuf.extractDataPackage(true).getBytes();
+ boolean ack = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.ACK_DATA);
+ boolean fack = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA);
+ if ( fack && getThrowOnFailedAck() ) throw new RemoteProcessException("Received a failed ack:org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA");
+ return ack || fack;
+ } else {
+ return false;
+ }
+ }
+
+
+ protected boolean write(SelectionKey key) throws IOException {
+ if ( (!isConnected()) || (this.socketChannel==null)) {
+ throw new IOException("NioSender is not connected, this should not occur.");
+ }
+ if ( current != null ) {
+ if ( remaining > 0 ) {
+ //weve written everything, or we are starting a new package
+ //protect against buffer overwrite
+ int byteswritten = socketChannel.write(writebuf);
+ remaining -= byteswritten;
+ //if the entire message was written from the buffer
+ //reset the position counter
+ if ( remaining < 0 ) {
+ remaining = 0;
+ }
+ }
+ return (remaining==0);
+ }
+ //no message to send, we can consider that complete
+ return true;
+ }
+
+ /**
+ * connect - blocking in this operation
+ *
+ * @throws IOException
+ * @todo Implement this org.apache.catalina.tribes.transport.IDataSender method
+ */
+ public synchronized void connect() throws IOException {
+ if ( connecting ) return;
+ connecting = true;
+ if ( isConnected() ) throw new IOException("NioSender is already in connected state.");
+ if ( readbuf == null ) {
+ readbuf = getReadBuffer();
+ } else {
+ readbuf.clear();
+ }
+ if ( writebuf == null ) {
+ writebuf = getWriteBuffer();
+ } else {
+ writebuf.clear();
+ }
+
+ InetSocketAddress addr = new InetSocketAddress(getAddress(),getPort());
+ if ( socketChannel != null ) throw new IOException("Socket channel has already been established. Connection might be in progress.");
+ socketChannel = SocketChannel.open();
+ socketChannel.configureBlocking(false);
+ socketChannel.connect(addr);
+ socketChannel.register(getSelector(),SelectionKey.OP_CONNECT,this);
+ }
+
+
+ /**
+ * disconnect
+ *
+ * @todo Implement this org.apache.catalina.tribes.transport.IDataSender method
+ */
+ public void disconnect() {
+ try {
+ connecting = false;
+ setConnected(false);
+ if ( socketChannel != null ) {
+ try {
+ try {socketChannel.socket().close();}catch ( Exception x){}
+ //error free close, all the way
+ //try {socket.shutdownOutput();}catch ( Exception x){}
+ //try {socket.shutdownInput();}catch ( Exception x){}
+ //try {socket.close();}catch ( Exception x){}
+ try {socketChannel.close();}catch ( Exception x){}
+ }finally {
+ socketChannel = null;
+ }
+ }
+ } catch ( Exception x ) {
+ log.error("Unable to disconnect NioSender. msg="+x.getMessage());
+ if ( log.isDebugEnabled() ) log.debug("Unable to disconnect NioSender. msg="+x.getMessage(),x);
+ } finally {
+ }
+
+ }
+
+ public void reset() {
+ if ( isConnected() && readbuf == null) {
+ readbuf = getReadBuffer();
+ }
+ if ( readbuf != null ) readbuf.clear();
+ if ( writebuf != null ) writebuf.clear();
+ current = null;
+ ackbuf.clear();
+ remaining = 0;
+ complete = false;
+ setAttempt(0);
+ setRequestCount(0);
+ setConnectTime(-1);
+ }
+
+ private ByteBuffer getReadBuffer() {
+ return getBuffer(getRxBufSize());
+ }
+
+ private ByteBuffer getWriteBuffer() {
+ return getBuffer(getTxBufSize());
+ }
+
+ private ByteBuffer getBuffer(int size) {
+ return (getDirectBuffer()?ByteBuffer.allocateDirect(size):ByteBuffer.allocate(size));
+ }
+
+ /**
+ * sendMessage
+ *
+ * @param data ChannelMessage
+ * @throws IOException
+ * @todo Implement this org.apache.catalina.tribes.transport.IDataSender method
+ */
+ public synchronized void setMessage(byte[] data) throws IOException {
+ setMessage(data,0,data.length);
+ }
+
+ public synchronized void setMessage(byte[] data,int offset, int length) throws IOException {
+ if ( data != null ) {
+ current = data;
+ remaining = length;
+ ackbuf.clear();
+ if ( writebuf != null ) writebuf.clear();
+ else writebuf = getBuffer(length);
+ if ( writebuf.capacity() < length ) writebuf = getBuffer(length);
+ writebuf.put(data,offset,length);
+ //writebuf.rewind();
+ //set the limit so that we don't write non wanted data
+ //writebuf.limit(length);
+ writebuf.flip();
+ if (isConnected()) {
+ socketChannel.register(getSelector(), SelectionKey.OP_WRITE, this);
+ }
+ }
+ }
+
+ public byte[] getMessage() {
+ return current;
+ }
+
+
+
+ public boolean isComplete() {
+ return complete;
+ }
+
+ public Selector getSelector() {
+ return selector;
+ }
+
+ public void setSelector(Selector selector) {
+ this.selector = selector;
+ }
+
+
+ public void setComplete(boolean complete) {
+ this.complete = complete;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.transport.nio;
+
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.transport.MultiPointSender;
+import org.apache.catalina.tribes.transport.SenderState;
+import org.apache.catalina.tribes.transport.AbstractSender;
+import java.net.UnknownHostException;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.group.RpcChannel;
+import org.apache.catalina.tribes.util.Logs;
+import org.apache.catalina.tribes.UniqueId;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class ParallelNioSender extends AbstractSender implements MultiPointSender {
+
+ protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(ParallelNioSender.class);
+ protected long selectTimeout = 5000; //default 5 seconds, same as send timeout
+ protected Selector selector;
+ protected HashMap nioSenders = new HashMap();
+
+ public ParallelNioSender() throws IOException {
+ selector = Selector.open();
+ setConnected(true);
+ }
+
+
+ public synchronized void sendMessage(Member[] destination, ChannelMessage msg) throws ChannelException {
+ long start = System.currentTimeMillis();
+ byte[] data = XByteBuffer.createDataPackage((ChannelData)msg);
+ NioSender[] senders = setupForSend(destination);
+ connect(senders);
+ setData(senders,data);
+
+ int remaining = senders.length;
+ ChannelException cx = null;
+ try {
+ //loop until complete, an error happens, or we timeout
+ long delta = System.currentTimeMillis() - start;
+ boolean waitForAck = (Channel.SEND_OPTIONS_USE_ACK & msg.getOptions()) == Channel.SEND_OPTIONS_USE_ACK;
+ while ( (remaining>0) && (delta<getTimeout()) ) {
+ try {
+ remaining -= doLoop(selectTimeout, getMaxRetryAttempts(),waitForAck,msg);
+ } catch (Exception x ) {
+ if ( cx == null ) {
+ if ( x instanceof ChannelException ) cx = (ChannelException)x;
+ else cx = new ChannelException("Parallel NIO send failed.", x);
+ } else {
+ if (x instanceof ChannelException) cx.addFaultyMember( ( (ChannelException) x).getFaultyMembers());
+ }
+ }
+ //bail out if all remaining senders are failing
+ if ( cx != null && cx.getFaultyMembers().length == remaining ) throw cx;
+ delta = System.currentTimeMillis() - start;
+ }
+ if ( remaining > 0 ) {
+ //timeout has occured
+ cx = new ChannelException("Operation has timed out("+getTimeout()+" ms.).");
+ for (int i=0; i<senders.length; i++ ) {
+ if (!senders[i].isComplete() ) cx.addFaultyMember(senders[i].getDestination(),null);
+ }
+ throw cx;
+ }
+ } catch (Exception x ) {
+ try { this.disconnect(); } catch (Exception ignore) {}
+ if ( x instanceof ChannelException ) throw (ChannelException)x;
+ else throw new ChannelException(x);
+ }
+
+ }
+
+ private int doLoop(long selectTimeOut, int maxAttempts, boolean waitForAck, ChannelMessage msg) throws IOException, ChannelException {
+ int completed = 0;
+ int selectedKeys = selector.select(selectTimeOut);
+
+ if (selectedKeys == 0) {
+ return 0;
+ }
+
+ Iterator it = selector.selectedKeys().iterator();
+ while (it.hasNext()) {
+ SelectionKey sk = (SelectionKey) it.next();
+ it.remove();
+ int readyOps = sk.readyOps();
+ sk.interestOps(sk.interestOps() & ~readyOps);
+ NioSender sender = (NioSender) sk.attachment();
+ try {
+ if (sender.process(sk,waitForAck)) {
+ completed++;
+ sender.setComplete(true);
+ if ( Logs.MESSAGES.isTraceEnabled() ) {
+ Logs.MESSAGES.trace("ParallelNioSender - Sent msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " to "+sender.getDestination().getName());
+ }
+ SenderState.getSenderState(sender.getDestination()).setReady();
+ }//end if
+ } catch (Exception x) {
+ SenderState state = SenderState.getSenderState(sender.getDestination());
+ int attempt = sender.getAttempt()+1;
+ boolean retry = (sender.getAttempt() <= maxAttempts && maxAttempts>0);
+ synchronized (state) {
+
+ //sk.cancel();
+ if (state.isSuspect()) state.setFailing();
+ if (state.isReady()) {
+ state.setSuspect();
+ if ( retry )
+ log.warn("Member send is failing for:" + sender.getDestination().getName() +" ; Setting to suspect and retrying.");
+ else
+ log.warn("Member send is failing for:" + sender.getDestination().getName() +" ; Setting to suspect.", x);
+ }
+ }
+ if ( !isConnected() ) {
+ log.warn("Not retrying send for:" + sender.getDestination().getName() + "; Sender is disconnected.");
+ ChannelException cx = new ChannelException("Send failed, and sender is disconnected. Not retrying.",x);
+ cx.addFaultyMember(sender.getDestination(),x);
+ throw cx;
+ }
+
+ byte[] data = sender.getMessage();
+ if ( retry ) {
+ try {
+ sender.disconnect();
+ sender.connect();
+ sender.setAttempt(attempt);
+ sender.setMessage(data);
+ }catch ( Exception ignore){
+ state.setFailing();
+ }
+ } else {
+ ChannelException cx = new ChannelException("Send failed, attempt:"+sender.getAttempt()+" max:"+maxAttempts,x);
+ cx.addFaultyMember(sender.getDestination(),x);
+ throw cx;
+ }//end if
+ }
+ }
+ return completed;
+
+ }
+
+ private void connect(NioSender[] senders) throws ChannelException {
+ ChannelException x = null;
+ for (int i=0; i<senders.length; i++ ) {
+ try {
+ if (!senders[i].isConnected()) senders[i].connect();
+ }catch ( IOException io ) {
+ if ( x==null ) x = new ChannelException(io);
+ x.addFaultyMember(senders[i].getDestination(),io);
+ }
+ }
+ if ( x != null ) throw x;
+ }
+
+ private void setData(NioSender[] senders, byte[] data) throws ChannelException {
+ ChannelException x = null;
+ for (int i=0; i<senders.length; i++ ) {
+ try {
+ senders[i].setMessage(data);
+ }catch ( IOException io ) {
+ if ( x==null ) x = new ChannelException(io);
+ x.addFaultyMember(senders[i].getDestination(),io);
+ }
+ }
+ if ( x != null ) throw x;
+ }
+
+
+ private NioSender[] setupForSend(Member[] destination) throws ChannelException {
+ ChannelException cx = null;
+ NioSender[] result = new NioSender[destination.length];
+ for ( int i=0; i<destination.length; i++ ) {
+ NioSender sender = (NioSender)nioSenders.get(destination[i]);
+ try {
+
+ if (sender == null) {
+ sender = new NioSender();
+ sender.transferProperties(this, sender);
+ nioSenders.put(destination[i], sender);
+ }
+ if (sender != null) {
+ sender.reset();
+ sender.setDestination(destination[i]);
+ sender.setSelector(selector);
+ result[i] = sender;
+ }
+ }catch ( UnknownHostException x ) {
+ if (cx == null) cx = new ChannelException("Unable to setup NioSender.", x);
+ cx.addFaultyMember(destination[i], x);
+ }
+ }
+ if ( cx != null ) throw cx;
+ else return result;
+ }
+
+ public void connect() {
+ //do nothing, we connect on demand
+ setConnected(true);
+ }
+
+
+ private synchronized void close() throws ChannelException {
+ ChannelException x = null;
+ Object[] members = nioSenders.keySet().toArray();
+ for (int i=0; i<members.length; i++ ) {
+ Member mbr = (Member)members[i];
+ try {
+ NioSender sender = (NioSender)nioSenders.get(mbr);
+ sender.disconnect();
+ }catch ( Exception e ) {
+ if ( x == null ) x = new ChannelException(e);
+ x.addFaultyMember(mbr,e);
+ }
+ nioSenders.remove(mbr);
+ }
+ if ( x != null ) throw x;
+ }
+
+ public void memberAdded(Member member) {
+
+ }
+
+ public void memberDisappeared(Member member) {
+ //disconnect senders
+ NioSender sender = (NioSender)nioSenders.remove(member);
+ if ( sender != null ) sender.disconnect();
+ }
+
+
+ public synchronized void disconnect() {
+ setConnected(false);
+ try {close(); }catch (Exception x){}
+
+ }
+
+ public void finalize() {
+ try {disconnect(); }catch ( Exception ignore){}
+ }
+
+ public boolean keepalive() {
+ //throw new UnsupportedOperationException("Method ParallelNioSender.checkKeepAlive() not implemented");
+ boolean result = false;
+ for ( Iterator i = nioSenders.entrySet().iterator(); i.hasNext(); ) {
+ Map.Entry entry = (Map.Entry)i.next();
+ NioSender sender = (NioSender)entry.getValue();
+ if ( sender.keepalive() ) {
+ nioSenders.remove(entry.getKey());
+ }
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.transport.nio;
+
+import java.io.IOException;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.transport.DataSender;
+import org.apache.catalina.tribes.transport.MultiPointSender;
+import org.apache.catalina.tribes.transport.PooledSender;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Copyright: Copyright (c) 2005</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class PooledParallelSender extends PooledSender implements MultiPointSender {
+ protected boolean connected = true;
+ public PooledParallelSender() {
+ super();
+ }
+
+ public void sendMessage(Member[] destination, ChannelMessage message) throws ChannelException {
+ if ( !connected ) throw new ChannelException("Sender not connected.");
+ ParallelNioSender sender = (ParallelNioSender)getSender();
+ try {
+ sender.sendMessage(destination, message);
+ sender.keepalive();
+ }finally {
+ if ( !connected ) disconnect();
+ returnSender(sender);
+ }
+ }
+
+ public DataSender getNewDataSender() {
+ try {
+ ParallelNioSender sender = new ParallelNioSender();
+ sender.transferProperties(this,sender);
+ return sender;
+ } catch ( IOException x ) {
+ throw new RuntimeException("Unable to open NIO selector.",x);
+ }
+ }
+
+ public synchronized void disconnect() {
+ this.connected = false;
+ super.disconnect();
+ }
+
+ public synchronized void connect() throws IOException {
+ this.connected = true;
+ super.connect();
+ }
+
+ public void memberAdded(Member member) {
+
+ }
+
+ public void memberDisappeared(Member member) {
+ //disconnect senders
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.group.AbsoluteOrder;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.membership.Membership;
+
+/**
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class Arrays {
+
+ public static boolean contains(byte[] source, int srcoffset, byte[] key, int keyoffset, int length) {
+ if ( srcoffset < 0 || srcoffset >= source.length) throw new ArrayIndexOutOfBoundsException("srcoffset is out of bounds.");
+ if ( keyoffset < 0 || keyoffset >= key.length) throw new ArrayIndexOutOfBoundsException("keyoffset is out of bounds.");
+ if ( length > (key.length-keyoffset) ) throw new ArrayIndexOutOfBoundsException("not enough data elements in the key, length is out of bounds.");
+ //we don't have enough data to validate it
+ if ( length > (source.length-srcoffset) ) return false;
+ boolean match = true;
+ int pos = keyoffset;
+ for ( int i=srcoffset; match && i<length; i++ ) {
+ match = (source[i] == key[pos++]);
+ }
+ return match;
+ }
+
+ public static String toString(byte[] data) {
+ return toString(data,0,data!=null?data.length:0);
+ }
+
+ public static String toString(byte[] data, int offset, int length) {
+ StringBuffer buf = new StringBuffer("{");
+ if ( data != null && length > 0 ) {
+ buf.append(data[offset++]);
+ for (int i = offset; i < length; i++) {
+ buf.append(", ").append(data[i]);
+ }
+ }
+ buf.append("}");
+ return buf.toString();
+ }
+
+ public static String toString(Object[] data) {
+ return toString(data,0,data!=null?data.length:0);
+ }
+
+ public static String toString(Object[] data, int offset, int length) {
+ StringBuffer buf = new StringBuffer("{");
+ if ( data != null && length > 0 ) {
+ buf.append(data[offset++]);
+ for (int i = offset; i < length; i++) {
+ buf.append(", ").append(data[i]);
+ }
+ }
+ buf.append("}");
+ return buf.toString();
+ }
+
+ public static String toNameString(Member[] data) {
+ return toNameString(data,0,data!=null?data.length:0);
+ }
+
+ public static String toNameString(Member[] data, int offset, int length) {
+ StringBuffer buf = new StringBuffer("{");
+ if ( data != null && length > 0 ) {
+ buf.append(data[offset++].getName());
+ for (int i = offset; i < length; i++) {
+ buf.append(", ").append(data[i].getName());
+ }
+ }
+ buf.append("}");
+ return buf.toString();
+ }
+
+ public static int add(int[] data) {
+ int result = 0;
+ for (int i=0;i<data.length; i++ ) result += data[i];
+ return result;
+ }
+
+ public static UniqueId getUniqudId(ChannelMessage msg) {
+ return new UniqueId(msg.getUniqueId());
+ }
+
+ public static UniqueId getUniqudId(byte[] data) {
+ return new UniqueId(data);
+ }
+
+ public static boolean equals(byte[] o1, byte[] o2) {
+ return java.util.Arrays.equals(o1,o2);
+ }
+
+ public static boolean equals(Object[] o1, Object[] o2) {
+ boolean result = o1.length == o2.length;
+ if ( result ) for (int i=0; i<o1.length && result; i++ ) result = o1[i].equals(o2[i]);
+ return result;
+ }
+
+ public static boolean sameMembers(Member[] m1, Member[] m2) {
+ AbsoluteOrder.absoluteOrder(m1);
+ AbsoluteOrder.absoluteOrder(m2);
+ return equals(m1,m2);
+ }
+
+ public static Member[] merge(Member[] m1, Member[] m2) {
+ AbsoluteOrder.absoluteOrder(m1);
+ AbsoluteOrder.absoluteOrder(m2);
+ ArrayList list = new ArrayList(java.util.Arrays.asList(m1));
+ for (int i=0; i<m2.length; i++) if ( !list.contains(m2[i]) ) list.add(m2[i]);
+ Member[] result = new Member[list.size()];
+ list.toArray(result);
+ AbsoluteOrder.absoluteOrder(result);
+ return result;
+ }
+
+ public static void fill(Membership mbrship, Member[] m) {
+ for (int i=0; i<m.length; i++ ) mbrship.addMember((MemberImpl)m[i]);
+ }
+
+ public static Member[] diff(Membership complete, Membership local, MemberImpl ignore) {
+ ArrayList result = new ArrayList();
+ MemberImpl[] comp = complete.getMembers();
+ for ( int i=0; i<comp.length; i++ ) {
+ if ( ignore!=null && ignore.equals(comp[i]) ) continue;
+ if ( local.getMember(comp[i]) == null ) result.add(comp[i]);
+ }
+ return (MemberImpl[])result.toArray(new MemberImpl[result.size()]);
+ }
+
+ public static Member[] remove(Member[] all, Member remove) {
+ return extract(all,new Member[] {remove});
+ }
+
+ public static Member[] extract(Member[] all, Member[] remove) {
+ List alist = java.util.Arrays.asList(all);
+ ArrayList list = new ArrayList(alist);
+ for (int i=0; i<remove.length; i++ ) list.remove(remove[i]);
+ return (Member[])list.toArray(new Member[list.size()]);
+ }
+
+ public static int indexOf(Member member, Member[] members) {
+ int result = -1;
+ for (int i=0; (result==-1) && (i<members.length); i++ )
+ if ( member.equals(members[i]) ) result = i;
+ return result;
+ }
+
+ public static int nextIndex(Member member, Member[] members) {
+ int idx = indexOf(member,members)+1;
+ if (idx >= members.length ) idx = ((members.length>0)?0:-1);
+
+//System.out.println("Next index:"+idx);
+//System.out.println("Member:"+member.getName());
+//System.out.println("Members:"+toNameString(members));
+ return idx;
+ }
+
+ public static int hashCode(byte a[]) {
+ if (a == null)
+ return 0;
+
+ int result = 1;
+ for (int i=0; i<a.length; i++) {
+ byte element = a[i];
+ result = 31 * result + element;
+ }
+ return result;
+ }
+
+
+
+
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.util;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+/**
+ *
+ * Simple class that holds references to global loggers
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class Logs {
+ public static Log MESSAGES = LogFactory.getLog( "org.apache.catalina.tribes.MESSAGES" );
+}
--- /dev/null
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.tribes.util;
+
+import java.text.MessageFormat;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.net.URLClassLoader;
+
+/**
+ * An internationalization / localization helper class which reduces
+ * the bother of handling ResourceBundles and takes care of the
+ * common cases of message formating which otherwise require the
+ * creation of Object arrays and such.
+ *
+ * <p>The StringManager operates on a package basis. One StringManager
+ * per package can be created and accessed via the getManager method
+ * call.
+ *
+ * <p>The StringManager will look for a ResourceBundle named by
+ * the package name given plus the suffix of "LocalStrings". In
+ * practice, this means that the localized information will be contained
+ * in a LocalStrings.properties file located in the package
+ * directory of the classpath.
+ *
+ * <p>Please see the documentation for java.util.ResourceBundle for
+ * more information.
+ *
+ * @author James Duncan Davidson [duncan@eng.sun.com]
+ * @author James Todd [gonzo@eng.sun.com]
+ */
+
+public class StringManager {
+
+ /**
+ * The ResourceBundle for this StringManager.
+ */
+
+ private ResourceBundle bundle;
+
+ private static org.apache.commons.logging.Log log=
+ org.apache.commons.logging.LogFactory.getLog( StringManager.class );
+
+ /**
+ * Creates a new StringManager for a given package. This is a
+ * private method and all access to it is arbitrated by the
+ * static getManager method call so that only one StringManager
+ * per package will be created.
+ *
+ * @param packageName Name of package to create StringManager for.
+ */
+
+ private StringManager(String packageName) {
+ String bundleName = packageName + ".LocalStrings";
+ try {
+ bundle = ResourceBundle.getBundle(bundleName);
+ return;
+ } catch( MissingResourceException ex ) {
+ // Try from the current loader ( that's the case for trusted apps )
+ ClassLoader cl=Thread.currentThread().getContextClassLoader();
+ if( cl != null ) {
+ try {
+ bundle=ResourceBundle.getBundle(bundleName, Locale.getDefault(), cl);
+ return;
+ } catch(MissingResourceException ex2) {
+ }
+ }
+ if( cl==null )
+ cl=this.getClass().getClassLoader();
+
+ if (log.isDebugEnabled())
+ log.debug("Can't find resource " + bundleName +
+ " " + cl);
+ if( cl instanceof URLClassLoader ) {
+ if (log.isDebugEnabled())
+ log.debug( ((URLClassLoader)cl).getURLs());
+ }
+ }
+ }
+
+ /**
+ * Get a string from the underlying resource bundle.
+ *
+ * @param key The resource name
+ */
+ public String getString(String key) {
+ return MessageFormat.format(getStringInternal(key), (Object [])null);
+ }
+
+
+ protected String getStringInternal(String key) {
+ if (key == null) {
+ String msg = "key is null";
+
+ throw new NullPointerException(msg);
+ }
+
+ String str = null;
+
+ if( bundle==null )
+ return key;
+ try {
+ str = bundle.getString(key);
+ } catch (MissingResourceException mre) {
+ str = "Cannot find message associated with key '" + key + "'";
+ }
+
+ return str;
+ }
+
+ /**
+ * Get a string from the underlying resource bundle and format
+ * it with the given set of arguments.
+ *
+ * @param key The resource name
+ * @param args Formatting directives
+ */
+
+ public String getString(String key, Object[] args) {
+ String iString = null;
+ String value = getStringInternal(key);
+
+ // this check for the runtime exception is some pre 1.1.6
+ // VM's don't do an automatic toString() on the passed in
+ // objects and barf out
+
+ try {
+ // ensure the arguments are not null so pre 1.2 VM's don't barf
+ Object nonNullArgs[] = args;
+ for (int i=0; i<args.length; i++) {
+ if (args[i] == null) {
+ if (nonNullArgs==args) nonNullArgs=(Object[])args.clone();
+ nonNullArgs[i] = "null";
+ }
+ }
+
+ iString = MessageFormat.format(value, nonNullArgs);
+ } catch (IllegalArgumentException iae) {
+ StringBuffer buf = new StringBuffer();
+ buf.append(value);
+ for (int i = 0; i < args.length; i++) {
+ buf.append(" arg[" + i + "]=" + args[i]);
+ }
+ iString = buf.toString();
+ }
+ return iString;
+ }
+
+ /**
+ * Get a string from the underlying resource bundle and format it
+ * with the given object argument. This argument can of course be
+ * a String object.
+ *
+ * @param key The resource name
+ * @param arg Formatting directive
+ */
+
+ public String getString(String key, Object arg) {
+ Object[] args = new Object[] {arg};
+ return getString(key, args);
+ }
+
+ /**
+ * Get a string from the underlying resource bundle and format it
+ * with the given object arguments. These arguments can of course
+ * be String objects.
+ *
+ * @param key The resource name
+ * @param arg1 Formatting directive
+ * @param arg2 Formatting directive
+ */
+
+ public String getString(String key, Object arg1, Object arg2) {
+ Object[] args = new Object[] {arg1, arg2};
+ return getString(key, args);
+ }
+
+ /**
+ * Get a string from the underlying resource bundle and format it
+ * with the given object arguments. These arguments can of course
+ * be String objects.
+ *
+ * @param key The resource name
+ * @param arg1 Formatting directive
+ * @param arg2 Formatting directive
+ * @param arg3 Formatting directive
+ */
+
+ public String getString(String key, Object arg1, Object arg2,
+ Object arg3) {
+ Object[] args = new Object[] {arg1, arg2, arg3};
+ return getString(key, args);
+ }
+
+ /**
+ * Get a string from the underlying resource bundle and format it
+ * with the given object arguments. These arguments can of course
+ * be String objects.
+ *
+ * @param key The resource name
+ * @param arg1 Formatting directive
+ * @param arg2 Formatting directive
+ * @param arg3 Formatting directive
+ * @param arg4 Formatting directive
+ */
+
+ public String getString(String key, Object arg1, Object arg2,
+ Object arg3, Object arg4) {
+ Object[] args = new Object[] {arg1, arg2, arg3, arg4};
+ return getString(key, args);
+ }
+ // --------------------------------------------------------------
+ // STATIC SUPPORT METHODS
+ // --------------------------------------------------------------
+
+ private static Hashtable managers = new Hashtable();
+
+ /**
+ * Get the StringManager for a particular package. If a manager for
+ * a package already exists, it will be reused, else a new
+ * StringManager will be created and returned.
+ *
+ * @param packageName The package name
+ */
+
+ public synchronized static StringManager getManager(String packageName) {
+ StringManager mgr = (StringManager)managers.get(packageName);
+
+ if (mgr == null) {
+ mgr = new StringManager(packageName);
+ managers.put(packageName, mgr);
+ }
+ return mgr;
+ }
+}
--- /dev/null
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.util;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * simple generation of a UUID
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class UUIDGenerator {
+ public static final int UUID_LENGTH = 16;
+ public static final int UUID_VERSION = 4;
+ public static final int BYTES_PER_INT = 4;
+ public static final int BITS_PER_BYTE = 8;
+
+ protected static SecureRandom secrand = null;
+ protected static Random rand = new Random(System.currentTimeMillis());
+ static {
+ secrand = new SecureRandom();
+ secrand.setSeed(rand.nextLong());
+ }
+
+ public static byte[] randomUUID(boolean secure) {
+ byte[] result = new byte[UUID_LENGTH];
+ return randomUUID(secure,result,0);
+ }
+
+ public static byte[] randomUUID(boolean secure, byte[] into, int offset) {
+ if ( (offset+UUID_LENGTH)>into.length )
+ throw new ArrayIndexOutOfBoundsException("Unable to fit "+UUID_LENGTH+" bytes into the array. length:"+into.length+" required length:"+(offset+UUID_LENGTH));
+ Random r = (secure&&(secrand!=null))?secrand:rand;
+ nextBytes(into,offset,UUID_LENGTH,r);
+ into[6+offset] &= 0x0F;
+ into[6+offset] |= (UUID_VERSION << 4);
+ into[8+offset] &= 0x3F; //0011 1111
+ into[8+offset] |= 0x80; //1000 0000
+ return into;
+ }
+
+ /**
+ * Same as java.util.Random.nextBytes except this one we dont have to allocate a new byte array
+ * @param into byte[]
+ * @param offset int
+ * @param length int
+ * @param r Random
+ */
+ public static void nextBytes(byte[] into, int offset, int length, Random r) {
+ int numRequested = length;
+ int numGot = 0, rnd = 0;
+ while (true) {
+ for (int i = 0; i < BYTES_PER_INT; i++) {
+ if (numGot == numRequested) return;
+ rnd = (i == 0 ? r.nextInt() : rnd >> BITS_PER_BYTE);
+ into[offset+numGot] = (byte) rnd;
+ numGot++;
+ }
+ }
+ }
+
+}
\ No newline at end of file