This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
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.
*/
public class ByteMessage implements 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 An IO error occurred
*/
@Override
public void readExternal(ObjectInput in ) throws IOException {
int length = in.readInt();
message = new byte[length];
in.readFully(message);
}
/**
* @see java.io.Externalizable#writeExternal
* @param out ObjectOutput
* @throws IOException An IO error occurred
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(message!=null?message.length:0);
if ( message!=null ) out.write(message,0,message.length);
}
}

View File

@@ -0,0 +1,372 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* registering 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
*/
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 five default flags that every channel implementation must implement<br>
* SEND_OPTIONS_BYTE_MESSAGE - The message is a pure byte message and no marshaling or unmarshaling 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 five 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 five 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 five 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 five default flags that every channel implementation must implement<br>
* SEND_OPTIONS_SECURE - Message is sent over an encrypted channel<br>
* @see #send(Member[], Serializable , int)
* @see #send(Member[], Serializable, int, ErrorHandler)
*/
public static final int SEND_OPTIONS_SECURE = 0x0010;
/**
* Send options. When a message is sent with this flag on
* the system sends the message using UDP instead of TCP
* @see #send(Member[], Serializable , int)
* @see #send(Member[], Serializable, int, ErrorHandler)
*/
public static final int SEND_OPTIONS_UDP = 0x0020;
/**
* Send options. When a message is sent with this flag on
* the system sends a UDP message on the Multicast address instead of UDP or TCP to individual addresses
* @see #send(Member[], Serializable , int)
* @see #send(Member[], Serializable, int, ErrorHandler)
*/
public static final int SEND_OPTIONS_MULTICAST = 0x0040;
/**
* 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 five 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, cannot 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
* @throws ChannelException if a serialization error happens.
* @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 &gt; 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);
/**
* Return the name of this channel.
* @return channel name
*/
public String getName();
/**
* Set the name of this channel
* @param name The new channel name
*/
public void setName(String name);
}

View File

@@ -0,0 +1,183 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
public class ChannelException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Empty list to avoid reinstantiating lists
*/
protected static final FaultyMember[] EMPTY_LIST = new FaultyMember[0];
/**
* Holds a list of faulty members
*/
private ArrayList<FaultyMember> faultyMembers=null;
/**
* Constructor, creates a ChannelException
* @see java.lang.Exception#Exception()
*/
public ChannelException() {
super();
}
/**
* Constructor, creates a ChannelException with an error message
* @param message The 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 The error message
* @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 the error message
* @see java.lang.Exception#getMessage()
*/
@Override
public String getMessage() {
StringBuilder buf = new StringBuilder(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 = 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
* @return <code>true</code> if the member was added
*/
public boolean addFaultyMember(Member mbr, Exception x ) {
return addFaultyMember(new FaultyMember(mbr,x));
}
/**
* Adds a list of faulty members
* @param mbrs FaultyMember[]
* @return the number of members added
*/
public int addFaultyMember(FaultyMember[] mbrs) {
int result = 0;
for (int i=0; mbrs!=null && i<mbrs.length; i++ ) {
if ( addFaultyMember(mbrs[i]) ) result++;
}
return result;
}
/**
* Adds a faulty member
* @param mbr FaultyMember
* @return <code>true</code> if the member was added
*/
public boolean addFaultyMember(FaultyMember mbr) {
if ( this.faultyMembers==null ) this.faultyMembers = new ArrayList<>();
if ( !faultyMembers.contains(mbr) ) return faultyMembers.add(mbr);
else return false;
}
/**
* Returns an array of members that failed and the reason they failed.
* @return FaultyMember[]
*/
public FaultyMember[] getFaultyMembers() {
if ( this.faultyMembers==null ) return EMPTY_LIST;
return 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>
*
* @version 1.0
*/
public static class FaultyMember {
protected final Exception cause;
protected final Member member;
public FaultyMember(Member mbr, Exception x) {
this.member = mbr;
this.cause = x;
}
public Member getMember() {
return member;
}
public Exception getCause() {
return cause;
}
@Override
public String toString() {
return "FaultyMember:"+member.toString();
}
@Override
public int hashCode() {
return (member!=null)?member.hashCode():0;
}
@Override
public boolean equals(Object o) {
if (member==null || (!(o instanceof FaultyMember)) || (((FaultyMember)o).member==null)) return false;
return member.equals(((FaultyMember)o).member);
}
}
}

View File

@@ -0,0 +1,190 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*/
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() &amp; 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 if a serialization error happens.
* @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.
*/
@Override
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);
/**
* Return the channel that is related to this interceptor
* @return Channel
*/
public Channel getChannel();
/**
* Set the channel that is related to this interceptor
* @param channel The channel
*/
public void setChannel(Channel channel);
interface InterceptorEvent {
int getEventType();
String getEventTypeDesc();
ChannelInterceptor getInterceptor();
}
}

View File

@@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 feature required
* to support message-response(RPC) calls<br>
*
* @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)
*/
@Override
public boolean equals(Object listener);
/**
*
* @return int
* @see Object#hashCode()
*/
@Override
public int hashCode();
}

View File

@@ -0,0 +1,107 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
*/
public interface ChannelMessage extends Serializable, Cloneable {
/**
* 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();
}

View File

@@ -0,0 +1,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
public interface ChannelReceiver extends Heartbeat {
public static final int MAX_UDP_SIZE = 65535;
/**
* Start listening for incoming messages on the host/port
* @throws java.io.IOException Listen failed
*/
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();
/**
* Returns the secure listening port
* @return port, -1 if a secure port is not activated
*/
public int getSecurePort();
/**
* Returns the UDP port
* @return port, -1 if the UDP port is not activated.
*/
public int getUdpPort();
/**
* 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();
/**
* Return the channel that is related to this ChannelReceiver
* @return Channel
*/
public Channel getChannel();
/**
* Set the channel that is related to this ChannelReceiver
* @param channel The channel
*/
public void setChannel(Channel channel);
}

View File

@@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.IOException;
/**
* 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
*/
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 IOException;
/**
* Stop the channel sender
*/
public void stop();
/**
* A channel heartbeat, use this method to clean up resources
*/
@Override
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;
/**
* Return the channel that is related to this ChannelSender
* @return Channel
*/
public Channel getChannel();
/**
* Set the channel that is related to this ChannelSender
* @param channel The channel
*/
public void setChannel(Channel channel);
}

View File

@@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
* @version 1.0
*/
public interface ErrorHandler {
/**
* Invoked if the message is dispatched async, and an error occurs
* @param x ChannelException - the error that happened
* @param id - the unique id for the message
* @see Channel#send(Member[], java.io.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[], java.io.Serializable, int, ErrorHandler)
*/
public void handleCompletion(UniqueId id);
}

View File

@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* @version 1.0
* @see Channel
* @see Channel#heartbeat()
*/
public interface Heartbeat {
/**
* Heartbeat invocation for resources cleanup etc
*/
public void heartbeat();
}

View File

@@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 javax.management.MBeanRegistration;
public interface JmxChannel extends MBeanRegistration {
/**
* If set to true, this channel is registered with jmx.
* @return true if this channel will be registered with jmx.
*/
public boolean isJmxEnabled();
/**
* If set to true, this channel is registered with jmx.
* @param jmxEnabled set to true if this channel should be registered with jmx.
*/
public void setJmxEnabled(boolean jmxEnabled);
/**
* Return the jmx domain which this channel is registered.
* @return jmxDomain
*/
public String getJmxDomain();
/**
* Set the jmx domain which this channel should be registered.
* @param jmxDomain The jmx domain which this channel should be registered.
*/
public void setJmxDomain(String jmxDomain);
/**
* Return the jmx prefix which will be used with channel ObjectName.
* @return jmxPrefix
*/
public String getJmxPrefix();
/**
* Set the jmx prefix which will be used with channel ObjectName.
* @param jmxPrefix The jmx prefix which will be used with channel ObjectName.
*/
public void setJmxPrefix(String jmxPrefix);
}

View File

@@ -0,0 +1,76 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*/
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<ChannelInterceptor> getInterceptors();
}

View File

@@ -0,0 +1,160 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
/**
* 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
*/
public interface Member extends Serializable {
/**
* 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};
/**
* @return 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 the listen port for this member, -1 if its not listening on an insecure port
* @see ChannelReceiver
*/
public int getPort();
/**
* Returns the secure listen port for the ChannelReceiver implementation.
* Returns -1 if its not listening to a secure port.
* @return the listen port for this member, -1 if its not listening on a secure port
* @see ChannelReceiver
*/
public int getSecurePort();
/**
* Returns the UDP port that this member is listening to for UDP messages.
* @return the listen UDP port for this member, -1 if its not listening on a UDP port
*/
public int getUdpPort();
/**
* 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();
public void setMemberAliveTime(long memberAliveTime);
/**
* 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();
public void setPayload(byte[] payload);
/**
* returns the command associated with this member
* @return byte[]
*/
public byte[] getCommand();
public void setCommand(byte[] command);
/**
* Domain for this cluster
* @return byte[]
*/
public byte[] getDomain();
/**
* Highly optimized version of serializing a member into a byte array
* Returns a cached byte[] reference, do not modify this data
* @param getalive calculate memberAlive time
* @return the data as a byte array
*/
public byte[] getData(boolean getalive);
/**
* Highly optimized version of serializing a member into a byte array
* Returns a cached byte[] reference, do not modify this data
* @param getalive calculate memberAlive time
* @param reset reset the cached data package, and create a new one
* @return the data as a byte array
*/
public byte[] getData(boolean getalive, boolean reset);
/**
* Length of a message obtained by {@link #getData(boolean)} or
* {@link #getData(boolean, boolean)}.
* @return the data length
*/
public int getDataLength();
/**
* @return boolean - true if the member is local member
*/
public boolean isLocal();
public void setLocal(boolean local);
}

View File

@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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)
*/
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 Member.getCommand will contain the Member.SHUTDOWN_PAYLOAD data
* @param member Member
* @see Member#SHUTDOWN_PAYLOAD
*/
public void memberDisappeared(Member member);
}

View File

@@ -0,0 +1,159 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
*/
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);
/**
* @return 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.IllegalArgumentException if the level is incorrect.
*/
public void stop(int level);
/**
* @return true if the the group contains members
*/
public boolean hasMembers();
/**
* Retrieve the specified member from the membership.
* @param mbr The member to retrieve
* @return the member
*/
public Member getMember(Member mbr);
/**
* @return a list of all the members in the cluster.
*/
public Member[] getMembers();
/**
* Get the local member.
* @return the member object that defines this member
* @param incAliveTime <code>true</code> to set the alive time
* on the local member
*/
public Member getLocalMember(boolean incAliveTime);
/**
* @return all members by name
*/
public String[] getMembersByName();
/**
* Get a member.
* @param name The member name
* @return the member
*/
public Member findMemberByName(String name);
/**
* Sets the local member properties for broadcasting.
*
* @param listenHost Listen to host
* @param listenPort Listen to port
* @param securePort Use a secure port
* @param udpPort Use UDP
*/
public void setLocalMemberProperties(String listenHost, int listenPort, int securePort, int udpPort);
/**
* 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);
/**
* Broadcasts a message to all members.
* @param message The message to broadcast
* @throws ChannelException Message broadcast failed
*/
public void broadcast(ChannelMessage message) throws ChannelException;
/**
* Return the channel that is related to this MembershipService
* @return Channel
*/
public Channel getChannel();
/**
* Set the channel that is related to this MembershipService
* @param channel The channel
*/
public void setChannel(Channel channel);
}

View File

@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
*
* @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);
}

View File

@@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* @version 1.0
*/
public class RemoteProcessException extends RuntimeException {
private static final long serialVersionUID = 1L;
public RemoteProcessException() {
super();
}
public RemoteProcessException(String message) {
super(message);
}
public RemoteProcessException(String message, Throwable cause) {
super(message, cause);
}
public RemoteProcessException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.util.Arrays;
/**
* <p>Title: Represents a globally unique Id</p>
*
* <p>Company: </p>
*
* @version 1.0
*/
public final class UniqueId implements Serializable{
private static final long serialVersionUID = 1L;
final byte[] id;
public UniqueId() {
this(null);
}
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);
}
@Override
public int hashCode() {
if ( id == null ) return 0;
return Arrays.hashCode(id);
}
@Override
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;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder("UniqueId");
buf.append(Arrays.toString(id));
return buf.toString();
}
}

View File

@@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.Arrays;
import java.util.Comparator;
import java.util.List;
import org.apache.catalina.tribes.Member;
/**
* <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:
* </p>
* <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>
*
*
* @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 <= 1 ) return;
Arrays.sort(members,comp);
}
public static void absoluteOrder(List<Member> members) {
if ( members == null || members.size() <= 1 ) return;
java.util.Collections.sort(members, comp);
}
public static class AbsoluteComparator implements Comparator<Member>,
Serializable {
private static final long serialVersionUID = 1L;
@Override
public int compare(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(b1,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;
}
}
}

View File

@@ -0,0 +1,359 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.Channel;
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.UniqueId;
import org.apache.catalina.tribes.membership.McastService;
import org.apache.catalina.tribes.membership.StaticMember;
import org.apache.catalina.tribes.transport.ReplicationTransmitter;
import org.apache.catalina.tribes.transport.SenderState;
import org.apache.catalina.tribes.transport.nio.NioReceiver;
import org.apache.catalina.tribes.util.Arrays;
import org.apache.catalina.tribes.util.Logs;
import org.apache.catalina.tribes.util.StringManager;
/**
* The channel coordinator object coordinates the membership service,
* the sender and the receiver.
* This is the last interceptor in the chain.
*/
public class ChannelCoordinator extends ChannelInterceptorBase implements MessageListener {
protected static final StringManager sm = StringManager.getManager(ChannelCoordinator.class);
private ChannelReceiver clusterReceiver;
private ChannelSender clusterSender;
private MembershipService membershipService;
private int startLevel = 0;
public ChannelCoordinator() {
this(new NioReceiver(), new ReplicationTransmitter(),
new McastService());
}
public ChannelCoordinator(ChannelReceiver receiver,
ChannelSender sender,
MembershipService service) {
this.optionFlag = Channel.SEND_OPTIONS_BYTE_MESSAGE |
Channel.SEND_OPTIONS_USE_ACK |
Channel.SEND_OPTIONS_SYNCHRONIZED_ACK;
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 payload TBA
*/
@Override
public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload)
throws ChannelException {
if ( destination == null ) destination = membershipService.getMembers();
if ((msg.getOptions()&Channel.SEND_OPTIONS_MULTICAST) == Channel.SEND_OPTIONS_MULTICAST) {
membershipService.broadcast(msg);
} else {
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.
*/
@Override
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.
*/
@Override
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(sm.getString("channelCoordinator.alreadyStarted",
Integer.toString(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.setChannel(getChannel());
clusterReceiver.start();
//synchronize, big time FIXME
Member localMember = getChannel().getLocalMember(false);
if (localMember instanceof StaticMember) {
// static member
StaticMember staticMember = (StaticMember)localMember;
staticMember.setHost(getClusterReceiver().getHost());
staticMember.setPort(getClusterReceiver().getPort());
staticMember.setSecurePort(getClusterReceiver().getSecurePort());
} else {
// multicast member
membershipService.setLocalMemberProperties(getClusterReceiver().getHost(),
getClusterReceiver().getPort(),
getClusterReceiver().getSecurePort(),
getClusterReceiver().getUdpPort());
}
valid = true;
}
if ( Channel.SND_TX_SEQ==(svc & Channel.SND_TX_SEQ) ) {
clusterSender.setChannel(getChannel());
clusterSender.start();
valid = true;
}
if ( Channel.MBR_RX_SEQ==(svc & Channel.MBR_RX_SEQ) ) {
membershipService.setMembershipListener(this);
membershipService.setChannel(getChannel());
if (membershipService instanceof McastService) {
((McastService)membershipService).setMessageListener(this);
}
membershipService.start(MembershipService.MBR_RX);
valid = true;
}
if ( Channel.MBR_TX_SEQ==(svc & Channel.MBR_TX_SEQ) ) {
membershipService.setChannel(getChannel());
membershipService.start(MembershipService.MBR_TX);
valid = true;
}
if (!valid) {
throw new IllegalArgumentException(sm.getString("channelCoordinator.invalid.startLevel"));
}
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(sm.getString("channelCoordinator.invalid.startLevel"));
}
startLevel = (startLevel & (~svc));
setChannel(null);
} catch (Exception x) {
throw new ChannelException(x);
}
}
@Override
public void memberAdded(Member member){
SenderState.getSenderState(member);
super.memberAdded(member);
}
@Override
public void memberDisappeared(Member member){
SenderState.removeSenderState(member);
super.memberDisappeared(member);
}
@Override
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);
}
@Override
public boolean accept(ChannelMessage msg) {
return true;
}
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);
}
@Override
public void heartbeat() {
if ( clusterSender!=null ) clusterSender.heartbeat();
super.heartbeat();
}
/**
* has members
*/
@Override
public boolean hasMembers() {
return this.getMembershipService().hasMembers();
}
/**
* Get all current cluster members
* @return all members or empty array
*/
@Override
public Member[] getMembers() {
return this.getMembershipService().getMembers();
}
/**
*
* @param mbr Member
* @return Member
*/
@Override
public Member getMember(Member mbr){
return this.getMembershipService().getMember(mbr);
}
/**
* Return the member that represents this node.
*
* @return Member
*/
@Override
public Member getLocalMember(boolean incAlive) {
return this.getMembershipService().getLocalMember(incAlive);
}
}

View File

@@ -0,0 +1,216 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 javax.management.ObjectName;
import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.ChannelException;
import org.apache.catalina.tribes.ChannelInterceptor;
import org.apache.catalina.tribes.ChannelMessage;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.jmx.JmxRegistry;
/**
* Abstract class for the interceptor base class.
*/
public abstract class ChannelInterceptorBase implements ChannelInterceptor {
private ChannelInterceptor next;
private ChannelInterceptor previous;
private Channel channel;
//default value, always process
protected int optionFlag = 0;
/**
* the ObjectName of this ChannelInterceptor.
*/
private ObjectName oname = null;
public ChannelInterceptorBase() {
}
public boolean okToProcess(int messageFlags) {
if (this.optionFlag == 0 ) return true;
return ((optionFlag&messageFlags) == optionFlag);
}
@Override
public final void setNext(ChannelInterceptor next) {
this.next = next;
}
@Override
public final ChannelInterceptor getNext() {
return next;
}
@Override
public final void setPrevious(ChannelInterceptor previous) {
this.previous = previous;
}
@Override
public void setOptionFlag(int optionFlag) {
this.optionFlag = optionFlag;
}
@Override
public final ChannelInterceptor getPrevious() {
return previous;
}
@Override
public int getOptionFlag() {
return optionFlag;
}
@Override
public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws
ChannelException {
if (getNext() != null) getNext().sendMessage(destination, msg, payload);
}
@Override
public void messageReceived(ChannelMessage msg) {
if (getPrevious() != null) getPrevious().messageReceived(msg);
}
@Override
public void memberAdded(Member member) {
//notify upwards
if (getPrevious() != null) getPrevious().memberAdded(member);
}
@Override
public void memberDisappeared(Member member) {
//notify upwards
if (getPrevious() != null) getPrevious().memberDisappeared(member);
}
@Override
public void heartbeat() {
if (getNext() != null) getNext().heartbeat();
}
/**
* has members
*/
@Override
public boolean hasMembers() {
if ( getNext()!=null )return getNext().hasMembers();
else return false;
}
/**
* Get all current cluster members
* @return all members or empty array
*/
@Override
public Member[] getMembers() {
if ( getNext()!=null ) return getNext().getMembers();
else return null;
}
/**
*
* @param mbr Member
* @return Member
*/
@Override
public Member getMember(Member mbr) {
if ( getNext()!=null) return getNext().getMember(mbr);
else return null;
}
/**
* Return the member that represents this node.
*
* @return Member
*/
@Override
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.
*/
@Override
public void start(int svc) throws ChannelException {
if ( getNext()!=null ) getNext().start(svc);
// register jmx
JmxRegistry jmxRegistry = JmxRegistry.getRegistry(channel);
if (jmxRegistry != null) this.oname = jmxRegistry.registerJmx(
",component=Interceptor,interceptorName=" + getClass().getSimpleName(), this);
}
/**
* 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.
*/
@Override
public void stop(int svc) throws ChannelException {
if (getNext() != null) getNext().stop(svc);
if (oname != null) {
JmxRegistry.getRegistry(channel).unregisterJmx(oname);
oname = null;
}
channel = null;
}
@Override
public void fireInterceptorEvent(InterceptorEvent event) {
//empty operation
}
/**
* Return the channel that is related to this interceptor
* @return Channel
*/
@Override
public Channel getChannel() {
return channel;
}
/**
* Set the channel that is related to this interceptor
* @param channel The channel
*/
@Override
public void setChannel(Channel channel) {
this.channel = channel;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
/**
* Extension to the {@link RpcCallback} interface. Allows a RPC messenger to get a confirmation if the reply
* was sent successfully to the original sender.
*
*/
public interface ExtendedRpcCallback extends RpcCallback {
/**
* The reply failed.
* @param request - the original message that requested the reply
* @param response - the reply message to the original message
* @param sender - the sender requested that reply
* @param reason - the reason the reply failed
*/
public void replyFailed(Serializable request, Serializable response, Member sender, Exception reason);
/**
* The reply succeeded
* @param request - the original message that requested the reply
* @param response - the reply message to the original message
* @param sender - the sender requested that reply
*/
public void replySucceeded(Serializable request, Serializable response, Member sender);
}

View File

@@ -0,0 +1,820 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.IOException;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.management.MBeanServer;
import javax.management.ObjectName;
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.Heartbeat;
import org.apache.catalina.tribes.JmxChannel;
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.RemoteProcessException;
import org.apache.catalina.tribes.UniqueId;
import org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor;
import org.apache.catalina.tribes.io.BufferPool;
import org.apache.catalina.tribes.io.ChannelData;
import org.apache.catalina.tribes.io.XByteBuffer;
import org.apache.catalina.tribes.jmx.JmxRegistry;
import org.apache.catalina.tribes.util.Arrays;
import org.apache.catalina.tribes.util.Logs;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* 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.
*/
public class GroupChannel extends ChannelInterceptorBase
implements ManagedChannel, JmxChannel, GroupChannelMBean {
private static final Log log = LogFactory.getLog(GroupChannel.class);
protected static final StringManager sm = StringManager.getManager(GroupChannel.class);
/**
* 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 final ChannelCoordinator coordinator = new ChannelCoordinator();
/**
* The first interceptor in the interceptor 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 final List<MembershipListener> membershipListeners = new CopyOnWriteArrayList<>();
/**
* A list of channel listeners that subscribe to incoming messages
*/
protected final List<ChannelListener> channelListeners = new CopyOnWriteArrayList<>();
/**
* If set to true, the GroupChannel will check to make sure that
*/
protected boolean optionCheck = false;
/**
* the name of this channel.
*/
protected String name = null;
/**
* the jmx domain which this channel is registered.
*/
private String jmxDomain = "ClusterChannel";
/**
* the jmx prefix which will be used with channel ObjectName.
*/
private String jmxPrefix = "";
/**
* If set to true, this channel is registered with jmx.
*/
private boolean jmxEnabled = true;
/**
* the ObjectName of this channel.
*/
private ObjectName oname = null;
/**
* 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 an interceptor stack like this:<br>
* <code>A -&gt; C -&gt; B</code><br>
* The complete stack will look like this:<br>
* <code>Channel -&gt; A -&gt; C -&gt; B -&gt; ChannelCoordinator</code><br>
* @param interceptor ChannelInterceptorBase
*/
@Override
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>
*/
@Override
public void heartbeat() {
super.heartbeat();
for (MembershipListener listener : membershipListeners) {
if ( listener instanceof Heartbeat ) ((Heartbeat)listener).heartbeat();
}
for (ChannelListener listener : channelListeners) {
if ( listener instanceof Heartbeat ) ((Heartbeat)listener).heartbeat();
}
}
/**
* Send a message to the destinations specified
* @param destination Member[] - destination.length &gt; 0
* @param msg Serializable - the message to send
* @param options 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
*/
@Override
public UniqueId send(Member[] destination, Serializable msg, int options)
throws ChannelException {
return send(destination,msg,options,null);
}
/**
*
* @param destination Member[] - destination.length &gt; 0
* @param msg Serializable - the message to send
* @param options 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
*/
@Override
public UniqueId send(Member[] destination, Serializable msg, int options, ErrorHandler handler)
throws ChannelException {
if ( msg == null ) throw new ChannelException(sm.getString("groupChannel.nullMessage"));
XByteBuffer buffer = null;
try {
if (destination == null || destination.length == 0) {
throw new ChannelException(sm.getString("groupChannel.noDestination"));
}
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 (RuntimeException | IOException e) {
throw new ChannelException(e);
} 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
*/
@Override
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 {
try {
fwd = XByteBuffer.deserialize(msg.getMessage().getBytesDirect(), 0,
msg.getMessage().getLength());
}catch (Exception sx) {
log.error(sm.getString("groupChannel.unable.deserialize", msg),sx);
return;
}
}
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 = 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 ) {
//this could be the channel listener throwing an exception, we should log it
//as a warning.
if ( log.isWarnEnabled() ) log.warn(sm.getString("groupChannel.receiving.error"),x);
throw new RemoteProcessException("Exception:"+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(sm.getString("groupChannel.sendFail.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
*/
@Override
public void memberAdded(Member member) {
//notify upwards
for (int i=0; i<membershipListeners.size(); i++ ) {
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
*/
@Override
public void memberDisappeared(Member member) {
//notify upwards
for (int i=0; i<membershipListeners.size(); i++ ) {
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 Cluster error
*/
protected synchronized void setupDefaultStack() throws ChannelException {
if (getFirstInterceptor() != null &&
((getFirstInterceptor().getNext() instanceof ChannelCoordinator))) {
addInterceptor(new MessageDispatchInterceptor());
}
Iterator<ChannelInterceptor> interceptors = getInterceptors();
while (interceptors.hasNext()) {
ChannelInterceptor channelInterceptor = interceptors.next();
channelInterceptor.setChannel(this);
}
coordinator.setChannel(this);
}
/**
* Validates the option flags that each interceptor is using and reports
* an error if two interceptor share the same flag.
* @throws ChannelException Error with option flag
*/
protected void checkOptionFlags() throws ChannelException {
StringBuilder conflicts = new StringBuilder();
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(sm.getString("groupChannel.optionFlag.conflict",
conflicts.toString()));
}
/**
* Starts the channel.
* @param svc int - what service to start
* @throws ChannelException Start error
* @see org.apache.catalina.tribes.Channel#start(int)
*/
@Override
public synchronized void start(int svc) throws ChannelException {
setupDefaultStack();
if (optionCheck) checkOptionFlags();
// register jmx
JmxRegistry jmxRegistry = JmxRegistry.getRegistry(this);
if (jmxRegistry != null) this.oname = jmxRegistry.registerJmx(",component=Channel", this);
super.start(svc);
if ( hbthread == null && heartbeat ) {
hbthread = new HeartbeatThread(this,heartbeatSleeptime);
hbthread.start();
}
}
/**
* Stops the channel.
* @param svc int
* @throws ChannelException Stop error
* @see org.apache.catalina.tribes.Channel#stop(int)
*/
@Override
public synchronized void stop(int svc) throws ChannelException {
if (hbthread != null) {
hbthread.stopHeartbeat();
hbthread = null;
}
super.stop(svc);
if (oname != null) {
JmxRegistry.getRegistry(this).unregisterJmx(oname);
oname = null;
}
}
/**
* 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
*/
@Override
public ChannelReceiver getChannelReceiver() {
return coordinator.getClusterReceiver();
}
/**
* Returns the channel sender component
* @return ChannelSender
*/
@Override
public ChannelSender getChannelSender() {
return coordinator.getClusterSender();
}
/**
* Returns the membership service component
* @return MembershipService
*/
@Override
public MembershipService getMembershipService() {
return coordinator.getMembershipService();
}
/**
* Sets the channel receiver component
* @param clusterReceiver ChannelReceiver
*/
@Override
public void setChannelReceiver(ChannelReceiver clusterReceiver) {
coordinator.setClusterReceiver(clusterReceiver);
}
/**
* Sets the channel sender component
* @param clusterSender ChannelSender
*/
@Override
public void setChannelSender(ChannelSender clusterSender) {
coordinator.setClusterSender(clusterSender);
}
/**
* Sets the membership component
* @param membershipService MembershipService
*/
@Override
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
*/
@Override
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
*/
@Override
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
*/
@Override
public void addChannelListener(ChannelListener channelListener) {
if (!this.channelListeners.contains(channelListener) ) {
this.channelListeners.add(channelListener);
} else {
throw new IllegalArgumentException(sm.getString("groupChannel.listener.alreadyExist",
channelListener,channelListener.getClass().getName()));
}
}
/**
*
* Removes a channel listener from the channel.<br>
* Channel listeners are uniquely identified using the equals(Object) method
* @param channelListener ChannelListener
*/
@Override
public void removeChannelListener(ChannelListener channelListener) {
channelListeners.remove(channelListener);
}
/**
* Returns an iterator of all the interceptors in this stack
* @return Iterator
*/
@Override
public Iterator<ChannelInterceptor> 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
*/
@Override
public void setHeartbeat(boolean heartbeat) {
this.heartbeat = heartbeat;
}
/**
* @see #setOptionCheck(boolean)
* @return boolean
*/
@Override
public boolean getOptionCheck() {
return optionCheck;
}
/**
* @see #setHeartbeat(boolean)
* @return boolean
*/
@Override
public boolean getHeartbeat() {
return heartbeat;
}
/**
* Returns the sleep time in milliseconds that the internal heartbeat will
* sleep in between invocations of <code>Channel.heartbeat()</code>
* @return long
*/
@Override
public long getHeartbeatSleeptime() {
return heartbeatSleeptime;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public boolean isJmxEnabled() {
return jmxEnabled;
}
@Override
public void setJmxEnabled(boolean jmxEnabled) {
this.jmxEnabled = jmxEnabled;
}
@Override
public String getJmxDomain() {
return jmxDomain;
}
@Override
public void setJmxDomain(String jmxDomain) {
this.jmxDomain = jmxDomain;
}
@Override
public String getJmxPrefix() {
return jmxPrefix;
}
@Override
public void setJmxPrefix(String jmxPrefix) {
this.jmxPrefix = jmxPrefix;
}
@Override
public ObjectName preRegister(MBeanServer server, ObjectName name)
throws Exception {
// NOOP
return null;
}
@Override
public void postRegister(Boolean registrationDone) {
// NOOP
}
@Override
public void preDeregister() throws Exception {
// NOOP
}
@Override
public void postDeregister() {
JmxRegistry.removeRegistry(this, true);
}
/**
*
* <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<ChannelInterceptor> {
private final ChannelInterceptor end;
private ChannelInterceptor start;
public InterceptorIterator(ChannelInterceptor start, ChannelInterceptor end) {
this.end = end;
this.start = start;
}
@Override
public boolean hasNext() {
return start!=null && start != end;
}
@Override
public ChannelInterceptor next() {
ChannelInterceptor result = null;
if ( hasNext() ) {
result = start;
start = start.getNext();
}
return result;
}
@Override
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 {
private static final Log log = LogFactory.getLog(HeartbeatThread.class);
protected static int counter = 1;
protected static synchronized int inc() {
return counter++;
}
protected volatile boolean doRun = true;
protected final GroupChannel channel;
protected final long sleepTime;
public HeartbeatThread(GroupChannel channel, long sleepTime) {
super();
this.setPriority(MIN_PRIORITY);
String channelName = "";
if (channel.getName() != null) channelName = "[" + channel.getName() + "]";
setName("GroupChannel-Heartbeat" + channelName + "-" +inc());
setDaemon(true);
this.channel = channel;
this.sleepTime = sleepTime;
}
public void stopHeartbeat() {
doRun = false;
interrupt();
}
@Override
public void run() {
while (doRun) {
try {
Thread.sleep(sleepTime);
channel.heartbeat();
} catch ( InterruptedException x ) {
// Ignore. Probably triggered by a call to stopHeartbeat().
// In the highly unlikely event it was a different trigger,
// simply ignore it and continue.
} catch ( Exception x ) {
log.error(sm.getString("groupChannel.unable.sendHeartbeat"),x);
}//catch
}//while
}//run
}//HeartbeatThread
}

View File

@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.ChannelException;
import org.apache.catalina.tribes.ChannelListener;
import org.apache.catalina.tribes.ErrorHandler;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.MembershipListener;
import org.apache.catalina.tribes.UniqueId;
public interface GroupChannelMBean {
// Attributes
public boolean getOptionCheck();
public boolean getHeartbeat();
public long getHeartbeatSleeptime();
// Operations
public void start(int svc) throws ChannelException;
public void stop(int svc) throws ChannelException;
public UniqueId send(Member[] destination, Serializable msg, int options)
throws ChannelException;
public UniqueId send(Member[] destination, Serializable msg, int options, ErrorHandler handler)
throws ChannelException;
public void addMembershipListener(MembershipListener listener);
public void addChannelListener(ChannelListener listener);
public void removeMembershipListener(MembershipListener listener);
public void removeChannelListener(ChannelListener listener);
public boolean hasMembers() ;
public Member[] getMembers() ;
public Member getLocalMember(boolean incAlive);
}

View File

@@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
/**
* @version 1.0
*/
public class InterceptorPayload {
private ErrorHandler errorHandler;
public ErrorHandler getErrorHandler() {
return errorHandler;
}
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
}

View File

@@ -0,0 +1,28 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
channelCoordinator.alreadyStarted=Channel already started for level:[{0}]
channelCoordinator.invalid.startLevel=Invalid start level, valid levels are:SND_RX_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ
groupChannel.listener.alreadyExist=Listener already exists:[{0}][{1}]
groupChannel.noDestination=No destination given
groupChannel.nullMessage=Cannot send a NULL message
groupChannel.optionFlag.conflict=Interceptor option flag conflict: [{0}]
groupChannel.receiving.error=Error receiving message:
groupChannel.sendFail.noRpcChannelReply=Unable to find rpc channel, failed to send NoRpcChannelReply.
groupChannel.unable.deserialize=Unable to deserialize message:[{0}]
groupChannel.unable.sendHeartbeat=Unable to send heartbeat through Tribes interceptor stack. Will try to sleep again.
rpcChannel.replyFailed=Unable to send back reply in RpcChannel.

View File

@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
channelCoordinator.invalid.startLevel=Nivel de inicion inválido, los niveles válidos son:SND_RX_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ\n
groupChannel.listener.alreadyExist=El escuchador ya existe:[{0}][{1}]
groupChannel.unable.sendHeartbeat=Imposible enviar heartbeat através del Tribes interceptor stack. Tratré de dormir y esperar nuevamente.\n

View File

@@ -0,0 +1,28 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
channelCoordinator.alreadyStarted=Canal déjà démarré pour le niveau: [{0}]
channelCoordinator.invalid.startLevel=Niveau de départ invalide, les niveaux valides sont : SND_RX_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ
groupChannel.listener.alreadyExist=L''écouteur existe déjà: [{0}][{1}]
groupChannel.noDestination=Aucune destination donnée
groupChannel.nullMessage=Impossible d'envoyer un message null
groupChannel.optionFlag.conflict=Conflit sur le drapeau optionnel d''un intercepteur: [{0}]
groupChannel.receiving.error=Erreur lors de la réception du message:
groupChannel.sendFail.noRpcChannelReply=Incapable de trouver le canal RPM, échec d'envoi de NoRpcChannelReply
groupChannel.unable.deserialize=Impossible de désérialiser le message [{0}]
groupChannel.unable.sendHeartbeat=Impossible d'envoyer lévénement périodique dans la pile d'intercepteurs de Tribes
rpcChannel.replyFailed=Impossible de renvoyer la réponse dans le RpcChannel

View File

@@ -0,0 +1,28 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
channelCoordinator.alreadyStarted=チャンネルは既にレベル:[{0}]で開始されました。
channelCoordinator.invalid.startLevel=不正な開始レベルです。正常なレベルは SND_RX_SEQ や SND_TX_SEQ、MBR_TX_SEQ や MBR_RX_SEQ です。
groupChannel.listener.alreadyExist=チャンネルリスナー [{0}][{1}] はすでに存在します。
groupChannel.noDestination=宛先が指定されていません。
groupChannel.nullMessage=Null メッセージを送信することが出来ません。
groupChannel.optionFlag.conflict=Interceptor のoption フラグが衝突しています: [{0}]
groupChannel.receiving.error=エラー受信メッセージ:
groupChannel.sendFail.noRpcChannelReply=RPC チャンネルが見つからないため NoRpcChannelReply を送信できません。
groupChannel.unable.deserialize=メッセージをデシリアライズできません: [{0}]
groupChannel.unable.sendHeartbeat=Tribesインターセプタスタックを介してハートビートを送信できません。 もう一sleep します。
rpcChannel.replyFailed=RpcChannel へ返信を送信できません。

View File

@@ -0,0 +1,28 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
channelCoordinator.alreadyStarted=채널이 이미 레벨 [{0}](으)로 시작되었습니다.
channelCoordinator.invalid.startLevel=유요하지 않은 시작 레벨입니다. 유효한 레벨은 SND_RX_SEQ, SND_TX_SEQ, MBR_TX_SEQ, MBR_RX_SEQ입니다.
groupChannel.listener.alreadyExist=리스너가 이미 존재합니다: [{0}][{1}]
groupChannel.noDestination=그룹 채널에서 전송 대상 멤버가 없습니다.
groupChannel.nullMessage=널 메시지를 전송할 수 없습니다.
groupChannel.optionFlag.conflict=인터셉터 옵션 플래그가 충돌합니다: [{0}]
groupChannel.receiving.error=메시지 수신 중 오류 발생
groupChannel.sendFail.noRpcChannelReply=RPC 채널을 찾을 수 없습니다. NoRpcChannelReply을 보내지 못했습니다.
groupChannel.unable.deserialize=메시지를 역직렬화할 수 없습니다:[{0}]
groupChannel.unable.sendHeartbeat=Tribes 인터셉터 스택을 통해 heartbeat를 보낼 수 없습니다. 다시 sleep을 시도할 것입니다.
rpcChannel.replyFailed=RpcChannel에서 응답을 되돌려 보낼 수 없습니다.

View File

@@ -0,0 +1,24 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
channelCoordinator.alreadyStarted=通道已经启动,级别:[{0}]
channelCoordinator.invalid.startLevel=启动级别无效有效级别为SND_RX_SEQSND_TX_SEQMBR_TX_SEQMBR_RX_SEQ
groupChannel.listener.alreadyExist=侦听器已存在:[{0}][{1}]
groupChannel.noDestination=没有指定目的地
groupChannel.nullMessage=无法发送空消息
groupChannel.optionFlag.conflict=拦截器选项标志冲突:[{0}]
groupChannel.unable.deserialize=无法反序列化消息:[{0}]
groupChannel.unable.sendHeartbeat=无法通过Tribes拦截器堆栈发送心跳。 会再试一次。

View File

@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
* @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;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
* @version 1.0
*/
public interface RpcCallback {
/**
* Allows sending a response to a received message.
* @param msg The message
* @param sender Member
* @return Serializable object, <code>null</code> 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 The message
* @param sender Member
*/
public void leftOver(Serializable msg, Member sender);
}

View File

@@ -0,0 +1,301 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.ChannelException;
import org.apache.catalina.tribes.ChannelListener;
import org.apache.catalina.tribes.ErrorHandler;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.UniqueId;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.catalina.tribes.util.UUIDGenerator;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* A channel to handle RPC messaging
*/
public class RpcChannel implements ChannelListener {
private static final Log log = LogFactory.getLog(RpcChannel.class);
protected static final StringManager sm = StringManager.getManager(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 int replyMessageOptions = 0;
private final ConcurrentMap<RpcCollectorKey, RpcCollector> responseMap = new ConcurrentHashMap<>();
/**
* 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 rpcOptions int - FIRST_REPLY, MAJORITY_REPLY or ALL_REPLY
* @param channelOptions channel sender options
* @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 Error sending message
*/
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
int sendOptions =
channelOptions & ~Channel.SEND_OPTIONS_SYNCHRONIZED_ACK;
RpcCollectorKey key = new RpcCollectorKey(UUIDGenerator.randomUUID(false));
RpcCollector collector = new RpcCollector(key,rpcOptions,destination.length);
try {
synchronized (collector) {
if ( rpcOptions != NO_REPLY ) responseMap.put(key, collector);
RpcMessage rmsg = new RpcMessage(rpcId, key.id, message);
channel.send(destination, rmsg, sendOptions);
if ( rpcOptions != NO_REPLY ) collector.wait(timeout);
}
} catch ( InterruptedException ix ) {
Thread.currentThread().interrupt();
} finally {
responseMap.remove(key);
}
return collector.getResponses();
}
@Override
public void messageReceived(Serializable msg, Member sender) {
RpcMessage rmsg = (RpcMessage)msg;
RpcCollectorKey key = new RpcCollectorKey(rmsg.uuid);
if ( rmsg.reply ) {
RpcCollector collector = responseMap.get(key);
if (collector == null) {
if (!(rmsg instanceof RpcMessage.NoRpcChannelReply))
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 {
boolean finished = false;
final ExtendedRpcCallback excallback = (callback instanceof ExtendedRpcCallback)?((ExtendedRpcCallback)callback) : null;
boolean asyncReply = ((replyMessageOptions & Channel.SEND_OPTIONS_ASYNCHRONOUS) == Channel.SEND_OPTIONS_ASYNCHRONOUS);
Serializable reply = callback.replyRequest(rmsg.message,sender);
ErrorHandler handler = null;
final Serializable request = msg;
final Serializable response = reply;
final Member fsender = sender;
if (excallback!=null && asyncReply) {
handler = new ErrorHandler() {
@Override
public void handleError(ChannelException x, UniqueId id) {
excallback.replyFailed(request, response, fsender, x);
}
@Override
public void handleCompletion(UniqueId id) {
excallback.replySucceeded(request, response, fsender);
}
};
}
rmsg.reply = true;
rmsg.message = reply;
try {
if (handler!=null) {
channel.send(new Member[] {sender}, rmsg,replyMessageOptions & ~Channel.SEND_OPTIONS_SYNCHRONIZED_ACK, handler);
} else {
channel.send(new Member[] {sender}, rmsg,replyMessageOptions & ~Channel.SEND_OPTIONS_SYNCHRONIZED_ACK);
}
finished = true;
} catch ( Exception x ) {
if (excallback != null && !asyncReply) {
excallback.replyFailed(rmsg.message, reply, sender, x);
} else {
log.error(sm.getString("rpcChannel.replyFailed"),x);
}
}
if (finished && excallback != null && !asyncReply) {
excallback.replySucceeded(rmsg.message, reply, sender);
}
}//end if
}
public void breakdown() {
channel.removeChannelListener(this);
}
@Override
public void finalize() throws Throwable {
breakdown();
super.finalize();
}
@Override
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;
}
public int getReplyMessageOptions() {
return replyMessageOptions;
}
public void setReplyMessageOptions(int replyMessageOptions) {
this.replyMessageOptions = replyMessageOptions;
}
/**
*
* Class that holds all response.
* @version 1.0
*/
public static class RpcCollector {
public final ArrayList<Response> responses = new ArrayList<>();
public final RpcCollectorKey key;
public final int options;
public int destcnt;
public RpcCollector(RpcCollectorKey key, int options, int destcnt) {
this.key = key;
this.options = options;
this.destcnt = destcnt;
}
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;
}
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override
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 responses.toArray(new Response[responses.size()]);
}
}
public static class RpcCollectorKey {
final byte[] id;
public RpcCollectorKey(byte[] id) {
this.id = id;
}
@Override
public int hashCode() {
return id[0]+id[1]+id[2]+id[3];
}
@Override
public boolean equals(Object o) {
if ( o instanceof RpcCollectorKey ) {
RpcCollectorKey r = (RpcCollectorKey)o;
return Arrays.equals(id,r.id);
} else return false;
}
}
}

View File

@@ -0,0 +1,111 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import org.apache.catalina.tribes.util.Arrays;
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;
}
@Override
public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
reply = in.readBoolean();
int length = in.readInt();
uuid = new byte[length];
in.readFully(uuid);
length = in.readInt();
rpcId = new byte[length];
in.readFully(rpcId);
message = (Serializable)in.readObject();
}
@Override
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);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder("RpcMessage[");
buf.append(super.toString());
buf.append("] rpcId=");
buf.append(Arrays.toString(rpcId));
buf.append("; uuid=");
buf.append(Arrays.toString(uuid));
buf.append("; msg=");
buf.append(message);
return buf.toString();
}
public static class NoRpcChannelReply extends RpcMessage {
public NoRpcChannelReply() {
}
public NoRpcChannelReply(byte[] rpcid, byte[] uuid) {
super(rpcid,uuid,null);
reply = true;
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
reply = true;
int length = in.readInt();
uuid = new byte[length];
in.readFully(uuid);
length = in.readInt();
rpcId = new byte[length];
in.readFully(rpcId);
}
@Override
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);
}
}
}

View File

@@ -0,0 +1,147 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.catalina.tribes.ChannelMessage;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.group.ChannelInterceptorBase;
import org.apache.catalina.tribes.membership.Membership;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* <p>Title: Member domain filter interceptor </p>
*
* <p>Description: Filters membership based on domain.
* </p>
*
* @version 1.0
*/
public class DomainFilterInterceptor extends ChannelInterceptorBase
implements DomainFilterInterceptorMBean {
private static final Log log = LogFactory.getLog(DomainFilterInterceptor.class);
protected static final StringManager sm = StringManager.getManager(DomainFilterInterceptor.class);
protected volatile Membership membership = null;
protected byte[] domain = new byte[0];
protected int logInterval = 100;
private final AtomicInteger logCounter = new AtomicInteger(logInterval);
@Override
public void messageReceived(ChannelMessage msg) {
if (Arrays.equals(domain, msg.getAddress().getDomain())) {
super.messageReceived(msg);
} else {
if (logCounter.incrementAndGet() >= logInterval) {
logCounter.set(0);
if (log.isWarnEnabled())
log.warn(sm.getString("domainFilterInterceptor.message.refused", msg.getAddress()));
}
}
}//messageReceived
@Override
public void memberAdded(Member member) {
if ( membership == null ) setupMembership();
boolean notify = false;
synchronized (membership) {
notify = Arrays.equals(domain,member.getDomain());
if ( notify ) notify = membership.memberAlive(member);
}
if ( notify ) {
super.memberAdded(member);
} else {
if(log.isInfoEnabled()) log.info(sm.getString("domainFilterInterceptor.member.refused", member));
}
}
@Override
public void memberDisappeared(Member member) {
if ( membership == null ) setupMembership();
boolean notify = false;
synchronized (membership) {
notify = Arrays.equals(domain,member.getDomain());
if ( notify ) membership.removeMember(member);
}
if ( notify ) super.memberDisappeared(member);
}
@Override
public boolean hasMembers() {
if ( membership == null ) setupMembership();
return membership.hasMembers();
}
@Override
public Member[] getMembers() {
if ( membership == null ) setupMembership();
return membership.getMembers();
}
@Override
public Member getMember(Member mbr) {
if ( membership == null ) setupMembership();
return membership.getMember(mbr);
}
@Override
public Member getLocalMember(boolean incAlive) {
return super.getLocalMember(incAlive);
}
protected synchronized void setupMembership() {
if ( membership == null ) {
membership = new Membership(super.getLocalMember(true));
}
}
@Override
public byte[] getDomain() {
return domain;
}
public void setDomain(byte[] domain) {
this.domain = domain;
}
public void setDomain(String domain) {
if ( domain == null ) return;
if (domain.startsWith("{"))
setDomain(org.apache.catalina.tribes.util.Arrays.fromString(domain));
else
setDomain(org.apache.catalina.tribes.util.Arrays.convert(domain));
}
@Override
public int getLogInterval() {
return logInterval;
}
@Override
public void setLogInterval(int logInterval) {
this.logInterval = logInterval;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
public interface DomainFilterInterceptorMBean {
public int getOptionFlag();
public byte[] getDomain();
public int getLogInterval();
public void setLogInterval(int logInterval);
}

View File

@@ -0,0 +1,610 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.ChannelException;
import org.apache.catalina.tribes.ChannelInterceptor;
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;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Adds encryption using a pre-shared key.
*
* The length of the key (in bytes) must be acceptable for the encryption
* algorithm being used. For example, for AES, you must use a key of either
* 16 bytes (128 bits, 24 bytes 192 bits), or 32 bytes (256 bits).
*
* You can supply the raw key bytes by calling {@link #setEncryptionKey(byte[])}
* or the hex-encoded binary bytes by calling
* {@link #setEncryptionKey(String)}.
*/
public class EncryptInterceptor extends ChannelInterceptorBase implements EncryptInterceptorMBean {
private static final Log log = LogFactory.getLog(EncryptInterceptor.class);
protected static final StringManager sm = StringManager.getManager(EncryptInterceptor.class);
private static final String DEFAULT_ENCRYPTION_ALGORITHM = "AES/CBC/PKCS5Padding";
private String providerName;
private String encryptionAlgorithm = DEFAULT_ENCRYPTION_ALGORITHM;
private byte[] encryptionKeyBytes;
private String encryptionKeyString;
private BaseEncryptionManager encryptionManager;
public EncryptInterceptor() {
}
@Override
public void start(int svc) throws ChannelException {
validateChannelChain();
if(Channel.SND_TX_SEQ == (svc & Channel.SND_TX_SEQ)) {
try {
encryptionManager = createEncryptionManager(getEncryptionAlgorithm(),
getEncryptionKeyInternal(),
getProviderName());
} catch (GeneralSecurityException gse) {
throw new ChannelException(sm.getString("encryptInterceptor.init.failed"), gse);
}
}
super.start(svc);
}
private void validateChannelChain() throws ChannelException {
ChannelInterceptor interceptor = getPrevious();
while(null != interceptor) {
if(interceptor instanceof TcpFailureDetector)
throw new ChannelConfigException(sm.getString("encryptInterceptor.tcpFailureDetector.ordering"));
interceptor = interceptor.getPrevious();
}
}
@Override
public void stop(int svc) throws ChannelException {
if(Channel.SND_TX_SEQ == (svc & Channel.SND_TX_SEQ)) {
encryptionManager.shutdown();
}
super.stop(svc);
}
@Override
public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload)
throws ChannelException {
try {
byte[] data = msg.getMessage().getBytes();
// See #encrypt(byte[]) for an explanation of the return value
byte[][] bytes = encryptionManager.encrypt(data);
XByteBuffer xbb = msg.getMessage();
// Completely replace the message
xbb.clear();
xbb.append(bytes[0], 0, bytes[0].length);
xbb.append(bytes[1], 0, bytes[1].length);
super.sendMessage(destination, msg, payload);
} catch (GeneralSecurityException gse) {
log.error(sm.getString("encryptInterceptor.encrypt.failed"));
throw new ChannelException(gse);
}
}
@Override
public void messageReceived(ChannelMessage msg) {
try {
byte[] data = msg.getMessage().getBytes();
data = encryptionManager.decrypt(data);
XByteBuffer xbb = msg.getMessage();
// Completely replace the message with the decrypted one
xbb.clear();
xbb.append(data, 0, data.length);
super.messageReceived(msg);
} catch (GeneralSecurityException gse) {
log.error(sm.getString("encryptInterceptor.decrypt.failed"), gse);
}
}
/**
* Sets the encryption algorithm to be used for encrypting and decrypting
* channel messages. You must specify the <code>algorithm/mode/padding</code>.
* Information on standard algorithm names may be found in the
* <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html">Java
* documentation</a>.
*
* Default is <code>AES/CBC/PKCS5Padding</code>.
*
* @param algorithm The algorithm to use.
*/
@Override
public void setEncryptionAlgorithm(String algorithm) {
if(null == getEncryptionAlgorithm())
throw new IllegalStateException(sm.getString("encryptInterceptor.algorithm.required"));
int pos = algorithm.indexOf('/');
if(pos < 0)
throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.required"));
pos = algorithm.indexOf('/', pos + 1);
if(pos < 0)
throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.required"));
encryptionAlgorithm = algorithm;
}
/**
* Gets the encryption algorithm being used to encrypt and decrypt channel
* messages.
*
* @return The algorithm being used, including the algorithm mode and padding.
*/
@Override
public String getEncryptionAlgorithm() {
return encryptionAlgorithm;
}
/**
* Sets the encryption key for encryption and decryption. The length of the
* key must be appropriate for the algorithm being used.
*
* @param key The encryption key.
*/
@Override
public void setEncryptionKey(byte[] key) {
if (null == key) {
encryptionKeyBytes = null;
} else {
encryptionKeyBytes = key.clone();
}
}
/**
* Gets the encryption key being used for encryption and decryption.
* The key is encoded using hex-encoding where e.g. the byte <code>0xab</code>
* will be shown as "ab". The length of the string in characters will
* be twice the length of the key in bytes.
*
* @param keyBytes The encryption key.
*/
public void setEncryptionKey(String keyBytes) {
this.encryptionKeyString = keyBytes;
if (null == keyBytes) {
setEncryptionKey((byte[])null);
} else {
setEncryptionKey(fromHexString(keyBytes.trim()));
}
}
/**
* Gets the encryption key being used for encryption and decryption.
*
* @return The encryption key.
*/
@Override
public byte[] getEncryptionKey() {
byte[] key = getEncryptionKeyInternal();
if(null != key)
key = key.clone();
return key;
}
private byte[] getEncryptionKeyInternal() {
return encryptionKeyBytes;
}
public String getEncryptionKeyString() {
return encryptionKeyString;
}
public void setEncryptionKeyString(String encryptionKeyString) {
setEncryptionKey(encryptionKeyString);
}
/**
* Sets the JCA provider name used for cryptographic activities.
*
* Default is the JVM platform default.
*
* @param provider The name of the JCA provider.
*/
@Override
public void setProviderName(String provider) {
providerName = provider;
}
/**
* Gets the JCA provider name used for cryptographic activities.
*
* Default is the JVM platform default.
*
* @return The name of the JCA provider.
*/
@Override
public String getProviderName() {
return providerName;
}
// Copied from org.apache.tomcat.util.buf.HexUtils
private static final int[] DEC = {
00, 01, 02, 03, 04, 05, 06, 07, 8, 9, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15,
};
private static int getDec(int index) {
// Fast for correct values, slower for incorrect ones
try {
return DEC[index - '0'];
} catch (ArrayIndexOutOfBoundsException ex) {
return -1;
}
}
private static byte[] fromHexString(String input) {
if (input == null) {
return null;
}
if ((input.length() & 1) == 1) {
// Odd number of characters
throw new IllegalArgumentException(sm.getString("hexUtils.fromHex.oddDigits"));
}
char[] inputChars = input.toCharArray();
byte[] result = new byte[input.length() >> 1];
for (int i = 0; i < result.length; i++) {
int upperNibble = getDec(inputChars[2*i]);
int lowerNibble = getDec(inputChars[2*i + 1]);
if (upperNibble < 0 || lowerNibble < 0) {
// Non hex character
throw new IllegalArgumentException(sm.getString("hexUtils.fromHex.nonHex"));
}
result[i] = (byte) ((upperNibble << 4) + lowerNibble);
}
return result;
}
private static BaseEncryptionManager createEncryptionManager(String algorithm,
byte[] encryptionKey, String providerName)
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
if(null == encryptionKey)
throw new IllegalStateException(sm.getString("encryptInterceptor.key.required"));
String algorithmName;
String algorithmMode;
// We need to break-apart the algorithm name e.g. AES/CBC/PKCS5Padding
// take just the algorithm part.
int pos = algorithm.indexOf('/');
if(pos >= 0) {
algorithmName = algorithm.substring(0, pos);
int pos2 = algorithm.indexOf('/', pos+1);
if(pos2 >= 0) {
algorithmMode = algorithm.substring(pos + 1, pos2);
} else {
algorithmMode = "CBC";
}
} else {
algorithmName = algorithm;
algorithmMode = "CBC";
}
if("GCM".equalsIgnoreCase(algorithmMode))
return new GCMEncryptionManager(algorithm, new SecretKeySpec(encryptionKey, algorithmName), providerName);
else if("CBC".equalsIgnoreCase(algorithmMode)
|| "OFB".equalsIgnoreCase(algorithmMode)
|| "CFB".equalsIgnoreCase(algorithmMode))
return new BaseEncryptionManager(algorithm,
new SecretKeySpec(encryptionKey, algorithmName),
providerName);
else
throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.unsupported-mode", algorithmMode));
}
private static class BaseEncryptionManager {
/**
* The fully-specified algorithm e.g. AES/CBC/PKCS5Padding.
*/
private final String algorithm;
/**
* The block size of the cipher.
*/
private final int blockSize;
/**
* The cryptographic provider name.
*/
private final String providerName;
/**
* The secret key to use for encryption and decryption operations.
*/
private final SecretKeySpec secretKey;
/**
* A pool of Cipher objects. Ciphers are expensive to create, but not
* to re-initialize, so we use a pool of them which grows as necessary.
*/
private final ConcurrentLinkedQueue<Cipher> cipherPool;
/**
* A pool of SecureRandom objects. Each encrypt operation requires access
* to a source of randomness. SecureRandom is thread-safe, but sharing a
* single instance will likely be a bottleneck.
*/
private final ConcurrentLinkedQueue<SecureRandom> randomPool;
public BaseEncryptionManager(String algorithm, SecretKeySpec secretKey, String providerName)
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
this.algorithm = algorithm;
this.providerName = providerName;
this.secretKey = secretKey;
cipherPool = new ConcurrentLinkedQueue<>();
Cipher cipher = createCipher();
blockSize = cipher.getBlockSize();
cipherPool.offer(cipher);
randomPool = new ConcurrentLinkedQueue<>();
}
public void shutdown() {
// Individual Cipher and SecureRandom objects need no explicit teardown
cipherPool.clear();
randomPool.clear();
}
private String getAlgorithm() {
return algorithm;
}
private SecretKeySpec getSecretKey() {
return secretKey;
}
/**
* Gets the size, in bytes, of the initialization vector for the
* cipher being used. The IV size is often, but not always, the block
* size for the cipher.
*
* @return The size of the initialization vector for this algorithm.
*/
protected int getIVSize() {
return blockSize;
}
private String getProviderName() {
return providerName;
}
private Cipher createCipher()
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
String providerName = getProviderName();
if(null == providerName) {
return Cipher.getInstance(getAlgorithm());
} else {
return Cipher.getInstance(getAlgorithm(), providerName);
}
}
private Cipher getCipher() throws GeneralSecurityException {
Cipher cipher = cipherPool.poll();
if(null == cipher) {
cipher = createCipher();
}
return cipher;
}
private void returnCipher(Cipher cipher) {
cipherPool.offer(cipher);
}
private SecureRandom getRandom() {
SecureRandom random = randomPool.poll();
if(null == random) {
random = new SecureRandom();
}
return random;
}
private void returnRandom(SecureRandom random) {
randomPool.offer(random);
}
/**
* Encrypts the input <code>bytes</code> into two separate byte arrays:
* one for the random initialization vector (IV) used for this message,
* and the second one containing the actual encrypted payload.
*
* This method returns a pair of byte arrays instead of a single
* concatenated one to reduce the number of byte buffers created
* and copied during the whole operation -- including message re-building.
*
* @param bytes The data to encrypt.
*
* @return The IV in [0] and the encrypted data in [1].
*
* @throws GeneralSecurityException If the input data cannot be encrypted.
*/
private byte[][] encrypt(byte[] bytes) throws GeneralSecurityException {
Cipher cipher = null;
// Always use a random IV For cipher setup.
// The recipient doesn't need the (matching) IV because we will always
// pre-pad messages with the IV as a nonce.
byte[] iv = generateIVBytes();
try {
cipher = getCipher();
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(), generateIV(iv, 0, getIVSize()));
// Prepend the IV to the beginning of the encrypted data
byte[][] data = new byte[2][];
data[0] = iv;
data[1] = cipher.doFinal(bytes);
return data;
} finally {
if(null != cipher)
returnCipher(cipher);
}
}
/**
* Decrypts the input <code>bytes</code>.
*
* @param bytes The data to decrypt.
*
* @return The decrypted data.
*
* @throws GeneralSecurityException If the input data cannot be decrypted.
*/
private byte[] decrypt(byte[] bytes) throws GeneralSecurityException {
Cipher cipher = null;
int ivSize = getIVSize();
AlgorithmParameterSpec IV = generateIV(bytes, 0, ivSize);
try {
cipher = getCipher();
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), IV);
// Decrypt remainder of the message.
return cipher.doFinal(bytes, ivSize, bytes.length - ivSize);
} finally {
if(null != cipher)
returnCipher(cipher);
}
}
protected byte[] generateIVBytes() {
byte[] ivBytes = new byte[getIVSize()];
SecureRandom random = null;
try {
random = getRandom();
// Always use a random IV For cipher setup.
// The recipient doesn't need the (matching) IV because we will always
// pre-pad messages with the IV as a nonce.
random.nextBytes(ivBytes);
return ivBytes;
} finally {
if(null != random)
returnRandom(random);
}
}
protected AlgorithmParameterSpec generateIV(byte[] ivBytes, int offset, int length) {
return new IvParameterSpec(ivBytes, offset, length);
}
}
/**
* Implements an EncryptionManager for using GCM block cipher modes.
*
* GCM works a little differently than some of the other block cipher modes
* supported by EncryptInterceptor. First of all, it requires a different
* kind of AlgorithmParameterSpec object to be used, and second, it
* requires a slightly different initialization vector and something called
* an "authentication tag".
*
* The choice of IV length can be somewhat arbitrary, but there is consensus
* that 96-bit (12-byte) IVs for GCM are the best trade-off between security
* and performance. For other block cipher modes, IV length is the same as
* the block size.
*
* The "authentication tag" is a computed authentication value based upon
* the message and the encryption process. GCM defines these tags as the
* number of bits to use for the authentication tag, and it's clear that
* the highest number of bits supported 128-bit provide the best security.
*/
private static class GCMEncryptionManager extends BaseEncryptionManager
{
public GCMEncryptionManager(String algorithm, SecretKeySpec secretKey, String providerName)
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
super(algorithm, secretKey, providerName);
}
@Override
protected int getIVSize() {
return 12; // See class javadoc for explanation of this magic number (12)
}
@Override
protected AlgorithmParameterSpec generateIV(byte[] bytes, int offset, int length) {
// See class javadoc for explanation of this magic number (128)
return new GCMParameterSpec(128, bytes, offset, length);
}
}
static class ChannelConfigException
extends ChannelException
{
private static final long serialVersionUID = 1L;
public ChannelConfigException(String message) {
super(message);
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
public interface EncryptInterceptorMBean {
// Config
public int getOptionFlag();
public void setOptionFlag(int optionFlag);
public void setEncryptionAlgorithm(String algorithm);
public String getEncryptionAlgorithm();
public void setEncryptionKey(byte[] key);
public byte[] getEncryptionKey();
public void setProviderName(String provider);
public String getProviderName();
}

View File

@@ -0,0 +1,250 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.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;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
*
* 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>
* FragmentationInterceptor.expire=&lt;milliseconds&gt; - how long do we keep the fragments in memory and wait for the rest to arrive <b>default=60,000ms -&gt; 60seconds</b>
* This setting is useful to avoid OutOfMemoryErrors<br>
* FragmentationInterceptor.maxSize=&lt;max message size&gt; - message size in bytes <b>default=1024*100 (around a tenth of a MB)</b><br>
* @version 1.0
*/
public class FragmentationInterceptor extends ChannelInterceptorBase implements FragmentationInterceptorMBean {
private static final Log log = LogFactory.getLog(FragmentationInterceptor.class);
protected static final StringManager sm = StringManager.getManager(FragmentationInterceptor.class);
protected final HashMap<FragKey, FragCollection> fragpieces = new HashMap<>();
private int maxSize = 1024*100;
private long expire = 1000 * 60; //one minute expiration
protected final boolean deepclone = true;
@Override
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);
}
}
@Override
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 = fragpieces.get(key);
if ( coll == null ) {
synchronized (fragpieces) {
coll = 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);
}
}
@Override
public void heartbeat() {
try {
Set<FragKey> 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(sm.getString("fragmentationInterceptor.heartbeat.failed"),x);
}
}
super.heartbeat();
}
@Override
public int getMaxSize() {
return maxSize;
}
@Override
public long getExpire() {
return expire;
}
@Override
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
@Override
public void setExpire(long expire) {
this.expire = expire;
}
public static class FragCollection {
private final long received = System.currentTimeMillis();
private final ChannelMessage msg;
private final 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(sm.getString("fragmentationInterceptor.fragments.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 final byte[] uniqueId;
private final long received = System.currentTimeMillis();
public FragKey(byte[] id ) {
this.uniqueId = id;
}
@Override
public int hashCode() {
return XByteBuffer.toInt(uniqueId,0);
}
@Override
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;
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
public interface FragmentationInterceptorMBean {
// Attributes
public int getMaxSize();
public long getExpire();
public void setMaxSize(int maxSize);
public void setExpire(long expire);
}

View File

@@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
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;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* @version 1.0
*/
public class GzipInterceptor extends ChannelInterceptorBase {
private static final Log log = LogFactory.getLog(GzipInterceptor.class);
protected static final StringManager sm = StringManager.getManager(GzipInterceptor.class);
public static final int DEFAULT_BUFFER_SIZE = 2048;
@Override
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);
super.sendMessage(destination, msg, payload);
} catch ( IOException x ) {
log.error(sm.getString("gzipInterceptor.compress.failed"));
throw new ChannelException(x);
}
}
@Override
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);
super.messageReceived(msg);
} catch ( IOException x ) {
log.error(sm.getString("gzipInterceptor.decompress.failed"),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();
}
/**
* @param data Data to decompress
* @return Decompressed data
* @throws IOException Compression error
*/
public static byte[] decompress(byte[] data) throws IOException {
ByteArrayOutputStream bout =
new ByteArrayOutputStream(DEFAULT_BUFFER_SIZE);
ByteArrayInputStream bin = new ByteArrayInputStream(data);
GZIPInputStream gin = new GZIPInputStream(bin);
byte[] tmp = new byte[DEFAULT_BUFFER_SIZE];
int length = gin.read(tmp);
while (length > -1) {
bout.write(tmp, 0, length);
length = gin.read(tmp);
}
return bout.toByteArray();
}
}

View File

@@ -0,0 +1,82 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
domainFilterInterceptor.member.refused=Member [{0}] was refused to join cluster
domainFilterInterceptor.message.refused=Received message from cluster[{0}] was refused.
encryptInterceptor.algorithm.required=Encryption algorithm is required, fully-specified e.g. AES/CBC/PKCS5Padding
encryptInterceptor.algorithm.unsupported-mode=EncryptInterceptor does not support block cipher mode [{0}]
encryptInterceptor.decrypt.error.short-message=Failed to decrypt message: premature end-of-message
encryptInterceptor.decrypt.failed=Failed to decrypt message
encryptInterceptor.encrypt.failed=Failed to encrypt message
encryptInterceptor.init.failed=Failed to initialize EncryptInterceptor
encryptInterceptor.key.required=Encryption key is required
encryptInterceptor.tcpFailureDetector.ordering=EncryptInterceptor must be upstream of TcpFailureDetector. Please re-order EncryptInterceptor to be listed before TcpFailureDetector in your channel interceptor pipeline.
fragmentationInterceptor.fragments.missing=Fragments are missing.
fragmentationInterceptor.heartbeat.failed=Unable to perform heartbeat clean up in the frag interceptor
gzipInterceptor.compress.failed=Unable to compress byte contents
gzipInterceptor.decompress.failed=Unable to decompress byte contents
messageDispatchInterceptor.AsyncMessage.failed=Error while processing async message.
messageDispatchInterceptor.completeMessage.failed=Unable to report back completed message.
messageDispatchInterceptor.errorMessage.failed=Unable to report back error message.
messageDispatchInterceptor.queue.full=Asynchronous queue is full, reached its limit of [{0}] bytes, current:[{1}] bytes.
messageDispatchInterceptor.unableAdd.queue=Unable to add the message to the async queue, queue bug?
messageDispatchInterceptor.warning.optionflag=Warning, you are overriding the asynchronous option flag, this will disable the Channel.SEND_OPTIONS_ASYNCHRONOUS that other apps might use.
nonBlockingCoordinator.electionMessage.sendfailed=Unable to send election message to:[{0}]
nonBlockingCoordinator.heartbeat.failed=Unable to perform heartbeat.
nonBlockingCoordinator.heartbeat.inconsistency=Heartbeat found inconsistency, restart election
nonBlockingCoordinator.memberAdded.failed=Unable to start election when member was added.
nonBlockingCoordinator.memberAlive.failed=Unable to perform member alive check, assuming member down.
nonBlockingCoordinator.memberDisappeared.failed=Unable to start election when member was removed.
nonBlockingCoordinator.processCoordinationMessage.failed=Error processing coordination message. Could be fatal.
orderInterceptor.messageAdded.sameCounter=Message added has the same counter, synchronization bug. Disable the order interceptor
staticMembershipInterceptor.no.failureDetector=There is no TcpFailureDetector. Automatic detection of static members does not work properly. By defining the StaticMembershipInterceptor under the TcpFailureDetector, automatic detection of the static members will work.
staticMembershipInterceptor.no.pingInterceptor=There is no TcpPingInterceptor. The health check of static members does not work properly. By defining the TcpPingInterceptor, the health check of static members will work.
staticMembershipInterceptor.sendLocalMember.failed=Local member notification failed.
staticMembershipInterceptor.sendShutdown.failed=Shutdown notification failed.
tcpFailureDetector.already.disappeared=Verification complete. Member already disappeared[{0}]
tcpFailureDetector.failureDetection.failed=Unable to perform failure detection check, assuming member down.[{0}]
tcpFailureDetector.heartbeat.failed=Unable to perform heartbeat on the TcpFailureDetector.
tcpFailureDetector.member.disappeared=Verification complete. Member disappeared[{0}]
tcpFailureDetector.memberDisappeared.verify=Received memberDisappeared[{0}] message. Will verify.
tcpFailureDetector.performBasicCheck.memberAdded=Member added, even though we weren''t notified:[{0}]
tcpFailureDetector.still.alive=Verification complete. Member still alive[{0}]
tcpFailureDetector.suspectMember.alive=Suspect member, confirmed alive.[{0}]
tcpFailureDetector.suspectMember.dead=Suspect member, confirmed dead.[{0}]
tcpPingInterceptor.ping.failed=Unable to send TCP ping.
tcpPingInterceptor.pingFailed.pingThread=Unable to send ping from TCP ping thread.
throughputInterceptor.report=ThroughputInterceptor Report[\n\
\tTx Msg:{0} messages\n\
\tSent:{1} MB (total)\n\
\tSent:{2} MB (application)\n\
\tTime:{3} seconds\n\
\tTx Speed:{4} MB/sec (total)\n\
\tTx Speed:{5} MB/sec (application)\n\
\tError Msg:{6}\n\
\tRx Msg:{7} messages\n\
\tRx Speed:{8} MB/sec (since 1st msg)\n\
\tReceived:{9} MB]\n
twoPhaseCommitInterceptor.heartbeat.failed=Unable to perform heartbeat on the TwoPhaseCommit interceptor.
twoPhaseCommitInterceptor.originalMessage.missing=Received a confirmation, but original message is missing. Id:[{0}]

View File

@@ -0,0 +1,32 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
domainFilterInterceptor.member.refused=Mitglied [{0}] wurde nicht zum Cluster zugelassen
encryptInterceptor.decrypt.error.short-message=Konnte die Nachricht nicht entschlüsseln: Vorzeitiges Ende der Nachricht
encryptInterceptor.decrypt.failed=Nachricht konnte nicht entschlüsselt werden
messageDispatchInterceptor.queue.full=Asynchrone Warteschlange ist voll. Das Limit von [{0}] Bytes ist erreicht mit aktuell [{1}] Bytes.
nonBlockingCoordinator.processCoordinationMessage.failed=Fehler beim Verarbeiten der Koordinationsnachricht. Könnte Fatal sein.
staticMembershipInterceptor.sendShutdown.failed=Benachrichtigung über den Shutdown schlug fehl.
tcpFailureDetector.failureDetection.failed=Überprüng zur Fehlererkennung fehlgeschlagen, Mitglied [{0}]
tcpFailureDetector.still.alive=Verifikation abgeschlossen. Member sind immer noch am Leben [{0}]
tcpPingInterceptor.ping.failed=Konnte kein TCP Ping senden.
twoPhaseCommitInterceptor.heartbeat.failed=Kann den Heartbeat auf dem TwoPhaseCommit Interceptor nicht durchführen.

View File

@@ -0,0 +1,47 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
domainFilterInterceptor.message.refused=El mensaje [{0}] recibido del cluster fue rechazado
encryptInterceptor.decrypt.error.short-message=Fallo al descifrar el mensaje: fin-de-mensaje prematuro
messageDispatchInterceptor.queue.full=La cola asincrónica esta llena, se alcanzó el limite de [{0}] bytes, actualmente:[{1}] bytes.\n
nonBlockingCoordinator.memberAlive.failed=No se puede verificar si el miembro esta vivo, asumiendo que el miembro esta inactivo.
nonBlockingCoordinator.processCoordinationMessage.failed=Error procesando el mensaje de coordinación. Puede ser fatal.\n
staticMembershipInterceptor.no.pingInterceptor=No existe TcpPingInterceptor. El verificador de estado de miembros estáticos no trabaja correctamente. Al definir el TcpPingInterceptor, el verificador de estado de miembros estáticos trabajará correctamente.
staticMembershipInterceptor.sendShutdown.failed=El aviso de apagado falló.
tcpFailureDetector.failureDetection.failed=No se pudo realizar la verificación de detección de fallos, se asume que el membro esta abajo.[{0}]
tcpFailureDetector.heartbeat.failed=Imposible ejecutar heartbeat en el TcpFailureDetector.
tcpFailureDetector.member.disappeared=Verificación completada. Miembro desaparecido[{0}]
tcpFailureDetector.still.alive=Verificación completa. El miembro aun esta vivo [{0}]
tcpFailureDetector.suspectMember.alive=Se confima que esta vivo el miembro.[{0}]\n
tcpPingInterceptor.ping.failed=Imposible enviar ping TCP
throughputInterceptor.report=ThroughputInterceptor Reporte[\n\
\tTx Msg:{0} mensajes\n\
\tEnviados:{2} MB (aplicación)\n\
\tTiempo:{3} segundos\n\
\tTx Speed:{4} MB/seg(total)\n\
\tTx Speed::{5} MB/seg(aplicación)\n\
\tMsg error:{6}\n\
\tRx Msg:{7} mensajes\n\
\tRx Speed:{8} MB/sec (desde 1er msg)\n\
\tRecivido:{9} MB]
twoPhaseCommitInterceptor.heartbeat.failed=Incapáz de ejecutar heartbeat en el interceptor TwoPhaseCommit

View File

@@ -0,0 +1,82 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
domainFilterInterceptor.member.refused=Le membre [{0}] a été refusé dans le cluster
domainFilterInterceptor.message.refused=Le message reçu du cluster [{0}] a été refusé
encryptInterceptor.algorithm.required=Un algorithme de cryptage est requis, avec une spécification complète telle que AES/CBC/PKCS5Padding
encryptInterceptor.algorithm.unsupported-mode=L''EncryptInterceptor ne supporte pas le mode de chiffrage de bloc [{0}]
encryptInterceptor.decrypt.error.short-message=Erreur de décryptage du message: fin de message prématuré
encryptInterceptor.decrypt.failed=Echec de décryptage du message
encryptInterceptor.encrypt.failed=Erreur de cryptage du message
encryptInterceptor.init.failed=Echec de l'initalisation d'EncryptInterceptor
encryptInterceptor.key.required=Une clé de cryptage est requise
encryptInterceptor.tcpFailureDetector.ordering=EncryptInterceptor doit être en amont de TcpFailureDetector, l'EncryptInterceptor doit être repositionné pour être listé avant TcpFailureDetector dans le pipeline d'intercepteurs du canal
fragmentationInterceptor.fragments.missing=Les fragments sont manquants
fragmentationInterceptor.heartbeat.failed=Impossible d'effectuer le nettoyage périodique de l'intercepteur de fragments
gzipInterceptor.compress.failed=Impossible de compresser un contenu binaire
gzipInterceptor.decompress.failed=Impossible de décompresser le contenu des octets
messageDispatchInterceptor.AsyncMessage.failed=Erreur lors du traitement du message asynchrone
messageDispatchInterceptor.completeMessage.failed=Impossible de renvoyer le message complet
messageDispatchInterceptor.errorMessage.failed=Impossible d'envoyer le message d'erreur
messageDispatchInterceptor.queue.full=La file d''attente asynchrone est pleine, ayant atteint sa limite de [{0}] octets. Actuellement: [{1}] octets.
messageDispatchInterceptor.unableAdd.queue=Impossible d'ajouter le message à la file asynchrone. Bogue de file ?
messageDispatchInterceptor.warning.optionflag=Attention, vous passez outre le drapeau d'option d'asynchronicité ("asynchronous option flag"), cela désactivera Channel.SEND_OPTIONS_ASYNCHRONOUS, que d'autres applications sont susceptibles d'utiliser.
nonBlockingCoordinator.electionMessage.sendfailed=Impossible d''envoyer le message d''élection à: [{0}]
nonBlockingCoordinator.heartbeat.failed=Impossible d'effectuer le signal périodique
nonBlockingCoordinator.heartbeat.inconsistency=Le coordinateur à trouvé un état inconsistant, redémarrage de l'élection
nonBlockingCoordinator.memberAdded.failed=Impossible de démarrer une élection quand le membre a été ajouté
nonBlockingCoordinator.memberAlive.failed=Impossible d'effectuer le test de vie du membre, assume membre inactif.
nonBlockingCoordinator.memberDisappeared.failed=Impossible de démarrer une élection lorsqu'un membre a été enlevé
nonBlockingCoordinator.processCoordinationMessage.failed=Echec de traitement de message de coordination. Pourrait être fatal.
orderInterceptor.messageAdded.sameCounter=Le message ajouté a le même compteur, à cause d'un bug de synchronisation, l'intercepteur d'ordre doit être désactivé
staticMembershipInterceptor.no.failureDetector=Il n'y a pas de détecteur TcpFailureDetector. La détection automatique de membres statiques ne fonctionne pas correctement. Par la définition d'un intercepteur StaticMembershipInterceptor sous le TcpFailureDetector, cette détection automatique fonctionnera.
staticMembershipInterceptor.no.pingInterceptor=Il n'y a pas de TcpPingInterceptor. Le test de bonne santé des membres statiques ne fonctionne pas correctement. En définissant le TcpPingInterceptor, le test de bonne santé des membres statiques fonctionnera.
staticMembershipInterceptor.sendLocalMember.failed=La notification du membre local a échouée
staticMembershipInterceptor.sendShutdown.failed=La notification d'arrêt a échoué
tcpFailureDetector.already.disappeared=La vérification est terminée, le membre avait déjà disparu [{0}]
tcpFailureDetector.failureDetection.failed=Impossible d''effectuer le test de détection de faute. Membre [{0}] supposé inactif.
tcpFailureDetector.heartbeat.failed=Incapable de faire une pulsation ("heatbeat") sur le TcpFailureDector
tcpFailureDetector.member.disappeared=La vérfication est complète, le membre a disparu [{0}]
tcpFailureDetector.memberDisappeared.verify=Reçu un message memberDisappeared[{0}], qui sera vérifié
tcpFailureDetector.performBasicCheck.memberAdded=Le membre a été ajouté bien qu''aucune notification n''ait été reçue: [{0}]
tcpFailureDetector.still.alive=Vérification terminée. Le membre [{0}] vit toujours
tcpFailureDetector.suspectMember.alive=Membre suspect, confirmé vivant.[{0}]
tcpFailureDetector.suspectMember.dead=Un membre suspect a été confirmé mort [{0}]
tcpPingInterceptor.ping.failed=Impossible d'envoyer un ping TCP.
tcpPingInterceptor.pingFailed.pingThread=Impossible d'envoyer un ping à partir du thread des ping TCP
throughputInterceptor.report=Rapport de l''intercepteur du débit ("ThroughputInterceptor Report") [\n\
\tMsg Transmis (Tx Msg):{0} messages\n\
\tEnvoyé (Sent):{1} MB (total)\n\
\tEnvoyé (Sent):{2} MB (application)\n\
\tDurée (Time):{3} secondes\n\
\tVitesse d''écriture (Tx Speed):{4} MB/sec (total)\n\
\tVitesse d''écriture (Tx Speed):{5} MB/sec (application)\n\
\tMsg d''erreur (Error Msg):{6}\n\
\tMsg Reçus (Rx Msg):{7} messages\n\
\tVitesse de Réception (Rx Speed):{8} MB/sec (depuis le 1er message)\n\
\tReçu:{9} MB]
twoPhaseCommitInterceptor.heartbeat.failed=Impossible d'exécuter un battement de coeur (heartbeat) sur l'intercepteur (interceptor) "TwoPhaseCommit".
twoPhaseCommitInterceptor.originalMessage.missing=Reçue une confirmation mais le message d''origine manque, id: [{0}]

View File

@@ -0,0 +1,82 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
domainFilterInterceptor.member.refused=メンバーはクラスター [{0}] への参加を拒否されました。
domainFilterInterceptor.message.refused=クラスター [{0}] から受信したメッセージは拒否されました。
encryptInterceptor.algorithm.required=暗号化アルゴリズムが必要です。完全指定。 AES/CBC/PKCS5Padding
encryptInterceptor.algorithm.unsupported-mode=EncryptInterceptorはブロック暗号モード [{0}]をサポートしていません。
encryptInterceptor.decrypt.error.short-message=メッセージの復号に失敗: メッセージの末尾が途切れています
encryptInterceptor.decrypt.failed=メッセージの復号に失敗しました。
encryptInterceptor.encrypt.failed=メッセージを暗号化できません。
encryptInterceptor.init.failed=EncryptInterceptorの初期化に失敗しました
encryptInterceptor.key.required=暗号化キーが必要です。
encryptInterceptor.tcpFailureDetector.ordering=EncryptInterceptorはTcpFailureDetectorの上流になければなりません。 チャネルインターセプターパイプラインのTcpFailureDetectorの前にリストされるようにEncryptInterceptorを再設定してください。
fragmentationInterceptor.fragments.missing=フラグメントが見つかりません。
fragmentationInterceptor.heartbeat.failed=fragmentationInterceptorでハートビートクリーンアップを実行できません。
gzipInterceptor.compress.failed=バイトデータを圧縮できません。
gzipInterceptor.decompress.failed=圧縮されたバイトデータを展開できません。
messageDispatchInterceptor.AsyncMessage.failed=非同期メッセージの処理中にエラーが発生しました。
messageDispatchInterceptor.completeMessage.failed=完了したメッセージを報告できません。
messageDispatchInterceptor.errorMessage.failed=エラーメッセージを返すことができません。
messageDispatchInterceptor.queue.full=非同期キューが満杯です。現在は [{1}] バイトで上限の [{0}] バイトに達しています。
messageDispatchInterceptor.unableAdd.queue=非同期キューにメッセージを登録できませんでした。キューの不具合かもしれません。
messageDispatchInterceptor.warning.optionflag=警告です。非同期オプションフラグを上書きしたため、他のアプリケーションが使用する可能性のある Channel.SEND_OPTIONS_ASYNCHRONOUS は無効化されます。
nonBlockingCoordinator.electionMessage.sendfailed=メンバー [{0}] に調停メッセージを送信できません。
nonBlockingCoordinator.heartbeat.failed=ハートビートを実行できません。
nonBlockingCoordinator.heartbeat.inconsistency=ハートビートが不一致を発見し、イレクションを再開します。
nonBlockingCoordinator.memberAdded.failed=メンバーが追加されたときにイレクションを開始できません。
nonBlockingCoordinator.memberAlive.failed=動作チェックが実行できなかったため、メンバーは停止しているものとして扱います。
nonBlockingCoordinator.memberDisappeared.failed=メンバーが削除されたときにイレクションを開始できません。
nonBlockingCoordinator.processCoordinationMessage.failed=調停メッセージを処理できませんでした。致命的な問題が発生している可能性があります。
orderInterceptor.messageAdded.sameCounter=同じカウンタにメッセージが追加されました。同期バグがあります。 Order インターセプタを無効にして下さい。
staticMembershipInterceptor.no.failureDetector=TcpFailureDetector がありません。静的メンバーの自動検出機能は正常に動作しません。TcpFailureDetector 配下に StaticMembershipInterceptor を定義すれば、静的メンバーの自動検出機能が動作するでしょう。
staticMembershipInterceptor.no.pingInterceptor=TcpPingInterceptorが存在しないため、静的メンバーのヘルスチェックは正常に機能しません。TcpPingInterceptorを定義すれば機能するでしょう。
staticMembershipInterceptor.sendLocalMember.failed=ローカルメンバーの通知は失敗しました。
staticMembershipInterceptor.sendShutdown.failed=シャットダウン通知が失敗しました
tcpFailureDetector.already.disappeared=検証完了。メンバーはすでに離脱していることを確認しました [{0}]
tcpFailureDetector.failureDetection.failed=故障検出チェックが実行できないため、メンバーが停止しているものとして扱います。
tcpFailureDetector.heartbeat.failed=TcpFailureDetector のハートビートチェックができませんでした。
tcpFailureDetector.member.disappeared=メンバ検証が完了しました。 メンバーが消えました[{0}]
tcpFailureDetector.memberDisappeared.verify=memberDisappeared[{0}]メッセージを受信しました。 メンバ検証します。
tcpFailureDetector.performBasicCheck.memberAdded=私たちに通知されなかったにもかかわらず、メンバーが追加されました:[{0}]
tcpFailureDetector.still.alive=故障検出チェックが完了しました。メンバー [{0}] は正常です。
tcpFailureDetector.suspectMember.alive=疑わしいクラスタメンバーの生存を確認しました。 [{0}]
tcpFailureDetector.suspectMember.dead=疑義メンバが死亡したことが確認されました。[{0}]
tcpPingInterceptor.ping.failed=TCP の ping メッセージを送信できませんでした。
tcpPingInterceptor.pingFailed.pingThread=TCP pingスレッドからpingを送信できません。
throughputInterceptor.report=ThroughputInterceptor Report[\n\
\ 送信メッセージ (Tx Msg):{0} messages\n\
\ 送信済み (Sent):{1} MB (total)\n\
\ 送信済み (Sent):{2} MB (application)\n\
\ 時間 (Time):{3} seconds\n\
\ 送信速度 (Tx Speed):{4} MB/sec (total)\n\
\ 送信速度 (Tx Speed):{5} MB/sec (application)\n\
\ エラーメッセージ (Error Msg):{6}\n\
\ 受信メッセージ (Rx Msg):{7} messages\n\
\ 受信速度 (Rx Speed):{8} MB/sec (since 1st msg)\n\
\ 受信済み (Received):{9} MB]
twoPhaseCommitInterceptor.heartbeat.failed=TwoPhaseCommit インターセプターのハートビートが失敗しました。
twoPhaseCommitInterceptor.originalMessage.missing=確認を受信しましたが、元のメッセージがありません。 Id[{0}]

View File

@@ -0,0 +1,83 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
domainFilterInterceptor.member.refused=멤버 [{0}]이(가) 클러스터에 참가하는 것이 거부되었습니다.
domainFilterInterceptor.message.refused=클러스터 [{0}](으)로부터 받은 메시지가 거부되었습니다.
encryptInterceptor.algorithm.required=암호화 알고리즘을 완전하게 지정해야 합니다. 예) AES/CBC/PKCS5Padding.
encryptInterceptor.algorithm.unsupported-mode=EncryptInterceptor가 블록 cipher 모드 [{0}]을(를) 지원하지 않습니다.
encryptInterceptor.decrypt.error.short-message=메시지를 해독하지 못했습니다: 메시지가 너무 일찍 끝났습니다 (premature end-of-message).
encryptInterceptor.decrypt.failed=메시지를 해독하지 못했습니다.
encryptInterceptor.encrypt.failed=메시지를 암호화하지 못했습니다.
encryptInterceptor.init.failed=EncryptInterceptor를 초기화하지 못했습니다.
encryptInterceptor.key.required=암호화 키가 필수적입니다.
encryptInterceptor.tcpFailureDetector.ordering=EncryptInterceptor는 반드시 TcpFailureDetector 보다 먼저 위치해야 합니다. 채널 인터셉터 파이프라인 내에서, EncryptInterceptor가 TcpFailureDetector 보다 먼저 위치하도록 조정하십시오.
fragmentationInterceptor.fragments.missing=Fragment들이 없습니다.
fragmentationInterceptor.heartbeat.failed=FragmentationInterceptor에서 heartbeat를 clean up 할 수 없습니다.
gzipInterceptor.compress.failed=바이트 컨텐트들을 압축할 수 없습니다.
gzipInterceptor.decompress.failed=바이트 컨텐트들의 압축을 풀 수 없습니다.
messageDispatchInterceptor.AsyncMessage.failed=비동기 메시지를 처리하는 중 오류 발생
messageDispatchInterceptor.completeMessage.failed=완료된 메시지를 되돌려 보고할 수 없습니다.
messageDispatchInterceptor.errorMessage.failed=오류 메시지를 되돌려 보고할 수 없습니다.
messageDispatchInterceptor.queue.full=비동기 큐가 꽉 차서 한계값인 [{0}] 바이트에 도달했습니다. 현재 값: [{1}] 바이트.
messageDispatchInterceptor.unableAdd.queue=비동기 큐에 메시지를 추가할 수 없습니다. 큐의 버그일까요?
messageDispatchInterceptor.warning.optionflag=경고: 귀하는 비동기 옵션 플래그를 오버라이드하고 있는데, 이는 다른 애플리케이션들이 사용할 수도 있는 Channel.SEND_OPTIONS_ASYNCHRONOUS 옵션을 사용 불능 상태로 만들 것입니다.
nonBlockingCoordinator.electionMessage.sendfailed=Election 메시지를 [{0}]에 보낼 수 없습니다.
nonBlockingCoordinator.heartbeat.failed=Heartbeat를 수행할 수 없습니다.
nonBlockingCoordinator.heartbeat.inconsistency=Heartbeat가 일관되지 않은 상태로 발견되었습니다. Election을 다시 시작합니다.
nonBlockingCoordinator.memberAdded.failed=멤버가 추가되었을 때, election을 시작할 수 없었습니다.
nonBlockingCoordinator.memberAlive.failed=멤버가 살아있는지 점검할 수 없습니다. 아마도 해당 멤버가 다운된 것 같습니다.
nonBlockingCoordinator.memberDisappeared.failed=멤버가 제거되었을 때, election을 시작할 수 없었습니다.
nonBlockingCoordinator.processCoordinationMessage.failed=CoordinationMessage 처리 중 오류 발생. 치명적인 오류일 수 있습니다.
orderInterceptor.messageAdded.sameCounter=추가된 메시지가 동일한 카운터를 가지고 있습니다. 동기화 결함입니다. OrderInterceptor를 사용불능 상태로 설정하십시오.
staticMembershipInterceptor.no.failureDetector=TcpFailureDetector가 없습니다. 정적 멤버들에 대한 자동 탐지가 정상 동작하지 않을 것입니다. TcpFailureDetector 아래에 StaticMembershipInterceptor를 정의하게 되면, 정적 멤버들에 대한 자동 탐지가 정상 동작할 것입니다.
staticMembershipInterceptor.no.pingInterceptor=TcpPingInterceptor가 존재하지 않습니다. 정적 멤버들에 대한 heath check는 제대로 동작하지 않을 것입니다. TcpPingInterceptor를 정의함으로써, 정적 멤버들에 대한 health check가 정상 동작할 것입니다.
staticMembershipInterceptor.sendLocalMember.failed=로컬 멤버 통지 실패
staticMembershipInterceptor.sendShutdown.failed=시스템을 셧다운하기 위한 통지가 실패했습니다.
tcpFailureDetector.already.disappeared=검증 완료. 멤버가 이미 사라졌습니다: [{0}]
tcpFailureDetector.failureDetection.failed=멤버에 대한 실패 탐지 점검을 수행할 수 없습니다. 아마도 해당 멤버 [{0}]이(가) 다운된 것 같습니다.
tcpFailureDetector.heartbeat.failed=TcpFailureDetector에서 heartbeat 점검을 수행할 수 없습니다.
tcpFailureDetector.member.disappeared=검증 완료. 멤버가 사라졌습니다: [{0}]
tcpFailureDetector.memberDisappeared.verify=멤버 사라짐 메시지를 받았습니다: [{0}]. 이를 검증할 것입니다.
tcpFailureDetector.performBasicCheck.memberAdded=통지 받지는 못했지만, 멤버가 추가되었습니다: [{0}]
tcpFailureDetector.still.alive=검증 완료. 멤버가 아직 살아 있습니다: [{0}]
tcpFailureDetector.suspectMember.alive=의심 멤버 서버가 살아 있음을 확인했습니다. [{0}]
tcpFailureDetector.suspectMember.dead=의심 멤버가 다운된 것으로 확인됨: [{0}]
tcpPingInterceptor.ping.failed=TCP ping을 보낼 수 없습니다.
tcpPingInterceptor.pingFailed.pingThread=TCP ping 쓰레드로부터, ping을 전송할 수 없습니다.
throughputInterceptor.report=ThroughputInterceptor의 보고 [\n\
\tTx Msg:{0} 메시지(들)\n\
\tSent:{1} MB (전체)\n\
\tSent:{2} MB (애플리케이션)\n\
\tTime:{3} 초\n\
\tTx Speed:{4} MB/sec (전체)\n\
\tTx Speed:{5} MB/sec (애플리케이션)\n\
\tError Msg:{6}\n\
\tRx Msg:{7} 메시지\n\
\tRx Speed:{8} MB/sec (첫번째 메시지 이후로)\n\
\tReceived:{9} MB]\n\
\n
twoPhaseCommitInterceptor.heartbeat.failed=해당 TwoPhaseCommit 인터셉터에서 heartbeat를 수행할 수 없습니다.
twoPhaseCommitInterceptor.originalMessage.missing=확인 플래그를 받았지만, 원본 메시지가 없습니다. ID:[{0}]

View File

@@ -0,0 +1,22 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
encryptInterceptor.decrypt.error.short-message=Невозможно расшифровать сообщение: слишком мало символов
nonBlockingCoordinator.memberAlive.failed=Невозможно проверить участника, считаем что упал
staticMembershipInterceptor.sendShutdown.failed=Не удалось сообщить об отключении.
tcpFailureDetector.still.alive=Проверка завершена. Участник ещё жив [{0}]

View File

@@ -0,0 +1,60 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
domainFilterInterceptor.member.refused=成员被拒绝加入集群 cluster[{0}]
domainFilterInterceptor.message.refused=从集群[{0}]中接收的消息被拒绝
encryptInterceptor.decrypt.error.short-message=解密消息失败: 结尾消息提前结束
encryptInterceptor.decrypt.failed=无法解密信息
encryptInterceptor.encrypt.failed=无法加密信息
encryptInterceptor.init.failed=初始化EncryptInterceptor失败
encryptInterceptor.tcpFailureDetector.ordering=加密拦截器必须位于TCP故障检测器的上游。请重新订购加密拦截器将其列在通道拦截器管道中的TCP故障检测器之前。
messageDispatchInterceptor.errorMessage.failed=无法回传错误信息
messageDispatchInterceptor.queue.full=异步队列已满,达到 [{0}] 字节的限制,当前:[{1}] 字节
messageDispatchInterceptor.unableAdd.queue=无法将消息添加到异步队列,队列 bug
messageDispatchInterceptor.warning.optionflag=警告!你正在覆盖异步选项标志,这将禁用其它程序可能用到的 Channel.SEND_OPTIONS_ASYNCHRONOUS。
nonBlockingCoordinator.heartbeat.inconsistency=心跳发现不一致,重新启动选举
nonBlockingCoordinator.memberAlive.failed=无法执行成员活动检查,猜测成员下线。
nonBlockingCoordinator.processCoordinationMessage.failed=处理协调消息时出错。 可能是致命的。
staticMembershipInterceptor.no.failureDetector=没有TcpFailureDetector。 自动检测静态成员无法正常工作。 通过在TcpFailureDetector下定义StaticMembershipInterceptor可以自动检测静态成员。
staticMembershipInterceptor.no.pingInterceptor=在没有TcpPingInterceptor的情况下静态成员的健康检查不会正常工作。只有定义了TcpPingInterceptor才能使健康检查正常进行。
staticMembershipInterceptor.sendShutdown.failed=关闭通知失败。
tcpFailureDetector.failureDetection.failed=无法进行失败监测,假定成员宕机。[{0}]
tcpFailureDetector.heartbeat.failed=TCP心跳检测器无法执行心跳
tcpFailureDetector.member.disappeared=认证完成。成员消失[{0}]
tcpFailureDetector.memberDisappeared.verify=(:收到的membermissed[{0}]消息。将验证。
tcpFailureDetector.still.alive=验证完成。成员 [{0}] 仍然存活
tcpFailureDetector.suspectMember.alive=验证可疑成员服务器还活着。[{0}]
tcpPingInterceptor.ping.failed=无法发送 TCP ping
tcpPingInterceptor.pingFailed.pingThread=不能从ping 线程发送ping
throughputInterceptor.report=吞吐量拦截器 报告[\n\
\ 传输消息: {0} 消息数.\n\
\ 发送: {1} MB(总共)\n\
\ 发送: {2} MB (应用)\n\
\ 耗时: {3} 秒\n\
\ 传输速率: {4}MB/sec (总共)\n\
\ 传输速率: {5}MB/sec (应用)\n\
\ 错误消息: {6}\n\
\ 接收消息: {7} 消息数\n\
\ 接收速率: {8} MB/sec (从第一个消息开始)\n\
\ 收到: {9}MB]
twoPhaseCommitInterceptor.heartbeat.failed=无法在两阶段提交拦截器上执行心跳。

View File

@@ -0,0 +1,335 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.ChannelException;
import org.apache.catalina.tribes.ChannelMessage;
import org.apache.catalina.tribes.ErrorHandler;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.UniqueId;
import org.apache.catalina.tribes.group.ChannelInterceptorBase;
import org.apache.catalina.tribes.group.InterceptorPayload;
import org.apache.catalina.tribes.util.ExecutorFactory;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.catalina.tribes.util.TcclThreadFactory;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* 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.
*/
public class MessageDispatchInterceptor extends ChannelInterceptorBase
implements MessageDispatchInterceptorMBean {
private static final Log log = LogFactory.getLog(MessageDispatchInterceptor.class);
protected static final StringManager sm =
StringManager.getManager(MessageDispatchInterceptor.class);
protected long maxQueueSize = 1024*1024*64; //64MB
protected volatile boolean run = false;
protected boolean useDeepClone = true;
protected boolean alwaysSend = true;
protected final AtomicLong currentSize = new AtomicLong(0);
protected ExecutorService executor = null;
protected int maxThreads = 10;
protected int maxSpareThreads = 2;
protected long keepAliveTime = 5000;
public MessageDispatchInterceptor() {
setOptionFlag(Channel.SEND_OPTIONS_ASYNCHRONOUS);
}
@Override
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(sm.getString("messageDispatchInterceptor.queue.full",
Long.toString(maxQueueSize), Long.toString(getCurrentSize())));
}
}
//add to queue
if (useDeepClone) {
msg = (ChannelMessage)msg.deepclone();
}
if (!addToQueue(msg, destination, payload)) {
throw new ChannelException(
sm.getString("messageDispatchInterceptor.unableAdd.queue"));
}
addAndGetCurrentSize(msg.getMessage().getLength());
} else {
super.sendMessage(destination, msg, payload);
}
}
public boolean addToQueue(final ChannelMessage msg, final Member[] destination,
final InterceptorPayload payload) {
Runnable r = new Runnable() {
@Override
public void run() {
sendAsyncData(msg, destination, payload);
}
};
executor.execute(r);
return true;
}
public void startQueue() {
if (run) {
return;
}
String channelName = "";
if (getChannel().getName() != null) channelName = "[" + getChannel().getName() + "]";
executor = ExecutorFactory.newThreadPool(maxSpareThreads, maxThreads, keepAliveTime,
TimeUnit.MILLISECONDS,
new TcclThreadFactory("MessageDispatchInterceptor.MessageDispatchThread" + channelName));
run = true;
}
public void stopQueue() {
run = false;
executor.shutdownNow();
setAndGetCurrentSize(0);
}
@Override
public void setOptionFlag(int flag) {
if ( flag != Channel.SEND_OPTIONS_ASYNCHRONOUS ) {
log.warn(sm.getString("messageDispatchInterceptor.warning.optionflag"));
}
super.setOptionFlag(flag);
}
public void setMaxQueueSize(long maxQueueSize) {
this.maxQueueSize = maxQueueSize;
}
public void setUseDeepClone(boolean useDeepClone) {
this.useDeepClone = useDeepClone;
}
@Override
public long getMaxQueueSize() {
return maxQueueSize;
}
public boolean getUseDeepClone() {
return useDeepClone;
}
@Override
public long getCurrentSize() {
return currentSize.get();
}
public long addAndGetCurrentSize(long inc) {
return currentSize.addAndGet(inc);
}
public long setAndGetCurrentSize(long value) {
currentSize.set(value);
return value;
}
@Override
public long getKeepAliveTime() {
return keepAliveTime;
}
@Override
public int getMaxSpareThreads() {
return maxSpareThreads;
}
@Override
public int getMaxThreads() {
return maxThreads;
}
public void setKeepAliveTime(long keepAliveTime) {
this.keepAliveTime = keepAliveTime;
}
public void setMaxSpareThreads(int maxSpareThreads) {
this.maxSpareThreads = maxSpareThreads;
}
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
}
@Override
public boolean isAlwaysSend() {
return alwaysSend;
}
@Override
public void setAlwaysSend(boolean alwaysSend) {
this.alwaysSend = alwaysSend;
}
@Override
public void start(int svc) throws ChannelException {
//start the thread
if (!run ) {
synchronized (this) {
// only start with the sender
if ( !run && ((svc & Channel.SND_TX_SEQ)==Channel.SND_TX_SEQ) ) {
startQueue();
}
}
}
super.start(svc);
}
@Override
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();
}
}
}
super.stop(svc);
}
protected void sendAsyncData(ChannelMessage msg, Member[] destination,
InterceptorPayload payload) {
ErrorHandler handler = null;
if (payload != null) {
handler = payload.getErrorHandler();
}
try {
super.sendMessage(destination, msg, null);
try {
if (handler != null) {
handler.handleCompletion(new UniqueId(msg.getUniqueId()));
}
} catch ( Exception ex ) {
log.error(sm.getString("messageDispatchInterceptor.completeMessage.failed"),ex);
}
} catch ( Exception x ) {
ChannelException cx = null;
if (x instanceof ChannelException) {
cx = (ChannelException) x;
} else {
cx = new ChannelException(x);
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("messageDispatchInterceptor.AsyncMessage.failed"),x);
}
try {
if (handler != null) {
handler.handleError(cx, new UniqueId(msg.getUniqueId()));
}
} catch ( Exception ex ) {
log.error(sm.getString("messageDispatchInterceptor.errorMessage.failed"),ex);
}
} finally {
addAndGetCurrentSize(-msg.getMessage().getLength());
}
}
// ---------------------------------------------- stats of the thread pool
/**
* Return the current number of threads that are managed by the pool.
* @return the current number of threads that are managed by the pool
*/
@Override
public int getPoolSize() {
if (executor instanceof ThreadPoolExecutor) {
return ((ThreadPoolExecutor) executor).getPoolSize();
} else {
return -1;
}
}
/**
* Return the current number of threads that are in use.
* @return the current number of threads that are in use
*/
@Override
public int getActiveCount() {
if (executor instanceof ThreadPoolExecutor) {
return ((ThreadPoolExecutor) executor).getActiveCount();
} else {
return -1;
}
}
/**
* Return the total number of tasks that have ever been scheduled for execution by the pool.
* @return the total number of tasks that have ever been scheduled for execution by the pool
*/
@Override
public long getTaskCount() {
if (executor instanceof ThreadPoolExecutor) {
return ((ThreadPoolExecutor) executor).getTaskCount();
} else {
return -1;
}
}
/**
* Return the total number of tasks that have completed execution by the pool.
* @return the total number of tasks that have completed execution by the pool
*/
@Override
public long getCompletedTaskCount() {
if (executor instanceof ThreadPoolExecutor) {
return ((ThreadPoolExecutor) executor).getCompletedTaskCount();
} else {
return -1;
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
public interface MessageDispatchInterceptorMBean {
public int getOptionFlag();
public boolean isAlwaysSend();
public void setAlwaysSend(boolean alwaysSend);
public long getMaxQueueSize();
public long getCurrentSize();
public long getKeepAliveTime();
public int getMaxSpareThreads();
public int getMaxThreads();
// pool stats
public int getPoolSize();
public int getActiveCount();
public long getTaskCount();
public long getCompletedTaskCount();
}

View File

@@ -0,0 +1,848 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
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.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.StringManager;
import org.apache.catalina.tribes.util.UUIDGenerator;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* <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 cannot 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="https://people.apache.org/~fhanik/tribes/docs/leader-election-initiate-election.jpg">Initiate an election</a><br><br>
* <a href="https://people.apache.org/~fhanik/tribes/docs/leader-election-message-arrives.jpg">Receive an election message</a><br><br>
*
* @version 1.0
*
*
*
*/
public class NonBlockingCoordinator extends ChannelInterceptorBase {
private static final Log log = LogFactory.getLog(NonBlockingCoordinator.class);
protected static final StringManager sm = StringManager.getManager(NonBlockingCoordinator.class);
/**
* 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 final long waitForCoordMsgTimeout = 15000;
/**
* Our current view
*/
protected volatile 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 volatile Membership suggestedView;
protected volatile boolean started = false;
protected final int startsvc = 0xFFFF;
protected final Object electionMutex = new Object();
protected final AtomicBoolean coordMsgReceived = new AtomicBoolean(false);
public NonBlockingCoordinator() {
super();
}
//============================================================================================================
// COORDINATION HANDLING
//============================================================================================================
public void startElection(boolean force) throws ChannelException {
synchronized (electionMutex) {
Member local = getLocalMember(false);
Member[] others = membership.getMembers();
fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START_ELECT,this,"Election initiated"));
if ( others.length == 0 ) {
this.viewId = new UniqueId(UUIDGenerator.randomUUID(false));
this.view = new Membership(local,AbsoluteOrder.comp, true);
this.handleViewConf(createElectionMsg(local,others,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]);
Member 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().interrupt();
}
String msg;
if (suggestedviewId == null && !coordMsgReceived.get()) {
if (Thread.interrupted()) {
msg = "Election abandoned, waiting interrupted.";
} else {
msg = "Election abandoned, waiting timed out.";
}
} else {
msg = "Election abandoned, received a message";
}
fireInterceptorEvent(new CoordinationEvent(
CoordinationEvent.EVT_ELECT_ABANDONED, this, msg));
}
}
}
private CoordinationMessage createElectionMsg(Member local, Member[] others, Member leader) {
Membership m = new Membership(local,AbsoluteOrder.comp,true);
Arrays.fill(m,others);
Member[] mbrs = m.getMembers();
m.reset();
CoordinationMessage msg = new CoordinationMessage(leader, local, mbrs,new UniqueId(UUIDGenerator.randomUUID(true)), COORD_REQUEST);
return msg;
}
protected void sendElectionMsg(Member local, Member 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(Member 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, msg.getMembers()[current], msg);
sent = true;
}catch ( ChannelException x ) {
log.warn(sm.getString("nonBlockingCoordinator.electionMessage.sendfailed", msg.getMembers()[current]));
current = Arrays.nextIndex(msg.getMembers()[current],msg.getMembers());
if ( current == next ) throw x;
}
}
}
public ChannelData createData(CoordinationMessage msg, Member 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 boolean alive(Member mbr) {
return memberAlive(mbr, waitForCoordMsgTimeout);
}
protected boolean memberAlive(Member mbr, long conTimeout) {
//could be a shutdown notification
if ( Arrays.equals(mbr.getCommand(),Member.SHUTDOWN_PAYLOAD) ) return false;
try (Socket socket = new Socket()) {
InetAddress ia = InetAddress.getByAddress(mbr.getHost());
InetSocketAddress addr = new InetSocketAddress(ia, mbr.getPort());
socket.connect(addr, (int) conTimeout);
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(sm.getString("nonBlockingCoordinator.memberAlive.failed"),x);
}
return false;
}
protected Membership mergeOnArrive(CoordinationMessage msg) {
fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_PRE_MERGE,this,"Pre merge"));
Member local = 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(diff[i]);
else memberAdded(diff[i],false);
}
fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_POST_MERGE,this,"Post merge"));
return merged;
}
protected void processCoordMessage(CoordinationMessage msg) throws ChannelException {
if ( !coordMsgReceived.get() ) {
coordMsgReceived.set(true);
synchronized (electionMutex) { electionMutex.notifyAll();}
}
Membership merged = mergeOnArrive(msg);
if (isViewConf(msg)) handleViewConf(msg, merged);
else handleToken(msg, merged);
}
protected void handleToken(CoordinationMessage msg, Membership merged) throws ChannelException {
Member local = getLocalMember(false);
if ( local.equals(msg.getSource()) ) {
//my message msg.src=local
handleMyToken(local, msg, merged);
} else {
handleOtherToken(local, msg, merged);
}
}
protected void handleMyToken(Member local, CoordinationMessage msg, 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, merged);
} else {
//membership change
suggestedView = new Membership(local,AbsoluteOrder.comp,true);
suggestedviewId = msg.getId();
Arrays.fill(suggestedView,merged.getMembers());
msg.view = merged.getMembers();
sendElectionMsgToNextInline(local,msg);
}
} else {
//leadership change
suggestedView = null;
suggestedviewId = null;
msg.view = merged.getMembers();
sendElectionMsgToNextInline(local,msg);
}
}
protected void handleOtherToken(Member local, CoordinationMessage msg, Membership merged) throws ChannelException {
if ( local.equals(msg.getLeader()) ) {
//I am the new leader
//startElection(false);
} else {
msg.view = merged.getMembers();
sendElectionMsgToNextInline(local,msg);
}
}
protected void handleViewConf(CoordinationMessage msg, Membership merged) throws ChannelException {
if ( viewId != null && msg.getId().equals(viewId) ) return;//we already have this view
view = new Membership(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;
}
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
//============================================================================================================
@Override
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(super.getLocalMember(true), AbsoluteOrder.comp, true);
fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START, this, "After start"));
startElection(false);
}
@Override
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();
}
}
@Override
public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
waitForRelease();
super.sendMessage(destination, msg, payload);
}
@Override
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);
}catch ( ChannelException x ) {
log.error(sm.getString("nonBlockingCoordinator.processCoordinationMessage.failed"),x);
}
} else {
super.messageReceived(msg);
}
}
@Override
public void memberAdded(Member member) {
memberAdded(member,true);
}
public void memberAdded(Member member,boolean elect) {
if (membership == null) setupMembership();
if (membership.memberAlive(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(sm.getString("nonBlockingCoordinator.memberAdded.failed"),x);
}
}
@Override
public void memberDisappeared(Member member) {
membership.removeMember(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(sm.getString("nonBlockingCoordinator.memberDisappeared.failed"),x);
}
}
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);
}
@Override
public void heartbeat() {
try {
Member local = 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,
sm.getString("nonBlockingCoordinator.heartbeat.inconsistency")));
startElection(true);
}
}
} catch ( Exception x ){
log.error(sm.getString("nonBlockingCoordinator.heartbeat.failed"),x);
} finally {
super.heartbeat();
}
}
/**
* has members
*/
@Override
public boolean hasMembers() {
return membership.hasMembers();
}
/**
* Get all current cluster members
* @return all members or empty array
*/
@Override
public Member[] getMembers() {
return membership.getMembers();
}
/**
*
* @param mbr Member
* @return Member
*/
@Override
public Member getMember(Member mbr) {
return membership.getMember(mbr);
}
/**
* Return the member that represents this node.
*
* @return Member
*/
@Override
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(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 final XByteBuffer buf;
protected Member leader;
protected Member source;
protected Member[] view;
protected UniqueId id;
protected byte[] type;
public CoordinationMessage(XByteBuffer buf) {
this.buf = buf;
parse();
}
public CoordinationMessage(Member leader,
Member source,
Member[] 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 Member getLeader() {
if ( leader == null ) parse();
return leader;
}
public Member getSource() {
if ( source == null ) parse();
return source;
}
public UniqueId getId() {
if ( id == null ) parse();
return id;
}
public Member[] 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 = XByteBuffer.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 = XByteBuffer.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 = XByteBuffer.toInt(buf.getBytesDirect(),offset);
offset += 4;
view = new Member[mbrCount];
for (int i=0; i<view.length; i++ ) {
int mbrLen = XByteBuffer.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);
}
}
@Override
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;
final int type;
final ChannelInterceptor interceptor;
final Member coord;
final Member[] mbrs;
final String info;
final Membership view;
final 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;
}
@Override
public int getEventType() {
return type;
}
@Override
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";
}
}
@Override
public ChannelInterceptor getInterceptor() {
return interceptor;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder("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();
}
}
}

View File

@@ -0,0 +1,334 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
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;
import org.apache.catalina.tribes.util.StringManager;
/**
*
* 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>
* OrderInterceptor.expire=&lt;milliseconds&gt; - if a message arrives out of order, how long before we act on it <b>default=3000ms</b><br>
* OrderInterceptor.maxQueue=&lt;max queue size&gt; - 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=&lt;boolean&gt; - 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>
*
*
* @version 1.1
*/
public class OrderInterceptor extends ChannelInterceptorBase {
protected static final StringManager sm = StringManager.getManager(OrderInterceptor.class);
private final HashMap<Member, Counter> outcounter = new HashMap<>();
private final HashMap<Member, Counter> incounter = new HashMap<>();
private final HashMap<Member, MessageOrder> incoming = new HashMap<>();
private long expire = 3000;
private boolean forwardExpired = true;
private int maxQueue = Integer.MAX_VALUE;
final ReentrantReadWriteLock inLock = new ReentrantReadWriteLock(true);
final ReentrantReadWriteLock outLock= new ReentrantReadWriteLock(true);
@Override
public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
if ( !okToProcess(msg.getOptions()) ) {
super.sendMessage(destination, msg, payload);
return;
}
ChannelException cx = null;
for (int i=0; i<destination.length; i++ ) {
try {
int nr = 0;
outLock.writeLock().lock();
try {
nr = incCounter(destination[i]);
} finally {
outLock.writeLock().unlock();
}
//reduce byte copy
msg.getMessage().append(nr);
try {
getNext().sendMessage(new Member[] {destination[i]}, msg, payload);
} finally {
msg.getMessage().trim(4);
}
}catch ( ChannelException x ) {
if ( cx == null ) cx = x;
cx.addFaultyMember(x.getFaultyMembers());
}
}//for
if ( cx != null ) throw cx;
}
@Override
public void messageReceived(ChannelMessage msg) {
if ( !okToProcess(msg.getOptions()) ) {
super.messageReceived(msg);
return;
}
int msgnr = XByteBuffer.toInt(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-4);
msg.getMessage().trim(4);
MessageOrder order = new MessageOrder(msgnr,(ChannelMessage)msg.deepclone());
inLock.writeLock().lock();
try {
if ( processIncoming(order) ) processLeftOvers(msg.getAddress(),false);
} finally {
inLock.writeLock().unlock();
}
}
protected void processLeftOvers(Member member, boolean force) {
MessageOrder tmp = 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
*/
protected boolean processIncoming(MessageOrder order) {
boolean result = false;
Member member = order.getMessage().getAddress();
Counter cnt = getInCounter(member);
MessageOrder tmp = 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;
}
@Override
public void memberAdded(Member member) {
//notify upwards
super.memberAdded(member);
}
@Override
public void memberDisappeared(Member member) {
//reset counters - lock free
incounter.remove(member);
outcounter.remove(member);
//clear the remaining queue
processLeftOvers(member,true);
//notify upwards
super.memberDisappeared(member);
}
protected int incCounter(Member mbr) {
Counter cnt = getOutCounter(mbr);
return cnt.inc();
}
protected Counter getInCounter(Member mbr) {
Counter cnt = incounter.get(mbr);
if ( cnt == null ) {
cnt = new Counter();
cnt.inc(); //always start at 1 for incoming
incounter.put(mbr,cnt);
}
return cnt;
}
protected Counter getOutCounter(Member mbr) {
Counter cnt = outcounter.get(mbr);
if ( cnt == null ) {
cnt = new Counter();
outcounter.put(mbr,cnt);
}
return cnt;
}
protected static class Counter {
private final AtomicInteger value = new AtomicInteger(0);
public int getCounter() {
return value.get();
}
public void setCounter(int counter) {
this.value.set(counter);
}
public int inc() {
return value.addAndGet(1);
}
}
protected static class MessageOrder {
private final long received = System.currentTimeMillis();
private MessageOrder next;
private final 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;
}
@SuppressWarnings("null") // prev cannot be null
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; // prev cannot be null here, warning suppressed
add.next = iter;
} else {
throw new ArithmeticException(sm.getString("orderInterceptor.messageAdded.sameCounter"));
}
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;
}
}

View File

@@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.catalina.tribes.ChannelException;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.group.AbsoluteOrder;
import org.apache.catalina.tribes.group.ChannelInterceptorBase;
/**
* A dinky coordinator, just uses a sorted version of the member array.
*
* @author rnewson
*
*/
public class SimpleCoordinator extends ChannelInterceptorBase {
private Member[] view;
private final AtomicBoolean membershipChanged = new AtomicBoolean();
private void membershipChanged() {
membershipChanged.set(true);
}
@Override
public void memberAdded(final Member member) {
super.memberAdded(member);
membershipChanged();
installViewWhenStable();
}
@Override
public void memberDisappeared(final Member member) {
super.memberDisappeared(member);
membershipChanged();
installViewWhenStable();
}
/**
* Override to receive view changes.
*
* @param view The members array
*/
protected void viewChange(final Member[] view) {
}
@Override
public void start(int svc) throws ChannelException {
super.start(svc);
installViewWhenStable();
}
private void installViewWhenStable() {
int stableCount = 0;
while (stableCount < 10) {
if (membershipChanged.compareAndSet(true, false)) {
stableCount = 0;
} else {
stableCount++;
}
try {
TimeUnit.MILLISECONDS.sleep(250);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
}
final Member[] members = getMembers();
final Member[] view = new Member[members.length+1];
System.arraycopy(members, 0, view, 0, members.length);
view[members.length] = getLocalMember(false);
Arrays.sort(view, AbsoluteOrder.comp);
if (Arrays.equals(view, this.view)) {
return;
}
this.view = view;
viewChange(view);
}
@Override
public void stop(int svc) throws ChannelException {
super.stop(svc);
}
public Member[] getView() {
return view;
}
public Member getCoordinator() {
return view == null ? null : view[0];
}
public boolean isCoordinator() {
return view == null ? false : getLocalMember(false).equals(
getCoordinator());
}
}

View File

@@ -0,0 +1,238 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.ArrayList;
import java.util.Arrays;
import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.ChannelException;
import org.apache.catalina.tribes.ChannelInterceptor;
import org.apache.catalina.tribes.ChannelMessage;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.group.AbsoluteOrder;
import org.apache.catalina.tribes.group.ChannelInterceptorBase;
import org.apache.catalina.tribes.io.ChannelData;
import org.apache.catalina.tribes.io.XByteBuffer;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
public class StaticMembershipInterceptor extends ChannelInterceptorBase
implements StaticMembershipInterceptorMBean {
private static final Log log = LogFactory.getLog(StaticMembershipInterceptor.class);
protected static final StringManager sm =
StringManager.getManager(StaticMembershipInterceptor.class);
protected static final byte[] MEMBER_START = new byte[] {
76, 111, 99, 97, 108, 32, 83, 116, 97, 116, 105, 99, 77, 101, 109, 98, 101, 114, 32, 78,
111, 116, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 68, 97, 116, 97};
protected static final byte[] MEMBER_STOP = new byte[] {
76, 111, 99, 97, 108, 32, 83, 116, 97, 116, 105, 99, 77, 101, 109, 98, 101, 114, 32, 83,
104, 117, 116, 100, 111, 119, 110, 32, 68, 97, 116, 97};
protected final ArrayList<Member> 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;
localMember.setLocal(true);
}
@Override
public void messageReceived(ChannelMessage msg) {
if (msg.getMessage().getLength() == MEMBER_START.length &&
Arrays.equals(MEMBER_START, msg.getMessage().getBytes())) {
// receive member start
Member member = getMember(msg.getAddress());
if (member != null) {
super.memberAdded(member);
}
} else if (msg.getMessage().getLength() == MEMBER_STOP.length &&
Arrays.equals(MEMBER_STOP, msg.getMessage().getBytes())) {
// receive member shutdown
Member member = getMember(msg.getAddress());
if (member != null) {
try {
member.setCommand(Member.SHUTDOWN_PAYLOAD);
super.memberDisappeared(member);
} finally {
member.setCommand(new byte[0]);
}
}
} else {
super.messageReceived(msg);
}
}
/**
* has members
*/
@Override
public boolean hasMembers() {
return super.hasMembers() || (members.size()>0);
}
/**
* Get all current cluster members
* @return all members or empty array
*/
@Override
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] = members.get(i);
AbsoluteOrder.absoluteOrder(result);
return result;
}//sync
}//end if
}
/**
*
* @param mbr Member
* @return Member
*/
@Override
public Member getMember(Member mbr) {
if ( members.contains(mbr) ) return members.get(members.indexOf(mbr));
else return super.getMember(mbr);
}
/**
* Return the member that represents this node.
*
* @return Member
*/
@Override
public Member getLocalMember(boolean incAlive) {
if (this.localMember != null ) return localMember;
else return super.getLocalMember(incAlive);
}
/**
* {@inheritDoc}
* <p>
* Sends notifications upwards.
*/
@Override
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 ChannelInterceptorBase base = this;
for (final Member member : members) {
Thread t = new Thread() {
@Override
public void run() {
base.memberAdded(member);
if (getfirstInterceptor().getMember(member) != null) {
sendLocalMember(new Member[]{member});
}
}
};
t.start();
}
super.start(svc & (~Channel.SND_RX_SEQ) & (~Channel.SND_TX_SEQ));
// check required interceptors
TcpFailureDetector failureDetector = null;
TcpPingInterceptor pingInterceptor = null;
ChannelInterceptor prev = getPrevious();
while (prev != null) {
if (prev instanceof TcpFailureDetector ) failureDetector = (TcpFailureDetector) prev;
if (prev instanceof TcpPingInterceptor) pingInterceptor = (TcpPingInterceptor) prev;
prev = prev.getPrevious();
}
if (failureDetector == null) {
log.warn(sm.getString("staticMembershipInterceptor.no.failureDetector"));
}
if (pingInterceptor == null) {
log.warn(sm.getString("staticMembershipInterceptor.no.pingInterceptor"));
}
}
/**
* {@inheritDoc}
* <p>
* Sends local member shutdown.
*/
@Override
public void stop(int svc) throws ChannelException {
// Sends local member shutdown.
Member[] members = getfirstInterceptor().getMembers();
sendShutdown(members);
super.stop(svc);
}
protected void sendLocalMember(Member[] members) {
try {
sendMemberMessage(members, MEMBER_START);
} catch (ChannelException cx) {
log.warn(sm.getString("staticMembershipInterceptor.sendLocalMember.failed"),cx);
}
}
protected void sendShutdown(Member[] members) {
try {
sendMemberMessage(members, MEMBER_STOP);
} catch (ChannelException cx) {
log.warn(sm.getString("staticMembershipInterceptor.sendShutdown.failed"),cx);
}
}
protected ChannelInterceptor getfirstInterceptor() {
ChannelInterceptor result = null;
ChannelInterceptor now = this;
do {
result = now;
now = now.getPrevious();
} while (now.getPrevious() != null);
return result;
}
protected void sendMemberMessage(Member[] members, byte[] message) throws ChannelException {
if ( members == null || members.length == 0 ) return;
ChannelData data = new ChannelData(true);
data.setAddress(getLocalMember(false));
data.setTimestamp(System.currentTimeMillis());
data.setOptions(getOptionFlag());
data.setMessage(new XByteBuffer(message, false));
super.sendMessage(members, data, null);
}
}

View File

@@ -0,0 +1,26 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.catalina.tribes.Member;
public interface StaticMembershipInterceptorMBean {
public int getOptionFlag();
public Member getLocalMember(boolean incAlive);
}

View File

@@ -0,0 +1,415 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
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.Membership;
import org.apache.catalina.tribes.membership.StaticMember;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* <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 &quot;timed out&quot;
* 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>
*
* @version 1.0
*/
public class TcpFailureDetector extends ChannelInterceptorBase implements TcpFailureDetectorMBean {
private static final Log log = LogFactory.getLog(TcpFailureDetector.class);
protected static final StringManager sm = StringManager.getManager(TcpFailureDetector.class);
protected static final 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 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 final HashMap<Member, Long> removeSuspects = new HashMap<>();
protected final HashMap<Member, Long> addSuspects = new HashMap<>();
protected int removeSuspectsTimeout = 300; // 5 minutes
@Override
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;
}
}
@Override
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
@Override
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(member) == null){
//if we add it here, then add it upwards too
//check to see if it is alive
if (memberAlive(member)) {
membership.memberAlive(member);
notify = true;
} else {
if (member instanceof StaticMember) {
addSuspects.put(member, Long.valueOf(System.currentTimeMillis()));
}
}
}
}
if ( notify ) super.memberAdded(member);
}
@Override
public void memberDisappeared(Member member) {
if ( membership == null ) setupMembership();
boolean shutdown = Arrays.equals(member.getCommand(),Member.SHUTDOWN_PAYLOAD);
if (shutdown) {
synchronized (membership) {
if (!membership.contains(member)) return;
membership.removeMember(member);
removeSuspects.remove(member);
if (member instanceof StaticMember) {
addSuspects.put(member, Long.valueOf(System.currentTimeMillis()));
}
}
super.memberDisappeared(member);
} else {
boolean notify = false;
if(log.isInfoEnabled())
log.info(sm.getString("tcpFailureDetector.memberDisappeared.verify", member));
synchronized (membership) {
if (!membership.contains(member)) {
if(log.isInfoEnabled())
log.info(sm.getString("tcpFailureDetector.already.disappeared", member));
return;
}
//check to see if the member really is gone
if (!memberAlive(member)) {
//not correct, we need to maintain the map
membership.removeMember(member);
removeSuspects.remove(member);
if (member instanceof StaticMember) {
addSuspects.put(member, Long.valueOf(System.currentTimeMillis()));
}
notify = true;
} else {
//add the member as suspect
removeSuspects.put(member, Long.valueOf(System.currentTimeMillis()));
}
}
if ( notify ) {
if(log.isInfoEnabled())
log.info(sm.getString("tcpFailureDetector.member.disappeared", member));
super.memberDisappeared(member);
} else {
if(log.isInfoEnabled())
log.info(sm.getString("tcpFailureDetector.still.alive", member));
}
}
}
@Override
public boolean hasMembers() {
if ( membership == null ) setupMembership();
return membership.hasMembers();
}
@Override
public Member[] getMembers() {
if ( membership == null ) setupMembership();
return membership.getMembers();
}
@Override
public Member getMember(Member mbr) {
if ( membership == null ) setupMembership();
return membership.getMember(mbr);
}
@Override
public Member getLocalMember(boolean incAlive) {
return super.getLocalMember(incAlive);
}
@Override
public void heartbeat() {
super.heartbeat();
checkMembers(false);
}
@Override
public void checkMembers(boolean checkAll) {
try {
if (membership == null) setupMembership();
synchronized (membership) {
if (!checkAll) performBasicCheck();
else performForcedCheck();
}
} catch (Exception x) {
log.warn(sm.getString("tcpFailureDetector.heartbeat.failed"),x);
}
}
protected void performForcedCheck() {
//update all alive times
Member[] members = super.getMembers();
for (int i = 0; members != null && i < members.length; i++) {
if (memberAlive(members[i])) {
if (membership.memberAlive(members[i])) super.memberAdded(members[i]);
addSuspects.remove(members[i]);
} else {
if (membership.getMember(members[i])!=null) {
membership.removeMember(members[i]);
removeSuspects.remove(members[i]);
if (members[i] instanceof StaticMember) {
addSuspects.put(members[i], Long.valueOf(System.currentTimeMillis()));
}
super.memberDisappeared(members[i]);
}
} //end if
} //for
}
protected void performBasicCheck() {
//update all alive times
Member[] members = super.getMembers();
for (int i = 0; members != null && i < members.length; i++) {
if (addSuspects.containsKey(members[i]) && membership.getMember(members[i]) == null) {
// avoid temporary adding member.
continue;
}
if (membership.memberAlive(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(sm.getString("tcpFailureDetector.performBasicCheck.memberAdded", members[i]));
super.memberAdded(members[i]);
} else {
membership.removeMember(members[i]);
} //end if
} //end if
} //for
//check suspect members if they are still alive,
//if not, simply issue the memberDisappeared message
Member[] keys = removeSuspects.keySet().toArray(new Member[removeSuspects.size()]);
for (int i = 0; i < keys.length; i++) {
Member m = keys[i];
if (membership.getMember(m) != null && (!memberAlive(m))) {
membership.removeMember(m);
if (m instanceof StaticMember) {
addSuspects.put(m, Long.valueOf(System.currentTimeMillis()));
}
super.memberDisappeared(m);
removeSuspects.remove(m);
if(log.isInfoEnabled())
log.info(sm.getString("tcpFailureDetector.suspectMember.dead", m));
} else {
if (removeSuspectsTimeout > 0) {
long timeNow = System.currentTimeMillis();
int timeIdle = (int) ((timeNow - removeSuspects.get(m).longValue()) / 1000L);
if (timeIdle > removeSuspectsTimeout) {
removeSuspects.remove(m); // remove suspect member
}
}
}
}
//check add suspects members if they are alive now,
//if they are, simply issue the memberAdded message
keys = addSuspects.keySet().toArray(new Member[addSuspects.size()]);
for (int i = 0; i < keys.length; i++) {
Member m = keys[i];
if ( membership.getMember(m) == null && (memberAlive(m))) {
membership.memberAlive(m);
super.memberAdded(m);
addSuspects.remove(m);
if(log.isInfoEnabled())
log.info(sm.getString("tcpFailureDetector.suspectMember.alive", m));
} //end if
}
}
protected synchronized void setupMembership() {
if ( membership == null ) {
membership = new Membership(super.getLocalMember(true));
}
}
protected boolean memberAlive(Member mbr) {
return memberAlive(mbr,TCP_FAIL_DETECT,performSendTest,performReadTest,readTestTimeout,connectTimeout,getOptionFlag());
}
protected 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;
try (Socket socket = new Socket()) {
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(getLocalMember(false));
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 | ConnectException | NoRouteToHostException noop) {
//do nothing, we couldn't connect
} catch (Exception x) {
log.error(sm.getString("tcpFailureDetector.failureDetection.failed", mbr),x);
}
return false;
}
@Override
public long getReadTestTimeout() {
return readTestTimeout;
}
@Override
public boolean getPerformSendTest() {
return performSendTest;
}
@Override
public boolean getPerformReadTest() {
return performReadTest;
}
@Override
public long getConnectTimeout() {
return connectTimeout;
}
@Override
public int getRemoveSuspectsTimeout() {
return removeSuspectsTimeout;
}
@Override
public void setPerformReadTest(boolean performReadTest) {
this.performReadTest = performReadTest;
}
@Override
public void setPerformSendTest(boolean performSendTest) {
this.performSendTest = performSendTest;
}
@Override
public void setReadTestTimeout(long readTestTimeout) {
this.readTestTimeout = readTestTimeout;
}
@Override
public void setConnectTimeout(long connectTimeout) {
this.connectTimeout = connectTimeout;
}
@Override
public void setRemoveSuspectsTimeout(int removeSuspectsTimeout) {
this.removeSuspectsTimeout = removeSuspectsTimeout;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
public interface TcpFailureDetectorMBean {
public int getOptionFlag();
// Attributes
public long getConnectTimeout();
public boolean getPerformSendTest();
public boolean getPerformReadTest();
public long getReadTestTimeout();
public int getRemoveSuspectsTimeout();
public void setPerformReadTest(boolean performReadTest);
public void setPerformSendTest(boolean performSendTest);
public void setReadTestTimeout(long readTestTimeout);
public void setConnectTimeout(long connectTimeout);
public void setRemoveSuspectsTimeout(int removeSuspectsTimeout);
// Operations
public void checkMembers(boolean checkAll);
}

View File

@@ -0,0 +1,198 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.catalina.tribes.ChannelException;
import org.apache.catalina.tribes.ChannelInterceptor;
import org.apache.catalina.tribes.ChannelMessage;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.group.ChannelInterceptorBase;
import org.apache.catalina.tribes.io.ChannelData;
import org.apache.catalina.tribes.io.XByteBuffer;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
*
* Sends a ping to all members.
* Configure this interceptor with the TcpFailureDetector below it,
* and the TcpFailureDetector will act as the membership guide.
* @version 1.0
*/
public class TcpPingInterceptor extends ChannelInterceptorBase implements TcpPingInterceptorMBean {
private static final Log log = LogFactory.getLog(TcpPingInterceptor.class);
protected static final StringManager sm = StringManager.getManager(TcpPingInterceptor.class);
protected static final byte[] TCP_PING_DATA = new byte[] {
79, -89, 115, 72, 121, -33, 67, -55, -97, 111, -119, -128, -95, 91, 7, 20,
125, -39, 82, 91, -21, -33, 67, -102, -73, 126, -66, -113, -127, 103, 30, -74,
55, 21, -66, -121, 69, 33, 76, -88, -65, 10, 77, 19, 83, 56, 21, 50,
85, -10, -108, -73, 58, -33, 33, 120, -111, 4, 125, -41, 114, -124, -64, -43};
protected long interval = 1000; //1 second
protected boolean useThread = false;
protected boolean staticOnly = false;
protected volatile boolean running = true;
protected PingThread thread = null;
protected static final AtomicInteger cnt = new AtomicInteger(0);
WeakReference<TcpFailureDetector> failureDetector = null;
WeakReference<StaticMembershipInterceptor> staticMembers = null;
@Override
public synchronized void start(int svc) throws ChannelException {
super.start(svc);
running = true;
if ( thread == null && useThread) {
thread = new PingThread();
thread.setDaemon(true);
String channelName = "";
if (getChannel().getName() != null) channelName = "[" + getChannel().getName() + "]";
thread.setName("TcpPingInterceptor.PingThread" + channelName +"-"+cnt.addAndGet(1));
thread.start();
}
//acquire the interceptors to invoke on send ping events
ChannelInterceptor next = getNext();
while ( next != null ) {
if ( next instanceof TcpFailureDetector )
failureDetector = new WeakReference<>((TcpFailureDetector)next);
if ( next instanceof StaticMembershipInterceptor )
staticMembers = new WeakReference<>((StaticMembershipInterceptor)next);
next = next.getNext();
}
}
@Override
public synchronized void stop(int svc) throws ChannelException {
running = false;
if (thread != null) {
thread.interrupt();
thread = null;
}
super.stop(svc);
}
@Override
public void heartbeat() {
super.heartbeat();
if (!getUseThread()) sendPing();
}
@Override
public long getInterval() {
return interval;
}
public void setInterval(long interval) {
this.interval = interval;
}
public void setUseThread(boolean useThread) {
this.useThread = useThread;
}
public void setStaticOnly(boolean staticOnly) {
this.staticOnly = staticOnly;
}
@Override
public boolean getUseThread() {
return useThread;
}
public boolean getStaticOnly() {
return staticOnly;
}
protected void sendPing() {
TcpFailureDetector tcpFailureDetector =
failureDetector != null ? failureDetector.get() : null;
if (tcpFailureDetector != null) {
// We have a reference to the failure detector
// Piggy back on it
tcpFailureDetector.checkMembers(true);
} else {
StaticMembershipInterceptor smi =
staticOnly && staticMembers != null ? staticMembers.get() : null;
if (smi != null) {
sendPingMessage(smi.getMembers());
} else {
sendPingMessage(getMembers());
}
}
}
protected void sendPingMessage(Member[] members) {
if ( members == null || members.length == 0 ) return;
ChannelData data = new ChannelData(true);//generates a unique Id
data.setAddress(getLocalMember(false));
data.setTimestamp(System.currentTimeMillis());
data.setOptions(getOptionFlag());
data.setMessage(new XByteBuffer(TCP_PING_DATA, false));
try {
super.sendMessage(members, data, null);
}catch (ChannelException x) {
log.warn(sm.getString("tcpPingInterceptor.ping.failed"),x);
}
}
@Override
public void messageReceived(ChannelMessage msg) {
//catch incoming
boolean process = true;
if ( okToProcess(msg.getOptions()) ) {
//check to see if it is a ping message, if so, process = false
process = ( (msg.getMessage().getLength() != TCP_PING_DATA.length) ||
(!Arrays.equals(TCP_PING_DATA,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 TCP ping packet:"+msg);
}//messageReceived
protected class PingThread extends Thread {
@Override
public void run() {
while (running) {
try {
sleep(interval);
sendPing();
}catch ( InterruptedException ix ) {
// Ignore. Probably triggered by a call to stop().
// In the highly unlikely event it was a different trigger,
// simply ignore it and continue.
}catch ( Exception x ) {
log.warn(sm.getString("tcpPingInterceptor.pingFailed.pingThread"),x);
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
public interface TcpPingInterceptorMBean {
public int getOptionFlag();
public long getInterval();
public boolean getUseThread();
}

View File

@@ -0,0 +1,160 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.text.DecimalFormat;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
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 org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
public class ThroughputInterceptor extends ChannelInterceptorBase
implements ThroughputInterceptorMBean {
private static final Log log = LogFactory.getLog(ThroughputInterceptor.class);
protected static final StringManager sm = StringManager.getManager(ThroughputInterceptor.class);
double mbTx = 0;
double mbAppTx = 0;
double mbRx = 0;
double timeTx = 0;
double lastCnt = 0;
final AtomicLong msgTxCnt = new AtomicLong(1);
final AtomicLong msgRxCnt = new AtomicLong(0);
final AtomicLong msgTxErr = new AtomicLong(0);
int interval = 10000;
final AtomicInteger access = new AtomicInteger(0);
long txStart = 0;
long rxStart = 0;
final DecimalFormat df = new DecimalFormat("#0.00");
@Override
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);
if ( access.get() == 1 ) access.addAndGet(-1);
throw x;
}
mbTx += (bytes*destination.length)/(1024d*1024d);
mbAppTx += bytes/(1024d*1024d);
if ( access.addAndGet(-1) == 0 ) {
long stop = System.currentTimeMillis();
timeTx += (stop - txStart) / 1000d;
if ((msgTxCnt.get() / (double) interval) >= lastCnt) {
lastCnt++;
report(timeTx);
}
}
msgTxCnt.addAndGet(1);
}
@Override
public void messageReceived(ChannelMessage msg) {
if ( rxStart == 0 ) rxStart = System.currentTimeMillis();
long bytes = XByteBuffer.getDataPackageLength(((ChannelData)msg).getDataPackageLength());
mbRx += bytes/(1024d*1024d);
msgRxCnt.addAndGet(1);
if ( msgRxCnt.get() % interval == 0 ) report(timeTx);
super.messageReceived(msg);
}
@Override
public void report(double timeTx) {
if ( log.isInfoEnabled() )
log.info(sm.getString("throughputInterceptor.report",
msgTxCnt, df.format(mbTx), df.format(mbAppTx), df.format(timeTx),
df.format(mbTx/timeTx), df.format(mbAppTx/timeTx), msgTxErr, msgRxCnt,
df.format(mbRx/((System.currentTimeMillis()-rxStart)/(double)1000)),
df.format(mbRx)));
}
@Override
public void setInterval(int interval) {
this.interval = interval;
}
@Override
public int getInterval() {
return interval;
}
@Override
public double getLastCnt() {
return lastCnt;
}
@Override
public double getMbAppTx() {
return mbAppTx;
}
@Override
public double getMbRx() {
return mbRx;
}
@Override
public double getMbTx() {
return mbTx;
}
@Override
public AtomicLong getMsgRxCnt() {
return msgRxCnt;
}
@Override
public AtomicLong getMsgTxCnt() {
return msgTxCnt;
}
@Override
public AtomicLong getMsgTxErr() {
return msgTxErr;
}
@Override
public long getRxStart() {
return rxStart;
}
@Override
public double getTimeTx() {
return timeTx;
}
@Override
public long getTxStart() {
return txStart;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.concurrent.atomic.AtomicLong;
public interface ThroughputInterceptorMBean {
public int getOptionFlag();
// Attributes
public int getInterval();
public void setInterval(int interval);
// stats
public double getLastCnt();
public double getMbAppTx();
public double getMbRx();
public double getMbTx();
public AtomicLong getMsgRxCnt();
public AtomicLong getMsgTxCnt();
public AtomicLong getMsgTxErr();
public long getRxStart();
public double getTimeTx();
public long getTxStart();
// Operations
public void report(double timeTx);
}

View File

@@ -0,0 +1,146 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 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.UniqueId;
import org.apache.catalina.tribes.group.ChannelInterceptorBase;
import org.apache.catalina.tribes.group.InterceptorPayload;
import org.apache.catalina.tribes.util.Arrays;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.catalina.tribes.util.UUIDGenerator;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
public class TwoPhaseCommitInterceptor extends ChannelInterceptorBase {
private static final byte[] START_DATA = new byte[] {113, 1, -58, 2, -34, -60, 75, -78, -101, -12, 32, -29, 32, 111, -40, 4};
private 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 final Log log = LogFactory.getLog(TwoPhaseCommitInterceptor.class);
protected static final StringManager sm = StringManager.getManager(TwoPhaseCommitInterceptor.class);
protected final HashMap<UniqueId, MapEntry> messages = new HashMap<>();
protected long expire = 1000 * 60; //one minute expiration
protected boolean deepclone = true;
@Override
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);
}
}
@Override
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 = messages.get(id);
if ( original != null ) {
super.messageReceived(original.msg);
messages.remove(id);
} else log.warn(sm.getString("twoPhaseCommitInterceptor.originalMessage.missing", 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;
}
@Override
public void heartbeat() {
try {
long now = System.currentTimeMillis();
@SuppressWarnings("unchecked")
Map.Entry<UniqueId,MapEntry>[] entries = messages.entrySet().toArray(new Map.Entry[messages.size()]);
for (int i=0; i<entries.length; i++ ) {
MapEntry entry = entries[i].getValue();
if ( entry.expired(now,expire) ) {
if(log.isInfoEnabled())
log.info("Message ["+entry.id+"] has expired. Removing.");
messages.remove(entry.id);
}//end if
}
} catch ( Exception x ) {
log.warn(sm.getString("twoPhaseCommitInterceptor.heartbeat.failed"),x);
} finally {
super.heartbeat();
}
}
public static class MapEntry {
public final ChannelMessage msg;
public final UniqueId id;
public final 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;
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
*
*
* @version 1.0
*/
public class BufferPool {
private static final Log log = LogFactory.getLog(BufferPool.class);
public static final int DEFAULT_POOL_SIZE = 100*1024*1024; //100MB
protected static final StringManager sm = StringManager.getManager(BufferPool.class);
protected static volatile BufferPool instance = null;
protected final BufferPoolAPI pool;
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 = new BufferPool15Impl();
pool.setMaxSize(DEFAULT_POOL_SIZE);
log.info(sm.getString("bufferPool.created",
Integer.toString(DEFAULT_POOL_SIZE), pool.getClass().getName()));
instance = new BufferPool(pool);
}
}
}
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();
}
}

View File

@@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
/**
*
* @version 1.0
*/
class BufferPool15Impl implements BufferPool.BufferPoolAPI {
protected int maxSize;
protected final AtomicInteger size = new AtomicInteger(0);
protected final ConcurrentLinkedQueue<XByteBuffer> queue =
new ConcurrentLinkedQueue<>();
@Override
public void setMaxSize(int bytes) {
this.maxSize = bytes;
}
@Override
public XByteBuffer getBuffer(int minSize, boolean discard) {
XByteBuffer buffer = 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;
}
@Override
public void returnBuffer(XByteBuffer buffer) {
if ( (size.get() + buffer.getCapacity()) <= maxSize ) {
size.addAndGet(buffer.getCapacity());
queue.offer(buffer);
}
}
@Override
public void clear() {
queue.clear();
size.set(0);
}
public int getMaxSize() {
return maxSize;
}
}

View File

@@ -0,0 +1,374 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.sql.Timestamp;
import java.util.Arrays;
import org.apache.catalina.tribes.Channel;
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;
/**
* 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
*/
public class ChannelData implements ChannelMessage {
private static final long serialVersionUID = 1L;
public static final ChannelData[] EMPTY_DATA_ARRAY = new ChannelData[0];
public static volatile 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
*/
@Override
public XByteBuffer getMessage() {
return message;
}
/**
* @param message The message to send.
*/
@Override
public void setMessage(XByteBuffer message) {
this.message = message;
}
/**
* @return Returns the timestamp.
*/
@Override
public long getTimestamp() {
return timestamp;
}
/**
* @param timestamp The timestamp to send
*/
@Override
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
/**
* @return Returns the uniqueId.
*/
@Override
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)
*
*/
@Override
public int getOptions() {
return options;
}
/**
* Sets the message options.
*
* @param options the message options
*/
@Override
public void setOptions(int options) {
this.options = options;
}
/**
* Returns the source or reply-to address
* @return Member
*/
@Override
public Member getAddress() {
return address;
}
/**
* Sets the source or reply-to address
* @param address Member
*/
@Override
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
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 = 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());
return data;
}
/**
* Deserializes a ChannelData object from a byte array
* @param xbuf 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)];
int addrlen = XByteBuffer.toInt(xbuf.getBytesDirect(),offset);
offset += 4; //addr length
//System.arraycopy(xbuf.getBytesDirect(),offset,addr,0,addr.length);
data.setAddress(MemberImpl.getMember(xbuf.getBytesDirect(),offset,addrlen));
//offset += addr.length; //addr data
offset += addrlen;
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;
}
@Override
public int hashCode() {
return XByteBuffer.toInt(getUniqueId(),0);
}
/**
* Compares to ChannelData objects, only compares on getUniqueId().equals(o.getUniqueId())
* @param o Object
* @return boolean
*/
@Override
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
*/
@Override
public ChannelData clone() {
ChannelData clone;
try {
clone = (ChannelData) super.clone();
} catch (CloneNotSupportedException e) {
// Cannot happen
throw new AssertionError();
}
if (this.message != null) {
clone.message = new XByteBuffer(this.message.getBytesDirect(),false);
}
return clone;
}
/**
* Complete clone
* @return ClusterData
*/
@Override
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);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
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) {
StringBuilder buf = new StringBuilder(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();
}
}

View File

@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* @version 1.0
*/
public class DirectByteArrayOutputStream extends OutputStream {
private final 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.
*/
@Override
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();
}
}

View File

@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
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);
}

View File

@@ -0,0 +1,27 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
bufferPool.created=Created a buffer pool with max size:[{0}] bytes of type: [{1}]
objectReader.retrieveFailed.socketReceiverBufferSize=Unable to retrieve the socket receiver buffer size, setting to default 43800 bytes.
replicationStream.conflict=conflicting non-public interface class loaders
xByteBuffer.discarded.invalidHeader=Discarded the package, invalid header
xByteBuffer.no.package=No package exists in XByteBuffer
xByteBuffer.size.larger.buffer=Size is larger than existing buffer.
xByteBuffer.unableCreate=Unable to create data package, buffer is too small.
xByteBuffer.unableTrim=Cannot trim more bytes than are available. length:[{0}] trim:[{1}]
xByteBuffer.wrong.class=Message has the wrong class. It should implement Serializable, instead it is:[{0}]

View File

@@ -0,0 +1,20 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
objectReader.retrieveFailed.socketReceiverBufferSize=Kann die Größe der Socket Puffer nicht auslesen. Setze sie auf den Standardwert von 43800 Bytes.
xByteBuffer.no.package=In XByteBuffer existiert kein Paket
xByteBuffer.unableCreate=Kann Daten Paket nicht erzeugen, da der Puffer zu klein ist
xByteBuffer.wrong.class=Nachricht hat eine falsche Klasse. Sie sollte Serializable implementieren, sie implementiert aber: [{0}]

View File

@@ -0,0 +1,23 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
objectReader.retrieveFailed.socketReceiverBufferSize=Incapáz de recuperar el tamaño del buffer receptor, fijando el valor por defecto a 43800 bytes.\n
replicationStream.conflict=hay conflicto en la clase de los cargadores de la interfaz no pública
xByteBuffer.no.package=No existe paquete en XByteBuffer
xByteBuffer.size.larger.buffer=El tamaño es mayor que el buffer
xByteBuffer.unableCreate=Imposible crear el paquete de datos, el buffer es demasiado pequeño.
xByteBuffer.wrong.class=El mensaje tiene la clase errónea. Debe implementar Serializable, pero en su lugar es:[{0}]

View File

@@ -0,0 +1,27 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
bufferPool.created=Création d''un pool de tampons de taille maximale: [{0}] octets de type: [{1}]
objectReader.retrieveFailed.socketReceiverBufferSize=Incapacité de récupérer la taille du tampon du socket de réception, forcé à 43800 octets
replicationStream.conflict=Conflit entre des interfaces non-publics ayant des chargeurs de classe différents
xByteBuffer.discarded.invalidHeader=L'en-tête est invalide donc le paquet a été abandonné
xByteBuffer.no.package=Il n'y a aucun package dans XByteBuffer
xByteBuffer.size.larger.buffer=La taille est plus grande que celle du buffer existant
xByteBuffer.unableCreate=Impossible de créer le package data, le tampon de mémoire est trop petit
xByteBuffer.unableTrim=Impossible d''élaguer plus d''octets que ce qui est disponible, longueur: [{0}] élagage: [{1}]
xByteBuffer.wrong.class=Message n''a pas la bonne classe. Cela doit implémenter Serializable au lieu de [{0}]

View File

@@ -0,0 +1,27 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
bufferPool.created=最大サイズ[{1}]のタイプの[{0}]バイトのバッファプールを作成しました。
objectReader.retrieveFailed.socketReceiverBufferSize=TCP ソケットのレシーバーバッファサイズを取得できなかったため初期値として 43800 byte を設定します。
replicationStream.conflict=public でないインターフェイスのクラスローダーが複数存在します。
xByteBuffer.discarded.invalidHeader=ヘッダーが不正なためパッケージを破棄します。
xByteBuffer.no.package=XByteBuffer にパッケージがありません。
xByteBuffer.size.larger.buffer=バッファ長より大きな値がサイズとして指定されました。
xByteBuffer.unableCreate=バッファーが小さすぎてデータパッケージを作成できません。
xByteBuffer.unableTrim=利用可能なバイト数より多くのバイトをトリムすることはできません。 長さ:[{0}]トリム:[{1}]
xByteBuffer.wrong.class=メッセージのClassが間違っています。 それはSerializableを実装するはずですが、代わりに[{0}]です。

View File

@@ -0,0 +1,27 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
bufferPool.created=타입이 [{1}](이)고 최대 크기가 [{0}] 바이트를 사용하여 버퍼 풀을 생성했습니다.
objectReader.retrieveFailed.socketReceiverBufferSize=소켓 receiver의 버퍼 크기를 알 수 없습니다. 기본 값인 43800 바이트로 설정합니다.
replicationStream.conflict=해당 클래스의 클래스로더와, 그 클래스의 특정 non-public 인터페이스의 클래스로더가 일치하지 않아 충돌합니다.
xByteBuffer.discarded.invalidHeader=유효하지 않은 헤더로 인해 패키지를 폐기했습니다.
xByteBuffer.no.package=XByteBuffer에 어떤 패키지도 존재하지 않습니다.
xByteBuffer.size.larger.buffer=크기가 기존 버퍼의 길이보다 큽니다.
xByteBuffer.unableCreate=데이터 패키지를 생성할 수 없습니다. 버퍼가 너무 작습니다.
xByteBuffer.unableTrim=가용한 버퍼 크기보다 더 많은 바이트들을 잘라낼 수는 없습니다. 버퍼 길이:[{0}], 잘라낼 길이:[{1}]
xByteBuffer.wrong.class=메시지가 Serializable 인터페이스를 구현하지 않은 클래스입니다. 클래스는 [{0}]입니다.

View File

@@ -0,0 +1,16 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
replicationStream.conflict=конфликтующие не-публичные загрузчики классов

View File

@@ -0,0 +1,23 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
objectReader.retrieveFailed.socketReceiverBufferSize=无法检索套接字接收器缓冲区大小设置为默认43800字节。
replicationStream.conflict=和非公开接口类加载器冲突
xByteBuffer.no.package=XByteBuffer中不存在数据包
xByteBuffer.size.larger.buffer=大小比现有缓冲区大。
xByteBuffer.unableCreate=不能创建数据包, buffer 太小
xByteBuffer.wrong.class=消息对应类不符合要求。 它应该实现Serializable而不是[{0}]。

View File

@@ -0,0 +1,167 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import org.apache.catalina.tribes.ChannelMessage;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* The object reader object is an object used in conjunction with
* java.nio TCP messages. This object stores the message bytes in a
* <code>XByteBuffer</code> until a full package has been received.
* This object uses an XByteBuffer which is an extendable object buffer that also allows
* for message encoding and decoding.
*/
public class ObjectReader {
private static final Log log = LogFactory.getLog(ObjectReader.class);
protected static final StringManager sm = StringManager.getManager(ObjectReader.class);
private XByteBuffer buffer;
protected long lastAccess = System.currentTimeMillis();
protected boolean accessed = false;
private volatile boolean cancelled;
public ObjectReader(int packetSize) {
this.buffer = new XByteBuffer(packetSize, true);
}
/**
* Creates an <code>ObjectReader</code> for a TCP NIO socket channel
* @param channel - the channel to be read.
*/
public ObjectReader(SocketChannel channel) {
this(channel.socket());
}
/**
* Creates an <code>ObjectReader</code> for a TCP socket
* @param socket Socket
*/
public ObjectReader(Socket socket) {
try{
this.buffer = new XByteBuffer(socket.getReceiveBufferSize(), true);
}catch ( IOException x ) {
//unable to get buffer size
log.warn(sm.getString("objectReader.retrieveFailed.socketReceiverBufferSize"));
this.buffer = new XByteBuffer(43800,true);
}
}
public synchronized void access() {
this.accessed = true;
this.lastAccess = System.currentTimeMillis();
}
public synchronized void finish() {
this.accessed = false;
this.lastAccess = System.currentTimeMillis();
}
public synchronized boolean isAccessed() {
return this.accessed;
}
/**
* Append new bytes to buffer.
* @see XByteBuffer#countPackages()
* @param data new transfer buffer
* @param len length in buffer
* @param count whether to return the count
* @return number of messages that was sent to callback (or -1 if count == false)
*/
public int append(ByteBuffer data, int len, boolean count) {
buffer.append(data,len);
int pkgCnt = -1;
if ( count ) pkgCnt = buffer.countPackages();
return pkgCnt;
}
public int append(byte[] data,int off,int len, boolean count) {
buffer.append(data,off,len);
int pkgCnt = -1;
if ( count ) pkgCnt = buffer.countPackages();
return pkgCnt;
}
/**
* Send buffer to cluster listener (callback).
* Is message complete receiver send message to callback?
*
* @see org.apache.catalina.tribes.transport.ReceiverBase#messageDataReceived(ChannelMessage)
* @see XByteBuffer#doesPackageExist()
* @see XByteBuffer#extractPackage(boolean)
*
* @return number of received packages/messages
*/
public ChannelMessage[] execute() {
int pkgCnt = buffer.countPackages();
ChannelMessage[] result = new ChannelMessage[pkgCnt];
for (int i=0; i<pkgCnt; i++) {
ChannelMessage data = buffer.extractPackage(true);
result[i] = data;
}
return result;
}
public int bufferSize() {
return buffer.getLength();
}
public boolean hasPackage() {
return buffer.countPackages(true)>0;
}
/**
* Returns the number of packages that the reader has read
* @return int
*/
public int count() {
return buffer.countPackages();
}
public void close() {
this.buffer = null;
}
public synchronized long getLastAccess() {
return lastAccess;
}
public boolean isCancelled() {
return cancelled;
}
public synchronized void setLastAccess(long lastAccess) {
this.lastAccess = lastAccess;
}
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View File

@@ -0,0 +1,170 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import org.apache.catalina.tribes.util.StringManager;
/**
* 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
*/
public final class ReplicationStream extends ObjectInputStream {
static final StringManager sm = StringManager.getManager(ReplicationStream.class);
/**
* 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 classLoaders The class loader array 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
*/
@Override
public Class<?> resolveClass(ObjectStreamClass classDesc)
throws ClassNotFoundException, IOException {
String name = classDesc.getName();
try {
return resolveClass(name);
} catch (ClassNotFoundException e) {
return super.resolveClass(classDesc);
}
}
public Class<?> resolveClass(String name) throws ClassNotFoundException {
boolean tryRepFirst = name.startsWith("org.apache.catalina.tribes");
try {
if (tryRepFirst)
return findReplicationClass(name);
else
return findExternalClass(name);
} catch (Exception x) {
if (tryRepFirst)
return findExternalClass(name);
else
return findReplicationClass(name);
}
}
/**
* ObjectInputStream.resolveProxyClass has some funky way of using
* the incorrect class loader to resolve proxy classes, let's do it our way instead
*/
@Override
protected Class<?> resolveProxyClass(String[] interfaces)
throws IOException, ClassNotFoundException {
ClassLoader latestLoader;
if (classLoaders != null && classLoaders.length > 0) {
latestLoader = classLoaders[0];
} else {
latestLoader = null;
}
ClassLoader nonPublicLoader = null;
boolean hasNonPublicInterface = false;
// define proxy in class loader of non-public interface(s), if any
Class<?>[] classObjs = new Class[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
Class<?> cl = this.resolveClass(interfaces[i]);
if (latestLoader==null) latestLoader = cl.getClassLoader();
if ((cl.getModifiers() & Modifier.PUBLIC) == 0) {
if (hasNonPublicInterface) {
if (nonPublicLoader != cl.getClassLoader()) {
throw new IllegalAccessError(
sm.getString("replicationStream.conflict"));
}
} else {
nonPublicLoader = cl.getClassLoader();
hasNonPublicInterface = true;
}
}
classObjs[i] = cl;
}
try {
return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader
: latestLoader, classObjs);
} catch (IllegalArgumentException e) {
throw new ClassNotFoundException(null, e);
}
}
public Class<?> findReplicationClass(String name)
throws ClassNotFoundException {
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);
}
@Override
public void close() throws IOException {
this.classLoaders = null;
super.close();
}
}

View File

@@ -0,0 +1,596 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* 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>
*/
public class XByteBuffer implements Serializable {
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(XByteBuffer.class);
protected static final StringManager sm = StringManager.getManager(XByteBuffer.class);
/**
* This is a package header, 7 bytes (FLT2002)
*/
private static final byte[] START_DATA = {70,76,84,50,48,48,50};
/**
* This is the package footer, 7 bytes (TLF2003)
*/
private static final byte[] END_DATA = {84,76,70,50,48,48,51};
/**
* 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.<br>
* TODO use a pool of byte[] for performance
* @param size the initial size of the byte buffer
* @param discard Flag for discarding invalid packages
*/
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(sm.getString("xByteBuffer.size.larger.buffer"));
bufSize = size;
}
public void trim(int length) {
if ( (bufSize - length) < 0 )
throw new ArrayIndexOutOfBoundsException(sm.getString("xByteBuffer.unableTrim",
Integer.toString(bufSize), Integer.toString(length)));
bufSize -= length;
}
public void reset() {
bufSize = 0;
}
public byte[] getBytesDirect() {
return this.buf;
}
/**
* @return 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 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(sm.getString("xByteBuffer.discarded.invalidHeader"));
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(sm.getString("xByteBuffer.discarded.invalidHeader"));
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(sm.getString("xByteBuffer.no.package"));
}
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) {
XByteBuffer xbuf = extractDataPackage(clearFromBuffer);
ChannelData cdata = ChannelData.getDataFromPackage(xbuf);
return cdata;
}
/**
* Creates a complete data package
* @param cdata - the message data to be contained within the package
* @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(sm.getString("xByteBuffer.unableCreate"));
}
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
*/
public static int toInt(byte[] b,int off){
return ( ( b[off+3]) & 0xFF) +
( ( ( b[off+2]) & 0xFF) << 8) +
( ( ( b[off+1]) & 0xFF) << 16) +
( ( ( 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
*/
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 a boolean and put it in a byte array.
* @param bool the integer
* @param data the byte buffer in which the boolean will be placed
* @param offset the offset in the byte array
* @return the byte array
*/
public static byte[] toBytes(boolean bool, byte[] data, int offset) {
data[offset] = (byte)(bool?1:0);
return data;
}
/**
* Converts a byte array entry to boolean.
* @param b byte array
* @param offset within byte array
* @return true if byte array entry is non-zero, false otherwise
*/
public static boolean toBoolean(byte[] b, int offset) {
return b[offset] != 0;
}
/**
* Converts an integer to four bytes.
* @param n the integer
* @param b the byte buffer in which the integer will be placed
* @param offset the offset in the byte array
* @return four bytes in an array
*/
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 a long to eight bytes.
* @param n the long
* @param b the byte buffer in which the integer will be placed
* @param offset the offset in the byte array
* @return eight bytes in an array
*/
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 = (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);
}
private static final AtomicInteger invokecount = new AtomicInteger(0);
public static Serializable deserialize(byte[] data, int offset, int length, ClassLoader[] cls)
throws IOException, ClassNotFoundException, ClassCastException {
invokecount.addAndGet(1);
Object message = null;
if ( cls == null ) cls = new ClassLoader[0];
if (data != null && length > 0) {
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(sm.getString("xByteBuffer.wrong.class", message.getClass().getName()));
}
}
/**
* Serializes a message into cluster data
* @param msg ClusterMessage
* @return serialized content as byte[] array
* @throws IOException Serialization error
*/
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;
}
}

View File

@@ -0,0 +1,144 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.jmx;
import java.lang.management.ManagementFactory;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.JmxChannel;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
public class JmxRegistry {
private static final Log log = LogFactory.getLog(JmxRegistry.class);
protected static final StringManager sm = StringManager.getManager(JmxRegistry.class);
private static ConcurrentHashMap<String, JmxRegistry> registryCache = new ConcurrentHashMap<>();
private MBeanServer mbserver = ManagementFactory.getPlatformMBeanServer();
private ObjectName baseOname = null;
private JmxRegistry() {
}
public static JmxRegistry getRegistry(Channel channel) {
if (channel == null || channel.getName() == null) {
return null;
}
JmxRegistry registry = registryCache.get(channel.getName());
if (registry != null) return registry;
if (!(channel instanceof JmxChannel)) return null;
JmxChannel jmxChannel = (JmxChannel) channel;
if (!jmxChannel.isJmxEnabled()) return null;
ObjectName baseOn = createBaseObjectName(jmxChannel.getJmxDomain(),
jmxChannel.getJmxPrefix(), channel.getName());
if (baseOn == null) return null;
// create registry
registry = new JmxRegistry();
registry.baseOname = baseOn;
// It doesn't matter if existing object gets over-written. This object
// holds minimal state and that state will be the same for all objects
// created for the same channel.
registryCache.put(channel.getName(), registry);
return registry;
}
public static void removeRegistry(Channel channel, boolean clear) {
JmxRegistry registry = registryCache.get(channel.getName());
if (registry == null) return;
if (clear) {
registry.clearMBeans();
}
registryCache.remove(channel.getName());
}
private static ObjectName createBaseObjectName(String domain, String prefix, String name) {
if (domain == null) {
log.warn(sm.getString("jmxRegistry.no.domain"));
return null;
}
ObjectName on = null;
StringBuilder sb = new StringBuilder(domain);
sb.append(':');
sb.append(prefix);
sb.append("type=Channel,channel=");
sb.append(name);
try {
on = new ObjectName(sb.toString());
} catch (MalformedObjectNameException e) {
log.error(sm.getString("jmxRegistry.objectName.failed", sb.toString()), e);
}
return on;
}
public ObjectName registerJmx(String keyprop, Object bean) {
String oNameStr = baseOname.toString() + keyprop;
ObjectName oName = null;
try {
oName = new ObjectName(oNameStr);
if (mbserver.isRegistered(oName)) {
mbserver.unregisterMBean(oName);
}
mbserver.registerMBean(bean, oName);
} catch (NotCompliantMBeanException e) {
log.warn(sm.getString("jmxRegistry.registerJmx.notCompliant", bean), e);
return null;
} catch (MalformedObjectNameException e) {
log.error(sm.getString("jmxRegistry.objectName.failed", oNameStr), e);
return null;
} catch (Exception e) {
log.error(sm.getString("jmxRegistry.registerJmx.failed", bean, oNameStr), e);
return null;
}
return oName;
}
public void unregisterJmx(ObjectName oname) {
if (oname ==null) return;
try {
mbserver.unregisterMBean(oname);
} catch (InstanceNotFoundException e) {
log.warn(sm.getString("jmxRegistry.unregisterJmx.notFound", oname), e);
} catch (Exception e) {
log.warn(sm.getString("jmxRegistry.unregisterJmx.failed", oname), e);
}
}
private void clearMBeans() {
String query = baseOname.toString() + ",*";
try {
ObjectName name = new ObjectName(query);
Set<ObjectName> onames = mbserver.queryNames(name, null);
for (ObjectName objectName : onames) {
unregisterJmx(objectName);
}
} catch (MalformedObjectNameException e) {
log.error(sm.getString("jmxRegistry.objectName.failed", query), e);
}
}
}

View File

@@ -0,0 +1,21 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
jmxRegistry.no.domain=JMX domain is not specified
jmxRegistry.objectName.failed=The requested ObjectName [{0}] is not valid
jmxRegistry.registerJmx.failed=Failed to register object [{0}] with name [{1}]
jmxRegistry.registerJmx.notCompliant=The requested object[{0}] is not compliant with JMX specification
jmxRegistry.unregisterJmx.failed=Failed to unregister MBean with name [{0}]
jmxRegistry.unregisterJmx.notFound=The requested ObjectName [{0}] has not been registered in the MBeanServer

View File

@@ -0,0 +1,16 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
jmxRegistry.registerJmx.failed=Fallo al registrar el objeto [{0}] con nombre [{1}]\n

View File

@@ -0,0 +1,21 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
jmxRegistry.no.domain=Le nom de domaine JMX n'est pas spécifié
jmxRegistry.objectName.failed=L''ObjectName [{0}] demandé est invalide
jmxRegistry.registerJmx.failed=Echec de l''enregistrement de l''objet [{0}] avec le nom [{1}]
jmxRegistry.registerJmx.notCompliant=L''objet demandé [{0}] n''est pas compatible avec la spécification JMX
jmxRegistry.unregisterJmx.failed=Echec du désenregistrement du MBean nommé [{0}]
jmxRegistry.unregisterJmx.notFound=L''ObjectName [{0}] demandé n''a pas été enregistré dans le serveur JMX

View File

@@ -0,0 +1,21 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
jmxRegistry.no.domain=JMX ドメインが未指定です。
jmxRegistry.objectName.failed=不正なオブジェクト名 [{0}] を要求されました。
jmxRegistry.registerJmx.failed=オブジェクト [{0}] を名前 [{1}] で登録できませんでした。
jmxRegistry.registerJmx.notCompliant=要求されたオブジェクト[{0}]はJMX仕様に準拠していません。
jmxRegistry.unregisterJmx.failed=名前[{0}]のMBeanの登録を解除できませんでした。
jmxRegistry.unregisterJmx.notFound=要求されたObjectName [{0}]はMBeanServerに登録されていません。

View File

@@ -0,0 +1,21 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
jmxRegistry.no.domain=JMX 도메인이 지정되지 않았습니다.
jmxRegistry.objectName.failed=요청된 ObjectName [{0}]은(는) 유효하지 않습니다.
jmxRegistry.registerJmx.failed=[{1}](이)라는 이름으로 객체 [{0}]을(를) 등록하지 못했습니다.
jmxRegistry.registerJmx.notCompliant=요청된 객체 [{0}]은(는) JMX 스펙과 호환되지 않습니다.
jmxRegistry.unregisterJmx.failed=[{0}](이)라는 이름의 MBean에 대한 등록을 제거하지 못했습니다.
jmxRegistry.unregisterJmx.notFound=요청된 객체[{0}]는 MBeanServer에 등록되지 않았습니다.

View File

@@ -0,0 +1,17 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
jmxRegistry.no.domain=未指定JMX域
jmxRegistry.registerJmx.failed=无法注册名称为 [{1}] 的对象 [{0}]

View File

@@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*/
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()));
}
}

View File

@@ -0,0 +1,56 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
McastService.domain=Unable to send domain update
McastService.parseSoTimeout=Unable to parse SoTimeout: [{0}]
McastService.parseTTL=Unable to parse TTL: [{0}]
McastService.payload=Unable to send payload update
McastService.stopFail=Unable to stop the mcast service, level: [{0}]
mcastService.exceed.maxPacketSize=Packet length[{0}] exceeds max packet size of [{1}] bytes.
mcastService.missing.property=McastService:Required property [{0}] is missing.
mcastService.noStart=Multicast send is not started or enabled.
mcastServiceImpl.bind=Attempting to bind the multicast socket to [{0}:{1}]
mcastServiceImpl.bind.failed=Binding to multicast address, failed. Binding to port only.
mcastServiceImpl.error.receiving=Error receiving mcast package. Sleeping 500ms
mcastServiceImpl.invalid.startLevel=Invalid start level. Only acceptable levels are Channel.MBR_RX_SEQ and Channel.MBR_TX_SEQ
mcastServiceImpl.invalid.stopLevel=Invalid stop level. Only acceptable levels are Channel.MBR_RX_SEQ and Channel.MBR_TX_SEQ
mcastServiceImpl.memberDisappeared.failed=Unable to process member disappeared message.
mcastServiceImpl.packet.tooLong=Multicast packet received was too long, dropping package:[{0}]
mcastServiceImpl.receive.running=McastService.receive already running.
mcastServiceImpl.recovery=Tribes membership, running recovery thread, multicasting is not functional.
mcastServiceImpl.recovery.failed=Recovery attempt number [{0}] failed, trying again in [{1}] seconds
mcastServiceImpl.recovery.startFailed=Recovery thread failed to start membership service.
mcastServiceImpl.recovery.stopFailed=Recovery thread failed to stop membership service.
mcastServiceImpl.recovery.successful=Membership recovery was successful.
mcastServiceImpl.send.failed=Unable to send mcast message.
mcastServiceImpl.send.running=McastService.send already running.
mcastServiceImpl.setInterface=Setting multihome multicast interface to:[{0}]
mcastServiceImpl.setSoTimeout=Setting cluster mcast soTimeout to [{0}]
mcastServiceImpl.setTTL=Setting cluster mcast TTL to [{0}]
mcastServiceImpl.unable.join=Unable to join multicast group, make sure your system has multicasting enabled.
mcastServiceImpl.unableReceive.broadcastMessage=Unable to receive broadcast message.
mcastServiceImpl.waitForMembers.done=Done sleeping, membership established, start level:[{0}]
mcastServiceImpl.waitForMembers.start=Sleeping for [{0}] milliseconds to establish cluster membership, start level:[{1}]
memberImpl.invalid.package.begin=Invalid package, should start with:[{0}]
memberImpl.invalid.package.end=Invalid package, should end with:[{0}]
memberImpl.large.payload=Payload is too large for tribes to handle.
memberImpl.notEnough.bytes=Not enough bytes in member package.
memberImpl.package.small=Member package too small to validate.
memberImpl.unableParse.hostname=Unable to parse hostname.
staticMember.invalid.uuidLength=UUID must be exactly 16 bytes, not:[{0}]

View File

@@ -0,0 +1,24 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
mcastServiceImpl.bind=Versuche Multicast Socket an [{0}:{1}] zu binden
mcastServiceImpl.recovery.failed=Wiederherstellungsversuch Nummer [{0}] ist fehlgeschlagen, versuche es in [{1}] Sekunden erneut
mcastServiceImpl.send.failed=Eine mcast-Nachricht kann nicht gesendet werden.
mcastServiceImpl.send.running=McastService.send läuft bereits
mcastServiceImpl.setSoTimeout=Setze Cluster mcast soTimeout auf [{0}]
mcastServiceImpl.setTTL=Setze Cluster mcast TTL auf [{0}]
mcastServiceImpl.unableReceive.broadcastMessage=Konnte Broadcast-Nachricht nicht empfangen.
memberImpl.notEnough.bytes=Nicht genug bytes im Mitgliederpaket

View File

@@ -0,0 +1,24 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
mcastServiceImpl.bind=Tratando de atar el socket multicast a [{0}:{1}]\n
mcastServiceImpl.recovery=Membresía Tribes ejecutando hilo de recuperación, multicasting no esta operativo.
mcastServiceImpl.send.running=McastService.send aún esta corriendo.
mcastServiceImpl.setInterface=Fijando interfaz multicase multihme a:[{0}]\n
mcastServiceImpl.setSoTimeout=Fijando cluster mcast para tiempo de espera gotado en [{0}]
mcastServiceImpl.setTTL=Fijando cluster cluster mcast TTL a [{0}]\n
mcastServiceImpl.unableReceive.broadcastMessage=Incapaz de recibir el mensaje de broadcast
memberImpl.notEnough.bytes=No hay suficientes bytes en el paquete miembro

View File

@@ -0,0 +1,56 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
McastService.domain=Impossible d'envoyer la mise à jour du domaine
McastService.parseSoTimeout=Impossible de lire SoTimeout: [{0}]
McastService.parseTTL=Impossible d''analyser le TTL: [{0}]
McastService.payload=Impossible d'envoyer les données de mise à jour
McastService.stopFail=Impossible d''arrêter le service mcast, niveau [{0}]
mcastService.exceed.maxPacketSize=La taille du paquet [{0}] excède la taille maximale qui est de [{1}] octets
mcastService.missing.property=McastService:La propriété obligatoire [{0}] manque.
mcastService.noStart=L'envoi multicast n'est pas démarré ou activé
mcastServiceImpl.bind=Tentive d'associer le socket multicast à [{0}:{1}]
mcastServiceImpl.bind.failed=Echec de l'association à ladresse multicast, association uniquement sur le port
mcastServiceImpl.error.receiving=Erreur en recevant un paquet multicast, attente de 500ms
mcastServiceImpl.invalid.startLevel=Niveau de départ invalide. Les seuls niveaux acceptables sont Channel.MBR_RX_SEQ et Channel.MBR_TX_SEQ
mcastServiceImpl.invalid.stopLevel=Niveau de stop invalide, les seuls niveaux valides sont Channel.MBR_RX_SEQ et Channel.MBR_TX_SEQ
mcastServiceImpl.memberDisappeared.failed=Impossible de traiter le message indiquant un membre disparu
mcastServiceImpl.packet.tooLong=Le paquet multicast reçu est trop long, il est abandonné: [{0}]
mcastServiceImpl.receive.running=McastService.receive est déjà en cours d'exécution
mcastServiceImpl.recovery=Le multicast est non fonctionnel, le registre de membres de Tribes exécute le processus de récupération
mcastServiceImpl.recovery.failed=La tentative de récupération numéro [{0}] échouée, nouvel essai dans [{1}] secondes
mcastServiceImpl.recovery.startFailed=Le thread de récupération n'a pas pu démarrer le registre de membres
mcastServiceImpl.recovery.stopFailed=Le thread de récupération n'a pu arrêter le registre de membres
mcastServiceImpl.recovery.successful=Succès de récupération du registre de membres
mcastServiceImpl.send.failed=Impossible d'envoyer le message mcast.
mcastServiceImpl.send.running=McastService.send est déjà en cours d'exécution.
mcastServiceImpl.setInterface=Définition de l''interface multicast multi réseaux comme [{0}]
mcastServiceImpl.setSoTimeout=Réglage du mcast soTimeout du cluster à [{0}]
mcastServiceImpl.setTTL=Le multicast TTL du cluster est fixé à [{0}]
mcastServiceImpl.unable.join=Incapable de rejoindre le le groupe de multidiffusion ("multicast"). Assurez-vous que la multidiffusion est activée sur votre système.
mcastServiceImpl.unableReceive.broadcastMessage=N'a pas pu recevoir de message général (broadcast)
mcastServiceImpl.waitForMembers.done=Fin de l''attente, le registre de membres est établi, démarrage de niveau: [{0}]
mcastServiceImpl.waitForMembers.start=Attente de [{0}] millisecondes pour établir le registre de membres du cluster, démarrage au niveau [{1}]
memberImpl.invalid.package.begin=Le paquet est invalide, il devrait démarrer par [{0}]
memberImpl.invalid.package.end=le paquet est invalide, il devrait se terminer par: [{0}]
memberImpl.large.payload=Le contenu est trop gros pour être géré par Tribes
memberImpl.notEnough.bytes=Pas assez d'octets dans le paquet membre
memberImpl.package.small=Le paquet du membre est trop petit pour être validé
memberImpl.unableParse.hostname=Incapable d'analyser le nom d'hôte (hostname)
staticMember.invalid.uuidLength=Un UUID doit faire exactement 16 octets et non [{0}]

View File

@@ -0,0 +1,56 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
McastService.domain=ドメイン更新の送信が出来ません。
McastService.parseSoTimeout=SoTimeoutを解析できません[{0}]
McastService.parseTTL=TTL 値を解釈できません: [{0}]
McastService.payload=Payload更新の送信が出来ません。
McastService.stopFail=マルチキャストサービスを停止できません。レベル:[{0}]
mcastService.exceed.maxPacketSize=パケット長[{0}]は[{1}]バイトの最大パケットサイズを超えています。
mcastService.missing.property=McastService: 必須プロパティ [{0}] がありません。
mcastService.noStart=マルチキャスト送信が開始または有効になっていません。
mcastServiceImpl.bind=[{0}:{1}] にマルチキャストソケットを束縛します。
mcastServiceImpl.bind.failed=マルチキャストアドレスへのバインドに失敗しました。 ポートのみにバインドします。
mcastServiceImpl.error.receiving=マルチキャストパッケージを受信中にエラーが発生しました。 500msスリープします。
mcastServiceImpl.invalid.startLevel=不正な開始レベルです。受け付けられるのは Channel.MBR_RX_SEQ と Channel.MBR_TX_SEQ です。
mcastServiceImpl.invalid.stopLevel=無効な停止レベルです。 受け入れ可能なレベルはChannel.MBR_RX_SEQとChannel.MBR_TX_SEQのみです。
mcastServiceImpl.memberDisappeared.failed=メンバー消失メッセージを処理できません。
mcastServiceImpl.packet.tooLong=受信したマルチキャストパケットが大きすぎるためパッケージを破棄します。受信したパケットサイズは [{0}] です。
mcastServiceImpl.receive.running=McastService.receive は既に実行中です。
mcastServiceImpl.recovery=リカバリスレッドがメンバーシップを復旧するため、マルチキャストできなくなります。
mcastServiceImpl.recovery.failed=リカバリの試み[{0}]が失敗しました。[{1}]秒後に再試行します。
mcastServiceImpl.recovery.startFailed=リカバリースレッドはメンバーシップサービスを開始できません。
mcastServiceImpl.recovery.stopFailed=リカバリースレッドはメンバーシップサービスを停止できません。
mcastServiceImpl.recovery.successful=メンバーシップの回復に成功しました。
mcastServiceImpl.send.failed=マルチキャストメッセージを送信出来ません。
mcastServiceImpl.send.running=McastService.sendはすでに実行中です。
mcastServiceImpl.setInterface=マルチホームマルチキャストインターフェイスを構成しました。 : [{0}]
mcastServiceImpl.setSoTimeout=クラスタ マルチキャスト soTimeoutを[{0}]に設定します
mcastServiceImpl.setTTL=クラスターのマルチキャスト TTL を [{0}] に設定しました。
mcastServiceImpl.unable.join=マルチキャストグループに参加できません、システムでマルチキャストが有効になっていることを確認してください。
mcastServiceImpl.unableReceive.broadcastMessage=ブロードキャストメッセージを受信できません。
mcastServiceImpl.waitForMembers.done=sleep完了、メンバーシップを確立し、開始レベル[{0}]
mcastServiceImpl.waitForMembers.start=クラスタメンバシップを確立するために[{0}]ミリ秒スリープ、開始レベル:[{1}]
memberImpl.invalid.package.begin=パッケージが不正です。[{0}] から開始しなければなりません。
memberImpl.invalid.package.end=不正なパッケージです。[{0}] で終端しなければなりません。
memberImpl.large.payload=ペイロードはtribes が処理するには大きすぎます。
memberImpl.notEnough.bytes=メンバーパッケージのバイト長が不足しています。
memberImpl.package.small=メンバーパッケージが小さすぎて検証できません。
memberImpl.unableParse.hostname=ホスト名を解析できません。
staticMember.invalid.uuidLength=UUIDは正確に16バイトでなければなりません。[{0}]

View File

@@ -0,0 +1,56 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
McastService.domain=도메인 변경 메시지를 보낼 수 없습니다.
McastService.parseSoTimeout=SoTimeout을 파싱할 수 없습니다: [{0}]
McastService.parseTTL=TTL을 파싱할 수 없습니다: [{0}]
McastService.payload=Payload 변경 메시지를 보낼 수 없습니다.
McastService.stopFail=멀티캐스트 서비스를 중단시킬 수 없습니다. 레벨: [{0}]
mcastService.exceed.maxPacketSize=패킷 길이 [{0}]이(가), 최대 패킷 크기인 [{1}] 바이트를 초과합니다.
mcastService.missing.property=McastService: 필수 프로퍼티 [{0}]이(가) 없습니다.
mcastService.noStart=멀티캐스트 전송이 시작되지 않았거나 사용가능 상태가 아닙니다.
mcastServiceImpl.bind=멀티캐스트 소켓을 [{0}:{1}](으)로 바인딩하려 시도 중
mcastServiceImpl.bind.failed=멀티캐스트 주소로 바인딩하지 못했습니다. 포트로만 바인딩합니다.
mcastServiceImpl.error.receiving=멀티캐스트 패키지를 받는 중 오류 발생. 500 밀리초 동안 잠에 들겠습니다.
mcastServiceImpl.invalid.startLevel=유효하지 않은 시작 레벨. 허용되는 레벨들은 Channel.MBR_RX_SEQ와 Channel.MBR_TX_SEQ 뿐입니다.
mcastServiceImpl.invalid.stopLevel=유효하지 않은 중지 레벨. 허용 레벨들은 Channel.MBR_RX_SEQ와 Channel.MBR_TX_SEQ입니다.
mcastServiceImpl.memberDisappeared.failed=멤버 사라짐 메시지를 처리할 수 없습니다.
mcastServiceImpl.packet.tooLong=수신된 멀티캐스트 패킷이 너무 깁니다. 패키지를 무시합니다: [{0}]
mcastServiceImpl.receive.running=McastService.receive가 이미 실행 중입니다.
mcastServiceImpl.recovery=Tribes 멤버십을 복구하려고, 별도의 쓰레드에서 멀티캐스팅을 시도하고 있습니다.
mcastServiceImpl.recovery.failed=복구 시도가 [{0}]회 실패했습니다. [{1}] 초 후에 다시 시도 하겠습니다.
mcastServiceImpl.recovery.startFailed=복구 쓰레드가 멤버십 서비스를 시작하지 못했습니다.
mcastServiceImpl.recovery.stopFailed=복구 쓰레드가 멤버십 서비스를 중지시키지 못했습니다.
mcastServiceImpl.recovery.successful=멤버십 복구가 성공적이었습니다.
mcastServiceImpl.send.failed=멀티캐스트 메시지를 보낼 수 없습니다.
mcastServiceImpl.send.running=McastService.send가 이미 실행 중입니다.
mcastServiceImpl.setInterface=멀티홈 멀티캐스트 인터페이스를 [{0}]에 설정합니다.
mcastServiceImpl.setSoTimeout=클러스터 멀티캐스트의 soTimeout을 [{0}](으)로 설정합니다.
mcastServiceImpl.setTTL=클러스터 멀티캐스트 TTL을 [{0}](으)로 설정합니다.
mcastServiceImpl.unable.join=멀티캐스트 그룹에 참가할 수 없습니다. 귀하의 시스템이 멀티캐스팅을 사용 가능하도록 설정되었는지 확인하십시오.
mcastServiceImpl.unableReceive.broadcastMessage=브로드캐스트 메시지를 수신할 수 없습니다.
mcastServiceImpl.waitForMembers.done=잠들기가 끝났습니다. 멤버십이 확립되었고, 시작 레벨은 [{0}]입니다.
mcastServiceImpl.waitForMembers.start=클러스터 멤버십을 확립하기 위해, [{0}] 밀리초 동안 잠에 듭니다. 시작 레벨: [{1}]
memberImpl.invalid.package.begin=유효하지 않은 패키지입니다. [{0}](으)로 시작해야 합니다.
memberImpl.invalid.package.end=유효하지 않은 패키지. [{0}](으)로 끝나야 합니다.
memberImpl.large.payload=Payload가 너무 커서 tribe들이 처리할 수 없습니다.
memberImpl.notEnough.bytes=멤버 데이터 바이트 배열에 충분한 데이터가 존재하지 않습니다.
memberImpl.package.small=멤버 패키지가 너무 작아서, 유효한지 확인할 수 없습니다.
memberImpl.unableParse.hostname=호스트 이름을 파싱할 수 없습니다.
staticMember.invalid.uuidLength=UUID는 정확히 16 바이트여야만 합니다. [{0}]이어서는 안됩니다.

View File

@@ -0,0 +1,41 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
McastService.parseSoTimeout=无法解析SoTimeout[{0}]
McastService.payload=无法发送负载更新
mcastService.missing.property=McastService缺少必需属性 [{0}]。
mcastServiceImpl.bind=尝试将多播套接字绑定到 [{0}:{1}]
mcastServiceImpl.bind.failed=绑定到多播地址失败。仅绑定到端口。
mcastServiceImpl.error.receiving=接收mcast包时出错。睡眠500毫秒
mcastServiceImpl.invalid.startLevel=无效的启动级别。只接受以下级别Channel.MBR_RX_SEQ或 Channel.MBR_TX_SEQ
mcastServiceImpl.invalid.stopLevel=无效的停止级别。只有Channel.MBR_RX_SEQ和Channel.MBR_TX_SEQ是可接受的级别
mcastServiceImpl.recovery=家族成员,运行恢复线程,广播不是功能。
mcastServiceImpl.recovery.stopFailed=恢复线程未能停止成员服务。
mcastServiceImpl.recovery.successful=成员身份恢复成功。
mcastServiceImpl.send.failed=无法发送多播信息
mcastServiceImpl.send.running=McastService.send已经运行
mcastServiceImpl.setInterface=设置多宿主多播接口为:[{0}]
mcastServiceImpl.setSoTimeout=设置集群多播超时时间:[{0}]
mcastServiceImpl.setTTL=设置集群多播TTL[{0}]
mcastServiceImpl.unable.join=无法加入多播组,请确保你的系统已启用多播。
mcastServiceImpl.unableReceive.broadcastMessage=无法接收广播消息。
memberImpl.large.payload=负载太大以至于难以处理
memberImpl.notEnough.bytes=成员包中的字节不够。
memberImpl.package.small=成员包太小以至于不能校验。
staticMember.invalid.uuidLength=UUID必须正好是16个字节而不是[{0}]

View File

@@ -0,0 +1,653 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.util.Properties;
import javax.management.ObjectName;
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.MembershipListener;
import org.apache.catalina.tribes.MembershipService;
import org.apache.catalina.tribes.MessageListener;
import org.apache.catalina.tribes.io.ChannelData;
import org.apache.catalina.tribes.io.XByteBuffer;
import org.apache.catalina.tribes.jmx.JmxRegistry;
import org.apache.catalina.tribes.util.Arrays;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.catalina.tribes.util.UUIDGenerator;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* 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.
*/
public class McastService
implements MembershipService,MembershipListener,MessageListener, McastServiceMBean {
private static final Log log = LogFactory.getLog(McastService.class);
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
/**
* 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 volatile MembershipListener listener;
/**
* A message listener delegate for broadcasts
*/
protected MessageListener msglistener;
/**
* The local member
*/
protected MemberImpl localMember ;
private int mcastSoTimeout;
private int mcastTTL;
protected byte[] payload;
protected byte[] domain;
private Channel channel;
/**
* the ObjectName of this McastService.
*/
private ObjectName oname = null;
/**
* Create a membership service.
*/
public McastService() {
//default values
setDefaults(this.properties);
}
/**
* Sets the properties for the membership service.
* @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.
*/
@Override
public void setProperties(Properties properties) {
hasProperty(properties,"mcastPort");
hasProperty(properties,"mcastAddress");
hasProperty(properties,"memberDropTime");
hasProperty(properties,"mcastFrequency");
hasProperty(properties,"tcpListenPort");
hasProperty(properties,"tcpListenHost");
setDefaults(properties);
this.properties = properties;
}
/**
* {@inheritDoc}
*/
@Override
public Properties getProperties() {
return properties;
}
/**
* @return the local member name
*/
@Override
public String getLocalMemberName() {
return localMember.toString() ;
}
/**
* {@inheritDoc}
*/
@Override
public Member getLocalMember(boolean alive) {
if ( alive && localMember != null && impl != null) localMember.setMemberAliveTime(System.currentTimeMillis()-impl.getServiceStartTime());
return localMember;
}
/**
* {@inheritDoc}
*/
@Override
public void setLocalMemberProperties(String listenHost, int listenPort, int securePort, int udpPort) {
properties.setProperty("tcpListenHost",listenHost);
properties.setProperty("tcpListenPort",String.valueOf(listenPort));
properties.setProperty("udpListenPort",String.valueOf(udpPort));
properties.setProperty("tcpSecurePort",String.valueOf(securePort));
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.setLocal(true);
}
localMember.setSecurePort(securePort);
localMember.setUdpPort(udpPort);
localMember.getData(true, true);
}catch ( IOException x ) {
throw new IllegalArgumentException(x);
}
}
public void setAddress(String addr) {
properties.setProperty("mcastAddress", addr);
}
@Override
public String getAddress() {
return properties.getProperty("mcastAddress");
}
public void setMcastBindAddress(String bindaddr) {
setBind(bindaddr);
}
public void setBind(String bindaddr) {
properties.setProperty("mcastBindAddress", bindaddr);
}
@Override
public String getBind() {
return properties.getProperty("mcastBindAddress");
}
public void setPort(int port) {
properties.setProperty("mcastPort", String.valueOf(port));
}
public void setRecoveryCounter(int recoveryCounter) {
properties.setProperty("recoveryCounter", String.valueOf(recoveryCounter));
}
@Override
public int getRecoveryCounter(){
String p = properties.getProperty("recoveryCounter");
if(p != null){
return Integer.parseInt(p);
}
return -1;
}
public void setRecoveryEnabled(boolean recoveryEnabled) {
properties.setProperty("recoveryEnabled", String.valueOf(recoveryEnabled));
}
@Override
public boolean getRecoveryEnabled() {
String p = properties.getProperty("recoveryEnabled");
if(p != null){
return Boolean.parseBoolean(p);
}
return false;
}
public void setRecoverySleepTime(long recoverySleepTime) {
properties.setProperty("recoverySleepTime", String.valueOf(recoverySleepTime));
}
@Override
public long getRecoverySleepTime(){
String p = properties.getProperty("recoverySleepTime");
if(p != null){
return Long.parseLong(p);
}
return -1;
}
public void setLocalLoopbackDisabled(boolean localLoopbackDisabled) {
properties.setProperty("localLoopbackDisabled",String.valueOf(localLoopbackDisabled));
}
@Override
public boolean getLocalLoopbackDisabled() {
String p = properties.getProperty("localLoopbackDisabled");
if(p != null){
return Boolean.parseBoolean(p);
}
return false;
}
@Override
public int getPort() {
String p = properties.getProperty("mcastPort");
return Integer.parseInt(p);
}
public void setFrequency(long time) {
properties.setProperty("mcastFrequency", String.valueOf(time));
}
@Override
public long getFrequency() {
String p = properties.getProperty("mcastFrequency");
return Long.parseLong(p);
}
public void setMcastDropTime(long time) {
setDropTime(time);
}
public void setDropTime(long time) {
properties.setProperty("memberDropTime", String.valueOf(time));
}
@Override
public long getDropTime() {
String p = properties.getProperty("memberDropTime");
return Long.parseLong(p);
}
/**
* 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(sm.getString("mcastService.missing.property", name));
}
/**
* Start broadcasting and listening to membership pings
* @throws java.lang.Exception if a IO error occurs
*/
@Override
public void start() throws java.lang.Exception {
start(MembershipService.MBR_RX);
start(MembershipService.MBR_TX);
}
@Override
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");
hasProperty(properties,"tcpSecurePort");
hasProperty(properties,"udpListenPort");
if ( impl != null ) {
impl.start(level);
return;
}
String host = getProperties().getProperty("tcpListenHost");
int port = Integer.parseInt(getProperties().getProperty("tcpListenPort"));
int securePort = Integer.parseInt(getProperties().getProperty("tcpSecurePort"));
int udpPort = Integer.parseInt(getProperties().getProperty("udpListenPort"));
if ( localMember == null ) {
localMember = new MemberImpl(host, port, 100);
localMember.setUniqueId(UUIDGenerator.randomUUID(true));
localMember.setLocal(true);
} else {
localMember.setHostname(host);
localMember.setPort(port);
localMember.setMemberAliveTime(100);
}
localMember.setSecurePort(securePort);
localMember.setUdpPort(udpPort);
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(sm.getString("McastService.parseTTL",
properties.getProperty("mcastTTL")), x);
}
}
if ( properties.getProperty("mcastSoTimeout") != null ) {
try {
soTimeout = Integer.parseInt(properties.getProperty("mcastSoTimeout"));
} catch ( Exception x ) {
log.error(sm.getString("McastService.parseSoTimeout",
properties.getProperty("mcastSoTimeout")), x);
}
}
impl = new McastServiceImpl(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,
this,
Boolean.parseBoolean(properties.getProperty("localLoopbackDisabled")));
String value = properties.getProperty("recoveryEnabled");
boolean recEnabled = Boolean.parseBoolean(value);
impl.setRecoveryEnabled(recEnabled);
int recCnt = Integer.parseInt(properties.getProperty("recoveryCounter"));
impl.setRecoveryCounter(recCnt);
long recSlpTime = Long.parseLong(properties.getProperty("recoverySleepTime"));
impl.setRecoverySleepTime(recSlpTime);
impl.setChannel(channel);
impl.start(level);
// register jmx
JmxRegistry jmxRegistry = JmxRegistry.getRegistry(channel);
if (jmxRegistry != null) {
this.oname = jmxRegistry.registerJmx(",component=Membership", this);
}
}
/**
* Stop broadcasting and listening to membership pings
*/
@Override
public void stop(int svc) {
try {
if ( impl != null && impl.stop(svc) ) {
if (oname != null) {
JmxRegistry.getRegistry(channel).unregisterJmx(oname);
oname = null;
}
impl.setChannel(null);
impl = null;
channel = null;
}
} catch ( Exception x) {
log.error(sm.getString(
"McastService.stopFail", Integer.valueOf(svc)), x);
}
}
/**
* Return all the members by name
*/
@Override
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
*/
@Override
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?
*/
@Override
public boolean hasMembers() {
if ( impl == null || impl.membership == null ) return false;
return impl.membership.hasMembers();
}
@Override
public Member getMember(Member mbr) {
if ( impl == null || impl.membership == null ) return null;
return impl.membership.getMember(mbr);
}
/**
* Return all the members
*/
protected static final Member[]EMPTY_MEMBERS = new Member[0];
@Override
public Member[] getMembers() {
if ( impl == null || impl.membership == null ) return EMPTY_MEMBERS;
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
*/
@Override
public void setMembershipListener(MembershipListener listener) {
this.listener = listener;
}
public void setMessageListener(MessageListener listener) {
this.msglistener = listener;
}
public void removeMessageListener() {
this.msglistener = null;
}
/**
* Remove the membership listener
*/
@Override
public void removeMembershipListener(){
listener = null;
}
@Override
public void memberAdded(Member member) {
MembershipListener listener = this.listener;
if (listener != null) {
listener.memberAdded(member);
}
}
/**
* Callback from the impl when a new member has been received
* @param member The member
*/
@Override
public void memberDisappeared(Member member) {
MembershipListener listener = this.listener;
if (listener != null) {
listener.memberDisappeared(member);
}
}
@Override
public void messageReceived(ChannelMessage msg) {
if (msglistener!=null && msglistener.accept(msg)) msglistener.messageReceived(msg);
}
@Override
public boolean accept(ChannelMessage msg) {
return true;
}
@Override
public void broadcast(ChannelMessage message) throws ChannelException {
if (impl==null || (impl.startLevel & Channel.MBR_TX_SEQ)!=Channel.MBR_TX_SEQ )
throw new ChannelException(sm.getString("mcastService.noStart"));
byte[] data = XByteBuffer.createDataPackage((ChannelData)message);
if (data.length>McastServiceImpl.MAX_PACKET_SIZE) {
throw new ChannelException(sm.getString("mcastService.exceed.maxPacketSize",
Integer.toString(data.length) ,
Integer.toString(McastServiceImpl.MAX_PACKET_SIZE)));
}
DatagramPacket packet = new DatagramPacket(data,0,data.length);
try {
impl.send(false, packet);
} catch (Exception x) {
throw new ChannelException(x);
}
}
@Override
public int getSoTimeout() {
return mcastSoTimeout;
}
public void setSoTimeout(int mcastSoTimeout) {
this.mcastSoTimeout = mcastSoTimeout;
properties.setProperty("mcastSoTimeout", String.valueOf(mcastSoTimeout));
}
@Override
public int getTtl() {
return mcastTTL;
}
public byte[] getPayload() {
return payload;
}
@Override
public byte[] getDomain() {
return domain;
}
public void setTtl(int mcastTTL) {
this.mcastTTL = mcastTTL;
properties.setProperty("mcastTTL", String.valueOf(mcastTTL));
}
@Override
public void setPayload(byte[] payload) {
this.payload = payload;
if ( localMember != null ) {
localMember.setPayload(payload);
try {
if (impl != null) impl.send(false);
}catch ( Exception x ) {
log.error(sm.getString("McastService.payload"), x);
}
}
}
@Override
public void setDomain(byte[] domain) {
this.domain = domain;
if ( localMember != null ) {
localMember.setDomain(domain);
try {
if (impl != null) impl.send(false);
}catch ( Exception x ) {
log.error(sm.getString("McastService.domain"), x);
}
}
}
public void setDomain(String domain) {
if ( domain == null ) return;
if ( domain.startsWith("{") ) setDomain(Arrays.fromString(domain));
else setDomain(Arrays.convert(domain));
}
@Override
public Channel getChannel() {
return channel;
}
@Override
public void setChannel(Channel channel) {
this.channel = channel;
}
protected void setDefaults(Properties properties) {
// default values
if (properties.getProperty("mcastPort") == null)
properties.setProperty("mcastPort","45564");
if (properties.getProperty("mcastAddress") == null)
properties.setProperty("mcastAddress","228.0.0.4");
if (properties.getProperty("memberDropTime") == null)
properties.setProperty("memberDropTime","3000");
if (properties.getProperty("mcastFrequency") == null)
properties.setProperty("mcastFrequency","500");
if (properties.getProperty("recoveryCounter") == null)
properties.setProperty("recoveryCounter", "10");
if (properties.getProperty("recoveryEnabled") == null)
properties.setProperty("recoveryEnabled", "true");
if (properties.getProperty("recoverySleepTime") == null)
properties.setProperty("recoverySleepTime", "5000");
if (properties.getProperty("localLoopbackDisabled") == null)
properties.setProperty("localLoopbackDisabled", "false");
}
/**
* Simple test program
* @param args Command-line arguments
* @throws Exception If an error occurs
*/
public static void main(String args[]) throws Exception {
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");
p.setProperty("tcpSecurePort","4100");
p.setProperty("udpListenPort","4200");
service.setProperties(p);
service.start();
Thread.sleep(60*1000*60);
}
}

View File

@@ -0,0 +1,695 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.BindException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.MembershipListener;
import org.apache.catalina.tribes.MessageListener;
import org.apache.catalina.tribes.io.ChannelData;
import org.apache.catalina.tribes.io.XByteBuffer;
import org.apache.catalina.tribes.util.ExecutorFactory;
import org.apache.catalina.tribes.util.StringManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* 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
*/
public class McastServiceImpl {
private static final Log log = LogFactory.getLog(McastService.class);
protected static final int MAX_PACKET_SIZE = 65535;
protected static final StringManager sm = StringManager.getManager(Constants.Package);
/**
* Internal flag used for the listen thread that listens to the multicasting socket.
*/
protected volatile boolean doRunSender = false;
protected volatile boolean doRunReceiver = false;
protected volatile 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 final MemberImpl member;
/**
* The multicast address
*/
protected final InetAddress address;
/**
* The multicast port
*/
protected final int port;
/**
* The time it takes for a member to expire.
*/
protected final long timeToExpiration;
/**
* How often to we send out a broadcast saying we are alive, must be smaller than timeToExpiration
*/
protected final 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 stuff goes down
*/
protected final MembershipListener service;
/**
* The actual listener for broadcast callbacks
*/
protected final MessageListener msgservice;
/**
* Thread to listen for pings
*/
protected ReceiverThread receiver;
/**
* Thread to send pings
*/
protected SenderThread sender;
/**
* Time to live for the multicast packets that are being sent out
*/
protected final int mcastTTL;
/**
* Read timeout on the mcast socket
*/
protected int mcastSoTimeout = -1;
/**
* bind address
*/
protected final InetAddress mcastBindAddress;
/**
* nr of times the system has to fail before a recovery is initiated
*/
protected int recoveryCounter = 10;
/**
* The time the recovery thread sleeps between recovery attempts
*/
protected long recoverySleepTime = 5000;
/**
* Add the ability to turn on/off recovery
*/
protected boolean recoveryEnabled = true;
/**
* Dont interrupt the sender/receiver thread, but pass off to an executor
*/
protected final ExecutorService executor =
ExecutorFactory.newThreadPool(0, 2, 2, TimeUnit.SECONDS);
/**
* disable/enable local loopback message
*/
protected final boolean localLoopbackDisabled;
private Channel channel;
/**
* Create a new mcast service instance.
* @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 ttl multicast ttl that will be set on the socket
* @param soTimeout Socket timeout
* @param service - the callback service
* @param msgservice Message listener
* @param localLoopbackDisabled - disable loopbackMode
* @throws IOException Init error
*/
public McastServiceImpl(
MemberImpl member,
long sendFrequency,
long expireTime,
int port,
InetAddress bind,
InetAddress mcastAddress,
int ttl,
int soTimeout,
MembershipListener service,
MessageListener msgservice,
boolean localLoopbackDisabled)
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.msgservice = msgservice;
this.sendFrequency = sendFrequency;
this.localLoopbackDisabled = localLoopbackDisabled;
init();
}
public void init() throws IOException {
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);
member.setCommand(new byte[0]);
if ( membership == null ) membership = new Membership(member);
}
protected void setupSocket() throws IOException {
if (mcastBindAddress != null) {
try {
log.info(sm.getString("mcastServiceImpl.bind", address, Integer.toString(port)));
socket = new MulticastSocket(new InetSocketAddress(address,port));
} catch (BindException e) {
/*
* On some platforms (e.g. Linux) it is not possible to bind
* to the multicast address. In this case only bind to the
* port.
*/
log.info(sm.getString("mcastServiceImpl.bind.failed"));
socket = new MulticastSocket(port);
}
} else {
socket = new MulticastSocket(port);
}
socket.setLoopbackMode(localLoopbackDisabled); //hint if we want disable loop back(local machine) messages
if (mcastBindAddress != null) {
if(log.isInfoEnabled())
log.info(sm.getString("mcastServiceImpl.setInterface", 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(sm.getString("mcastServiceImpl.setSoTimeout",
Integer.toString(mcastSoTimeout)));
}
socket.setSoTimeout(mcastSoTimeout);
if ( mcastTTL >= 0 ) {
if(log.isInfoEnabled())
log.info(sm.getString("mcastServiceImpl.setTTL", Integer.toString(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(sm.getString("mcastServiceImpl.receive.running"));
try {
if ( sender == null ) socket.joinGroup(address);
}catch (IOException iox) {
log.error(sm.getString("mcastServiceImpl.unable.join"));
throw iox;
}
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(sm.getString("mcastServiceImpl.send.running"));
if ( receiver == null ) socket.joinGroup(address);
//make sure at least one packet gets out there
send(false);
doRunSender = true;
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(sm.getString("mcastServiceImpl.invalid.startLevel"));
}
//pause, once or twice
waitForMembers(level);
startLevel = (startLevel | level);
}
private void waitForMembers(int level) {
long memberwait = sendFrequency*2;
if(log.isInfoEnabled())
log.info(sm.getString("mcastServiceImpl.waitForMembers.start",
Long.toString(memberwait), Integer.toString(level)));
try {Thread.sleep(memberwait);}catch (InterruptedException ignore){}
if(log.isInfoEnabled())
log.info(sm.getString("mcastServiceImpl.waitForMembers.done", Integer.toString(level)));
}
/**
* Stops the service.
* @param level Stop status
* @return <code>true</code> if the stop is complete
* @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(sm.getString("mcastServiceImpl.invalid.stopLevel"));
}
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);
send(false);
//leave mcast group
try {socket.leaveGroup(address);}catch ( Exception ignore){}
try {socket.close();}catch ( Exception ignore){}
member.setServiceStartTime(-1);
}
return (startLevel == 0);
}
/**
* Receive a datagram packet, locking wait
* @throws IOException Received failed
*/
public void receive() throws IOException {
boolean checkexpired = true;
try {
socket.receive(receivePacket);
if(receivePacket.getLength() > MAX_PACKET_SIZE) {
log.error(sm.getString("mcastServiceImpl.packet.tooLong",
Integer.toString(receivePacket.getLength())));
} else {
byte[] data = new byte[receivePacket.getLength()];
System.arraycopy(receivePacket.getData(), receivePacket.getOffset(), data, 0, data.length);
if (XByteBuffer.firstIndexOf(data,0,MemberImpl.TRIBES_MBR_BEGIN)==0) {
memberDataReceived(data);
} else {
memberBroadcastsReceived(data);
}
}
} 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
}
if (checkexpired) checkExpired();
}
private void memberDataReceived(byte[] data) {
final Member m = MemberImpl.getMember(data);
if (log.isTraceEnabled()) log.trace("Mcast receive ping from member " + m);
Runnable t = null;
if (Arrays.equals(m.getCommand(), Member.SHUTDOWN_PAYLOAD)) {
if (log.isDebugEnabled()) log.debug("Member has shutdown:" + m);
membership.removeMember(m);
t = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
try {
Thread.currentThread().setName("Membership-MemberDisappeared.");
service.memberDisappeared(m);
}finally {
Thread.currentThread().setName(name);
}
}
};
} else if (membership.memberAlive(m)) {
if (log.isDebugEnabled()) log.debug("Mcast add member " + m);
t = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
try {
Thread.currentThread().setName("Membership-MemberAdded.");
service.memberAdded(m);
}finally {
Thread.currentThread().setName(name);
}
}
};
} //end if
if ( t != null ) {
executor.execute(t);
}
}
private void memberBroadcastsReceived(final byte[] b) {
if (log.isTraceEnabled()) log.trace("Mcast received broadcasts.");
XByteBuffer buffer = new XByteBuffer(b,true);
if (buffer.countPackages(true)>0) {
int count = buffer.countPackages();
final ChannelData[] data = new ChannelData[count];
for (int i=0; i<count; i++) {
try {
data[i] = buffer.extractPackage(true);
}catch (IllegalStateException ise) {
log.debug("Unable to decode message.",ise);
}
}
Runnable t = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
try {
Thread.currentThread().setName("Membership-MemberAdded.");
for (int i=0; i<data.length; i++ ) {
try {
if (data[i]!=null && !member.equals(data[i].getAddress())) {
msgservice.messageReceived(data[i]);
}
} catch (Throwable t) {
if (t instanceof ThreadDeath) {
throw (ThreadDeath) t;
}
if (t instanceof VirtualMachineError) {
throw (VirtualMachineError) t;
}
log.error(sm.getString("mcastServiceImpl.unableReceive.broadcastMessage"),t);
}
}
}finally {
Thread.currentThread().setName(name);
}
}
};
executor.execute(t);
}
}
protected final Object expiredMutex = new Object();
protected void checkExpired() {
synchronized (expiredMutex) {
Member[] expired = membership.expire(timeToExpiration);
for (int i = 0; i < expired.length; i++) {
final Member member = expired[i];
if (log.isDebugEnabled())
log.debug("Mcast expire member " + expired[i]);
try {
Runnable t = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
try {
Thread.currentThread().setName("Membership-MemberExpired.");
service.memberDisappeared(member);
}finally {
Thread.currentThread().setName(name);
}
}
};
executor.execute(t);
} catch (Exception x) {
log.error(sm.getString("mcastServiceImpl.memberDisappeared.failed"), x);
}
}
}
}
/**
* Send a ping.
* @param checkexpired <code>true</code> to check for expiration
* @throws IOException Send error
*/
public void send(boolean checkexpired) throws IOException {
send(checkexpired,null);
}
private final Object sendLock = new Object();
public void send(boolean checkexpired, DatagramPacket packet) throws IOException {
checkexpired = (checkexpired && (packet==null));
//ignore if we haven't started the sender
//if ( (startLevel&Channel.MBR_TX_SEQ) != Channel.MBR_TX_SEQ ) return;
if (packet==null) {
member.inc();
if(log.isTraceEnabled()) {
log.trace("Mcast send ping from member " + member);
}
byte[] data = member.getData();
packet = new DatagramPacket(data,data.length);
} else if (log.isTraceEnabled()) {
log.trace("Sending message broadcast "+packet.getLength()+ " bytes from "+ member);
}
packet.setAddress(address);
packet.setPort(port);
//TODO this operation is not thread safe
synchronized (sendLock) {
socket.send(packet);
}
if ( checkexpired ) checkExpired();
}
public long getServiceStartTime() {
return (member!=null) ? member.getServiceStartTime() : -1l;
}
public int getRecoveryCounter() {
return recoveryCounter;
}
public boolean isRecoveryEnabled() {
return recoveryEnabled;
}
public long getRecoverySleepTime() {
return recoverySleepTime;
}
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
public class ReceiverThread extends Thread {
int errorCounter = 0;
public ReceiverThread() {
super();
String channelName = "";
if (channel.getName() != null) channelName = "[" + channel.getName() + "]";
setName("Tribes-MembershipReceiver" + channelName);
}
@Override
public void run() {
while ( doRunReceiver ) {
try {
receive();
errorCounter=0;
} 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 ) {
if (errorCounter==0 && doRunReceiver) log.warn(sm.getString("mcastServiceImpl.error.receiving"),x);
else if (log.isDebugEnabled()) log.debug("Error receiving mcast package"+(doRunReceiver?". Sleeping 500ms":"."),x);
if (doRunReceiver) {
try { Thread.sleep(500); } catch ( Exception ignore ){}
if ( (++errorCounter)>=recoveryCounter ) {
errorCounter=0;
RecoveryThread.recover(McastServiceImpl.this);
}
}
}
}
}
}//class ReceiverThread
public class SenderThread extends Thread {
final long time;
int errorCounter=0;
public SenderThread(long time) {
this.time = time;
String channelName = "";
if (channel.getName() != null) channelName = "[" + channel.getName() + "]";
setName("Tribes-MembershipSender" + channelName);
}
@Override
public void run() {
while ( doRunSender ) {
try {
send(true);
errorCounter = 0;
} catch ( Exception x ) {
if (errorCounter==0) log.warn(sm.getString("mcastServiceImpl.send.failed"),x);
else log.debug("Unable to send mcast message.",x);
if ( (++errorCounter)>=recoveryCounter ) {
errorCounter=0;
RecoveryThread.recover(McastServiceImpl.this);
}
}
try { Thread.sleep(time); } catch ( Exception ignore ) {}
}
}
}//class SenderThread
protected static class RecoveryThread extends Thread {
private static final AtomicBoolean running = new AtomicBoolean(false);
public static synchronized void recover(McastServiceImpl parent) {
if (!parent.isRecoveryEnabled()) {
return;
}
if (!running.compareAndSet(false, true)) {
return;
}
Thread t = new RecoveryThread(parent);
String channelName = "";
if (parent.channel.getName() != null) channelName = "[" + parent.channel.getName() + "]";
t.setName("Tribes-MembershipRecovery" + channelName);
t.setDaemon(true);
t.start();
}
final McastServiceImpl parent;
public RecoveryThread(McastServiceImpl parent) {
this.parent = parent;
}
public boolean stopService() {
try {
parent.stop(Channel.MBR_RX_SEQ | Channel.MBR_TX_SEQ);
return true;
} catch (Exception x) {
log.warn(sm.getString("mcastServiceImpl.recovery.stopFailed"), x);
return false;
}
}
public boolean startService() {
try {
parent.init();
parent.start(Channel.MBR_RX_SEQ | Channel.MBR_TX_SEQ);
return true;
} catch (Exception x) {
log.warn(sm.getString("mcastServiceImpl.recovery.startFailed"), x);
return false;
}
}
@Override
public void run() {
boolean success = false;
int attempt = 0;
try {
while (!success) {
if(log.isInfoEnabled())
log.info(sm.getString("mcastServiceImpl.recovery"));
if (stopService() & startService()) {
success = true;
if(log.isInfoEnabled())
log.info(sm.getString("mcastServiceImpl.recovery.successful"));
}
try {
if (!success) {
if(log.isInfoEnabled())
log.info(sm.getString("mcastServiceImpl.recovery.failed",
Integer.toString(++attempt),
Long.toString(parent.recoverySleepTime)));
Thread.sleep(parent.recoverySleepTime);
}
}catch (InterruptedException ignore) {
}
}
}finally {
running.set(false);
}
}
}
public void setRecoveryCounter(int recoveryCounter) {
this.recoveryCounter = recoveryCounter;
}
public void setRecoveryEnabled(boolean recoveryEnabled) {
this.recoveryEnabled = recoveryEnabled;
}
public void setRecoverySleepTime(long recoverySleepTime) {
this.recoverySleepTime = recoverySleepTime;
}
}

View File

@@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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;
public interface McastServiceMBean {
// Attributes
public String getAddress();
public int getPort();
public long getFrequency();
public long getDropTime();
public String getBind();
public int getTtl();
public byte[] getDomain();
public int getSoTimeout();
public boolean getRecoveryEnabled();
public int getRecoveryCounter();
public long getRecoverySleepTime();
public boolean getLocalLoopbackDisabled();
public String getLocalMemberName();
// Operation
public Properties getProperties();
public boolean hasMembers();
public String[] getMembersByName();
public Member findMemberByName(String name);
}

View File

@@ -0,0 +1,687 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.io.XByteBuffer;
import org.apache.catalina.tribes.transport.SenderState;
import org.apache.catalina.tribes.util.StringManager;
/**
* A <b>membership</b> implementation using simple multicast.
* This is the representation of a multicast member.
* Carries the host, and port of the this or other cluster nodes.
*/
public class MemberImpl implements Member, java.io.Externalizable {
/**
* Should a call to getName or getHostName try to do a DNS lookup?
* default is false
*/
public static final boolean DO_DNS_LOOKUPS = Boolean.parseBoolean(System.getProperty("org.apache.catalina.tribes.dns_lookups","false"));
public static final transient byte[] TRIBES_MBR_BEGIN = new byte[] {84, 82, 73, 66, 69, 83, 45, 66, 1, 0};
public static final transient byte[] TRIBES_MBR_END = new byte[] {84, 82, 73, 66, 69, 83, 45, 69, 1, 0};
protected static final StringManager sm = StringManager.getManager(Constants.Package);
/**
* The listen host for this member
*/
protected volatile byte[] host = new byte[0];
protected transient volatile String hostname;
/**
* The tcp listen port for this member
*/
protected volatile int port;
/**
* The udp listen port for this member
*/
protected volatile int udpPort = -1;
/**
* The tcp/SSL listen port for this member
*/
protected volatile int securePort = -1;
/**
* Counter for how many broadcast messages have been sent from this member
*/
protected AtomicInteger msgCount = new AtomicInteger(0);
/**
* The number of milliseconds since this member was
* created, is kept track of using the start time
*/
protected volatile long memberAliveTime = 0;
/**
* For the local member only
*/
protected transient long serviceStartTime;
/**
* To avoid serialization over and over again, once the local dataPkg
* has been set, we use that to transmit data
*/
protected transient byte[] dataPkg = null;
/**
* Unique session Id for this member
*/
protected volatile byte[] uniqueId = new byte[16];
/**
* Custom payload that an app framework can broadcast
* Also used to transport stop command.
*/
protected volatile byte[] payload = new byte[0];
/**
* Command, so that the custom payload doesn't have to be used
* This is for internal tribes use, such as SHUTDOWN_COMMAND
*/
protected volatile byte[] command = new byte[0];
/**
* Domain if we want to filter based on domain.
*/
protected volatile byte[] domain = new byte[0];
/**
* The flag indicating that this member is a local member.
*/
protected volatile boolean local = false;
/**
* Empty constructor for serialization
*/
public MemberImpl() {
}
/**
* Construct a new member object.
*
* @param host - the tcp listen host
* @param port - the tcp listen port
* @param aliveTime - the number of milliseconds since this member was created
*
* @throws IOException If there is an error converting the host name to an
* IP address
*/
public MemberImpl(String host,
int port,
long aliveTime) throws IOException {
setHostname(host);
this.port = port;
this.memberAliveTime=aliveTime;
}
public MemberImpl(String host,
int port,
long aliveTime,
byte[] payload) throws IOException {
this(host,port,aliveTime);
setPayload(payload);
}
@Override
public boolean isReady() {
return SenderState.getSenderState(this).isReady();
}
@Override
public boolean isSuspect() {
return SenderState.getSenderState(this).isSuspect();
}
@Override
public boolean isFailing() {
return SenderState.getSenderState(this).isFailing();
}
/**
* Increment the message count.
*/
protected void inc() {
msgCount.incrementAndGet();
}
/**
* Create a data package to send over the wire representing this member.
* This is faster than serialization.
* @return - the bytes for this member deserialized
*/
public byte[] getData() {
return getData(true);
}
@Override
public byte[] getData(boolean getalive) {
return getData(getalive,false);
}
@Override
public synchronized int getDataLength() {
return TRIBES_MBR_BEGIN.length+ //start pkg
4+ //data length
8+ //alive time
4+ //port
4+ //secure port
4+ //udp port
1+ //host length
host.length+ //host
4+ //command length
command.length+ //command
4+ //domain length
domain.length+ //domain
16+ //unique id
4+ //payload length
payload.length+ //payload
TRIBES_MBR_END.length; //end pkg
}
@Override
public synchronized byte[] getData(boolean getalive, boolean reset) {
if (reset) {
dataPkg = null;
}
// Look in cache first
if (dataPkg != null) {
if (getalive) {
// You'd be surprised, but System.currentTimeMillis
// shows up on the profiler
long alive = System.currentTimeMillis() - getServiceStartTime();
byte[] result = dataPkg.clone();
XByteBuffer.toBytes(alive, result, TRIBES_MBR_BEGIN.length + 4);
dataPkg = result;
}
return dataPkg;
}
//package looks like
//start package TRIBES_MBR_BEGIN.length
//package length - 4 bytes
//alive - 8 bytes
//port - 4 bytes
//secure port - 4 bytes
//udp port - 4 bytes
//host length - 1 byte
//host - hl bytes
//clen - 4 bytes
//command - clen bytes
//dlen - 4 bytes
//domain - dlen bytes
//uniqueId - 16 bytes
//payload length - 4 bytes
//payload plen bytes
//end package TRIBES_MBR_END.length
long alive=System.currentTimeMillis()-getServiceStartTime();
byte[] data = new byte[getDataLength()];
int bodylength = (getDataLength() - TRIBES_MBR_BEGIN.length - TRIBES_MBR_END.length - 4);
int pos = 0;
//TRIBES_MBR_BEGIN
System.arraycopy(TRIBES_MBR_BEGIN,0,data,pos,TRIBES_MBR_BEGIN.length);
pos += TRIBES_MBR_BEGIN.length;
//body length
XByteBuffer.toBytes(bodylength,data,pos);
pos += 4;
//alive data
XByteBuffer.toBytes(alive,data,pos);
pos += 8;
//port
XByteBuffer.toBytes(port,data,pos);
pos += 4;
//secure port
XByteBuffer.toBytes(securePort,data,pos);
pos += 4;
//udp port
XByteBuffer.toBytes(udpPort,data,pos);
pos += 4;
//host length
data[pos++] = (byte) host.length;
//host
System.arraycopy(host,0,data,pos,host.length);
pos+=host.length;
//clen - 4 bytes
XByteBuffer.toBytes(command.length,data,pos);
pos+=4;
//command - clen bytes
System.arraycopy(command,0,data,pos,command.length);
pos+=command.length;
//dlen - 4 bytes
XByteBuffer.toBytes(domain.length,data,pos);
pos+=4;
//domain - dlen bytes
System.arraycopy(domain,0,data,pos,domain.length);
pos+=domain.length;
//unique Id
System.arraycopy(uniqueId,0,data,pos,uniqueId.length);
pos+=uniqueId.length;
//payload
XByteBuffer.toBytes(payload.length,data,pos);
pos+=4;
System.arraycopy(payload,0,data,pos,payload.length);
pos+=payload.length;
//TRIBES_MBR_END
System.arraycopy(TRIBES_MBR_END,0,data,pos,TRIBES_MBR_END.length);
pos += TRIBES_MBR_END.length;
//create local data
dataPkg = data;
return data;
}
/**
* Deserializes a member from data sent over the wire.
*
* @param data The bytes received
* @param member The member object to populate
*
* @return The populated member object.
*/
public static Member getMember(byte[] data, MemberImpl member) {
return getMember(data,0,data.length,member);
}
public static Member getMember(byte[] data, int offset, int length, MemberImpl member) {
//package looks like
//start package TRIBES_MBR_BEGIN.length
//package length - 4 bytes
//alive - 8 bytes
//port - 4 bytes
//secure port - 4 bytes
//udp port - 4 bytes
//host length - 1 byte
//host - hl bytes
//clen - 4 bytes
//command - clen bytes
//dlen - 4 bytes
//domain - dlen bytes
//uniqueId - 16 bytes
//payload length - 4 bytes
//payload plen bytes
//end package TRIBES_MBR_END.length
int pos = offset;
if (XByteBuffer.firstIndexOf(data,offset,TRIBES_MBR_BEGIN)!=pos) {
throw new IllegalArgumentException(sm.getString("memberImpl.invalid.package.begin", org.apache.catalina.tribes.util.Arrays.toString(TRIBES_MBR_BEGIN)));
}
if ( length < (TRIBES_MBR_BEGIN.length+4) ) {
throw new ArrayIndexOutOfBoundsException(sm.getString("memberImpl.package.small"));
}
pos += TRIBES_MBR_BEGIN.length;
int bodylength = XByteBuffer.toInt(data,pos);
pos += 4;
if ( length < (bodylength+4+TRIBES_MBR_BEGIN.length+TRIBES_MBR_END.length) ) {
throw new ArrayIndexOutOfBoundsException(sm.getString("memberImpl.notEnough.bytes"));
}
int endpos = pos+bodylength;
if (XByteBuffer.firstIndexOf(data,endpos,TRIBES_MBR_END)!=endpos) {
throw new IllegalArgumentException(sm.getString("memberImpl.invalid.package.end", org.apache.catalina.tribes.util.Arrays.toString(TRIBES_MBR_END)));
}
byte[] alived = new byte[8];
System.arraycopy(data, pos, alived, 0, 8);
pos += 8;
byte[] portd = new byte[4];
System.arraycopy(data, pos, portd, 0, 4);
pos += 4;
byte[] sportd = new byte[4];
System.arraycopy(data, pos, sportd, 0, 4);
pos += 4;
byte[] uportd = new byte[4];
System.arraycopy(data, pos, uportd, 0, 4);
pos += 4;
byte hl = data[pos++];
byte[] addr = new byte[hl];
System.arraycopy(data, pos, addr, 0, hl);
pos += hl;
int cl = XByteBuffer.toInt(data, pos);
pos += 4;
byte[] command = new byte[cl];
System.arraycopy(data, pos, command, 0, command.length);
pos += command.length;
int dl = XByteBuffer.toInt(data, pos);
pos += 4;
byte[] domain = new byte[dl];
System.arraycopy(data, pos, domain, 0, domain.length);
pos += domain.length;
byte[] uniqueId = new byte[16];
System.arraycopy(data, pos, uniqueId, 0, 16);
pos += 16;
int pl = XByteBuffer.toInt(data, pos);
pos += 4;
byte[] payload = new byte[pl];
System.arraycopy(data, pos, payload, 0, payload.length);
pos += payload.length;
member.setHost(addr);
member.setPort(XByteBuffer.toInt(portd, 0));
member.setSecurePort(XByteBuffer.toInt(sportd, 0));
member.setUdpPort(XByteBuffer.toInt(uportd, 0));
member.setMemberAliveTime(XByteBuffer.toLong(alived, 0));
member.setUniqueId(uniqueId);
member.payload = payload;
member.domain = domain;
member.command = command;
member.dataPkg = new byte[length];
System.arraycopy(data, offset, member.dataPkg, 0, length);
return member;
}
public static Member getMember(byte[] data) {
return getMember(data,new MemberImpl());
}
public static Member getMember(byte[] data, int offset, int length) {
return getMember(data,offset,length,new MemberImpl());
}
/**
* Return the name of this object
* @return a unique name to the cluster
*/
@Override
public String getName() {
return "tcp://"+getHostname()+":"+getPort();
}
/**
* Return the listen port of this member
* @return - tcp listen port
*/
@Override
public int getPort() {
return this.port;
}
/**
* Return the TCP listen host for this member
* @return IP address or host name
*/
@Override
public byte[] getHost() {
return host;
}
public String getHostname() {
if ( this.hostname != null ) return hostname;
else {
try {
byte[] host = this.host;
if (DO_DNS_LOOKUPS)
this.hostname = java.net.InetAddress.getByAddress(host).getHostName();
else
this.hostname = org.apache.catalina.tribes.util.Arrays.toString(host,0,host.length,true);
return this.hostname;
}catch ( IOException x ) {
throw new RuntimeException(sm.getString("memberImpl.unableParse.hostname"),x);
}
}
}
public int getMsgCount() {
return msgCount.get();
}
/**
* 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 cluster.
* @return nr of milliseconds since this member started.
*/
@Override
public long getMemberAliveTime() {
return memberAliveTime;
}
public long getServiceStartTime() {
return serviceStartTime;
}
@Override
public byte[] getUniqueId() {
return uniqueId;
}
@Override
public byte[] getPayload() {
return payload;
}
@Override
public byte[] getCommand() {
return command;
}
@Override
public byte[] getDomain() {
return domain;
}
@Override
public int getSecurePort() {
return securePort;
}
@Override
public int getUdpPort() {
return udpPort;
}
@Override
public void setMemberAliveTime(long time) {
memberAliveTime=time;
}
/**
* String representation of this object
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder(getClass().getName());
buf.append("[");
buf.append(getName()).append(",");
buf.append(getHostname()).append(",");
buf.append(port).append(", alive=");
buf.append(memberAliveTime).append(", ");
buf.append("securePort=").append(securePort).append(", ");
buf.append("UDP Port=").append(udpPort).append(", ");
buf.append("id=").append(bToS(this.uniqueId)).append(", ");
buf.append("payload=").append(bToS(this.payload,8)).append(", ");
buf.append("command=").append(bToS(this.command,8)).append(", ");
buf.append("domain=").append(bToS(this.domain,8));
buf.append("]");
return buf.toString();
}
public static String bToS(byte[] data) {
return bToS(data,data.length);
}
public static String bToS(byte[] data, int max) {
StringBuilder buf = new StringBuilder(4*16);
buf.append("{");
for (int i=0; data!=null && i<data.length; i++ ) {
buf.append(String.valueOf(data[i])).append(" ");
if ( i==max ) {
buf.append("...("+data.length+")");
break;
}
}
buf.append("}");
return buf.toString();
}
/**
* @see java.lang.Object#hashCode()
* @return The hash code
*/
@Override
public int hashCode() {
return getHost()[0]+getHost()[1]+getHost()[2]+getHost()[3];
}
/**
* Returns true if the param o is a McastMember with the same name
*
* @param o The object to test for equality
*/
@Override
public boolean equals(Object o) {
if ( o instanceof MemberImpl ) {
return Arrays.equals(this.getHost(),((MemberImpl)o).getHost()) &&
this.getPort() == ((MemberImpl)o).getPort() &&
Arrays.equals(this.getUniqueId(),((MemberImpl)o).getUniqueId());
}
else
return false;
}
public synchronized void setHost(byte[] host) {
this.host = host;
}
public void setHostname(String host) throws IOException {
hostname = host;
synchronized (this) {
this.host = java.net.InetAddress.getByName(host).getAddress();
}
}
public void setMsgCount(int msgCount) {
this.msgCount.set(msgCount);
}
public synchronized void setPort(int port) {
this.port = port;
this.dataPkg = null;
}
public void setServiceStartTime(long serviceStartTime) {
this.serviceStartTime = serviceStartTime;
}
public synchronized void setUniqueId(byte[] uniqueId) {
this.uniqueId = uniqueId!=null?uniqueId:new byte[16];
getData(true,true);
}
@Override
public synchronized void setPayload(byte[] payload) {
// longs to avoid any possibility of overflow
long oldPayloadLength = 0;
if (this.payload != null) {
oldPayloadLength = this.payload.length;
}
long newPayloadLength = 0;
if (payload != null) {
newPayloadLength = payload.length;
}
if (newPayloadLength > oldPayloadLength) {
// It is possible that the max packet size will be exceeded
if ((newPayloadLength - oldPayloadLength + getData(false, false).length) >
McastServiceImpl.MAX_PACKET_SIZE) {
throw new IllegalArgumentException(sm.getString("memberImpl.large.payload"));
}
}
this.payload = payload != null ? payload : new byte[0];
getData(true, true);
}
@Override
public synchronized void setCommand(byte[] command) {
this.command = command!=null?command:new byte[0];
getData(true,true);
}
public synchronized void setDomain(byte[] domain) {
this.domain = domain!=null?domain:new byte[0];
getData(true,true);
}
public synchronized void setSecurePort(int securePort) {
this.securePort = securePort;
this.dataPkg = null;
}
public synchronized void setUdpPort(int port) {
this.udpPort = port;
this.dataPkg = null;
}
@Override
public boolean isLocal() {
return local;
}
@Override
public void setLocal(boolean local) {
this.local = local;
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int length = in.readInt();
byte[] message = new byte[length];
in.readFully(message);
getMember(message,this);
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
byte[] data = this.getData();
out.writeInt(data.length);
out.write(data);
}
}

View File

@@ -0,0 +1,350 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import org.apache.catalina.tribes.Member;
/**
* 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 Peter Rossbach
*/
public class Membership implements Cloneable {
protected static final Member[] EMPTY_MEMBERS = new Member[0];
// Non-final to support clone()
private Object membersLock = new Object();
/**
* The local member.
*/
protected final Member local;
/**
* A map of all the members in the cluster.
*/
protected HashMap<Member, MbrEntry> map = new HashMap<>(); // Guarded by membersLock
/**
* A list of all the members in the cluster.
*/
protected volatile Member[] members = EMPTY_MEMBERS; // Guarded by membersLock
/**
* Comparator for sorting members by alive time.
*/
protected final Comparator<Member> memberComparator;
@Override
public Membership clone() {
synchronized (membersLock) {
Membership clone;
try {
clone = (Membership) super.clone();
} catch (CloneNotSupportedException e) {
// Can't happen
throw new AssertionError();
}
// Standard clone() method will copy the map object. Replace that
// with a new map but with the same contents.
@SuppressWarnings("unchecked")
final HashMap<Member, MbrEntry> tmpclone = (HashMap<Member, MbrEntry>) map.clone();
clone.map = tmpclone;
// Standard clone() method will copy the array object. Replace that
// with a new array but with the same contents.
clone.members = members.clone();
// Standard clone() method will copy the lock object. Replace that
// with a new object.
clone.membersLock = new Object();
return clone;
}
}
/**
* Constructs a new membership
* @param local - has to be the name of the local member. Used to filter the local member from the cluster membership
* @param includeLocal - TBA
*/
public Membership(Member local, boolean includeLocal) {
this(local, new MemberComparator(), includeLocal);
}
public Membership(Member local) {
this(local, false);
}
public Membership(Member local, Comparator<Member> comp) {
this(local, comp, false);
}
public Membership(Member local, Comparator<Member> comp, boolean includeLocal) {
this.local = local;
this.memberComparator = comp;
if (includeLocal) {
addMember(local);
}
}
/**
* Reset the membership and start over fresh. i.e., delete all the members
* and wait for them to ping again and join this membership.
*/
public void reset() {
synchronized (membersLock) {
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.<br>
* - false if this member is the local member or updated.
*/
public boolean memberAlive(Member member) {
// Ignore ourselves
if (member.equals(local)) {
return false;
}
boolean result = false;
synchronized (membersLock) {
MbrEntry entry = map.get(member);
if (entry == null) {
entry = addMember(member);
result = true;
} else {
// Update the member alive time
Member 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());
// Re-order. Can't sort in place since a call to
// getMembers() may then receive an intermediate result.
Member[] newMembers = members.clone();
Arrays.sort(newMembers, memberComparator);
members = newMembers;
}
}
entry.accessed();
}
return result;
}
/**
* Add a member to this component and sort array with memberComparator
*
* @param member The member to add
*
* @return The member entry created for this new member.
*/
public MbrEntry addMember(Member member) {
MbrEntry entry = new MbrEntry(member);
synchronized (membersLock) {
if (!map.containsKey(member) ) {
map.put(member, entry);
Member results[] = new Member[members.length + 1];
System.arraycopy(members, 0, results, 0, members.length);
results[members.length] = member;
Arrays.sort(results, memberComparator);
members = results;
}
}
return entry;
}
/**
* Remove a member from this component.
*
* @param member The member to remove
*/
public void removeMember(Member member) {
synchronized (membersLock) {
map.remove(member);
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;
Member results[] = new Member[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 Member[] expire(long maxtime) {
synchronized (membersLock) {
if (!hasMembers()) {
return EMPTY_MEMBERS;
}
ArrayList<Member> list = null;
for (MbrEntry entry : map.values()) {
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) {
Member[] result = new Member[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.
*
* @return <code>true</code> if there are one or more members, otherwise
* <code>false</code>
*/
public boolean hasMembers() {
return members.length > 0;
}
public Member getMember(Member mbr) {
Member[] members = this.members;
if (members.length > 0) {
for (int i = 0; i < members.length; i++) {
if (members[i].equals(mbr)) {
return members[i];
}
}
}
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.
*
* @return An array of the current members
*/
public Member[] getMembers() {
return members;
}
// --------------------------------------------- Inner Class
private static class MemberComparator implements Comparator<Member>, Serializable {
private static final long serialVersionUID = 1L;
@Override
public int compare(Member m1, Member 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 final Member mbr;
protected long lastHeardFrom;
public MbrEntry(Member mbr) {
this.mbr = mbr;
}
/**
* Indicate that this member has been accessed.
*/
public void accessed(){
lastHeardFrom = System.currentTimeMillis();
}
/**
* Obtain the member associated with this entry.
*
* @return The member for this entry.
*/
public Member getMember() {
return mbr;
}
/**
* Check if this member has expired.
*
* @param maxtime The time threshold
*
* @return <code>true</code> if the member has expired, otherwise
* <code>false</code>
*/
public boolean hasExpired(long maxtime) {
long delta = System.currentTimeMillis() - lastHeardFrom;
return delta > maxtime;
}
}
}

Some files were not shown because too many files have changed in this diff Show More