All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cosylab.epics.caj.CAJChannel Maven / Gradle / Ivy

Go to download

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>

There is a newer version: 2.4.2
Show newest version
/*
 * 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.CAStatus;
import gov.aps.jca.Channel;
import gov.aps.jca.Context;
import gov.aps.jca.Monitor;
import gov.aps.jca.dbr.DBR;
import gov.aps.jca.dbr.DBRFactory;
import gov.aps.jca.dbr.DBRType;
import gov.aps.jca.dbr.Severity;
import gov.aps.jca.event.AccessRightsEvent;
import gov.aps.jca.event.AccessRightsListener;
import gov.aps.jca.event.ConnectionEvent;
import gov.aps.jca.event.ConnectionListener;
import gov.aps.jca.event.GetListener;
import gov.aps.jca.event.MonitorListener;
import gov.aps.jca.event.PutListener;

import java.io.IOException;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.cosylab.epics.caj.impl.CAConstants;
import com.cosylab.epics.caj.impl.CATransport;
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.requests.ClearChannelRequest;
import com.cosylab.epics.caj.impl.requests.CreateChannelRequest;
import com.cosylab.epics.caj.impl.requests.EventAddRequest;
import com.cosylab.epics.caj.impl.requests.ReadNotifyRequest;
import com.cosylab.epics.caj.impl.requests.SearchRequest;
import com.cosylab.epics.caj.impl.requests.WriteNotifyRequest;
import com.cosylab.epics.caj.impl.requests.WriteRequest;

/**
 * Implementation of CAJ JCA Channel.
 * @author Matej Sekoranja
 * @version $id$
 */
public class CAJChannel extends Channel implements TransportClient {
	
	// Get Logger
	private static final Logger logger = Logger.getLogger(CAJChannel.class.getName());
	
	/**
	 * Client channel ID.
	 */
	protected int channelID;

	/**
	 * Context.
	 */
	protected CAJContext context;

	/**
	 * Channel name.
	 */
	protected String name;

	/**
	 * Channel access rights.
	 */
	protected int accessRights = 0;

	/**
	 * Process priority.
	 */
	protected short priority;

	/**
	 * Last reported connection status.
	 */
	protected boolean lastReportedConnectionState = false;

	/**
	 * Connection status.
	 */
	protected ConnectionState connectionState = ConnectionState.NEVER_CONNECTED;

	/**
	 * Channel data type.
	 */
	protected DBRType type = DBRType.UNKNOWN;

	/**
	 * Data type element count.
	 */
	protected int elementCount = 0;
	
	/**
	 * List of connection listeners.
	 */
	// TODO consider using weak references
	// TODO use them
	protected ArrayList connectionListeners = new ArrayList();

	/**
	 * List of access rights listeners.
	 */
	// TODO consider using weak references
	// TODO use them
	protected ArrayList accessRightsListeners = new ArrayList();

	/**
	 * Sync. connection (i.e. w/ callback) flag.
	 */
	protected boolean syncConnection = false;

	/**
	 * IO sequence number.
	 */
	protected int sequenceNumberIO;

	/**
	 * Map of channels (keys are CIDs).
	 * Lazy initialization used, since not all channel will have monitors.
	 */
	// TODO consider using WeakHashMap (and call Channel.destroy() in finalize() method).
	protected Map monitors = null;

	/**
	 * List of all channels pending requests. 
	 */
	protected Map responseRequests = new HashMap();
	
	/**
	 * Number of channel search tries. 
	 */
	protected int searchTries = 0;

	/**
	 * Allow reconnection flag. 
	 */
	protected boolean allowCreation = true;

	/**
	 * Reference counting. 
	 */
	protected int references = 1;

	/* ****************** */
	/* CA protocol fields */ 
	/* ****************** */

	/**
	 * Server transport.
	 */
	protected CATransport transport = null;

	/**
	 * Server channel ID.
	 */
	protected int serverChannelID = 0xFFFFFFFF;

	/* ****************** */
	 
	/**
	 * Constructor.
	 * @param context CAJContext
	 * @param channelID Client channel ID
	 * @param name Channel name
	 * @param listener ConnectionListener
	 * @param priority Process priority
	 * @throws CAException JCA Exception
	 */
	protected CAJChannel(CAJContext context, int channelID, String name,
			ConnectionListener listener, short priority) throws CAException
	{
		this.context = context;
		this.channelID = channelID;
		this.name = name;
		this.priority = priority;

		// register before issuing search request
		context.registerChannel(this);

		// add listener
		if (listener != null)
			addConnectionListener(listener);
		else
		{
			// add pending request, if sync (i.e. w/o callback)
			syncConnection = true;
			sequenceNumberIO = context.incrementPendingRequests();
		}

		// this has to be submitted immediately
		initiateSearch();
	}

	/**
	 * Create a channel, i.e. submit create channel request to the server.
	 * This method is called after search is complete.
	 * sid, typeCode, elementCount might not be
	 * valid, this depends on protocol revision.
	 * @param transport CA transport implementation.
	 * @param sid sid
	 * @param typeCode type code 
	 * @param elementCount element count
	 * @return true if real create channel request needs to be sent to the server.
	 */
	public synchronized boolean createChannel(CATransport transport, int sid, short typeCode, int elementCount) 
	{

		// do not allow duplicate creation to the same transport
		if (!allowCreation)
			return false;
		allowCreation = false;
		
		// TODO is this really necesarry... 1. priority is to take channel from the existing one,
		// so transport must be the same... or until disconnected, i.e. transport is null
		// remote address check was already done in context... here reference check is done
		// check existing transport
		if (this.transport != null && this.transport != transport)
		{
			disconnectPendingIO(false);
			this.transport.release(this);
		}
		else if (this.transport == transport)
		{
			// request to sent create request to same transport, ignore
			// this happens when server is slower (processing search requests) than client generating it
			return false;
		}
		
		this.transport = transport;
		
		// revision < v4.4 supply this info already now
		if (transport.getMinorRevision() < 4)
		{
			this.serverChannelID = sid;
			this.type = DBRType.forValue(typeCode);
			if (this.type == null)
				this.type = DBRType.UNKNOWN;
			this.elementCount = elementCount;
		}

		// do not submit CreateChannelRequest here, connection loss while submitting and lock
		// on this channel instance may cause deadlock
		return true;
	}
	
	// this is completely not sycned
	public void issueCreateChannelRequest()
	{
		try
		{
			// submit (immediately)
			new CreateChannelRequest(transport, name, channelID).submit();
		}
		catch (IOException ioex)
		{
			createChannelFailed();
		}
	}

	/**
	 * Cancelation status.
	 */
	protected volatile boolean canceled = false;

	/**
	 * Called when connecton completed (successfully or not). 
	 * @see com.cosylab.epics.caj.impl.ResponseRequest#cancel()
	 */
	public synchronized void cancel() {
		if (canceled)
			return;
		canceled = true;
		if (syncConnection)
		{
			syncConnection = false;
			context.decrementPendingRequests(sequenceNumberIO);
		}
	}

	/**
	 * @see com.cosylab.epics.caj.impl.ResponseRequest#timeout()
	 */
	public void timeout() {
		createChannelFailed();
	}

	/**
	 * Create channel failed.
	 */
	public synchronized void createChannelFailed()
	{
		cancel();
		// ... and search again
		initiateSearch();
	}

	/**
	 * Called when channel created succeeded on the server.
	 * sid might not be valid, this depends on protocol revision.
	 * @param sid sid
	 * @param typeCode type code
	 * @param elementCount element count
	 * @throws IllegalStateException IllegalStateException
	 */
	public synchronized void connectionCompleted(int sid, short typeCode, int elementCount) 
		throws IllegalStateException
	{
		try
		{
			// do this silently
			//checkNotClosedState();
			if (connectionState == ConnectionState.CLOSED)
				return;
			// TODO revise this reconnection...
			//else if (connectionState == ConnectionState.CONNECTED)
			//	throw new IllegalStateException("Channel already connected.");
			
			// revision < v4.1 do not have access rights, grant all
			if (transport.getMinorRevision() < 1)
				setAccessRights(CAConstants.CA_PROTO_ACCESS_RIGHT_READ |
								CAConstants.CA_PROTO_ACCESS_RIGHT_WRITE);
		
			// revision < v4.4 supply this info already now
			if (transport.getMinorRevision() >= 4)
				serverChannelID = sid;
		
			this.type = DBRType.forValue(typeCode);
			if (this.type == null)
				this.type = DBRType.UNKNOWN;
			this.elementCount = elementCount;

			// user might create monitors in listeners, so this has to be done before this can happen
			// however, it would not be nice if events would come before connection event is fired
			// but this cannot happen since transport (TCP) is serving in this thread 
			resubscribeSubscriptions();
			setConnectionState(ConnectionState.CONNECTED);
		}
		finally
		{
			// end connection request
			cancel();
		}
	}

	/**
	 * @see gov.aps.jca.Channel#destroy()
	 */
	public synchronized void destroy() throws CAException, IllegalStateException {
		destroy(false);
	}

	/**
     * @param force force destruction regardless of reference count
     * @see gov.aps.jca.Channel#destroy()
	 * @param force force destruction regardless of reference count
	 * @throws CAException JCA Exception
	 * @throws IllegalStateException  illegal state exception
	 */
	public synchronized void destroy(boolean force) throws CAException, IllegalStateException {
		
		if (connectionState == ConnectionState.CLOSED)
			throw new IllegalStateException("Channel already destroyed.");
			
		// do destruction via context
		context.destroyChannel(this, force);
		
	}

	/**
	 * Increment reference.
	 */
	public synchronized void acquire() {
		references++;
	}

	/**
	 * Actual destory method, to be called CAJContext.
	 * @param force force destruction regardless of reference count
	 * @throws CAException JCA Exception
	 * @throws IllegalStateException method has been invoked at an illegal or inappropriate time
	 * @throws IOException I/O exception 
	 */
	public synchronized void destroyChannel(boolean force) throws CAException, IllegalStateException, IOException {

		if (connectionState == ConnectionState.CLOSED)
			throw new IllegalStateException("Channel already destroyed.");

		references--;
		if (references > 0 && !force)
			return;
		
		// stop searching...
		context.getChannelSearchManager().unregisterChannel(this);
		cancel();

		destroyAllMonitors();
		
		disconnectPendingIO(true);

		if (connectionState == ConnectionState.CONNECTED)
		{
			if (transport != null)
			{
				try
				{
					// NOTE: this does not submit message immediately, waits for flush to be triggered somehow.. hmmm.
					// possible deadlock if sent immediately (since lock on this channel instance is held)
					new ClearChannelRequest(transport, channelID, serverChannelID).submit();
				}
				catch (IOException ioex)
				{
					// TODO remove?
					logger.log(Level.SEVERE, "", ioex);
				}
			}
			
			// TODO transport is closed immedately,
			// so it might happen that ClearChannelRequest is not sent/response received
			disconnect(false);
		}
		else if (transport != null)
		{
			// unresponsive state, do not forget to release transport
			transport.release(this);
			transport = null;
		}

		setConnectionState(ConnectionState.CLOSED);
		
		// unregister
		context.unregisterChannel(this);

		synchronized (accessRightsListeners)
		{
			accessRightsListeners.clear();
		}
				
		/*
		// this makes problem to the queued dispatchers...
		synchronized (connectionListeners)
		{
			connectionListeners.clear();
		}
		*/
	}

	/**
	 * Disconnected notification.
	 * @param initiateSearch	flag to indicate if searching (connect) procedure should be initiated
	 */
	public synchronized void disconnect(boolean initiateSearch) {
//System.err.println("CHANNEL disconnect");
		
		if (connectionState != ConnectionState.CONNECTED && transport == null)
			return;
			
		setConnectionState(ConnectionState.DISCONNECTED);

		disconnectPendingIO(false);

		// release transport
		if (transport != null)
		{
			transport.release(this);
			transport = null;
		}
		
		if (initiateSearch)
			initiateSearch();

	}
	
	/**
	 * Initiate search (connect) procedure.
	 */
	public synchronized void initiateSearch()
	{
		allowCreation = true;
		context.getChannelSearchManager().registerChannel(this);
	}

	/**
     * Send search message.
	 * @param transport transport to be used when sending
	 * @param buffer  buffer to be filled
     * @return success status.
	 */
	public synchronized boolean generateSearchRequestMessage(Transport transport, ByteBuffer buffer)
	{
		ByteBuffer result = SearchRequest.generateSearchRequestMessage(transport, buffer, name, channelID);
		if (result == null)
			return false;
		
		if (searchTries < Integer.MAX_VALUE)
			searchTries++;
		
		return true;
	}
	 
	/**
	 * @see com.cosylab.epics.caj.impl.TransportClient#transportClosed()
	 */
	public void transportClosed() {
//System.err.println("CHANNEL transportClosed");
		disconnect(true);
	}

	/**
	 * @see com.cosylab.epics.caj.impl.TransportClient#transportChanged()
	 */
	public synchronized void transportChanged() {
//System.err.println("CHANNEL transportChanged");
		if (connectionState == ConnectionState.DISCONNECTED)
			initiateSearch();
	}

	/**
	 * @see com.cosylab.epics.caj.impl.TransportClient#transportResponsive(com.cosylab.epics.caj.impl.Transport)
	 */
	public synchronized void transportResponsive(Transport transport) {
//System.err.println("CHANNEL transportResponsive");
		if (connectionState == ConnectionState.DISCONNECTED)
		{
			/*
			// responsive again, stop searching
			context.getChannelSearchManager().unregisterChannel(this);
			 */
			 
			updateSubscriptions();
			
			// reconnect using existing IDs, data
			connectionCompleted(serverChannelID, (short)type.getValue(), elementCount);
		}
	}

	/**
	 * @see com.cosylab.epics.caj.impl.TransportClient#transportUnresponsive()
	 */
	public synchronized void transportUnresponsive() {
//System.err.println("CHANNEL transportUnresponsive");
		if (connectionState == ConnectionState.CONNECTED)
		{
			
			// NOTE: 2 types of disconnected state - distinguish them
			setConnectionState(ConnectionState.DISCONNECTED);

			// ... CA notifies also w/ no access rights callback, although access right are not changed 
			
			
			//transportClosed();
		}
	}

	/**
	 * @see gov.aps.jca.Channel#getConnectionListeners()
	 */
	public ConnectionListener[] getConnectionListeners()
		throws IllegalStateException {
		synchronized (connectionListeners)
		{
			ConnectionListener[] listeners = new ConnectionListener[connectionListeners.size()];
			return (ConnectionListener[])connectionListeners.toArray(listeners);
		}
	}

	/**
	 * @see gov.aps.jca.Channel#addConnectionListener(gov.aps.jca.event.ConnectionListener)
	 */
	public void addConnectionListener(ConnectionListener l)
		throws CAException, IllegalStateException {
		checkNotClosedState();

		if (l == null)
			throw new IllegalArgumentException("l == null");
			
		synchronized (connectionListeners)
		{
			if (!connectionListeners.contains(l))
				connectionListeners.add(l);
		}
	}

	/**
	 * 
     * @see gov.aps.jca.Channel#addConnectionListener(gov.aps.jca.event.ConnectionListener)
     * 
	 * @param l ConnectionListener
	 * @throws CAException JCA Exception
	 * @throws IllegalStateException if ConnectionListener is null or the channel is in no state to perform this operation (ie destroyed, etc...)
	 */
	public synchronized void addConnectionListenerAndFireIfConnected(ConnectionListener l)
		throws CAException, IllegalStateException {
		checkNotClosedState();

		if (l == null)
			throw new IllegalArgumentException("l == null");
		
		if (connectionState == ConnectionState.CONNECTED)
			context.getEventDispatcher().dispatch(new ConnectionEvent(this, true), l);

		addConnectionListener(l);
	}

	/**
	 * @see gov.aps.jca.Channel#removeConnectionListener(gov.aps.jca.event.ConnectionListener)
	 */
	public void removeConnectionListener(ConnectionListener l)
		throws CAException, IllegalStateException {
		checkNotClosedState();

		if (l == null)
			throw new IllegalArgumentException("l == null");
		
		synchronized (connectionListeners)
		{
			connectionListeners.remove(l);
		}
	}

	/**
     * Set connection state and if changed, notifies listeners.
	 * @param connectionState state to set.
	 */
	private synchronized void setConnectionState(ConnectionState connectionState)
	{
		if (this.connectionState != connectionState)
		{
			this.connectionState = connectionState;
			
			boolean connectionStatusToReport = (connectionState == ConnectionState.CONNECTED);
			if (connectionStatusToReport != lastReportedConnectionState)
			{
				lastReportedConnectionState = connectionStatusToReport;
				synchronized (connectionListeners)
				{
					context.getEventDispatcher().dispatch(
						new ConnectionEvent(this, connectionStatusToReport),
						connectionListeners
					);
				}
			}
		}
	}
	
	/**
	 * @see gov.aps.jca.Channel#getAccessRightsListeners()
	 */
	public AccessRightsListener[] getAccessRightsListeners()
		throws IllegalStateException {
		synchronized (accessRightsListeners)
		{
			AccessRightsListener[] listeners = new AccessRightsListener[accessRightsListeners.size()];
			return (AccessRightsListener[])accessRightsListeners.toArray(listeners);
		}
	}

	/**
	 * @see gov.aps.jca.Channel#addAccessRightsListener(gov.aps.jca.event.AccessRightsListener)
	 */
	public void addAccessRightsListener(AccessRightsListener l)
		throws CAException, IllegalStateException {
		checkNotClosedState();

		if (l == null)
			throw new IllegalArgumentException("l == null");
			
		synchronized (accessRightsListeners)
		{
			if (!accessRightsListeners.contains(l))
				accessRightsListeners.add(l);
		}
	}

	/**
	 * @see gov.aps.jca.Channel#removeAccessRightsListener(gov.aps.jca.event.AccessRightsListener)
	 */
	public void removeAccessRightsListener(AccessRightsListener l)
		throws CAException, IllegalStateException {
		checkNotClosedState();

		if (l == null)
			throw new IllegalArgumentException("l == null");
	
		synchronized (accessRightsListeners)
		{
			accessRightsListeners.remove(l);
		}
	}

	/**
	 * Set access rights.
	 * @param accessRights	access rights to set
	 */
	public synchronized void setAccessRights(int accessRights) 
	{
		if (this.accessRights != accessRights)
		{
			this.accessRights = accessRights;

			// do not use access rights accessors since they check state
			boolean readAccess  = (accessRights & CAConstants.CA_PROTO_ACCESS_RIGHT_READ) != 0; 
			boolean writeAccess = (accessRights & CAConstants.CA_PROTO_ACCESS_RIGHT_WRITE) != 0; 

			synchronized (accessRightsListeners)
			{
				context.getEventDispatcher().dispatch(
					new AccessRightsEvent(this, readAccess, writeAccess),
					accessRightsListeners
				);
			}
		}
	}

	/**
	 * @see gov.aps.jca.Channel#getName()
	 */
	public String getName() throws IllegalStateException {
		return name;
	}

	/**
	 * @see gov.aps.jca.Channel#getFieldType()
	 */
	public synchronized DBRType getFieldType() throws IllegalStateException {
		checkState();
		return type;
	}

	/**
	 * @see gov.aps.jca.Channel#getElementCount()
	 */
	public synchronized int getElementCount() throws IllegalStateException {
		checkState();
		return elementCount;
	}

	/**
	 * @see gov.aps.jca.Channel#getConnectionState()
	 */
	public synchronized ConnectionState getConnectionState() throws IllegalStateException {
		return connectionState;
	}

	/**
	 * NOTE: synchronization guarantees that transport is non-null and state == CONNECTED.
	 * @see gov.aps.jca.Channel#getHostName()
	 */
	public synchronized String getHostName() throws IllegalStateException {
		connectionRequiredCheck();
		return transport.getRemoteAddress().getHostName();
	}

	/**
	 * @see gov.aps.jca.Channel#getReadAccess()
	 */
	public synchronized boolean getReadAccess() throws IllegalStateException {
		checkState();
		return (accessRights & CAConstants.CA_PROTO_ACCESS_RIGHT_READ) != 0;
	}

	/**
	 * @see gov.aps.jca.Channel#getWriteAccess()
	 */
	public synchronized boolean getWriteAccess() throws IllegalStateException {
		checkState();
		return (accessRights & CAConstants.CA_PROTO_ACCESS_RIGHT_WRITE) != 0;
	}

	/**
	 * @see gov.aps.jca.Channel#put(byte[])
	 */
	public void put(byte[] value) throws CAException, IllegalStateException {
		put(DBRType.BYTE, value.length, value);
	}

	/**
	 * @see gov.aps.jca.Channel#put(byte[], gov.aps.jca.event.PutListener)
	 */
	public void put(byte[] value, PutListener l)
		throws CAException, IllegalStateException {
		put(DBRType.BYTE, value.length, value, l);
	}

	/**
	 * @see gov.aps.jca.Channel#put(short[])
	 */
	public void put(short[] value) throws CAException, IllegalStateException {
		put(DBRType.SHORT, value.length, value);
	}

	/**
	 * @see gov.aps.jca.Channel#put(short[], gov.aps.jca.event.PutListener)
	 */
	public void put(short[] value, PutListener l)
		throws CAException, IllegalStateException {
		put(DBRType.SHORT, value.length, value, l);
	}

	/**
	 * @see gov.aps.jca.Channel#put(int[])
	 */
	public void put(int[] value) throws CAException, IllegalStateException {
		put(DBRType.INT, value.length, value);
	}

	/**
	 * @see gov.aps.jca.Channel#put(int[], gov.aps.jca.event.PutListener)
	 */
	public void put(int[] value, PutListener l)
		throws CAException, IllegalStateException {
			put(DBRType.INT, value.length, value, l);
	}

	/**
	 * @see gov.aps.jca.Channel#put(float[])
	 */
	public void put(float[] value) throws CAException, IllegalStateException {
		put(DBRType.FLOAT, value.length, value);
	}

	/**
	 * @see gov.aps.jca.Channel#put(float[], gov.aps.jca.event.PutListener)
	 */
	public void put(float[] value, PutListener l)
		throws CAException, IllegalStateException {
		put(DBRType.FLOAT, value.length, value, l);
	}

	/**
	 * @see gov.aps.jca.Channel#put(double[])
	 */
	public void put(double[] value) throws CAException, IllegalStateException {
		put(DBRType.DOUBLE, value.length, value);
	}

	/**
	 * @see gov.aps.jca.Channel#put(double[], gov.aps.jca.event.PutListener)
	 */
	public void put(double[] value, PutListener l)
		throws CAException, IllegalStateException {
		put(DBRType.DOUBLE, value.length, value, l);
	}

	/**
	 * @see gov.aps.jca.Channel#put(java.lang.String[])
	 */
	public void put(String[] value) throws CAException, IllegalStateException {
		put(DBRType.STRING, value.length, value);
	}

	/**
	 * @see gov.aps.jca.Channel#put(java.lang.String[], gov.aps.jca.event.PutListener)
	 */
	public void put(String[] value, PutListener l)
		throws CAException, IllegalStateException {
		put(DBRType.STRING, value.length, value, l);
	}

	/**
	 * @see gov.aps.jca.Channel#putACKS(gov.aps.jca.dbr.Severity, gov.aps.jca.event.PutListener)
	 */
	public void putACKS(Severity severity, PutListener l)
		throws CAException, IllegalStateException {
		put(DBRType.PUT_ACKS, 1, new short[] { (short)severity.getValue()}, l);
	}

	/**
	 * @see gov.aps.jca.Channel#putACKS(gov.aps.jca.dbr.Severity)
	 */
	public void putACKS(Severity severity) throws CAException, IllegalStateException {
		put(DBRType.PUT_ACKS, 1, new short[] { (short)severity.getValue()} );
	}

	/**
	 * @see gov.aps.jca.Channel#putACKT(boolean, gov.aps.jca.event.PutListener)
	 */
	public void putACKT(boolean value, PutListener l) throws CAException, IllegalStateException {
		put(DBRType.PUT_ACKT, 1, new short[] { value ? (short)1 : (short)0 }, l);
	}

	/**
	 * @see gov.aps.jca.Channel#putACKT(boolean)
	 */
	public void putACKT(boolean value) throws CAException, IllegalStateException {
		put(DBRType.PUT_ACKT, 1, new short[] { value ? (short)1 : (short)0 });
	}

	/**
	 * @see gov.aps.jca.Channel#get(gov.aps.jca.dbr.DBRType, int)
	 */
	public DBR get(DBRType type, int count)
		throws CAException, IllegalStateException {
		connectionRequiredCheck();

		// sync get requires predefined count; variable length array not possible
		if (count <= 0)
			throw new IllegalArgumentException("count <= 0");
		
		if (!getReadAccess())
			throw new CAException("No read access rights granted."); 

		Transport t = getTransport();
		if (t != null)
		{
			try
			{
				DBR retVal = DBRFactory.create(type, count);
				new ReadNotifyRequest(this, null, retVal, t, getServerChannelID(), type.getValue(), count).submit();
				return retVal;
			}
			catch (IOException ioex)
			{
				throw new CAException("Failed to retrieve value.", ioex);
			} 
		}
		else
			throw new IllegalStateException("No channel transport available, channel disconnected.");
	}

	public DBR get(DBR preallocatedDBR, DBRType type, int count)
	throws CAException, IllegalStateException {
	connectionRequiredCheck();

	if (!getReadAccess())
		throw new CAException("No read access rights granted."); 

	Transport t = getTransport();
	if (t != null)
	{
		try
		{
			DBR retVal = preallocatedDBR; //DBRFactory.create(type, count);
			new ReadNotifyRequest(this, null, retVal, t, getServerChannelID(), type.getValue(), count).submit();
			return retVal;
		}
		catch (IOException ioex)
		{
			throw new CAException("Failed to retrieve value.", ioex);
		} 
	}
	else
		throw new IllegalStateException("No channel transport available, channel disconnected.");
}

	/**
	 * @see gov.aps.jca.Channel#get(gov.aps.jca.dbr.DBRType, int, gov.aps.jca.event.GetListener)
	 */
	public void get(DBRType type, int count, GetListener l)
		throws CAException, IllegalStateException {
		connectionRequiredCheck();

		if (!getReadAccess())
			throw new CAException("No read access rights granted."); 

		Transport t = getTransport();
		if (t != null)
		{
			try
			{
                if (count == 0 && t.getMinorRevision() < 13)
                    count = getElementCount();

                new ReadNotifyRequest(this, l, null, t, getServerChannelID(), type.getValue(), count).submit();
			}
			catch (IOException ioex)
			{
				throw new CAException("Failed to retrieve value.", ioex);
			} 
		}
		else
			throw new IllegalStateException("No channel transport available, channel disconnected.");
	}

	/**
	 * Put value.
	 * @param type DBRType
	 * @param count count
	 * @param value value to be put
	 * @throws CAException JCA Exception
	 * @throws IllegalStateException No channel transport available, channel disconnected.
	 */
	public void put(DBRType type, int count, Object value)
		throws CAException, IllegalStateException {
		connectionRequiredCheck();

		if (!getWriteAccess())
			throw new CAException("No write access rights granted."); 

		Transport t = getTransport();
		if (t != null)
		{
			try
			{
				new WriteRequest(this, t, getServerChannelID(), channelID, type.getValue(), count, value).submit();
			}
			catch (IOException ioex)
			{
				throw new CAException("Failed to set value.", ioex);
			} 
		}
		else
			throw new IllegalStateException("No channel transport available, channel disconnected.");
	}

	/**
	 * Put value.
	 * @param type DBRType
	 * @param count data count 
	 * @param value value to put
	 * @param l PutListener
	 * @throws CAException JCA Exception
	 * @throws IllegalStateException No channel transport available, channel disconnected.
	 */
	public void put(DBRType type, int count, Object value, PutListener l)
		throws CAException, IllegalStateException {
		connectionRequiredCheck();
		
		if (!getWriteAccess())
			throw new CAException("No write access rights granted."); 

		Transport t = getTransport();
		if (t != null)
		{
			try
			{
				if (l == null)
					new WriteRequest(this, t, getServerChannelID(), channelID, type.getValue(), count, value).submit();
				else
					new WriteNotifyRequest(this, l, t, getServerChannelID(), type.getValue(), count, value).submit();
			}
			catch (IOException ioex)
			{
				throw new CAException("Failed to set value.", ioex);
			} 
		}
		else
			throw new IllegalStateException("No channel transport available, channel disconnected.");

	}

	/**
	 * @see gov.aps.jca.Channel#addMonitor(gov.aps.jca.dbr.DBRType, int, int, gov.aps.jca.event.MonitorListener)
	 */
	public synchronized Monitor addMonitor(
		DBRType type,
		int count,
		int mask,
		MonitorListener l)
		throws CAException, IllegalStateException {
		if (count == 0)
        {
			Transport t = getTransport();
			if (t != null && t.getMinorRevision() < 13)
	    		count = getElementCount();
        }
		checkNotClosedState();
		checkMonitorSize(type, count, context.getMaxArrayBytes());
		return new CAJMonitor(context, type, count, this, l, mask);
	}

	/**
	 * Get client channel ID.
	 * @return client channel ID.
	 */
	public int getChannelID() {
		return channelID;
	}

	/**
	 * @see gov.aps.jca.Channel#getContext()
	 */
	public Context getContext() throws IllegalStateException {
		return context;
	}

	/**
	 * Checks if channel is in connected state,
	 * if not throws IllegalStateException if not.
	 */
	private void connectionRequiredCheck()
	{
		if (connectionState != ConnectionState.CONNECTED)
			throw new IllegalStateException("Channel not connected.");
	}

	/**
	 * Checks if channel is in connected or disconnected state,
	 * if not throws IllegalStateException if not.
	 */
	private void checkState()
	{
		// connectionState is always non-null
		if (connectionState != ConnectionState.CONNECTED && connectionState != ConnectionState.DISCONNECTED)
			throw new IllegalStateException("Channel not in connected or disconnected state, state = '" + connectionState.getName() + "'.");
	}
	
	/**
	 * Checks if channel is not it closed state.
	 * if not throws IllegalStateException if not.
	 */
	private void checkNotClosedState()
	{
		if (connectionState == ConnectionState.CLOSED)
			throw new IllegalStateException("Channel closed.");
	}

	/**
	 * @see gov.aps.jca.Channel#printInfo(java.io.PrintStream)
	 */
	public synchronized void printInfo(PrintStream out) throws IllegalStateException {
		if (connectionState != ConnectionState.CONNECTED)
		{
			out.println("CHANNEL  : " + name);
			out.println("TYPE     : " + type);
			out.println("COUNT    : " + elementCount);
			out.println("STATE    : " + connectionState);
		}
		else
			super.printInfo(out);
	}

	/**
	 * Get transport used by this channel.
	 * @return transport used by this channel.
	 */
	public synchronized CATransport getTransport() {
		return transport;
	}

	/**
	 * Get SID.
	 * @return SID.
	 */
	public synchronized int getServerChannelID() {
		return serverChannelID;
	}

	/**
	 * Register monitor.
	 * @param monitor
	 */
	void registerMonitor(CAJMonitor monitor)
	{
		// lazy initialization (double-check sync. pattern)
		if (monitors == null)
		{
			synchronized (this)
			{
				if (monitors == null)
					monitors = new HashMap();
			}
		}
			
		synchronized (monitors)
		{
			monitors.put(new Integer(monitor.getSID()), monitor);
		}
	}

	/**
	 * Unregister monitor.
	 * @param monitor
	 */
	void unregisterMonitor(CAJMonitor monitor)
	{
		if (monitors == null)
			return;

		synchronized (monitors)
		{
			monitors.remove(new Integer(monitor.getSID()));
		}
	}

	/**
	 * Destroy all monitors.
	 */
	private void destroyAllMonitors() {
		
		if (monitors == null)
			return;
			
		CAJMonitor[] monitorsArray;
		synchronized (monitors)
		{
			monitorsArray = new CAJMonitor[monitors.size()];
			monitors.values().toArray(monitorsArray);
			monitors.clear();
		}

		for (int i = 0; i < monitorsArray.length; i++)
		{
			try
			{
				monitorsArray[i].clear();
			}
			catch (Throwable th)
			{
				logger.log(Level.SEVERE, "", th);
			}
		}
	}


	/** 
	 * Register a response request.
	 * @param responseRequest response request to register.
	 */
	public void registerResponseRequest(ResponseRequest responseRequest)
	{
		synchronized (responseRequests)
		{
			responseRequests.put(responseRequest, null);
		}
	}

	/* 
	 * Unregister a response request.
	 * @param responseRequest response request to unregister.
	 */
	public void unregisterResponseRequest(ResponseRequest responseRequest)
	{
		synchronized (responseRequests)
		{
			responseRequests.remove(responseRequest);
		}
	}
	
	/**
	 * Disconnects (destroys) all channels pending IO.
	 * @param destroy	true if channel is being destroyed.
	 */
	private void disconnectPendingIO(boolean destroy)
	{
		CAStatus status;
		if (destroy)
			status = CAStatus.CHANDESTROY;
		else
			status = CAStatus.DISCONN;
			
		ResponseRequest[] rrs;
		synchronized (responseRequests)
		{
			rrs = new ResponseRequest[responseRequests.size()];
			responseRequests.keySet().toArray(rrs);
		}
		
		for (int i = 0; i < rrs.length; i++)
		{
			try
			{
				rrs[i].exception(status.getStatusCode(), null);
			}
			catch (Throwable th)
			{
				// TODO remove
				logger.log(Level.SEVERE, "", th);
			}
		}
		
	}
	
	/**
	 * Resubscribe subscriptions. 
	 */
	private void resubscribeSubscriptions()
	{
		synchronized (responseRequests)
		{
			// sync get
			Transport transport = getTransport();
			
			ResponseRequest[] rrs = new ResponseRequest[responseRequests.size()];
			responseRequests.keySet().toArray(rrs);
			for (int i = 0; i < rrs.length; i++)
			{
				try
				{
					if (rrs[i] instanceof EventAddRequest)
					{
						EventAddRequest ear = (EventAddRequest)rrs[i];
						ear.resubscribeSubscription(transport);
					}
				}
				catch (Throwable th)
				{
					// TODO remove
					logger.log(Level.SEVERE, "", th);
				}
			}
		}
	}

	/**
	 * Update subscribtions. 
	 */
	private void updateSubscriptions()
	{
		synchronized (responseRequests)
		{
			ResponseRequest[] rrs = new ResponseRequest[responseRequests.size()];
			responseRequests.keySet().toArray(rrs);
			for (int i = 0; i < rrs.length; i++)
			{
				try
				{
					if (rrs[i] instanceof EventAddRequest)
					{
						EventAddRequest ear = (EventAddRequest)rrs[i];
						ear.updateSubscription();
					}
				}
				catch (Throwable th)
				{
					// TODO remove
					logger.log(Level.SEVERE, "", th);
				}
			}
		}
	}

	/**
	 * Get number of search tried for this channel.
	 * @return number of search tried for this channel.
	 */
	public int getSearchTries() {
		return searchTries;
	}

	/**
	 * Get process priority.
	 * @return process priority.
	 */
	public short getPriority() {
		return priority;
	}

	
	protected final AtomicReference timerIdRef = new AtomicReference();
	
	public void setTimerId(Object timerId)
	{
		timerIdRef.set(timerId);
	}
	
	public Object getTimerId()
	{
		return timerIdRef.get();
	}
	

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString()
	{
		return getClass().getSimpleName() + " = { name = " + name + ", connectionState = " + connectionState.getName() + " }";
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy