
com.cosylab.epics.caj.CAJContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jca Show documentation
Show all versions of jca Show documentation
JCA is an EPICS Channel Access library for Java. For more information concerning EPICS or Channel Access please refer to the <a href="http://www.aps.anl.gov/epics">EPICS Web pages</a> or read the <a href="http://www.aps.anl.gov/epics/base/R3-14/8-docs/CAref.html">Channel Access manual (3.14)</a>.
<p>This module also includes CAJ, A 100% pure Java implementation of the EPICS Channel Access library.</p>
/*
* Copyright (c) 2004 by Cosylab
*
* The full license specifying the redistribution, modification, usage and other
* rights and obligations is included with the distribution of this project in
* the file "LICENSE-CAJ". If the license is not included visit Cosylab web site,
* .
*
* THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, NOT EVEN THE
* IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, ASSUMES
* _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE RESULTING FROM THE USE, MODIFICATION,
* OR REDISTRIBUTION OF THIS SOFTWARE.
*/
package com.cosylab.epics.caj;
import gov.aps.jca.CAException;
import gov.aps.jca.Channel;
import gov.aps.jca.Context;
import gov.aps.jca.JCALibrary;
import gov.aps.jca.TimeoutException;
import gov.aps.jca.Version;
import gov.aps.jca.configuration.Configurable;
import gov.aps.jca.configuration.Configuration;
import gov.aps.jca.configuration.ConfigurationException;
import gov.aps.jca.event.ConnectionListener;
import gov.aps.jca.event.ContextExceptionEvent;
import gov.aps.jca.event.ContextExceptionListener;
import gov.aps.jca.event.ContextMessageListener;
import gov.aps.jca.event.DirectEventDispatcher;
import gov.aps.jca.event.EventDispatcher;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.cosylab.epics.caj.impl.BroadcastConnector;
import com.cosylab.epics.caj.impl.BroadcastTransport;
import com.cosylab.epics.caj.impl.CABeaconHandler;
import com.cosylab.epics.caj.impl.CAConnector;
import com.cosylab.epics.caj.impl.CAConstants;
import com.cosylab.epics.caj.impl.CAContext;
import com.cosylab.epics.caj.impl.CAJNameClient;
import com.cosylab.epics.caj.impl.CAResponseHandler;
import com.cosylab.epics.caj.impl.CATransport;
import com.cosylab.epics.caj.impl.CATransportRegistry;
import com.cosylab.epics.caj.impl.CachedByteBufferAllocator;
import com.cosylab.epics.caj.impl.ChannelSearchManager;
import com.cosylab.epics.caj.impl.ConnectionException;
import com.cosylab.epics.caj.impl.RepeaterRegistrationTask;
import com.cosylab.epics.caj.impl.ResponseRequest;
import com.cosylab.epics.caj.impl.Transport;
import com.cosylab.epics.caj.impl.TransportClient;
import com.cosylab.epics.caj.impl.reactor.Reactor;
import com.cosylab.epics.caj.impl.reactor.ReactorHandler;
import com.cosylab.epics.caj.impl.reactor.lf.LeaderFollowersHandler;
import com.cosylab.epics.caj.impl.reactor.lf.LeaderFollowersThreadPool;
import com.cosylab.epics.caj.impl.sync.NamedLockPattern;
import com.cosylab.epics.caj.util.InetAddressUtil;
import com.cosylab.epics.caj.util.IntHashMap;
import com.cosylab.epics.caj.util.Timer;
import com.cosylab.epics.caj.util.logging.ConsoleLogHandler;
/**
* Implementation of CAJ JCA Context
.
* @author Matej Sekoranja
* @version $id$
*/
public class CAJContext extends Context implements CAContext, CAJConstants, Configurable {
/**
* Major version.
*/
private static final int CAJ_VERSION_MAJOR = 1;
/**
* Minor version.
*/
private static final int CAJ_VERSION_MINOR = 1;
/**
* Maintenance version.
*/
private static final int CAJ_VERSION_MAINTENANCE = 16;
/**
* Development version.
*/
private static final int CAJ_VERSION_DEVELOPMENT = 1;
/**
* Version.
*/
public static final Version VERSION = new Version(
"Channel Access in Java", "Java",
CAJ_VERSION_MAJOR, CAJ_VERSION_MINOR,
CAJ_VERSION_MAINTENANCE, CAJ_VERSION_DEVELOPMENT);
/**
* String value of the JVM property key to turn on single threaded model.
*/
public static final String CAJ_SINGLE_THREADED_MODEL = "CAJ_SINGLE_THREADED_MODEL";
/**
* State value of non-initialized context.
*/
private static final int NOT_INITIALIZED = 0;
/**
* State value of initialized context.
*/
private static final int INITIALIZED = 1;
/**
* State value of destroyed context.
*/
private static final int DESTROYED = 2;
/**
* Initialization status.
*/
private volatile int state = NOT_INITIALIZED;
/**
* Context logger.
*/
protected Logger logger = Logger.global;
/**
* A space-separated list of broadcast address for process variable name resolution.
* Each address must be of the form: ip.number:port or host.name:port
*/
protected String addressList = "";
/**
* Define whether or not the network interfaces should be discovered at runtime.
*/
protected boolean autoAddressList = true;
protected String nameServersList = "";
/**
* If the context doesn't see a beacon from a server that it is connected to for
* connectionTimeout seconds then a state-of-health message is sent to the server over TCP/IP.
* If this state-of-health message isn't promptly replied to then the context will assume that
* the server is no longer present on the network and disconnect.
*/
protected float connectionTimeout = 30.0f;
/**
* Period in second between two beacon signals.
*/
protected float beaconPeriod = 15.0f;
/**
* Port number for the repeater to listen to.
*/
protected int repeaterPort = CAConstants.CA_REPEATER_PORT;
/**
* Port number for the server to listen to.
*/
protected int serverPort = CAConstants.CA_SERVER_PORT;
/**
* Length in bytes of the maximum array size that may pass through CA.
*/
protected int maxArrayBytes = 16384;
/**
* List of context message listeners.
*/
// TODO consider using weak references
protected ArrayList contextMessageListeners = new ArrayList();
/**
* List of context exception listeners.
*/
// TODO consider using weak references
protected ArrayList contextExceptionListeners = new ArrayList();
/**
* Event dispatcher.
*/
protected EventDispatcher eventDispatcher = new DirectEventDispatcher();
/**
* Timer.
*/
protected Timer timer = null;
/**
* Reactor.
*/
protected Reactor reactor = null;
/**
* Leader/followers thread pool.
*/
protected LeaderFollowersThreadPool leaderFollowersThreadPool = null;
/**
* Registration confirmation status.
*/
protected boolean registrationConfirmed = false;
/**
* Registration confirmation condition variable.
*/
private Object registrationConfirmedCondition = new Object();
/**
* Broadcast transport needed for channel searches.
*/
protected BroadcastTransport broadcastTransport = null;
/**
* CA connector (creates CA virtual circuit).
*/
protected CAConnector connector = null;
public ArrayList nameClients = new ArrayList<>();
/**
* CA transport (virtual circuit) registry.
* This registry contains all active transports - connections to CA servers.
*/
protected CATransportRegistry transportRegistry = null;
/**
* Cached byte buffer allocator.
* Used by transports which as send buffers.
*/
protected CachedByteBufferAllocator cachedBufferAllocator = new CachedByteBufferAllocator();
/**
* Context instance.
*/
private NamedLockPattern namedLocker;
/**
* Context instance.
*/
private static final int LOCK_TIMEOUT = 20 * 1000; // 20s
/**
* Map of channels (keys are CIDs).
*/
// TODO consider using WeakHashMap (and call Channel.destroy() in finalize() method).
protected IntHashMap channelsByCID = new IntHashMap();
/**
* Map of channels (keys are names).
*/
// TODO consider using WeakHashMap (and call Channel.destroy() in finalize() method).
protected Map channelsByName = new HashMap();
/**
* Last CID cache.
*/
private int lastCID = 0;
/**
* Map of pending response requests (keys are IOID).
*/
// TODO consider using WeakHashMap (and call ResponseRequest.destroy() in finalize() method).
protected IntHashMap pendingResponseRequests = new IntHashMap();
/**
* Last IOID cache.
*/
private int lastIOID = 0;
/**
* Pending requests (get, create channel) counter.
*/
private AtomicInteger pendingRequestsCount = new AtomicInteger(0);
/**
* IO sequence number (to prevent future IO request to interfere with current pendIO).
*/
private AtomicInteger sequenceNumberIO = new AtomicInteger(0);
/**
* Zelo pending requests condition - triggered when counter drops to 0.
*/
private Object zeroPendingRequestsCondition = new Object();
/**
* Channel search manager.
* Manages UDP search requests.
*/
private ChannelSearchManager channelSearchManager;
/**
* Beacon handler map.
*/
protected Map beaconHandlers = new HashMap();
/**
* Last UDP recived sequence number.
*/
private AtomicInteger lastReceivedSequenceNumber = new AtomicInteger(0);
private AtomicBoolean doNotShareChannels = new AtomicBoolean(System.getProperties().containsValue("CAJ_DO_NOT_SHARE_CHANNELS"));
private volatile String userName = System.getProperty("user.name", "nobody");
/**
* Constructor.
*/
public CAJContext()
{
initializeLogger();
loadConfiguration();
}
/**
* Get context version.
* @see Context#getVersion()
*/
public Version getVersion()
{
return VERSION;
}
public boolean isDoNotShareChannels() {
return doNotShareChannels.get();
}
public void setDoNotShareChannels(boolean doNotShareChannels) {
// Ignore if the value is the same, to avoid an
// exception if the context was initialized but the new setting
// is equal to the old.
// Note that the value could change after this,
// so in principle one could still get an exception.
if (this.doNotShareChannels.get() == doNotShareChannels) {
return;
}
if (state == NOT_INITIALIZED) {
this.doNotShareChannels.set(doNotShareChannels);
} else {
throw new IllegalStateException("Cannot change doNotShareChannels after the Context is initialized.");
}
}
/**
* Initialize context logger.
*/
protected void initializeLogger()
{
JCALibrary jcaLibrary = JCALibrary.getInstance();
String thisClassName = this.getClass().getName();
String loggerName = jcaLibrary.getProperty(thisClassName + ".logger", thisClassName);
logger = Logger.getLogger(loggerName);
if (System.getProperties().containsKey(CAJ_DEBUG))
{
logger.setLevel(Level.ALL);
logger.addHandler(new ConsoleLogHandler());
}
}
/**
* Get system environment variable.
* @param name env. var. name
* @param defaultValue default value
* @return value of env. var., default value if not defined
*
private static String getEnvironmentVariable(String name, String defaultValue) {
String val = null;
try {
val = System.getenv(name);
} catch (Throwable th) {
// noop
}
if (val == null)
return defaultValue;
else
return val;
}*/
/**
* Load configuration.
*/
protected void loadConfiguration()
{
JCALibrary jcaLibrary = JCALibrary.getInstance();
String eventDispatcherClassName = null;
final String thisClassName = this.getClass().getName();
if (Boolean.getBoolean("jca.use_env"))
{
// Context default configuration
eventDispatcherClassName = jcaLibrary.getProperty( gov.aps.jca.Context.class.getName()+".event_dispatcher", eventDispatcherClassName );
String tmp = System.getenv("EPICS_CA_ADDR_LIST");
if (tmp != null) addressList = tmp;
tmp = System.getenv("EPICS_CA_AUTO_ADDR_LIST");
if (tmp != null)
autoAddressList = !tmp.equalsIgnoreCase("NO");
else
autoAddressList = true;
tmp = System.getenv("EPICS_CA_NAME_SERVERS");
if (tmp != null) nameServersList = tmp;
tmp = System.getenv("EPICS_CA_CONN_TMO");
if (tmp != null) connectionTimeout = Float.parseFloat(tmp);
tmp = System.getenv("EPICS_CA_BEACON_PERIOD");
if (tmp != null) beaconPeriod = Float.parseFloat(tmp);
tmp = System.getenv("EPICS_CA_REPEATER_PORT");
if (tmp != null) repeaterPort = Integer.parseInt(tmp);
tmp = System.getenv("EPICS_CA_SERVER_PORT");
if (tmp != null) serverPort = Integer.parseInt(tmp);
tmp = System.getenv("EPICS_CA_MAX_ARRAY_BYTES");
if (tmp != null) maxArrayBytes = Integer.parseInt(tmp);
}
else
{
// load default Context configuration
final String contextClassName = Context.class.getName();
addressList = jcaLibrary.getProperty(contextClassName + ".addr_list", addressList);
autoAddressList = jcaLibrary.getPropertyAsBoolean(contextClassName + ".auto_addr_list", autoAddressList);
nameServersList = jcaLibrary.getProperty(contextClassName + ".name_servers", nameServersList);
connectionTimeout = jcaLibrary.getPropertyAsFloat(contextClassName + ".connection_timeout", connectionTimeout);
beaconPeriod = jcaLibrary.getPropertyAsFloat(contextClassName + ".beacon_period", beaconPeriod);
repeaterPort = jcaLibrary.getPropertyAsInt(contextClassName + ".repeater_port", repeaterPort);
serverPort = jcaLibrary.getPropertyAsInt(contextClassName + ".server_port", serverPort);
maxArrayBytes = jcaLibrary.getPropertyAsInt(contextClassName + ".max_array_bytes", maxArrayBytes);
eventDispatcherClassName = jcaLibrary.getProperty(contextClassName + ".event_dispatcher");
// load CAJ specific configuration (overrides default)
addressList = jcaLibrary.getProperty(thisClassName + ".addr_list", addressList);
autoAddressList = jcaLibrary.getPropertyAsBoolean(thisClassName + ".auto_addr_list", autoAddressList);
nameServersList = jcaLibrary.getProperty(thisClassName + ".name_servers", nameServersList);
connectionTimeout = jcaLibrary.getPropertyAsFloat(thisClassName + ".connection_timeout", connectionTimeout);
beaconPeriod = jcaLibrary.getPropertyAsFloat(thisClassName + ".beacon_period", beaconPeriod);
repeaterPort = jcaLibrary.getPropertyAsInt(thisClassName + ".repeater_port", repeaterPort);
serverPort = jcaLibrary.getPropertyAsInt(thisClassName + ".server_port", serverPort);
maxArrayBytes = jcaLibrary.getPropertyAsInt(thisClassName + ".max_array_bytes", maxArrayBytes);
}
eventDispatcherClassName = jcaLibrary.getProperty(thisClassName + ".event_dispatcher", eventDispatcherClassName);
if (eventDispatcherClassName != null)
{
try
{
eventDispatcher = (EventDispatcher)Class.forName(eventDispatcherClassName).newInstance();
} catch(Throwable th) {
logger.log(Level.WARNING, "Failed to instantiate '" + eventDispatcherClassName + "' event dispatcher.", th);
}
}
}
/**
* @see gov.aps.jca.configuration.Configurable#configure(gov.aps.jca.configuration.Configuration)
*/
public void configure(Configuration configuration)
throws ConfigurationException {
// address list
try {
addressList = configuration.getChild("addr_list", false).getValue();
} catch(Exception ex) {
addressList= configuration.getAttribute("addr_list", addressList);
}
// auto address list
try {
autoAddressList = configuration.getChild("auto_addr_list", false).getValueAsBoolean();
} catch(Exception ex) {
autoAddressList = configuration.getAttributeAsBoolean("auto_addr_list", autoAddressList);
}
// connection timeout
try {
connectionTimeout = configuration.getChild("connection_timeout", false).getValueAsFloat();
} catch(Exception ex) {
connectionTimeout = configuration.getAttributeAsFloat("connection_timeout", connectionTimeout);
}
// beacon period
try {
beaconPeriod = configuration.getChild("beacon_period", false).getValueAsFloat();
} catch(Exception ex) {
beaconPeriod = configuration.getAttributeAsFloat("beacon_period", beaconPeriod);
}
// repeater port
try {
repeaterPort = configuration.getChild("repeater_port", false).getValueAsInteger();
} catch(Exception ex) {
repeaterPort = configuration.getAttributeAsInteger("repeater_port", repeaterPort);
}
// server port
try {
serverPort = configuration.getChild("server_port", false).getValueAsInteger();
} catch(Exception ex) {
serverPort = configuration.getAttributeAsInteger("server_port", serverPort);
}
// max. array bytes
try {
maxArrayBytes = configuration.getChild("max_array_bytes", false).getValueAsInteger();
} catch(Exception ex) {
maxArrayBytes = configuration.getAttributeAsInteger("max_array_bytes", maxArrayBytes);
}
// event dispathcer
Configuration conf = configuration.getChild("event_dispatcher", false);
if (conf != null)
{
String eventDispatcherClassName = null;
try {
eventDispatcherClassName = conf.getAttribute("class");
} catch (ConfigurationException noAttribute) {
logger.log(Level.WARNING, "Failed to obtain 'event_dispatcher' node's 'class' attribute.", noAttribute);
}
if (eventDispatcherClassName != null)
{
try
{
eventDispatcher = (EventDispatcher)Class.forName(eventDispatcherClassName).newInstance();
} catch(Throwable th) {
logger.log(Level.WARNING, "Failed to instantiate '" + eventDispatcherClassName + "' event dispatcher.", th);
}
}
}
}
/**
* @see gov.aps.jca.Context#getContextMessageListeners()
*/
public ContextMessageListener[] getContextMessageListeners()
throws IllegalStateException {
synchronized (contextMessageListeners)
{
ContextMessageListener[] listeners = new ContextMessageListener[contextMessageListeners.size()];
return (ContextMessageListener[])contextMessageListeners.toArray(listeners);
}
}
/**
* @see gov.aps.jca.Context#addContextMessageListener(gov.aps.jca.event.ContextMessageListener)
*/
public void addContextMessageListener(ContextMessageListener l)
throws CAException, IllegalStateException {
checkState();
if (l == null)
throw new IllegalArgumentException("l == null");
synchronized(contextMessageListeners)
{
if (!contextMessageListeners.contains(l))
contextMessageListeners.add(l);
}
}
/**
* @see gov.aps.jca.Context#removeContextMessageListener(gov.aps.jca.event.ContextMessageListener)
*/
public void removeContextMessageListener(ContextMessageListener l)
throws CAException, IllegalStateException {
checkState();
if (l == null)
throw new IllegalArgumentException("l == null");
synchronized(contextMessageListeners)
{
contextMessageListeners.remove(l);
}
}
/**
* @see gov.aps.jca.Context#getContextExceptionListeners()
*/
public ContextExceptionListener[] getContextExceptionListeners()
throws IllegalStateException {
synchronized (contextExceptionListeners)
{
ContextExceptionListener[] listeners = new ContextExceptionListener[contextExceptionListeners.size()];
return (ContextExceptionListener[])contextExceptionListeners.toArray(listeners);
}
}
/**
* @see gov.aps.jca.Context#addContextExceptionListener(gov.aps.jca.event.ContextExceptionListener)
*/
public void addContextExceptionListener(ContextExceptionListener l)
throws CAException, IllegalStateException {
checkState();
if (l == null)
throw new IllegalArgumentException("l == null");
synchronized(contextExceptionListeners)
{
if (!contextExceptionListeners.contains(l))
contextExceptionListeners.add(l);
}
}
/**
* @see gov.aps.jca.Context#removeContextExceptionListener(gov.aps.jca.event.ContextExceptionListener)
*/
public void removeContextExceptionListener(ContextExceptionListener l)
throws CAException, IllegalStateException {
checkState();
if (l == null)
throw new IllegalArgumentException("l == null");
synchronized(contextExceptionListeners)
{
contextExceptionListeners.remove(l);
}
}
/**
* Notifies context listeners about exception.
* @param event context exception event to be fired.
*/
public void notifyException(ContextExceptionEvent event) {
ContextExceptionListener[] listeners = getContextExceptionListeners();
for (int i = 0; i < listeners.length; i++)
{
try
{
listeners[i].contextException(event);
}
catch (Throwable th)
{
// TODO remove
logger.log(Level.SEVERE, "", th);
}
}
}
/**
* Check context state and tries to establish necessary state.
* @throws CAException JCA Exception
* @throws IllegalStateException IllegalStateException
*/
protected void checkState() throws CAException, IllegalStateException {
if (state == DESTROYED)
throw new IllegalStateException("Context destroyed.");
else if (state == NOT_INITIALIZED)
{
// double-locking pattern used to prevent unnecessary initialization calls
synchronized (this)
{
if (state == NOT_INITIALIZED)
initialize();
}
}
}
/**
* @see gov.aps.jca.Context#initialize()
*/
public synchronized void initialize() throws CAException {
if (state == DESTROYED)
throw new IllegalStateException("Context destroyed.");
else if (state == INITIALIZED)
throw new IllegalStateException("Context already initialized.");
super.initialize();
internalInitialize();
state = INITIALIZED;
}
/**
* @throws CAException
*/
private void internalInitialize() throws CAException {
try {
CARepeater.startRepeater(repeaterPort);
} catch (Throwable th) { /* noop */ }
timer = new Timer();
connector = new CAConnector(this);
transportRegistry = new CATransportRegistry();
namedLocker = new NamedLockPattern();
try
{
reactor = new Reactor();
if (System.getProperties().containsKey(CAJ_SINGLE_THREADED_MODEL))
{
logger.config("Using single threaded model.");
// single thread processing
new Thread(
new Runnable() {
/**
* @see java.lang.Runnable#run()
*/
public void run() {
// do the work
while (reactor.process());
}
}, "CA reactor").start();
}
else
{
// leader/followers processing
leaderFollowersThreadPool = new LeaderFollowersThreadPool();
// spawn initial leader
leaderFollowersThreadPool.promoteLeader(
new Runnable() {
/**
* @see java.lang.Runnable#run()
*/
public void run() {
reactor.process();
}
}
);
}
}
catch (IOException ioex)
{
throw new CAException("Failed to initialize reactor.", ioex);
}
// setup UDP transport
initializeUDPTransport();
initializeNameServers();
// setup search manager
channelSearchManager = new ChannelSearchManager(this);
}
private void initializeNameServers() {
InetSocketAddress[] list = InetAddressUtil.getSocketAddressList(nameServersList, serverPort, null);
for(InetSocketAddress ep : list) {
nameClients.add(new CAJNameClient(this, ep));
}
for(CAJNameClient client : nameClients) {
client.connect();
}
}
/**
* Initialized UDP transport (broadcast socket and repeater connection).
*/
private void initializeUDPTransport() {
// setup UDP transport
try
{
InetSocketAddress repeaterLocalAddress = new InetSocketAddress("127.0.0.1", repeaterPort);
BroadcastConnector broadcastConnector = new BroadcastConnector(this);
broadcastTransport = (BroadcastTransport)broadcastConnector.connect(
null, new CAResponseHandler(this),
repeaterLocalAddress, CAConstants.CA_MINOR_PROTOCOL_REVISION,
CAConstants.CA_DEFAULT_PRIORITY);
// moved from BroadcastConnector due to JDK7 problem
ReactorHandler handler = broadcastTransport;
if (getLeaderFollowersThreadPool() != null)
handler = new LeaderFollowersHandler(getReactor(), handler, getLeaderFollowersThreadPool());
try {
DatagramChannel channel = broadcastTransport.getChannel();
// explicitly bind first
channel.socket().setReuseAddress(true);
channel.socket().bind(new InetSocketAddress(0));
// and register to the selector
getReactor().register(channel, SelectionKey.OP_READ, handler);
} catch (Throwable e) {
// TODO
throw new RuntimeException(e);
}
// set broadcast address list
if (addressList != null && addressList.length() > 0)
{
// if auto is true, add it to specified list
InetSocketAddress[] appendList = null;
if (autoAddressList == true)
appendList = broadcastTransport.getBroadcastAddresses();
InetSocketAddress[] list = InetAddressUtil.getSocketAddressList(addressList, serverPort, appendList);
if (list != null && list.length > 0)
broadcastTransport.setBroadcastAddresses(list);
}
else if (autoAddressList == false)
{
logger.log(Level.WARNING, "Empty broadcast search address list, all connects will fail.");
broadcastTransport.setBroadcastAddresses(null);
}
RepeaterRegistrationTask registrationTask = new RepeaterRegistrationTask(this, repeaterLocalAddress);
// try immediately (often repeater is OK and will confirm immediately)
synchronized (registrationConfirmedCondition)
{
registrationTask.registrationRequest();
try
{
// spurious wakeup wont hurt here...
if (!registrationConfirmed)
registrationConfirmedCondition.wait(100);
} catch (InterruptedException ie) {}
}
// retry every second
if (!registrationConfirmed)
registrationTask.runInBackground(1000);
}
catch (ConnectionException ce)
{
logger.log(Level.SEVERE, "Failed to initialize UDP transport.", ce);
}
catch (UnknownHostException uhe)
{
logger.log(Level.SEVERE, "Failed to obtain local host address.", uhe);
}
}
/**
* @see gov.aps.jca.Context#destroy()
*/
public synchronized void destroy() throws CAException, IllegalStateException {
if (state == DESTROYED)
throw new IllegalStateException("Context already destroyed.");
// go into destroyed state ASAP
state = DESTROYED;
internalDestroy();
}
/**
* @throws CAException
*/
private void internalDestroy() throws CAException {
// stop searching
if (channelSearchManager != null)
channelSearchManager.cancel();
for(CAJNameClient client : nameClients) {
client.cancel();
}
// stop timer
if (timer != null)
timer.shutDown();
//
// cleanup
//
// stop waiting
synchronized (zeroPendingRequestsCondition)
{
zeroPendingRequestsCondition.notifyAll();
}
// this will also close all CA transports
destroyAllChannels();
// shutdown reactor
if (reactor != null)
reactor.shutdown();
// shutdown LF thread pool
if (leaderFollowersThreadPool != null)
leaderFollowersThreadPool.shutdown();
// TODO still some events can be in queue (e.g. channel destroyed)
// reposibility of the event dispatcher?
// shutdown dispatcher
if (eventDispatcher != null)
eventDispatcher.dispose();
synchronized (contextMessageListeners)
{
contextMessageListeners.clear();
}
synchronized (contextExceptionListeners)
{
contextExceptionListeners.clear();
}
}
/**
* Destroy all channels.
*/
private void destroyAllChannels() {
Channel[] channelsArray = getChannels();
synchronized (channelsByCID)
{
channelsByCID.clear();
channelsByName.clear();
}
for (int i = 0; i < channelsArray.length; i++)
{
try
{
// force destruction regardless of reference count
((CAJChannel)channelsArray[i]).destroy(true);
}
catch (Throwable th)
{
logger.log(Level.SEVERE, "", th);
}
}
}
/**
* @see gov.aps.jca.Context#createChannel(java.lang.String, gov.aps.jca.event.ConnectionListener, short)
*/
public Channel createChannel(String name, ConnectionListener l, short priority)
throws CAException, IllegalStateException {
checkState();
if (name == null || name.length() == 0)
throw new IllegalArgumentException("null or empty channel name");
else if (name.length() > Math.min(CAConstants.MAX_UDP_SEND - CAConstants.CA_MESSAGE_HEADER_SIZE, 0xFFFF))
throw new CAException("name too long");
if (priority < Channel.PRIORITY_MIN || priority > Channel.PRIORITY_MAX)
throw new IllegalArgumentException("priority out of bounds");
// lookup for channel w/o named lock
CAJChannel channel = getChannel(name, priority, true);
if (channel != null)
{
if (l != null)
channel.addConnectionListenerAndFireIfConnected(l);
return channel;
}
boolean lockAcquired = namedLocker.acquireSynchronizationObject(name, LOCK_TIMEOUT);
if (lockAcquired)
{
try
{
// ... lookup for channel, if created while acquiring lock
channel = getChannel(name, priority, true);
if (channel != null)
{
if (l != null)
channel.addConnectionListenerAndFireIfConnected(l);
return channel;
}
int cid = generateCID();
channel = new CAJChannel(this, cid, name, l, priority);
return channel;
}
finally
{
namedLocker.releaseSynchronizationObject(name);
}
}
else
{
throw new CAException("Failed to obtain synchronization lock for '" + name + "', possible deadlock.", null);
}
}
/**
* Destroy channel.
* @param channel CAJChannel
* @param force force destruction regardless of reference count
* @throws CAException force destruction regardless of reference count
* @throws IllegalStateException Signals that a method has been invoked at an illegal or inappropriate time
*/
public void destroyChannel(CAJChannel channel, boolean force)
throws CAException, IllegalStateException {
boolean lockAcquired = namedLocker.acquireSynchronizationObject(channel.getName(), LOCK_TIMEOUT);
if (lockAcquired)
{
try
{
channel.destroyChannel(force);
}
catch (IOException ioex)
{
logger.log(Level.SEVERE, "Failed to cleanly destroy channel.", ioex);
throw new CAException("Failed to cleanly destroy channel.", ioex);
}
finally
{
namedLocker.releaseSynchronizationObject(channel.getName());
}
}
else
{
throw new CAException("Failed to obtain synchronization lock for '" + channel.getName() + "', possible deadlock.", null);
}
}
/**
* Register channel.
* @param channel CAJChannel
*/
void registerChannel(CAJChannel channel)
{
synchronized (channelsByCID)
{
channelsByCID.put(channel.getChannelID(), channel);
if (!doNotShareChannels.get())
channelsByName.put(getUniqueChannelName(channel.getName(), channel.getPriority()), channel);
}
}
/**
* Unregister channel.
* @param channel CAJChannel
*/
void unregisterChannel(CAJChannel channel)
{
synchronized (channelsByCID)
{
channelsByCID.remove(channel.getChannelID());
if (!doNotShareChannels.get())
channelsByName.remove(getUniqueChannelName(channel.getName(), channel.getPriority()));
}
}
/**
* Searches for a channel with given channel ID.
* @param channelID CID.
* @return channel with given CID, null
if non-existant.
*/
public CAJChannel getChannel(int channelID)
{
synchronized (channelsByCID)
{
return (CAJChannel)channelsByCID.get(channelID);
}
}
/**
* Generate unique channel string from channel name and priority.
* @param name channel name.
* @param priority channel priority.
* @return unique channel string.
*/
private final String getUniqueChannelName(String name, short priority)
{
// this name is illegal for CA, so this funcion is unique
return name + '\0' + priority;
}
/**
* Searches for a channel with given channel name.
* @param name channel name.
* @param priority channel priority.
* @param acquire whether to acquire ownership (increment ref. counting)
* @return channel with given name, null
if non-existant.
*/
public CAJChannel getChannel(String name, short priority, boolean acquire)
{
if (doNotShareChannels.get())
return null;
synchronized (channelsByName)
{
CAJChannel channel = (CAJChannel)channelsByName.get(getUniqueChannelName(name, priority));
if (channel != null && acquire)
channel.acquire();
return channel;
}
}
/**
* @see gov.aps.jca.Context#getChannels()
*/
public Channel[] getChannels() {
synchronized (channelsByCID)
{
Channel[] ch = new Channel[channelsByCID.size()];
return (Channel[])channelsByCID.toArray(ch);
}
}
/**
* @see gov.aps.jca.Context#pendIO(double)
*/
public void pendIO(double timeout)
throws TimeoutException, CAException, IllegalStateException {
checkState();
final long time = System.currentTimeMillis();
flushIO();
long timeToWaitInMS = 0;
if (timeout >= 0.0)
{
try
{
synchronized (zeroPendingRequestsCondition)
{
// wait until completed
if (timeout == 0.0)
{
while (pendingRequestsCount.get() > 0 && state != DESTROYED)
zeroPendingRequestsCondition.wait();
}
else
{
final long endTime = time + (long)(timeout*1000);
while (pendingRequestsCount.get() > 0 && (timeToWaitInMS = (endTime - System.currentTimeMillis())) > 0 && state != DESTROYED)
{
zeroPendingRequestsCondition.wait(timeToWaitInMS);
}
}
}
} catch (InterruptedException e) { /* noop */ }
}
int stillPending;
// reset pendingRequestsCount and increase sequenceNumberIO (new session)
synchronized (sequenceNumberIO)
{
sequenceNumberIO.incrementAndGet();
stillPending = pendingRequestsCount.getAndSet(0);
}
// throw timeout exception if not all requests where processed
if (stillPending > 0)
{
if (state == DESTROYED)
throw new CAException("context destroyed during pendIO");
if (timeToWaitInMS <= 0)
throw new TimeoutException("pendIO timed out");
}
}
/**
* @see gov.aps.jca.Context#testIO()
*/
public boolean testIO() throws CAException, IllegalStateException {
checkState();
return pendingRequestsCount.get() == 0;
}
/**
* @see gov.aps.jca.Context#pendEvent(double)
*/
public void pendEvent(double timeout)
throws CAException, IllegalStateException {
checkState();
long time = System.currentTimeMillis();
flushIO();
time = System.currentTimeMillis() - time;
long timeToWaitInMS = (long)(timeout*1000) - time;
if (timeout == 0.0 || timeToWaitInMS > 0)
{
// ... CAJ does not know any background activity (it is all preemptive), so just sleep
try {
if (timeout == 0.0)
// sleep until forever (or interrupted)
Thread.currentThread().join();
else
Thread.sleep(timeToWaitInMS);
} catch (InterruptedException e) { /* noop */ }
}
}
/**
* @see gov.aps.jca.Context#poll()
*/
public void poll() throws CAException, IllegalStateException {
checkState();
flushIO();
// ... CAJ does not know any background activity (it is all preemptive)
}
/**
* @see gov.aps.jca.Context#flushIO()
*/
public void flushIO() throws CAException, IllegalStateException {
checkState();
Transport[] transports = transportRegistry.toArray();
for (int i = 0; i < transports.length; i++)
((CATransport)transports[i]).flush();
}
/**
* @see gov.aps.jca.Context#attachCurrentThread()
*/
public void attachCurrentThread()
throws CAException, IllegalStateException {
checkState();
// noop
}
/**
* @see gov.aps.jca.Context#printInfo(java.io.PrintStream)
*/
public void printInfo(PrintStream out) throws IllegalStateException {
super.printInfo(out);
out.println("ADDR_LIST : " + addressList);
out.println("AUTO_ADDR_LIST : " + autoAddressList);
if (broadcastTransport != null)
out.println("AUTO_ADDR_LIST (active): " + Arrays.toString(broadcastTransport.getBroadcastAddresses()));
out.println("NAME_SERVERS : " + nameServersList);
out.println("CONNECTION_TIMEOUT : " + connectionTimeout);
out.println("BEACON_PERIOD : " + beaconPeriod);
out.println("REPEATER_PORT : " + repeaterPort);
out.println("SERVER_PORT : " + serverPort);
out.println("MAX_ARRAY_BYTES : " + maxArrayBytes);
out.println("EVENT_DISPATCHER: " + eventDispatcher);
out.print("STATE : ");
switch (state)
{
case NOT_INITIALIZED:
out.println("NOT_INITIALIZED");
break;
case INITIALIZED:
out.println("INITIALIZED");
break;
case DESTROYED:
out.println("DESTROYED");
break;
default:
out.println("UNKNOWN");
}
}
/**
* Get initialization status.
* @return initialization status.
*/
public boolean isInitialized() {
return state == INITIALIZED;
}
/**
* Get destruction status.
* @return destruction status.
*/
public boolean isDestroyed() {
return state == DESTROYED;
}
/**
* Get search address list.
* @return get search address list.
*/
public String getAddressList() {
return addressList;
}
/**
* Get auto search-list flag.
* @return auto search-list flag.
*/
public boolean isAutoAddressList() {
return autoAddressList;
}
/**
* Get beacon period (in seconds).
* @return beacon period (in seconds).
*/
public float getBeaconPeriod() {
return beaconPeriod;
}
/**
* Get connection timeout (in seconds).
* @return connection timeout (in seconds).
*/
public float getConnectionTimeout() {
return connectionTimeout;
}
/**
* Get logger.
* @return logger.
*/
public Logger getLogger() {
return logger;
}
/**
* Get max size of payload.
* @return max size of payload.
*/
public int getMaxArrayBytes() {
return maxArrayBytes;
}
/**
* Get repeater port.
* @return repeater port.
*/
public int getRepeaterPort() {
return repeaterPort;
}
/**
* Get server port.
* @return server port.
*/
public int getServerPort() {
return serverPort;
}
/**
* Get broadcast port.
* @return broadcast port.
*/
public int getBroadcastPort() {
return getServerPort();
}
/**
* Get event dispatcher.
* @return event dispatcher.
*/
public final EventDispatcher getEventDispatcher() {
return eventDispatcher;
}
// ************************************************************************** //
/**
* Get context reactor.
* @return context reactor.
*/
public Reactor getReactor() {
return reactor;
}
/**
* Broadcast transport.
* @return broadcast transport.
*/
public BroadcastTransport getBroadcastTransport() {
return broadcastTransport;
}
/**
* Get CA transport (virtual circuit) registry.
* @return CA transport (virtual circuit) registry.
*/
public CATransportRegistry getTransportRegistry() {
return transportRegistry;
}
/**
* Get timer.
* @return timer.
*/
public Timer getTimer() {
return timer;
}
/**
* Get channel search manager.
* @return channel search manager.
*/
public ChannelSearchManager getChannelSearchManager() {
return channelSearchManager;
}
/**
* Get cached byte allocator.
* @return cached byte allocator.
*/
public CachedByteBufferAllocator getCachedBufferAllocator() {
return cachedBufferAllocator;
}
/**
* Get LF thread pool.
* @return LF thread pool, can be null
if disabled.
*/
public LeaderFollowersThreadPool getLeaderFollowersThreadPool() {
return leaderFollowersThreadPool;
}
/**
* Get repeater registration status.
* @return repeater registration status.
*/
public boolean isRegistrationConfirmed() {
return registrationConfirmed;
}
/**
* Repeater registration confirmation.
* @param responseFrom response from address.
*/
public void repeaterConfirm(InetSocketAddress responseFrom)
{
logger.fine("Repeater " + responseFrom + " confirmed registration.");
registrationConfirmed = true;
// notify other regarding confirmation
synchronized (registrationConfirmedCondition)
{
registrationConfirmedCondition.notifyAll();
}
}
/**
* Called each time beacon anomaly is detected.
*/
public void beaconAnomalyNotify()
{
if (channelSearchManager != null)
channelSearchManager.beaconAnomalyNotify();
}
/**
* Search response from server (channel found).
* @param cid client channel ID.
* @param sid server channel ID.
* @param type channel type code.
* @param count channel element count.
* @param minorRevision server minor CA revision.
* @param serverAddress server address.
*/
public void searchResponse(int cid, int sid, short type, int count,
short minorRevision, InetSocketAddress serverAddress)
{
CAJChannel channel = getChannel(cid);
if (channel == null)
return;
boolean issueCreateRequest;
// check for multiple responses
synchronized (channel)
{
CATransport transport = channel.getTransport();
if (transport != null)
{
// multiple defined PV or reconnect request (same server address)
if (!transport.getRemoteAddress().equals(serverAddress))
{
logger.info("More than one PVs with name '" + channel.getName() +
"' detected, additional response from: " + serverAddress);
return;
}
}
// do not search anymore (also unregisters)
int seqNo = lastReceivedSequenceNumber.get();
channelSearchManager.searchResponse(channel, seqNo, seqNo != 0, System.currentTimeMillis());
transport = getTransport(channel, serverAddress, minorRevision, channel.getPriority());
if (transport == null)
{
channel.createChannelFailed();
return;
}
// create channel
issueCreateRequest = channel.createChannel(transport, sid, type, count);
}
if (issueCreateRequest)
channel.issueCreateChannelRequest();
}
/**
* Get, or create if necessary, transport of given server address.
* @param client requesting connection (transport).
* @param serverAddress required transport address
* @param minorRevision transport revision to be used
* @param priority process priority.
* @return transport for given address
*/
public CATransport getTransport(TransportClient client, InetSocketAddress serverAddress, short minorRevision, short priority)
{
try
{
return (CATransport)connector.connect(client, new CAResponseHandler(this), serverAddress, minorRevision, priority);
}
catch (ConnectionException cex)
{
logger.log(Level.SEVERE, "Failed to create transport for: " + serverAddress, cex);
}
return null;
}
/**
* Generate Client channel ID (CID).
* @return Client channel ID (CID).
*/
private int generateCID()
{
synchronized (channelsByCID)
{
// search first free (theoretically possible loop of death)
while (getChannel(++lastCID) != null);
// reserve CID
channelsByCID.put(lastCID, null);
return lastCID;
}
}
/**
* Searches for a response request with given channel IOID.
* @param ioid I/O ID.
* @return request response with given I/O ID.
*/
public ResponseRequest getResponseRequest(int ioid)
{
synchronized (pendingResponseRequests)
{
return (ResponseRequest)pendingResponseRequests.get(ioid);
}
}
/**
* Register response request.
* @param request request to register.
* @return request ID (IOID).
*/
public int registerResponseRequest(ResponseRequest request)
{
synchronized (pendingResponseRequests)
{
int ioid = generateIOID();
pendingResponseRequests.put(ioid, request);
return ioid;
}
}
/**
* Unregister response request.
* @param request ResponseRequest
* @return removed object, can be null
*/
public ResponseRequest unregisterResponseRequest(ResponseRequest request)
{
synchronized (pendingResponseRequests)
{
return (ResponseRequest)pendingResponseRequests.remove(request.getIOID());
}
}
/**
* Generate IOID.
* @return IOID.
*/
private int generateIOID()
{
synchronized (pendingResponseRequests)
{
// search first free (theoretically possible loop of death)
while (pendingResponseRequests.get(++lastIOID) != null);
// reserve IOID
pendingResponseRequests.put(lastIOID, null);
return lastIOID;
}
}
/**
* Increment pending requests counter.
* @return IO sequence number (pendIO sequence number).
*/
public int incrementPendingRequests()
{
synchronized (sequenceNumberIO)
{
pendingRequestsCount.incrementAndGet();
return sequenceNumberIO.get();
}
}
/**
* Decrement pending requests counter.
* @param usedSequenceNumberIO IO sequence number returned by incrementPendingRequests
*/
public void decrementPendingRequests(int usedSequenceNumberIO)
{
if (usedSequenceNumberIO == sequenceNumberIO.get())
{
int count = pendingRequestsCount.decrementAndGet();
// notify if zero
if (count == 0)
{
synchronized (zeroPendingRequestsCondition)
{
zeroPendingRequestsCondition.notifyAll();
}
}
}
}
/**
* Set last UDP recived sequence number.
* @param seqNo last UDP recived sequence number.
*/
public final void setLastReceivedSequenceNumber(int seqNo)
{
lastReceivedSequenceNumber.set(seqNo);
}
/**
* Set last UDP recived sequence number.
* @param seqNo last UDP recived sequence number.
* @return last received sequence number
*/
public final int getLastReceivedSequenceNumber(int seqNo)
{
return lastReceivedSequenceNumber.get();
}
/**
* @see com.cosylab.epics.caj.impl.CAContext#invalidateLastReceivedSequence()
*/
public final void invalidateLastReceivedSequence()
{
lastReceivedSequenceNumber.set(0);
}
/**
* Get (and if necessary create) beacon handler.
* @param responseFrom remote source address of received beacon.
* @return beacon handler for particular server.
*/
public CABeaconHandler getBeaconHandler(InetSocketAddress responseFrom)
{
synchronized (beaconHandlers) {
CABeaconHandler handler = (CABeaconHandler)beaconHandlers.get(responseFrom);
if (handler == null)
{
handler = new CABeaconHandler(this, responseFrom);
beaconHandlers.put(responseFrom, handler);
}
return handler;
}
}
/**
* Modifies client username and notifies connected servers about it.
* @param userName username
*/
public void modifyUserName(String userName)
{
if (userName == null)
throw new NullPointerException("userName == null");
this.userName = userName;
Transport[] transports = getTransportRegistry().toArray();
for (int i = 0; i < transports.length; i++)
{
CATransport transport = (CATransport)transports[i];
try {
transport.updateUserName();
}
catch (Throwable th) {
logger.log(Level.WARNING, "Failed to update username for transport: " + transport.getRemoteAddress(), th);
}
}
}
public String getUserName()
{
return userName;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy