init
This commit is contained in:
120
java/org/apache/catalina/tribes/group/AbsoluteOrder.java
Normal file
120
java/org/apache/catalina/tribes/group/AbsoluteOrder.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
359
java/org/apache/catalina/tribes/group/ChannelCoordinator.java
Normal file
359
java/org/apache/catalina/tribes/group/ChannelCoordinator.java
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
820
java/org/apache/catalina/tribes/group/GroupChannel.java
Normal file
820
java/org/apache/catalina/tribes/group/GroupChannel.java
Normal 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 -> C -> B</code><br>
|
||||
* The complete stack will look like this:<br>
|
||||
* <code>Channel -> A -> C -> B -> 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 > 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 > 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
|
||||
|
||||
|
||||
|
||||
}
|
||||
62
java/org/apache/catalina/tribes/group/GroupChannelMBean.java
Normal file
62
java/org/apache/catalina/tribes/group/GroupChannelMBean.java
Normal 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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 へ返信を送信できません。
|
||||
@@ -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에서 응답을 되돌려 보낼 수 없습니다.
|
||||
@@ -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_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ
|
||||
|
||||
groupChannel.listener.alreadyExist=侦听器已存在:[{0}][{1}]
|
||||
groupChannel.noDestination=没有指定目的地
|
||||
groupChannel.nullMessage=无法发送空消息
|
||||
groupChannel.optionFlag.conflict=拦截器选项标志冲突:[{0}]
|
||||
groupChannel.unable.deserialize=无法反序列化消息:[{0}]
|
||||
groupChannel.unable.sendHeartbeat=无法通过Tribes拦截器堆栈发送心跳。 会再试一次。
|
||||
53
java/org/apache/catalina/tribes/group/Response.java
Normal file
53
java/org/apache/catalina/tribes/group/Response.java
Normal 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;
|
||||
}
|
||||
}
|
||||
46
java/org/apache/catalina/tribes/group/RpcCallback.java
Normal file
46
java/org/apache/catalina/tribes/group/RpcCallback.java
Normal 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);
|
||||
|
||||
}
|
||||
301
java/org/apache/catalina/tribes/group/RpcChannel.java
Normal file
301
java/org/apache/catalina/tribes/group/RpcChannel.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
111
java/org/apache/catalina/tribes/group/RpcMessage.java
Normal file
111
java/org/apache/catalina/tribes/group/RpcMessage.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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=<milliseconds> - how long do we keep the fragments in memory and wait for the rest to arrive <b>default=60,000ms -> 60seconds</b>
|
||||
* This setting is useful to avoid OutOfMemoryErrors<br>
|
||||
* FragmentationInterceptor.maxSize=<max message size> - 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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}]
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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}]
|
||||
@@ -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}]
|
||||
@@ -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}]
|
||||
@@ -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}]
|
||||
@@ -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=无法在两阶段提交拦截器上执行心跳。
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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=<milliseconds> - if a message arrives out of order, how long before we act on it <b>default=3000ms</b><br>
|
||||
* OrderInterceptor.maxQueue=<max queue size> - how much can the queue grow to ensure ordering.
|
||||
* This setting is useful to avoid OutOfMemoryErrors<b>default=Integer.MAX_VALUE</b><br>
|
||||
* OrderInterceptor.forwardExpired=<boolean> - this flag tells the interceptor what to
|
||||
* do when a message has expired or the queue has grown larger than the maxQueue value.
|
||||
* true means that the message is sent up the stack to the receiver that will receive and out of order message
|
||||
* false means, forget the message and reset the message counter. <b>default=true</b>
|
||||
*
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 "timed out"
|
||||
* This failure detector will intercept the memberDisappeared message(unless its a true shutdown message)
|
||||
* and connect to the member using TCP.
|
||||
* </p>
|
||||
* <p>
|
||||
* The TcpFailureDetector works in two ways. <br>
|
||||
* 1. It intercepts memberDisappeared events
|
||||
* 2. It catches send errors
|
||||
* </p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user