package org.jgroups.stack;
import org.jgroups.Event;
import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.annotations.Property;
import org.jgroups.conf.ClassConfigurator;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.TP;
import org.jgroups.util.MessageBatch;
import org.jgroups.util.SocketFactory;
import org.jgroups.util.ThreadFactory;
import org.jgroups.util.Util;
import org.w3c.dom.Node;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* The Protocol class provides a set of common services for protocol layers. Each layer has to
* be a subclass of Protocol and override a number of methods (typically just {@code up()},
* {@code down()} and {@code getName()}. Layers are stacked in a certain order to form
* a protocol stack. <a href=org.jgroups.Event.html>Events</a> are passed from lower
* layers to upper ones and vice versa. E.g. a Message received by the UDP layer at the bottom
* will be passed to its higher layer as an Event. That layer will in turn pass the Event to
* its layer and so on, until a layer handles the Message and sends a response or discards it,
* the former resulting in another Event being passed down the stack.
* <p/>
* The important thing to bear in mind is that Events have to passed on between layers in FIFO
* order which is guaranteed by the Protocol implementation and must be guranteed by subclasses
* implementing their on Event queuing.<p>
* <b>Note that each class implementing interface Protocol MUST provide an empty, public
* constructor !</b>
*
* @author Bela Ban
*/
public abstract class Protocol {
protected Protocol up_prot, down_prot;
protected ProtocolStack stack;
@Property(description="Determines whether to collect statistics (and expose them via JMX). Default is true")
protected boolean stats=true;
@Property(description="Enables ergonomics: dynamically find the best values for properties at runtime")
protected boolean ergonomics=true;
@Property(description="Fully qualified name of a class implementing ProtocolHook, will be called after creation of " +
"the protocol (before init())",writable=false)
protected String after_creation_hook;
@Property(description="Give the protocol a different ID if needed so we can have multiple " +
"instances of it in the same stack",writable=false)
protected short id=ClassConfigurator.getProtocolId(getClass());
protected final Log log=LogFactory.getLog(this.getClass());
/**
* Sets the level of a logger. This method is used to dynamically change the logging level of a running system,
* e.g. via JMX. The appender of a level needs to exist.
* @param level The new level. Valid values are "fatal", "error", "warn", "info", "debug", "trace"
* (capitalization not relevant)
*/
public <T extends Protocol> T setLevel(String level) {log.setLevel(level); return (T)this;}
@Property(name="level", description="logger level (see javadocs)")
public String getLevel() {return log.getLevel();}
public <T extends Protocol> T level(String level) {return setLevel(level);}
public boolean isErgonomics() {return ergonomics;}
public <T extends Protocol> T setErgonomics(boolean ergonomics) {this.ergonomics=ergonomics; return (T)this;}
public ProtocolStack getProtocolStack() {return stack;}
public boolean statsEnabled() {return stats;}
public void enableStats(boolean flag) {stats=flag;}
public String getName() {return getClass().getSimpleName();}
public short getId() {return id;}
public <T extends Protocol> T setId(short id) {this.id=id; return (T)this;}
public <T extends Protocol> T getUpProtocol() {return (T)up_prot;}
public <T extends Protocol> T getDownProtocol() {return (T)down_prot;}
public <T extends Protocol> T setUpProtocol(Protocol prot) {this.up_prot=prot; return (T)this;}
public <T extends Protocol> T setDownProtocol(Protocol prot) {this.down_prot=prot; return (T)this;}
public <T extends Protocol> T setProtocolStack(ProtocolStack s) {this.stack=s; return (T)this;}
public String afterCreationHook() {return after_creation_hook;}
public Log getLog() {return log;}
public Object getValue(String name) {
if(name == null) return null;
Field field=Util.getField(getClass(), name);
if(field == null)
throw new IllegalArgumentException("field \"" + name + "\n not found");
return Util.getField(field, this);
}
public Protocol setValue(String name, Object value) {
if(name == null || value == null)
return this;
Field field=Util.getField(getClass(), name);
if(field == null)
throw new IllegalArgumentException("field " + name + " not found");
Property prop=field.getAnnotation(Property.class);
if(prop != null) {
String deprecated_msg=prop.deprecatedMessage();
if(deprecated_msg != null && !deprecated_msg.isEmpty())
log.warn("Field " + getName() + "." + name + " is deprecated: " + deprecated_msg);
}
Util.setField(field, this, value);
return this;
}
/**
* After configuring the protocol itself from the properties defined in the XML config, a protocol might have
* additional objects which need to be configured. This callback allows a protocol developer to configure those
* other objects. This call is guaranteed to be invoked <em>after</em> the protocol itself has
* been configured. See AUTH for an example.
* @return
*/
protected List<Object> getConfigurableObjects() {return null;}
/** Called by the XML parser when subelements are found in the configuration of a protocol. This allows
* a protocol to define protocol-specific information and to parse it */
public void parse(Node node) throws Exception {;}
/** Returns the protocol IDs of all protocols above this one (excluding the current protocol) */
public short[] getIdsAbove() {
short[] retval;
List<Short> ids=new ArrayList<>();
Protocol current=up_prot;
while(current != null) {
ids.add(current.getId());
current=current.up_prot;
}
retval=new short[ids.size()];
for(int i=0; i < ids.size(); i++)
retval[i]=ids.get(i);
return retval;
}
protected TP getTransport() {
Protocol retval=this;
while(retval != null && retval.down_prot != null) {
retval=retval.down_prot;
}
return (TP)retval;
}
/** Supposed to be overwritten by subclasses. Usually the transport returns a valid non-null thread factory, but
* thread factories can also be created by individual protocols
* @return
*/
public ThreadFactory getThreadFactory() {
return down_prot != null? down_prot.getThreadFactory(): null;
}
/**
* Returns the SocketFactory associated with this protocol, if overridden in a subclass, or passes the call down
* @return SocketFactory
*/
public SocketFactory getSocketFactory() {
return down_prot != null? down_prot.getSocketFactory() : null;
}
/**
* Sets a SocketFactory. Socket factories are typically provided by the transport ({@link org.jgroups.protocols.TP})
* @param factory
*/
public void setSocketFactory(SocketFactory factory) {
if(down_prot != null)
down_prot.setSocketFactory(factory);
}
@ManagedOperation(description="Resets all stats")
public void resetStatistics() {resetStats();}
public void resetStats() {
;
}
/**
* Called after instance has been created (null constructor) and before protocol is started.
* Properties are already set. Other protocols are not yet connected and events cannot yet be sent.
* @exception Exception Thrown if protocol cannot be initialized successfully. This will cause the
* ProtocolStack to fail, so the channel constructor will throw an exception
*/
public void init() throws Exception {
}
/**
* This method is called on a {@link JChannel#connect(String)}. Starts work.
* Protocols are connected and queues are ready to receive events.
* Will be called <em>from bottom to top</em>. This call will replace
* the <b>START</b> and <b>START_OK</b> events.
* @exception Exception Thrown if protocol cannot be started successfully. This will cause the ProtocolStack
* to fail, so {@link JChannel#connect(String)} will throw an exception
*/
public void start() throws Exception {
}
/**
* This method is called on a {@link JChannel#disconnect()}. Stops work (e.g. by closing multicast socket).
* Will be called <em>from top to bottom</em>. This means that at the time of the method invocation the
* neighbor protocol below is still working. This method will replace the
* <b>STOP</b>, <b>STOP_OK</b>, <b>CLEANUP</b> and <b>CLEANUP_OK</b> events. The ProtocolStack guarantees that
* when this method is called all messages in the down queue will have been flushed
*/
public void stop() {
}
/**
* This method is called on a {@link JChannel#close()}.
* Does some cleanup; after the call the VM will terminate
*/
public void destroy() {
}
/** List of events that are required to be answered by some layer above */
@SuppressWarnings("MethodMayBeStatic")
public List<Integer> requiredUpServices() {return null;}
/** List of events that are required to be answered by some layer below */
public List<Integer> requiredDownServices() {return null;}
/** List of events that are provided to layers above (they will be handled when sent down from above) */
public List<Integer> providedUpServices() {return null;}
/** List of events that are provided to layers below (they will be handled when sent down below) */
public List<Integer> providedDownServices() {return null;}
/** Returns all services provided by protocols below the current protocol */
public final List<Integer> getDownServices() {
List<Integer> retval=new ArrayList<>();
Protocol prot=down_prot;
while(prot != null) {
List<Integer> tmp=prot.providedUpServices();
if(tmp != null && !tmp.isEmpty())
retval.addAll(tmp);
prot=prot.down_prot;
}
return retval;
}
/** Returns all services provided by the protocols above the current protocol */
public final List<Integer> getUpServices() {
List<Integer> retval=new ArrayList<>();
Protocol prot=up_prot;
while(prot != null) {
List<Integer> tmp=prot.providedDownServices();
if(tmp != null && !tmp.isEmpty())
retval.addAll(tmp);
prot=prot.up_prot;
}
return retval;
}
/**
* An event is to be sent down the stack. A protocol may want to examine its type and perform some action on it,
* depending on the event's type. If the event is a message MSG, then the protocol may need to add a header to it
* (or do nothing at all) before sending it down the stack using {@code down_prot.down()}.
*/
public Object down(Event evt) {
return down_prot.down(evt);
}
/**
* A message is sent down the stack. Protocols may examine the message and do something (e.g. add a header) with it
* before passing it down.
* @since 4.0
*/
public Object down(Message msg) {
return down_prot.down(msg);
}
/**
* An event was received from the protocol below. Usually the current protocol will want to examine the event type
* and - depending on its type - perform some computation (e.g. removing headers from a MSG event type, or updating
* the internal membership list when receiving a VIEW_CHANGE event).
* Finally the event is either a) discarded, or b) an event is sent down the stack using {@code down_prot.down()}
* or c) the event (or another event) is sent up the stack using {@code up_prot.up()}.
*/
public Object up(Event evt) {
return up_prot.up(evt);
}
/**
* A single message was received. Protocols may examine the message and do something (e.g. add a header) with it
* before passing it up.
* @since 4.0
*/
public Object up(Message msg) {
return up_prot.up(msg);
}
/**
* Sends up a multiple messages in a {@link MessageBatch}. The sender of the batch is always the same, and so is the
* destination (null == multicast messages). Messages in a batch can be OOB messages, regular messages, or mixed
* messages, although the transport itself will create initial MessageBatches that contain only either OOB or
* regular messages.<p/>
* The default processing below sends messages up the stack individually, based on a matching criteria
* (calling {@link #accept(org.jgroups.Message)}), and - if true - calls {@link #up(org.jgroups.Event)}
* for that message and removes the message. If the batch is not empty, it is passed up, or else it is dropped.<p/>
* Subclasses should check if there are any messages destined for them (e.g. using
* {@link MessageBatch#getMatchingMessages(short,boolean)}), then possibly remove and process them and finally pass
* the batch up to the next protocol. Protocols can also modify messages in place, e.g. ENCRYPT could decrypt all
* encrypted messages in the batch, not remove them, and pass the batch up when done.
* @param batch The message batch
*/
public void up(MessageBatch batch) {
for(Iterator<Message> it=batch.iterator(); it.hasNext();) {
Message msg=it.next();
if(msg != null && accept(msg)) {
it.remove();
try {
up(msg);
}
catch(Throwable t) {
log.error(Util.getMessage("PassUpFailure"), t);
}
}
}
if(!batch.isEmpty())
up_prot.up(batch);
}
/**
* Called by the default implementation of {@link #up(org.jgroups.util.MessageBatch)} for each message to determine
* if the message should be removed from the message batch (and handled by the current protocol) or not.
* @param msg The message. Guaranteed to be non-null
* @return True if the message should be handled by this protocol (will be removed from the batch), false if the
* message should remain in the batch and be passed up.<p/>
* The default implementation tries to find a header matching the current protocol's ID and returns true if there
* is a match, or false otherwise
*/
protected boolean accept(Message msg) {
short tmp_id=getId();
return tmp_id > 0 && msg.getHeader(tmp_id) != null;
}
}