package de.desy.tine.client;
import java.net.InetAddress;
import java.lang.reflect.Array;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import de.desy.tine.addrUtils.*;
import de.desy.tine.bitfieldUtils.TBitfield;
import de.desy.tine.bitfieldUtils.TBitfieldRegistry;
import de.desy.tine.client.TLinkFactory.RelinkedItem;
import de.desy.tine.dataUtils.*;
import de.desy.tine.definitions.*;
import de.desy.tine.exceptions.BoundToInactiveLinkException;
import de.desy.tine.exceptions.InvalidDataReferenceException;
import de.desy.tine.exceptions.UnresolvedAddressException;
import de.desy.tine.headers.*;
import de.desy.tine.io.TBucket;
import de.desy.tine.queryUtils.TQuery;
import de.desy.tine.server.logger.DbgLog;
import de.desy.tine.server.logger.MsgLog;
import de.desy.tine.server.properties.TPropertyList;
import de.desy.tine.server.properties.TStockProperties;
import de.desy.tine.structUtils.TStructDescription;
import de.desy.tine.structUtils.TStructRegistry;
/**
* TLink.java
*
* User interface calls to access tine data links.
*
* The methods presented here deal with linking local application data with
* remote data sources. Primarily you will need to create a TLink object,
* passing the necessary identifying parameters of the remote server to which
* the link is to be established. When the TLink object is instantiated,
* you will then need to retrieve the data using one of the retrieval
* methods, e.g. execute() for synchronous acquisition, or attach() for
* asynchronous acquisition, or receive() for the acquisition of network
* globals.
*/
public class TLink // mdavid: removed 'final' statement
//public final class TLink // mdavid: commented
{
/*
* (non-Javadoc)
*
* mdavid: actually, isn't a good idea to rely on finalize,
* since the JVM doesn't guarantee that finalize is ever called on any
* object. May be use "Runtime.getRuntime().addShutdownHook(new Thread(<Runnable_Cleaner>))" instead.
*
* @see java.lang.Object#finalize()
*/
protected void finalize() throws Throwable
{
if (active)
{
MsgLog.log("finalize TLink","closing active link to /"+cntName+"/"+expName+"/"+devName+"["+devProperty+"] in finalizer",0,null,1);
close();
}
// super.finalize(); mdavid: commented, unnecessary - Object has no finalize implementation
}
private Object criticalMutex = new Object();
private boolean isInCriticalSection = false;
protected boolean getCriticalSection()
{ // protect the watchdog thread and io thread from deadlocking on the link
if (isInCriticalSection) return false;
synchronized (criticalMutex)
{
isInCriticalSection = true;
}
return isInCriticalSection;
}
protected void freeCriticalSection()
{
if (isInCriticalSection) isInCriticalSection = false;
}
public String cntName;
public String expName;
public String devName;
public String devProperty;
public TDataType dOutput;
public TDataType dInput;
public TDataType dError;
private static int maxOutputBufferSize = 0xffff;
private String arrayDelimiter;
public short devAccess;
public int devTimeout;
public static int defaultTimeout = 500; // milliseconds
short renewalMultiplier = 1;
public short getRenewalMultiplier() { return renewalMultiplier; }
public void setRenewalMultiplier(short value) { renewalMultiplier = value; }
private boolean isRenewal = false;
public boolean isRenewal() { return isRenewal; }
public void setIsRenewal(boolean value) { isRenewal = value; }
public short srvId; // for network subscriptions
private InetAddress mcastGrp = null;
public InetAddress getMulticastGroup() { return mcastGrp; }
public void setMulticastGroup(InetAddress group) { mcastGrp = group; }
private int tineProtocol = TTransport.DEFAULT_PROTOCOL_LEVEL;
boolean active;
boolean keepActive = false;
protected boolean isInAlarmState = false;
protected boolean canSetAlarms = true;
public void setAutoLinkErrorAlarms(boolean value) { canSetAlarms = value; }
public boolean getAutoLinkErrorAlarms() { return canSetAlarms; }
public boolean isActive()
{
//if (linkStatus == -1 || linkStatus == TErrorList.not_signalled) return false; // pending (don't count it yet)
if (linkStatus == TErrorList.not_signalled) return false; // pending (don't count it yet)
if (sub == null) return false;
if ((sub.mode & TMode.CM_REGISTER) == TMode.CM_REGISTER) return false;
if (boundTo != null) return false;
return active;
}
boolean terminate;
public int removedFromMcaList = 0;
boolean delayEstablishLink = false;
void delayEstablishLink(boolean value)
{
delayEstablishLink = value;
if (value) tf.hasDeferredLinks(true);
}
boolean needsNotification = false;
boolean isInsideCallback = false;
boolean isWildcardLink = false;
boolean isGlobalsLink = false;
boolean isGlobalsLinkPinned = false;
boolean isQuery = false;
boolean useErrValue = false;
boolean useErrObject = false;
boolean isRedirected = false;
boolean needsWakeUpCall = false;
String rdrKey = null;
public String getRedirectionKey()
{
return rdrKey != null ? rdrKey : "";
}
RelinkedItem relnkItem = null;
TLinkHook hook = null;
Object hookObj = null;
public boolean adjustDefaultValues = false;
public boolean retryOnTimeoutError = true;
public boolean removeOnClose = false; // was true
public boolean hasRenewed = false;
public boolean hasNotifiedOnce = false;
public boolean hasObtainedStatus = false;
public boolean isBeingWatched = false;
public boolean needsToStartLinkWatchdog = false;
public boolean cannotNotifyFromWatchdogThread = false;
public boolean canTimeout = true;
public boolean isLinkReassignment = false;
public boolean isAlive()
{
if (isLinkReassignment) return true;
if (!TErrorList.isLinkSuccess(linkStatus)) return false;
if (linkTimeouts > 0) return false;
return true;
}
public TSrvEntry srvAddr;
public TSrvEntry getServerAddress() { return srvAddr; }
public String toString()
{
String s = getFullDeviceNameAndProperty();
String tag = " " + TMode.toString(sub.mode);
if (isRedirected)
{
tag += " is redirected from "+rdrKey;
}
if (relnkItem != null)
{
String dtag = "";
String dfmt = TFormat.toString(relnkItem.getRelinkDataFormat())+"s";
if (relnkItem.getBitfield() != null)
{
dtag = relnkItem.getBitfield().getTag();
dfmt = "bitfields";
}
if (relnkItem.getStructDescription() != null)
{
dfmt = "structures";
dtag = relnkItem.getStructDescription().getTagName();
}
tag += " is relinked from "+relnkItem.getRelinkDataLength()+" "+dtag+" "+dfmt;
}
if (boundTo != null)
{
tag += " is bound to "+boundTo.getFullDeviceNameAndProperty();
if (boundTo.mcaIndex > 0) tag += " (an MCA element)";
else if (mcaIndex > 0) tag += " (as an MCA element)";
}
s += tag + " @" + sub.pollingInterval + " msec";
s += " <"+linkStatus+"> ";
return s;
}
TContract con;
TSubscription sub;
public TSubscription getSubscription() { return sub; }
TPHdr pHdr;
TReqHdr reqHdr;
TLink boundTo = null;
boolean cancelledWithDependencies = false;
private TLinkGroup grp = null;
public boolean isGrouped() {return grp == null ? false : true; }
public TLinkGroup getGroup() { return grp; }
LinkedList<TLink> dependencies = null;
public LinkedList<TLink> getDependencies() { return dependencies; }
public boolean hasDependencies()
{
if (dependencies != null && dependencies.size() > 0) return true;
return false;
}
public boolean isBound() { return boundTo == null ? false : true; }
public TLink getBoundLink() { return boundTo; }
public void setBoundLink(TLink parent) { boundTo = parent; }
public boolean isCancelledWithDependencies() { return cancelledWithDependencies; }
public void addDependency(TLink tlink)
{
if (dependencies == null) dependencies = new LinkedList<TLink>();
dependencies.add(tlink);
// chkDependencyListIntegrity();
}
public void rmvDependency(TLink tlink)
{
if (dependencies == null) return;
dependencies.remove(tlink);
// chkDependencyListIntegrity();
}
private void chkDependencyListIntegrity()
{
if (dependencies == null || dependencies.size() == 0) return;
Iterator<TLink> it = dependencies.iterator();
TLink lnk;
while (it.hasNext())
{
lnk = it.next();
if (!lnk.active) dependencies.remove(lnk);
}
}
public static final int STATUS_LOCAL = 1;
public static final int STATUS_REMOTE = 2;
public int linkStatusLastNotification = 0;
public long lastLinkNotification = 0;
public long lastLinkSuppressedNotification = 0;
public int linkStatus = TErrorList.not_signalled;
protected int lastLinkStatus = TErrorList.not_signalled;
public int linkStatusSource = STATUS_LOCAL;
public boolean isStatusLocal() { return linkStatusSource == STATUS_LOCAL; }
public boolean isDoocsServer()
{
if (sub == null || sub.contract == null) return false;
return (sub.contract.eqmName.compareToIgnoreCase("DCSEQM") == 0);
}
public int linkCounter;
public boolean linkStale;
public double linkErrValue;
public String linkErrString;
public byte[] linkStatusStringBuffer = new byte[TStrings.STATUS_SIZE];
public InetAddress linkPeer;
TCallback tcb;
int callbackId;
//EventListener tlsnr;
TLinkCallback tlcb;
TWildcardLink twcl;
private TDataTolerance dtf = null;
private int mcaIndex = -1;
private String mcaDevice = null;
public void setMcaDevice(String dev) { mcaDevice = dev; }
public String getMcaDevice() { return mcaDevice; }
private Object usrObject = null;
public void setUserObject(Object object) { usrObject = object; }
public Object getUserObject() { return usrObject; }
public int getMcaIndex() { return mcaIndex; }
public void setMcaIndex(int index) { mcaIndex = index; }
public boolean isMcaParent() { return (tlcb instanceof TMcaLink); }
public TDataTolerance getNotificationTolerance() { return dtf; }
/**
* Sets the tolerance level for asynchronous event notification
*
* Link callback notification can be suppressed if changes in the link
* data do not exceed the tolerance applied in this call. The tolerance
* can have an absolute part (in the units of the data) and/or a
* percent part.
*
* @param absoluteValue is the absolute tolerance any particular data element
* is allowed to have before an event notification is called.
* @param percentValue is the percent tolerance any particular data element
* is allowed to have before an event notification is called.
*/
public void setNotificationTolerance(float absoluteValue, float percentValue)
{
dtf = new TDataTolerance(absoluteValue,percentValue);
}
public void setNotificationTolerance(double absoluteValue, double percentValue)
{
dtf = new TDataTolerance((float)absoluteValue,(float)percentValue);
}
public void setNotificationTolerance(int absoluteValue, int percentValue)
{
dtf = new TDataTolerance(absoluteValue,percentValue);
}
public boolean isWithinTolerance()
{
if (linkStatus > 0) return false; // error condition => always true
if (lastLinkStatus > 0 && linkStatus == 0)
{ // back to normal conditions ...
lastLinkStatus = 0;
return false;
}
if (dtf == null) return false; // no tolerance specified => always true
if (dOutput == null) return false; // no output data object
if (isGrouped()) return false; // no tolerances for grouped calls
TDataType dRef = dtf.getReference();
if (dRef == null) // first call ?
{
dRef = new TDataType(dOutput.getArrayLength(),dOutput.getFormat());
dRef.dataCopy(dOutput);
dtf.setReference(dRef);
return false;
}
if (!hasNotifiedOnce) return false;
if (isMcaParent()) return false; // treat member links individually
try
{
int len = dOutput.getArrayLength();
if (dRef.getArrayLength() < len) len = dRef.getArrayLength();
float abs = dtf.absolute;
float pcn = (float)(dtf.percent/100.0);
Object dObj = dOutput.getDataObject();
Object rObj = dRef.getDataObject();
if (dObj == null || rObj == null) return false;
if (dObj.getClass().isArray())
{ // somehow jddd has made this check necessary
int alen = Array.getLength(dObj);
if (alen < len) len = alen;
}
switch (dOutput.getFormat())
{
case TFormat.CF_BYTE:
{
byte[] athis = (byte[])dObj;
byte[] alast = (byte[])rObj;
for (int i=0; i<len; i++)
if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
{
dRef.dataCopy(dOutput);
return false;
}
break;
}
case TFormat.CF_SHORT:
{
short[] athis = (short[])dObj;
short[] alast = (short[])rObj;
for (int i=0; i<len; i++)
if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
{
dRef.dataCopy(dOutput);
return false;
}
break;
}
case TFormat.CF_LONG:
{
int[] athis = (int[])dObj;
int[] alast = (int[])rObj;
for (int i=0; i<len; i++)
if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
{
dRef.dataCopy(dOutput);
return false;
}
break;
}
case TFormat.CF_FLOAT:
{
float[] athis = (float[])dObj;
float[] alast = (float[])rObj;
for (int i=0; i<len; i++)
if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
{
dRef.dataCopy(dOutput);
return false;
}
break;
}
case TFormat.CF_DOUBLE:
{
double[] athis = (double[])dObj;
double[] alast = (double[])rObj;
for (int i=0; i<len; i++)
if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
{
dRef.dataCopy(dOutput);
return false;
}
break;
}
case TFormat.CF_DLONG:
{
long[] athis = (long[])dObj;
long[] alast = (long[])rObj;
for (int i=0; i<len; i++)
if (Math.abs(athis[i]-alast[i]) > abs + pcn*(float)alast[i])
{
dRef.dataCopy(dOutput);
return false;
}
break;
}
default:
if (!dRef.equals(true,dOutput))
{
dRef.dataCopy(dOutput);
return false;
}
}
return true;
}
catch (Exception e)
{
MsgLog.log("TLink.isWithinTolerance", "unexpected exception",TErrorList.code_failure,e,1);
}
return false;
}
public int getLinkAccessMode()
{
if (sub != null) return sub.mode;
return TMode.CM_CANCEL;
}
public int getLinkPollingInterval()
{
if (sub != null) return sub.pollingInterval;
return 0;
}
/**
* Returns the current status for this link
*
* @return The Link status as tine error code
*/
public int getLinkStatus()
{
return linkStatus < 0 ? lastLinkStatus : linkStatus;
}
/**
* Returns the context of this link
*
* @return The Context of the current link
*/
public String getContext()
{
return cntName;
}
/**
* Returns the device server addressed in this link
*
* @return The device server addressed in the current link
*/
public String getDeviceServer()
{
return expName;
}
public String getEquipmentModuleName()
{
return con != null ? con.eqmName : null;
}
/**
* Returns the device (module) name addressed in this link
*
* @return The device (module) name addressed in the current link
*/
public String getDeviceName()
{
return devName;
}
/**
* Returns the device property addressed in this link
*
* @return The device property addressed in the current link
*/
public String getProperty()
{
return devProperty;
}
/**
* Returns the fully qualified device name addressed in this link
*
* @return The the fully qualified device name addressed in the current link
*/
public String getFullDeviceName()
{
// return new String("/"+cntName+"/"+expName+"/"+devName); // mdavid: commented
return ("/"+cntName+"/"+expName+"/"+devName); // mdavid: improved
}
/**
* Returns the fully qualified device name and property addressed in this link
*
* @return The the fully qualified device name and property addressed in the current link
*/
public String getFullDeviceNameAndProperty()
{
// return new String("/"+cntName+"/"+expName+"/"+devName+"["+devProperty+"]"); // mdavid: commented
return (getFullDeviceName()+"["+devProperty+"]"); // mdavid: improved
}
/**
* Returns the output data object as a TDataType
*
* @return The output data type
*/
public TDataType getOutputDataObject()
{
return dOutput;
}
public void setOutputDataObject(TDataType dout)
{
if (boundTo == null)
{ // already bound ? (a jDDD specialty)
tf.cancel(this, false);
cancelledWithDependencies = false;
}
dOutput = dout;
con = new TContract(this);
sub = new TSubscription(con,this);
TLink xlnk = getExistingTLink(this,dout,this.dInput);
if (xlnk != null)
{ // will NOT add this TLink to the link table !
int lnkId = this.linkId;
bindToParentLink(xlnk,dout,this.dInput);
devAccess = TMode.CM_REGISTER;
this.linkId = lnkId; // set it back for removal from the table
tf.removeTLink(this);
}
if (hasDependencies())
{ // already has dependencies ? (a jDDD specialty)
LinkedList<TLink> xlst = getDependencies();
TDataType dt;
for (int k=0; k<xlst.size(); k++)
{
if ((xlnk = (TLink)xlst.get(k)) == null) continue;
dt = xlnk.getOutputDataObject();
if (dt.dFormat != TFormat.CF_DEFAULT) continue;
int dlen = dOutput.dArrayLength;
if (xlnk.getMcaDevice() != null) dlen = 1;
dt.setDataObject(dlen,dOutput.dFormat);
dt.dArrayLength = dlen;
dt.dFormat = dOutput.dFormat;
}
}
}
/**
* Returns the output data input as a TDataType
*
* @return The input data type
*/
public TDataType getInputDataObject()
{
return dInput;
}
public void setInputDataObject(TDataType din)
{
if (boundTo == null)
{ // already bound ? (a jDDD specialty)
tf.cancel(this, false);
cancelledWithDependencies = false;
}
dInput = din;
con = new TContract(this);
sub = new TSubscription(con,this);
TLink xlnk = getExistingTLink(this,dOutput,this.dInput);
if (xlnk != null)
{ // will NOT add this TLink to the link table !
int lnkId = this.linkId;
bindToParentLink(xlnk,dOutput,this.dInput);
devAccess = TMode.CM_REGISTER;
this.linkId = lnkId; // set it back for removal from the table
tf.removeTLink(this);
}
}
public void setDataObjects(TDataType dout,TDataType din)
{
if (boundTo == null)
{ // already bound ? (a jDDD specialty)
tf.cancel(this, false);
cancelledWithDependencies = false;
}
dOutput = dout;
dInput = din;
con = new TContract(this);
sub = new TSubscription(con,this);
TLink xlnk = getExistingTLink(this,dout,din);
if (xlnk != null)
{ // will NOT add this TLink to the link table !
int lnkId = this.linkId;
bindToParentLink(xlnk,dout,din);
devAccess = TMode.CM_REGISTER;
this.linkId = lnkId; // set it back for removal from the table
tf.removeTLink(this);
}
if (hasDependencies())
{ // already has dependencies ? (a jDDD specialty)
LinkedList<TLink> xlst = getDependencies();
TDataType dt;
for (int k=0; k<xlst.size(); k++)
{
if ((xlnk = (TLink)xlst.get(k)) == null) continue;
dt = xlnk.getOutputDataObject();
if (dt.dFormat != TFormat.CF_DEFAULT) continue;
int dlen = dOutput.dArrayLength;
if (xlnk.getMcaDevice() != null) dlen = 1;
dt.setDataObject(dlen,dOutput.dFormat);
dt.dArrayLength = dlen;
dt.dFormat = dOutput.dFormat;
}
}
}
/**
* The last link timestamp
*
* @return The last link timestamp as a UTC java long value (milliseconds
* since 1.1.1970)
*/
public long getLastTimeStamp()
{
if (dOutput == null) return (long)0;
return (long)(dOutput.timestamp) * 1000 + (long)(dOutput.timestampUSEC/1000);
}
/**
* The last link timestamp as a TINE double timestamp value
*
* @return The last link timestamp as a UTC java long value (seconds
* since 1.1.1970 with decimal fractional)
*/
public double getLastDataTimeStamp()
{
if (dOutput == null) return (double)0;
return (double)(dOutput.timestamp) + ((double)(dOutput.timestampUSEC)/1000000.0);
}
public int getSynchronizationOffset()
{
if (dOutput == null) return 0;
return dOutput.getSynchronizationOffset();
}
private int groupLatency = 0;
public int getGroupLatency() { return groupLatency; }
public void setGroupLatency(int value) { groupLatency = value; }
/**
* The last link status in plain text
*
* @return The last link status in plain text
*/
public String getLastError()
{
return getError(linkStatus < 0 ? lastLinkStatus : linkStatus);
}
/**
* The specified error code in plain text
*
* @param code Is the tine error code for which the error text is desired
*
* @return The specified error code in plain text
*/
public String getError(int code)
{
String msg = isStatusLocal() ? "(local) " : "";
if (code >= 512)
msg += new String(linkStatusStringBuffer).trim();
else
msg += TErrorList.getErrorString(code);
return msg;
}
Date date = new Date();
/**
* Specifies an error value to fill in associated data buffers
* in case of link error.
*
* @param errValue Is the error value to use in case of link errors
*
* 0 if successful
*/
public int setErrorValue(double errValue)
{
if (sub.mode == TMode.CM_CANCEL) return TErrorList.illegal_mode;
linkErrValue = errValue;
useErrValue = true;
return 0;
}
/**
* Specifies an error string to fill in associated data buffers
* in case of link error.
*
* @param errString Is the string to use in case of link errors
*
* 0 if successful
*/
public int setErrorValue(String errString)
{
if (sub.mode == TMode.CM_CANCEL) return TErrorList.illegal_mode;
linkErrString = errString;
useErrValue = true;
return 0;
}
public int setDebugLevel(int level)
{
return tf.setDebugLevel(level);
}
public int getDebugLevel()
{
return tf.getDebugLevel();
}
public String getModuleAddressInfo()
{
return srvAddr.toString(expName,cntName);
}
/* mdavid: commented, unused
* private TLink getExistingTLink(String devname, String devproperty, TDataType dout, TDataType din, short devaccess)
{
return tf.getExistingLink(devname,devproperty,dout,din,devaccess);
}*/
private TLink getExistingTLink(TLink lnk, TDataType dout, TDataType din)
{
return tf.getExistingLink(lnk,dout,din);
}
private void redirectLink()
{
tf.redirectLink(this);
}
private void relinkLink()
{
tf.reLinkLink(this);
}
private boolean isBlackListed()
{
return tf.isLinkBlackListed(this);
}
// TODO: why is this so complicated ?
// a name can be /<context> only (a global)
// a name can be /<context>/<server> (no device name : e.g. some stock properties)
// a name can be /<context>/<server>/<device> (most cases)
// a name can be <server> (bad idea, but historically allowed) (no context, no device)
// a name can be <server>/<device> (bad idea, but historically allowed) (no context)
private void splitDeviceName(String fullname)
{
int indx = fullname.indexOf('/');
cntName = "DEFAULT";
devName = "";
if (indx == 0)
{ // context given
fullname = fullname.substring(1);
if ((indx=fullname.indexOf('/')) == -1)
{ // no server name given -> assume globals server
cntName = fullname;
expName = "GLOBALS";
return;
}
else
{ // set the context and move along
cntName = fullname.substring(0,indx);
fullname = fullname.substring(indx+1);
indx = fullname.indexOf('/');
}
}
else
{ // no context specified (discouraged!)
expName = fullname;
if (indx > 0)
{ // device name given
expName = fullname.substring(0,indx);
devName = fullname.substring(indx+1);
}
return;
}
if (indx == -1)
{ // no device name
expName = fullname;
devName = "";
}
else
{ // normal case
expName = fullname.substring(0,indx);
devName = fullname.substring(indx+1);
}
}
private void remapStringArrayToStringBuffer(TDataType d, boolean hasInputData)
{
try
{
if (Integer.parseInt(d.getTag()) > 0) return; // already done ?
}
catch (NumberFormatException e) {};
d.setTag(""+d.dArrayLength);
}
protected void bindToParentLink(TLink xlnk,TDataType dout,TDataType din)
{
if (TLinkFactory.debugLevel > 1) DbgLog.log("makeLink", this.getFullDeviceName()+" is marked as bound to "+xlnk.linkId);
synchronized (this)
{
MsgLog.log("bindToParentLink", "bind dependency to "+xlnk.getFullDeviceNameAndProperty(),0,null,1);
//sub = xlnk.sub; //<< p.d. 29.5.12 this is not only not necessary, but a bad idea
sub = new TSubscription(xlnk.sub.contract, this);
con = xlnk.con;
reqHdr = xlnk.reqHdr;
linkId = xlnk.linkId;
if (xlnk.isWildcardLink)
{
isWildcardLink = true;
twcl = xlnk.twcl;
}
TDataType xdin = xlnk.getInputDataObject();
if (xdin != null && din != null && din != xdin)
{ // we have to lock the input data !
din.isLocked = xdin.isLocked = true;
}
TDataType xdout = xlnk.getOutputDataObject();
if (xdout != null && dout != null && xdout.hasBeenUpdated)
{
if (dout.acquireDefaultFormat) synchronized (dout)
{
if (mcaDevice == null)
{
if (dout.getDataSize() < xdout.getDataSize())
{ // another jDDD specialty
this.linkStatus = TErrorList.buffer_too_small;
tf.fireCallbackEvent("bindToParentLink",this);
return;
}
dout.dFormat = xdout.dFormat;
dout.dArrayLength = xdout.dArrayLength;
dout.dCompletionLength = xdout.dCompletionLength;
}
else
{
dout.dArrayLength = 1;
dout.dCompletionLength = 1;
}
}
if (dout.dFormat != TFormat.CF_DEFAULT)
{ // if the parent doesn't know yet either don't do this
dout.setDataObject(dout.dArrayLength,dout.dFormat);
dout.dataCopy(xdout);
dout.setDataTimeStamp(xdout.getDataTimeStamp());
}
}
boundTo = xlnk;
xlnk.addDependency(this);
}
}
private TLink findLinkWithOutputDataType(TDataType dout)
{
return tf.findLinkWithOutputDataType(dout);
}
private Thread linkThread = null;
public Thread getThread() { return linkThread; }
public void wakeUpCall()
{
synchronized (this) {notifyAll();}
if (TLinkFactory.debugLevel > 1)
DbgLog.log("TLink.wakupCall","notify dependent link "+getFullDeviceNameAndProperty()+" on thread "+linkThread.getName());
needsWakeUpCall = false;
}
private synchronized int makeLink(String devname, String devproperty, TDataType dout, TDataType din, short devaccess)
{
linkThread = Thread.currentThread();
if (devproperty == null)
{
return TErrorList.argument_list_error;
}
if (devname == null)
{ // assume a SITE global is desired
devname = "/SITE";
}
String fullname;
if (hook == null) hook = tf.getTLinkHook();
if (hook != null && (fullname=hook.getLinkName(devname)) != null)
{
/* mdavid: commented
Object objOut = dout == null ? null : dout.getDataObject();
Object objIn = din == null ? null : din.getDataObject();
if (dout != null) dOutput = dout; else dOutput = new TDataType();
if (din != null) dInput = din; else dInput = new TDataType();
*/
// mdavid: changed
Object objOut = null;
if (dout != null) {
objOut = dout.getDataObject();
dOutput = dout;
} else {
dOutput = new TDataType();
}
Object objIn = null;
if (din != null) {
objIn = din.getDataObject();
dInput = din;
} else {
dInput = new TDataType();
}
hookObj = hook.create(fullname,devproperty,objOut,objIn);
/* mdavid: commented
devName = new String(fullname);
devProperty = new String(devproperty);
*/
devName = fullname; // mdavid: improved
devProperty = devproperty; // mdavid: improved
expName = "cdi";
cntName = "localhost";
return 0;
}
// fullname = new String(devname); // mdavid: commented
fullname = devname; // mdavid: changed
active = true;
if (din != null && !din.isLocked)
{ // in case of 'late additions' ...
din.putData();
}
if (dout != null) dOutput = dout; else dOutput = new TDataType();
if (din != null) dInput = din; else dInput = new TDataType();
if (arrayDelimiter != null) dOutput.setArrayDelimiter(arrayDelimiter);
// splitDeviceName will assign devName, expName, cntName
splitDeviceName(fullname);
// devProperty = new String(devproperty); // mdavid: commented
devProperty = devproperty; // mdavid: improved
devAccess = devaccess;
devTimeout = 1000;
if (TFormat.isVariableLengthString(dOutput.dFormat))
remapStringArrayToStringBuffer(dOutput, false);
if (TFormat.isVariableLengthString(dInput.dFormat))
remapStringArrayToStringBuffer(dInput, true);
if (dOutput.dArrayLength == 0)
{ // strip the read access off if no data returned
devAccess &= ~(TAccess.CA_READ);
dOutput.dFormat = TFormat.CF_NULL;
}
TPropertyList pl = TStockProperties.getPropertyList();
isQuery = pl != null ? TStockProperties.getPropertyList().hasProperty(devProperty) : false;
if (isBlackListed())
{ // link parameters have been black listed -> don't send out
if (TLinkFactory.debugLevel > 0) DbgLog.log("makeLink","link " + getFullDeviceName() + " " + getProperty() + " has been black listed");
linkStatus = tf.getBlackListedLinkStatus(this);
if (linkStatus == TErrorList.not_signalled) linkStatus = TErrorList.link_blacklisted;
return linkStatus;
}
redirectLink();
relinkLink();
// isGlobalsLink = (expName.compareToIgnoreCase("GLOBALS") == 0) && !TQuery.isStockProperty(devProperty);
isGlobalsLink = (expName.compareToIgnoreCase("GLOBALS") == 0) && tf.isGlobalKeyword(cntName, devProperty);
if (expName.compareToIgnoreCase("CYCLER") == 0 && devProperty.contains("CycleNumber"))
{ // a very special "GLOBAL"
isGlobalsLink = true;
}
srvAddr = new TSrvEntry(this);
if (srvAddr.fecAddr == null && !isGlobalsLink)
{
linkStatus = TErrorList.address_unknown;
tf.addLinkToBlackList(this);
String msg = "Could not resolve address for "+getFullDeviceName()+"\n"+srvAddr.getResolutionHistory();
//RuntimeException e = new RuntimeException(msg);
UnresolvedAddressException e = new UnresolvedAddressException(msg);
MsgLog.log("makeLink",msg,TErrorList.address_unknown,e,1);
throw e;
}
// redirecLink() or relinkLink() may have altered the original deviceName
//TLink xlnk = getExistingTLink(this.getFullDeviceName(),this.devProperty,dout,din,devaccess);
TLink xlnk = getExistingTLink(this,dout,din);
if (xlnk != null)
{ // will NOT add this TLink to the link table !
bindToParentLink(xlnk,dout,din);
devAccess = TMode.CM_REGISTER;
return 0;
}
else
{ // a new link: does it try to use the same output data object as another ?
TLink clnk = findLinkWithOutputDataType(dout);
if (clnk != null)
{
if ((xlnk = getExistingTLink(this,dout,din)) != null)
{ // jddd panels manage to get here ...
bindToParentLink(xlnk,dout,din);
return 0;
}
linkStatus = TErrorList.invalid_reference;
String msg = fullname+"["+devproperty+"] is attempting to use data object from "+clnk.getFullDeviceNameAndProperty();
MsgLog.log("makeLink",msg,TErrorList.invalid_reference,null,0);
throw new InvalidDataReferenceException(msg);
//return TErrorList.invalid_reference;
}
}
linkId = tf.registerLink(this); // adds a placeholder in the link table and returns the link id
if (linkId < 0)
{
linkStatus = TErrorList.resources_exhausted;
DbgLog.log("makeLink","link table is full, please close some links and try again!");
return linkStatus;
}
if (linkId == 0 && this != TLinkFactory.linkTable[0] && TLinkFactory.linkTable[0] != null)
{ // an ENS rider
synchronized (TLinkFactory.siblings)
{
TLinkFactory.siblings.add(this);
MsgLog.log("TLink.makelink", "add ENS sibling link",0,null,1);
}
}
this.tineProtocol = srvAddr.isLegacy ? TTransport.DEFAULT_PROTOCOL_LEVEL-1 : TTransport.DEFAULT_PROTOCOL_LEVEL;
con = new TContract(this);
sub = new TSubscription(con,this); // going out ...
reqHdr = new TReqHdr(tf.getUserName(),tineProtocol);
String defaultTimeoutStr;
if ((defaultTimeoutStr = System.getProperty("default.timeout")) != null)
{
try { defaultTimeout = Integer.parseInt(defaultTimeoutStr); } catch (Exception ignore) {};
}
tf.activateLink(this); // sets the placeholder to this TLink !
return 0;
}
public TLink(String deviceName, String deviceProperty, TDataType dout, TDataType din, short devaccess)
{
makeLink(deviceName,deviceProperty,dout,din,devaccess);
}
/**
* Defines a tine Data link
*
* This defines a tine Data Link, verifies that the requested deviceName
* exists and returns a non-null link object if successful. This does not
* establish the link! You must use either
* the execute method or the attach method to establish the link.
*
* @param devname is the fully specified device name as in /<context>/<server>/<device>
* or <server>/<device>
* @param devproperty is the desired property
* @param dout is a reference to the output data object (which will receive
* the data from the server)
* @param din is a reference to the input data object (which will send data
* to the server)
* @param devaccess is the requested control access mode (TMode.CA_READ, TMode.CA_WRITE
* etc.)
*
* @return A valid TLink instance if successful or null if not
*
* @throws UnresolvedAddressException if fullDeviceNameAndProperty does not point to a valid address
* (i.e. if the address pointed to is unable to be resolved by the system)
* @throws InvalidDataReferenceException if the output data object used (dout above)
* is already used as the output data object in another TLink. For instance,
* TLink("/TEST/Server1/Device1","Property1",dout,null,TAccess.CA_READ) already
* exists. An attempt to create
* TLink("/TEST/Server1/Device1","Property2",dout,null,TAccess.CA_READ) will
* throw this exception as 'dout' is the same object already in use in the
*
* \b Example:
*
* \include eg_TLink1.java
*
* @see de.desy.tine.client.TLink#attach
* @see de.desy.tine.client.TLink#execute
* @see de.desy.tine.dataUtils.TDataType#TDataType
*/
public TLink(String devname, String devproperty, TDataType dout, TDataType din, int devaccess)
{
makeLink(devname,devproperty,dout,din,(short)devaccess);
}
private String[] parseFullDeviceNameAndProperty(String fullDeviceNameAndProperty)
{
String[] parts = new String[2];
// parts[0] => device name; parts[1] = property
if (fullDeviceNameAndProperty != null)
{
int idx = fullDeviceNameAndProperty.indexOf('[');
if (idx > 0)
{ // of the form "/context/server/device[property]"
int idx2 = fullDeviceNameAndProperty.indexOf(']');
parts[0] = fullDeviceNameAndProperty.substring(0,idx);
if (idx2 < idx) idx2 = fullDeviceNameAndProperty.length();
parts[1] = fullDeviceNameAndProperty.substring(idx+1,idx2);
}
else
{
idx = fullDeviceNameAndProperty.lastIndexOf('/');
if (idx > 0)
{ // of the form "/context/server/device/property"
parts[0] = fullDeviceNameAndProperty.substring(0,idx);
parts[1] = fullDeviceNameAndProperty.substring(idx+1);
}
else
{ // just the property : a globals call ?
parts[0] = null;
parts[1] = fullDeviceNameAndProperty;
}
}
}
return parts;
}
/**
* Defines a tine Data link
*
* This defines a tine Data Link, verifies that the requested deviceName
* exists and returns a non-null link object if successful. This does not
* establish the link! You must use either
* the execute method or the attach method to establish the link.
*
* @param fullDeviceNameAndProperty is the fully specified, property-appended
* device name as in /<context>/<server>/<device>/<property>
* or <server>/<device>/<property>
* or /<context>/<server>/<device>[<property>]
* @param dout is a reference to the output data object (which will receive
* the data from the server)
* @param din is a reference to the input data object (which will send data
* to the server)
* @param devaccess is the requested control access mode (TMode.CA_READ, TMode.CA_WRITE
* etc.)
*
* @return A valid TLink instance if successful
*
* @throws UnresolvedAddressException if fullDeviceNameAndProperty does not point to a valid address
* (i.e. if the address pointed to is unable to be resolved by the system)
* @throws InvalidDataReferenceException if the output data object used (dout above)
* is already used as the output data object in another TLink. For instance,
* TLink("/TEST/Server1/Device1","Property1",dout,null,TAccess.CA_READ) already
* exists. An attempt to create
* TLink("/TEST/Server1/Device1","Property2",dout,null,TAccess.CA_READ) will
* throw this exception as 'dout' is the same object already in use in the
* initial link.
*
* \b Example:
*
* \include eg_TLink2.java
*
* @see de.desy.tine.client.TLink#attach
* @see de.desy.tine.client.TLink#execute
* @see de.desy .tine.dataUtils.TDataType#TDataType
*/
public TLink(String fullDeviceNameAndProperty, TDataType dout, TDataType din, short devaccess)
{
if (fullDeviceNameAndProperty == null)
{
String msg = "Could not resolve address for empty field";
UnresolvedAddressException e = new UnresolvedAddressException(msg);
MsgLog.log("makeLink",msg,TErrorList.address_unknown,e,1);
throw e;
}
String[] parts = parseFullDeviceNameAndProperty(fullDeviceNameAndProperty);
makeLink(parts[0],parts[1],dout,din,devaccess);
}
/**
* Defines a read-only tine Data link with default behavior.
*
* This defines a tine Data Link, verifies that the requested deviceName
* exists and returns a non-null link object if successful. This does not
* establish the link! You must use either
* the execute method or the attach method to establish the link.
*
* @param devname is the fully specified device name as in /<context>/<server>/<device>
* or <server>/<device>
* @param devproperty is the desired property
*
* @return A valid TLink instance if successful or null if not
*
* @throws UnresolvedAddressException if fullDeviceNameAndProperty does not point to a valid address
* (i.e. if the address pointed to is unable to be resolved by the system)
* @throws InvalidDataReferenceException if the output data object used (dout above)
* is already used as the output data object in another TLink. For instance,
* TLink("/TEST/Server1/Device1","Property1",dout,null,TAccess.CA_READ) already
* exists. An attempt to create
* TLink("/TEST/Server1/Device1","Property2",dout,null,TAccess.CA_READ) will
* throw this exception as 'dout' is the same object already in use in the
*
* \b Example:
*
* \include eg_TLink3.java
*
* @see de.desy.tine.client.TLink#attach
* @see de.desy.tine.client.TLink#execute
*/
public TLink(String deviceName, String deviceProperty)
{
TDataType dout = new TDataType(TLink.maxOutputBufferSize,TFormat.CF_DEFAULT);
TDataType din = new TDataType();
makeLink(deviceName,deviceProperty,dout,din,TAccess.CA_READ);
}
public static String[] splitDeviceAndPropertyFromName(String fullDeviceNameAndProperty)
{
String[] names = new String[2];
int cnt = 1;
int indx;
if (fullDeviceNameAndProperty == null) return names;
indx = fullDeviceNameAndProperty.indexOf('[');
if (indx > 0)
{ // "check for /<context>/<server>/<device>[<property>]" form
int stop = fullDeviceNameAndProperty.length();
if (stop < indx+2) return names;
/* mdavid: commented
names[0] = new String(fullDeviceNameAndProperty.substring(0,indx));
names[1] = new String(fullDeviceNameAndProperty.substring(indx+1,stop-1));*/
names[0] = fullDeviceNameAndProperty.substring(0,indx); // mdavid
names[1] = fullDeviceNameAndProperty.substring(indx+1,stop-1); // mdavid
}
else
{ // "check for /<context>/<server>/<device>/<property>" form
indx = fullDeviceNameAndProperty.indexOf('/');
if (indx == 0) cnt = 3;
for (int i=0; i<cnt; i++)
{
indx += fullDeviceNameAndProperty.substring(indx+1).indexOf('/') + 1;
}
names[0] = fullDeviceNameAndProperty.substring(0,indx);
names[1] = fullDeviceNameAndProperty.substring(indx+1);
}
return names;
}
/**
* Defines a read-only tine Data link with default behavior.
*
* This defines a tine Data Link, verifies that the requested deviceName
* exists and returns a non-null link object if successful. This does not
* establish the link! You must use either
* the execute method or the attach method to establish the link.
*
* @param fullDeviceNameAndProperty is the fully specified, property-appended
* device name as in /<context>/<server>/<device>/<property>
* or <server>/<device>/<property>
*
* @return A valid TLink instance if successful or null if not
*
* @throws UnresolvedAddressException if fullDeviceNameAndProperty does not point to a valid address
* (i.e. if the address pointed to is unable to be resolved by the system)
* @throws InvalidDataReferenceException if the output data object used (dout above)
* is already used as the output data object in another TLink. For instance,
* TLink("/TEST/Server1/Device1","Property1",dout,null,TAccess.CA_READ) already
* exists. An attempt to create
* TLink("/TEST/Server1/Device1","Property2",dout,null,TAccess.CA_READ) will
* throw this exception as 'dout' is the same object already in use in the
*
* \b Example:
*
* \include eg_TLink4.java
*
* @see de.desy.tine.client.TLink#attach
* @see de.desy.tine.client.TLink#execute
*/
public TLink(String fullDeviceNameAndProperty)
{
if (fullDeviceNameAndProperty == null) return; // TODO: exception
String[] names = splitDeviceAndPropertyFromName(fullDeviceNameAndProperty);
String devname = names[0], devproperty = names[1];
TDataType dout = new TDataType(TLink.maxOutputBufferSize,TFormat.CF_DEFAULT);
TDataType din = new TDataType();
makeLink(devname,devproperty,dout,din,TAccess.CA_READ);
}
/**
* Defines a tine GLobal Data link
*
* This defines a tine Data Link and assumes that the requested parameter
* is a network global, available on the tine multicast group.
* This method does not establish the link! You must use either
* the execute method or the attach method to establish the link.
*
* @param globalKeyword is the desired network global parameter
* @param dout is a reference to the output data object (which will receive
* the network global data).
*
* @return A valid TLink instance if successful or null if not
*
* \b Example:
*
* \include eg_TLink5.java
*
* @see de.desy.tine.client.TLink#attach
* @see de.desy.tine.client.TLink#receive
* @see de.desy.tine.dataUtils.TDataType#TDataType
*/
public TLink(String globalKeyword, TDataType dout)
{
makeLink(null,globalKeyword,dout,null,TAccess.CA_READ);
}
/**
* Attaches the data sets to the created link at the default polling rate (1000 msec)
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation. The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
*
* @param mode is the tine control mode parameter, that is one of TMode.CM_POLL,
* TMode.CM_REFRESH, TMode.SINGLE, etc. in possible combination with
* TMode.CM_NETWORK or TMode.CM_CONNECT, etc.
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TCallback() class interface.
*
* @return A positive link handle if successful, otherwise -1. On
* failure the linkStatus property of TLink can be examined.
*
* \b Example:
*
* \include eg_attach1.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TLinkCallback
* @see de.desy.tine.client.TLink#execute
*/
public int attach(int mode,TCallback f)
{
return attach(mode,f,1000,-1);
}
public int attach(short mode,TCallback f)
{
return attach((int)mode,f);
}
/**
* Stop thread execution until this link has been signalled.
*/
public synchronized int Twait()
{
if (sub.id < 0) return -1;
try
{
while (!this.terminate)
{
wait();
}
}
catch (InterruptedException e) {}
return -1;
}
/**
* Executes a synchronous link using the data sets supplied at
* link creation.
*
* Synchronous. This method executes the link using the data sets and
* parameters provided at instantiation. The call will block for 1000 msec
* or until the call completes.
*
* \note The TLink object created will remain resident until a call to
* the cancel() method is made. Thus repeated calls to execute() can
* be made without invoking separate calls to TLink().
*
* @return 0 if successful, otherwise a tine return code which can be
* interpreted using getError() or getLastError().
*
* \b Example:
*
* \include eg_execute1.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
public int execute()
{
return execute(1000);
}
/**
* Executes a synchronous link using the data sets supplied at
* link creation and Closes the link upon completion.
*
* Synchronous. This method executes the link using the data sets and
* parameters provided at instantiation. The call will block for the
* duration of the timeout specified or until the call completes.
* When the link has completed (successfully or not) the TLink will
* be automatically closed and hence removed from the link tables,
* obviating the need for an additional call to closeLink().
*
* @return 0 if successful, otherwise a tine return code which can be
* interpreted using getError() or getLastError().
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
public int executeAndClose()
{
return executeAndClose(1000);
}
/**
* Executes a synchronous link using the data sets supplied at
* link creation.
*
* Synchronous. This method executes the link using the data sets and
* parameters provided at instantiation. The call will block for the
* duration of the timeout specified or until the call completes.
*
* @param timeout The amount of time in milliseconds to block execution
* until the call completes.
* @param appendMode will be appended to the tine Control Mode, which is
* TMode.CM_SINGLE. (e.g. TMode.CM_CONNECT).
*
* \note The TLink object created will remain resident until a call to
* the cancel() method is made. Thus repeated calls to execute() can
* be made without invoking separate calls to TLink().
*
* @return 0 if successful, otherwise a tine return code which can be
* interpreted using getError() or getLastError().
*
* \b Example:
*
* \include eg_execute2.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
public int execute(int timeout, short appendMode)
{
boolean retry = TLinkFactory.alwaysRetry; // 1st cut
if (!retry) retry = (devAccess & TAccess.CA_RETRY) == TAccess.CA_RETRY; // 2nd cut
if ((devAccess & TAccess.CA_NORETRY) == TAccess.CA_NORETRY) retry = false; // takes precedence if set
return execute(timeout,appendMode,retry);
}
/**
* Executes a synchronous link using the data sets supplied at
* link creation.
*
* Synchronous. This method executes the link using the data sets and
* parameters provided at instantiation. The call will block for the
* duration of the timeout specified or until the call completes.
*
* @param timeout The amount of time in milliseconds to block execution
* until the call completes.
* @param retryOnError if 'true' will instruct the tine engine to issue
* an automatic retry following a connection timeout.
*
* \note The TLink object created will remain resident until a call to
* the cancel() method is made. Thus repeated calls to execute() can
* be made without invoking separate calls to TLink().
*
* @return 0 if successful, otherwise a tine return code which can be
* interpreted using getError() or getLastError().
*
* \b Example:
*
* \include eg_execute3.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
public int execute(int timeout, boolean retryOnError)
{
return execute(timeout,(short)0,retryOnError);
}
/**
* Executes a synchronous link using the data sets supplied at
* link creation and Closes the link upon completion.
*
* Synchronous. This method executes the link using the data sets and
* parameters provided at instantiation. The call will block for the
* duration of the timeout specified or until the call completes.
* When the link has completed (successfully or not) the TLink will
* be automatically closed and hence removed from the link tables,
* obviating the need for an additional call to closeLink().
*
* @param timeout The amount of time in milliseconds to block execution
* until the call completes.
* @param appendMode will be appended to the tine Control Mode, which is
* TMode.CM_SINGLE. (e.g. TMode.CM_CONNECT).
*
* @return 0 if successful, otherwise a tine return code which can be
* interpreted using getError() or getLastError().
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
public int executeAndClose(int timeout, short appendMode)
{
boolean retry = (devAccess & TAccess.CA_RETRY) == TAccess.CA_RETRY;
removeOnClose = true;
int cc = execute(timeout,appendMode,retry);
if (linkId > 0) close(); //TLinkFactory.linkTable[linkId] = null;
if (linkId == 0)
{
synchronized (TLinkFactory.siblings)
{
if (TLinkFactory.siblings.contains(this))
{
MsgLog.log("TLinkFactory.cancel", "remove ENS sibling",0,null,1);
TLinkFactory.siblings.remove(this);
}
}
}
return cc;
}
private int validLinkStatus(int status)
{
return status < 0 ? TErrorList.connection_timeout : status;
}
private boolean isListening = false;
private int closeListener()
{
if (!isListening) return TErrorList.code_failure;
if (blockCloseListener) return TErrorList.not_accepted;
isListening = false;
tf.removeListener(this);
return 0;
}
/**
* Executes synchronously, returning the link results.
* Upon the initial call an asynchronous 'listener' monitor
* link is establihes with the update interval
* given by the 'timeout' parameter. Subsequent calls simply
* update the output data object with the current contents of the
* underlying monitor link AND keep the underlying monitor link
* active.
*
* If a 'dead time' interval of 5 minutes without a subsequent
* call to executeAndListen() occurs, then monitor link is closed.
*
* For 'simple' applications which 'poll' for data in a timer, the
* performance can be improved dramatically by replacing calls to the
* 'execute()' method with 'executeAndListen().
*
* With this simple method, the underlying monitor will use the
* default monitor interval of 1000 msec and the default mode
* of TMode.CM_TIMER.
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
public int executeAndListen()
{
return executeAndListen(1000,TMode.CM_TIMER);
}
/**
* Executes synchronously, returning the link results.
* Upon the initial call an asynchronous 'listener' monitor
* link is establihes with the update interval
* given by the 'timeout' parameter. Subsequent calls simply
* update the output data object with the current contents of the
* underlying monitor link AND keep the underlying monitor link
* active.
*
* If a 'dead time' interval of 5 minutes without a subsequent
* call to executeAndListen() occurs, then monitor link is closed.
*
* For 'simple' applications which 'poll' for data in a timer, the
* performance can be improved dramatically by replacing calls to the
* 'execute()' method with 'executeAndListen().
*
* @param interval Is analogous to the timeout parameter in the
* 'execute()' method family and gives the amount of time in milliseconds
* to block execution until the initial call completes. It also establishes
* the monitor interval for the underlying asynchronous link.
* The underlying monitor will use TMode.CM_TIMER with this method.
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
public int executeAndListen(int interval)
{
return executeAndListen(interval,TMode.CM_TIMER);
}
public int executeAndListen(int interval,short mode,boolean retryOnError)
{
return executeAndListen(interval,mode);
}
protected boolean blockCloseListener = false;
/**
* Executes synchronously, returning the link results.
* Upon the initial call an asynchronous 'listener' monitor
* link is establihes with the update interval
* given by the 'timeout' parameter. Subsequent calls simply
* update the output data object with the current contents of the
* underlying monitor link AND keep the underlying monitor link
* active.
*
* If a 'dead time' interval of 5 minutes without a subsequent
* call to executeAndListen() occurs, then monitor link is closed.
*
* For 'simple' applications which 'poll' for data in a timer, the
* performance can be improved dramatically by replacing calls to the
* 'execute()' method with 'executeAndListen().
*
* @param interval Is analogous to the timeout parameter in the
* 'execute()' method family and gives the amount of time in milliseconds
* to block execution until the initial call completes. It also establishes
* the monitor interval for the underlying asynchronous link.
* @param mode can be used to completely control the mode of the
* underlying asynchronous monitor (default is TMode.CM_TIMER).
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
public synchronized int executeAndListen(int interval, short mode)
{
short bmode = TMode.getBaseMode(mode);
if (bmode <= TMode.CM_CANCEL || bmode == TMode.CM_REGISTER) bmode = TMode.CM_TIMER;
TLinkFactory.ListenerItem lsnr = tf.getListener(this,bmode,interval);
if (lsnr == null) return execute(interval);
isListening = true;
if (!lsnr.isActive()) lsnr.start();
lsnr.keepAlive();
int ls = linkStatus;
if (ls == TErrorList.get_subscription_id) ls = 0;
if (TLinkFactory.debugLevel > 0) DbgLog.log("executeAndListen","listener link ("+linkId+") "+getFullDeviceNameAndProperty()+
"returned <"+linkStatus+">");
return ls;
}
private boolean isThrowingBoundToInactiveLink = false;
private boolean useAppendedAccessInConstructor = false;
/**
* Executes a synchronous link using the data sets supplied at
* link creation.
*
* Synchronous. This method executes the link using the data sets and
* parameters provided at instantiation. The call will block for the
* duration of the timeout specified or until the call completes.
*
* @param timeout The amount of time in milliseconds to block execution
* until the call completes.
* @param appendMode will be appended to the tine Control Mode, which is
* TMode.CM_SINGLE. (e.g. TMode.CM_CONNECT).
* @param retryOnError if 'true' will instruct the tine engine to issue
* an automatic retry following a connection timeout.
*
* \note The TLink object created will remain resident until a call to
* the cancel() method is made. Thus repeated calls to execute() can
* be made without invoking separate calls to TLink().
*
* @return 0 if successful, otherwise a tine return code which can be
* interpreted using getError() or getLastError().
*
* \b Example:
*
* \include eg_execute4.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
//public synchronized int execute(int timeout, short appendMode,boolean retryOnError)
public int execute(int timeout, short appendMode,boolean retryOnError)
{
if (isListening) return executeAndListen(timeout, appendMode, retryOnError);
linkStatusSource = TLink.STATUS_LOCAL;
if (this.srvAddr == null)
{ // link has probably been blacklisted
if (linkStatus == TErrorList.link_blacklisted)
linkStatus = tf.getBlackListedLinkStatus(this);
return linkStatus;
}
if (tf.fecEntryWithTimeout == this.srvAddr.fecAddr)
{ // if fecEntryWithTimeout is non-null and equal to this address then there's no sense trying !
if (TLinkFactory.debugLevel > 0)
DbgLog.log("execute","execute link ("+linkId+") "+getFullDeviceName()+"["+getProperty()+"] not allowed within link watchdog callback");
setErrorValue("link timeout");
linkStatus = TErrorList.link_timeout;
return TErrorList.not_allowed;
}
if (TLinkFactory.debugLevel > 0) DbgLog.log("execute","execute link ("+linkId+") "+getFullDeviceName()+"["+getProperty()+"]");
if (hookObj != null) return hook.execute(hookObj,timeout);
if (this.isBlackListed())
{
MsgLog.log("execute","link execution error "+getFullDeviceNameAndProperty()+" ("+linkId+") link is blacklisted",TErrorList.link_blacklisted,null,1);
return TErrorList.link_blacklisted;
}
if (this.sub == null)
{
if (linkStatus == TErrorList.resources_exhausted)
MsgLog.log("execute","\nLink Table ("+tf.getLinkTable().length+" elements) is full",linkStatus,null,0);
else
MsgLog.log("execute",getFullDeviceName()+ " ["+getProperty()+"] has a null subscription <"+linkStatus+">",linkStatus,null,0);
return linkStatus == 0 ? TErrorList.code_failure : linkStatus;
}
short bmode = TMode.getBaseMode(this.sub.mode);
if (!isBound() && TMode.canClose(bmode))
{ // synchronous link on top of a persistent link
while (!hasObtainedStatus)
{
try { Thread.sleep(1); }
catch (InterruptedException e) {}
}
return validLinkStatus(getLinkStatus());
}
if (isGlobalsLink)
{
if (TAccess.isWrite((byte)devAccess)) return TErrorList.illegal_read_write;
MsgLog.log("execute", "synchronous link to "+ cntName+" global variable "+devProperty+" using multicasted value",0,null,1);
_attach(TMode.CM_GLOBAL|TMode.CM_WAIT,null,null,1000,null,0);
if (linkStatus == 0)
{
if (isBound()) this.boundTo.isGlobalsLinkPinned = true;
else isGlobalsLinkPinned = true;
}
return linkStatus;
}
synchronized (this)
{
if (isBound())
{ // different TLinks to the same thing in synchronous mode :
TLink blnk = this.boundTo;
if (blnk.mcaDevice != null && blnk.isBound())
{ // bound to an already-'bound' mca link
blnk = blnk.boundTo; // get the right mode !
}
if (blnk == null)
{ // bad news, indeed!
if (TLinkFactory.debugLevel > 0)
{
DbgLog.log("execute","bound link ("+linkId+") "+getFullDeviceNameAndProperty()+
"does not have a listening parent! ");
}
return TErrorList.code_failure;
}
// blnk is always the MCA parent (if this is an MCA link) !
bmode = TMode.getBaseMode(blnk.sub.mode);
if (TMode.isActive(bmode))
{ // has been or is being used -> just copy the data ...
while (!blnk.hasObtainedStatus)
{
try { Thread.sleep(1); }
catch (InterruptedException e) {}
}
int idx = getMcaIndex() - 1;
if (idx >= 0 && idx < blnk.dOutput.dArrayLength)
{
dOutput.dataCopy(blnk.dOutput, idx, true);
}
else
{ // somehow attached to an MCA link, but don't know I'm an MCA link ? (is this possible?)
dOutput.pushBytes(this.boundTo.getOutputDataObject().getDataBuffer());
}
this.linkStatus = blnk.linkStatus;
this.linkErrString = blnk.linkErrString;
int ls = validLinkStatus(blnk.getLinkStatus());
if (ls == TErrorList.get_subscription_id) ls = 0;
if (TLinkFactory.debugLevel > 0)
{
DbgLog.log("execute","bound link ("+linkId+") "+getFullDeviceNameAndProperty()+
"returned <"+linkStatus+">");
}
return ls;
}
// parent is not active !!
if (!isThrowingBoundToInactiveLink)
{ // try to peek at the bound but inactive results
short smod = blnk.sub.mode; // (REGISTER or CANCEL ?)
int sstat = blnk.linkStatus;
boolean sobt = blnk.hasObtainedStatus;
boolean snot = blnk.hasNotifiedOnce;
this.linkStatus = blnk.execute(timeout);
this.linkErrString = blnk.linkErrString;
blnk.sub.mode = smod; // set it back (REGISTER or CANCEL)
blnk.linkStatus = sstat;
blnk.hasNotifiedOnce = snot;
blnk.hasObtainedStatus = sobt;
return validLinkStatus(linkStatus);
}
// if we're here then throw the exception
this.linkStatus = TErrorList.link_exists;
this.linkErrString = "link is bound to an inactive link";
throw new BoundToInactiveLinkException();
}
else
{ /* allow new data from the input reference under some circumstances */
if (dInput != null && dInput.dArrayLength > 0 && !dInput.isLocked)
dInput.putData();
}
int sts = 0;
if (this.linkId < 0)
{
sts = this.linkStatus = TErrorList.out_of_client_memory;
return validLinkStatus(sts);
}
while (!getCriticalSection()) Thread.yield();
if (retryOnError)
{
if (timeout < 100) timeout = 100;
devAccess |= TAccess.CA_RETRY; // make sure it's set
}
else
{
if (timeout < 1) timeout = 1;
devAccess |= TAccess.CA_NORETRY; // make sure it's set
}
timeout = makeValidPollingInterval(timeout, bmode);
devTimeout = timeout;
this.sub.pollingInterval = devTimeout;
long ltime = System.currentTimeMillis();
this.sub.linkStartTime = ltime;
this.sub.linkLastTime = ltime;
if (bmode == TMode.CM_REGISTER || bmode <= TMode.CM_SINGLE)
{ // this could already be an established monitor link ...
bmode = this.sub.mode = TMode.CM_SINGLE;
devAccess |= TAccess.CA_SYNC;
}
if (appendMode == 0 &&
useAppendedAccessInConstructor &&
((devAccess & TAccess.CA_CONNECT) == TAccess.CA_CONNECT))
{
appendMode = TMode.CM_CONNECT;
}
if (tf.isUseConnectedSockets() || TMode.isConnected(appendMode))
{
this.sub.mode |= appendMode;
retryOnError = false; // turn this off for connected sockets
}
//if (TLinkFactory.alwaysRetry) retryOnError = true;
retryOnTimeoutError = retryOnError;
linkStatus = TErrorList.link_not_open;
//linkStale = true; <- don't do this here!
freeCriticalSection();
TWildcardLink wc = null;
if (this.devName.indexOf("*") >= 0 &&
!TQuery.isStockProperty(this.devProperty) &&
!TQuery.isMetaProperty(this.devProperty))
{
if (!isWildcardLink)
{ // if not yet so flagged ...
wc = tf.getWildcardLink();
wc.parent = this;
wc.format = this.dOutput.dFormat;
/* reset these to the wildcard callback */
this.tlcb = wc.scb;
//wait = TRUE;
this.twcl = wc;
this.isWildcardLink = true;
}
}
if ((sts=tf.sendLinkRequest(this)) != 0) // will set the link to active
{
MsgLog.log("execute","can't establish link",sts,null,1);
devAccess &= ~(TAccess.CA_SYNC);
if (wc != null) tf.rmvWildcardLink(wc);
return validLinkStatus(sts);
}
TLinkFactory.adjustLinkTable(this,TLinkFactory.adjustLinkTableAdd);
long t0 = System.currentTimeMillis();
boolean waitWithTimeout = tf.cannotNotifyFromWatchdogThread || this.cannotNotifyFromWatchdogThread;
if (!retryOnError) waitWithTimeout = true;
try
{
while (true)
{
active = true;
if (waitWithTimeout)
{
wait(timeout);
}
else
{
wait();
}
if (this.twcl == null || this.twcl.canNotify) break;
}
if (TLinkFactory.debugLevel > 0)
{
DbgLog.log("execute","link ("+linkId+") "+getFullDeviceNameAndProperty()+
"returned <"+linkStatus+"> in "+(System.currentTimeMillis()-t0)+" msec");
}
if (bmode == TMode.CM_SINGLE) this.active = false;
devAccess &= ~(TAccess.CA_SYNC);
if (linkStatus != 0 && (linkStatus & TErrorList.CE_SENDDATA) != TErrorList.CE_SENDDATA)
{
if (linkStatus == TErrorList.async_access_required ||
linkStatus == TErrorList.mcast_access_required)
{
if ((expName.compareToIgnoreCase("GLOBALS") != 0))
{
blockCloseListener = true;
if (TLinkFactory.debugLevel > 0)
DbgLog.log("execute", "synchronous calls not allowed -> switching to listener");
return executeAndListen(timeout, appendMode, retryOnError);
}
else
{
linkStatus = TErrorList.async_access_required;
}
}
MsgLog.log("execute","link execution error "+getFullDeviceNameAndProperty()+" ("+linkId+") "+TErrorList.getErrorString(linkStatus),linkStatus,null,1);
}
// if (wc != null) tf.rmvWildcardLink(wc); <- happens when close()ed.
linkStatus = validLinkStatus(linkStatus);
return linkStatus;
}
catch (InterruptedException e)
{
linkStatus = TErrorList.net_read_error;
MsgLog.log("execute","Interrupted io on "+getFullDeviceName()+" ("+linkId+")",linkStatus,e,1);
//terminate = true;
}
if (!removeOnClose) devAccess &= ~(TAccess.CA_SYNC);
if (linkStatus != 0 && (linkStatus & TErrorList.CE_SENDDATA) != TErrorList.CE_SENDDATA)
{
MsgLog.log("execute","link execution error "+getFullDeviceNameAndProperty()+" ("+linkId+") "+TErrorList.getErrorString(linkStatus),linkStatus,null,1);
}
linkStatus = validLinkStatus(linkStatus);
return linkStatus;
} // synchronized block
}
/**
* Executes a synchronous link using the data sets supplied at
* link creation.
*
* Synchronous. This method executes the link using the data sets and
* parameters provided at instantiation. The call will block for the
* duration of the timeout specified or until the call completes.
*
* @param timeout The amount of time in milliseconds to block execution
* until the call completes.
*
* \note The TLink object created will remain resident until a call to
* the cancel() method is made. Thus repeated calls to execute() can
* be made without invoking separate calls to TLink().
*
* @return 0 if successful, otherwise a tine return code which can be
* interpreted using getError() or getLastError().
*
* \b Example:
*
* \include eg_execute5.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
public int execute(int timeout)
{
return execute(timeout,(short)0);
}
/**
* Executes a synchronous link using the data sets supplied at
* link creation and Closes the link upon completion.
*
* Synchronous. This method executes the link using the data sets and
* parameters provided at instantiation. The call will block for the
* duration of the timeout specified or until the call completes.
* When the link has completed (successfully or not) the TLink will
* be automatically closed and hence removed from the link tables,
* obviating the need for an additional call to closeLink().
*
* @param timeout The amount of time in milliseconds to block execution
* until the call completes.
*
* @return 0 if successful, otherwise a tine return code which can be
* interpreted using getError() or getLastError().
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#attach
*/
public int executeAndClose(int timeout)
{
return executeAndClose(timeout,(short)0);
}
/**
* Terminates an active link
* mdavid: changed and marked as 'deprecated'
* @deprecated use {@link #close()} instead
*/
public int cancel()
{
return close();
/* mdavid: commented
if (isListening) return closeListener();
if (hookObj != null) hook.close(hookObj);
if (dInput != null)
{
dInput.lockedMessage = null;
dInput.isLocked = false;
}
if (isBeingWatched)
{
TWatchdogLink wdl = tf.getWatchdogLink("/"+cntName+"/"+expName);
if (wdl != null) wdl.remove(this);
}
if (isGrouped()) grp.removeMember(this);
return tf.cancel(this);
*/
}
/**
* Terminates an active link
*
* Synonymous with cancel().
* mdavid: changed
*/
public int close()
{
if (isListening) return closeListener();
if (hookObj != null) hook.close(hookObj);
if (dInput != null)
{
dInput.lockedMessage = null;
dInput.isLocked = false;
}
if (isBeingWatched)
{
TWatchdogLink wdl = tf.getWatchdogLink("/"+cntName+"/"+expName);
if (wdl != null) wdl.remove(this);
}
if (isGrouped()) grp.removeMember(this);
return tf.cancel(this);
}
// mdavid: commented
// public int close()
// {
// return cancel();
// }
public int linkId;
int linkBlacklists = 0;
int linkTimeouts = 0;
long lastEnsAddressRequest = 0;
public int linkInvalidCount = 0;
boolean notifyPending = true;
boolean needsToSendLinkRequest = false;
//static TErrorList tel;
private TLinkFactory tf = TLinkFactory.getInstance();
TBucket tb;
public TBucket getBucket() { return tb; }
public void rmvBucket() { tb = null; }
protected int mapSingleFieldToBitfield()
{
TLink lnk = this;
String tag = lnk.dOutput.getTag();
if (tag == null || tag.length() == 0) return TErrorList.invalid_structure_tag;
int cc = TQuery.AcquireAndRegisterBitfieldInfo(lnk.cntName, lnk.expName, tag, lnk.dOutput.dFormat);
if (cc != 0)
{
lnk.linkStatus = cc;
lnk.linkStale = true;
return cc;
}
TBitfield bf = TBitfieldRegistry.getBitfield(tag);
if (bf == null)
{
cc = lnk.linkStatus = TErrorList.invalid_structure_tag;
lnk.linkStale = true;
return cc;
}
boolean fieldKnown = false;
String[] parts;
String dev = lnk.devName;
String prp = lnk.devProperty;
String fld = null;
if (!fieldKnown)
{
parts = lnk.devProperty.split("\\.");
if (parts.length > 1 && bf.isField(parts[parts.length-1]))
{
prp = lnk.devProperty.substring(0, lnk.devProperty.indexOf(parts[parts.length-1])-1);
fieldKnown = true;
fld = parts[parts.length-1];
}
}
if (!fieldKnown)
{
parts = lnk.devName.split("\\.");
if (parts.length > 1 && bf.isField(parts[parts.length-1]))
{
dev = lnk.devName.substring(0, lnk.devName.indexOf(parts[parts.length-1])-1);
fieldKnown = true;
fld = parts[parts.length-1];
}
}
if (fieldKnown)
{
tf.addLinkToReLinkList(lnk, bf,fld);
lnk.devName = dev;
lnk.devProperty = prp;
}
else
{ // this should not happen if every thing goes well
cc = lnk.linkStatus = TErrorList.invalid_field;
// TODO: find a way of calling the callback with this status
return cc;
}
lnk.dOutput.dFormat = bf.getFormat();
lnk.dOutput.setBitField(bf);
lnk.dOutput.setField(fld);
return 0;
}
protected int mapSingleFieldToStruct()
{
TLink lnk = this;
if (lnk.dOutput == null) return TErrorList.invalid_data;
if (lnk.dOutput.getField() != null) return 0; // already mapped
String tag = lnk.dOutput.getTag();
if (tag == null || tag.length() == 0) return TErrorList.invalid_structure_tag;
int cc = TQuery.AcquireAndRegisterStructInfo(lnk.cntName, lnk.expName, tag);
if (cc != 0)
{
lnk.linkStatus = cc;
lnk.linkStale = true;
return cc;
}
String key = lnk.cntName == null ? null : "/"+lnk.cntName+"/"+lnk.expName;
if (dOutput.getStructureKey() == null) dOutput.setStructureKey(key);
TStructDescription tsd = TStructRegistry.get(tag,key);
if (tsd == null)
{
cc = lnk.linkStatus = TErrorList.invalid_structure_tag;
lnk.linkStale = true;
return cc;
}
boolean fieldKnown = false;
String[] parts;
String prp = lnk.devProperty;
String fld = null;
if (!fieldKnown)
{
parts = lnk.devProperty.split("\\.");
if (parts.length > 1 && tsd.hasField(parts[parts.length-1]))
{
prp = lnk.devProperty.substring(0, lnk.devProperty.indexOf(parts[parts.length-1])-1);
fld = parts[parts.length-1];
fieldKnown = true;
}
}
if (fieldKnown)
{
RelinkedItem rli = tf.addLinkToReLinkList(lnk, tsd, fld);
lnk.devProperty = prp;
if (rli != null)
{
rli.setDestination(lnk);
lnk.dOutput = rli.getDataObject();
}
}
else
{ // this should not happen if every thing goes well
cc = lnk.linkStatus = TErrorList.invalid_field;
// TODO: find a way of calling the callback with this status
return cc;
}
lnk.dOutput.setField(fld);
return 0;
}
protected int mapInvalidDataRequest()
{
TLink lnk = this;
RelinkedItem rli = tf.getRelinkedItem(lnk);
if (rli == null) return 0; // nothing to do
if (lnk.dOutput == null) return TErrorList.invalid_data;
if (lnk.dOutput.dArrayLength == rli.getRelinkDataLength() &&
lnk.dOutput.dFormat == rli.getRelinkDataFormat())
{ // nothing to do
return 0;
}
tf.addLinkToReLinkList(lnk, rli.getRelinkDataLength(), rli.getRelinkDataFormat());
lnk.dOutput = rli.getDataObject();
return 0;
}
public boolean isQueryLink()
{
if (expName.startsWith("ENS") || expName.startsWith("GENS"))
return true;
if (devProperty.indexOf("PROPS") >= 0 ||
devProperty.indexOf("PROPERTIES") >= 0)
return true;
if (devProperty.indexOf("DEVICES") >= 0)
return true;
return false;
}
public boolean canSendPacked()
{
if (linkStatusLastNotification == TErrorList.buffer_too_small) return false; // self-correction ?
if (isRenewal()) return false;
if (tf.isUseConnectedSockets()) return false;
if (TMode.isConnected(sub.mode)) return false;
if (isQueryLink()) return false;
if ((devAccess & TAccess.CA_SYNC) != 0) return false;
if (sub == null || sub.contract == null || sub.contract.eqmName == null) return false;
if ((sub.contract.eqmName.compareTo(TSrvEntry.SRVEQM_NAME)) == 0) return false;
if ((sub.mode & TMode.CM_GLOBAL) == TMode.CM_GLOBAL) return false;
if (dInput != null && (dInput.numblks > 1)) return false;
return true;
}
TLink() {}
protected TLink(int tid, String devname, String devproperty, TDataType dout, TDataType din, short devaccess)
{ // called only by TLinkFactory.createTLink() via TSrvEntry().getSrvAddrFromENS
// String fullname = new String(devname); mdavid: commented
active = true;
if (dout != null) dOutput = dout; else dOutput = new TDataType();
dInput = din;
//tf = TLinkFactory.getInstance();
// splitDeviceName(fullname); mdavid: commented
splitDeviceName(devname); // mdavid: improved
// devProperty = new String(devproperty); // mdavid
devProperty = devproperty;
devAccess = devaccess;
devTimeout = 500;
relinkLink();
linkId = tid = tf.registerLink(this); // an ENS link will return 0 here
if (TLinkFactory.debugLevel > 0) DbgLog.log("TLink","assigned link id " + linkId + " to /" + cntName + "/" + expName);
srvAddr = new TSrvEntry(expName,cntName);
if (srvAddr.fecAddr == null && TLinkFactory.debugLevel > 0)
DbgLog.log("TLink","Cannot resolve Server Address for /" + cntName + "/" + expName);
// this constructor bypasses makeLink()
this.tineProtocol = srvAddr.isLegacy ? TTransport.DEFAULT_PROTOCOL_LEVEL-1 : TTransport.DEFAULT_PROTOCOL_LEVEL;
con = new TContract(this);
sub = new TSubscription(con,this); // going out ...
String un = tf.getUserName();
if (un == null || un.length() == 0) un = System.getProperty("user.name");
reqHdr = new TReqHdr(un,tineProtocol);
tf.activateLink(this);
if (tid == 0) removeOnClose = false;
}
/**
* Attaches the data sets to the created link at the given polling rate
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation. The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
*
* @param mode is the tine control mode parameter, that is one of TMode.CM_POLL,
* TMode.CM_REFRESH, TMode.SINGLE, etc. in possible combination with
* TMode.CM_NETWORK or TMode.CM_CONNECT, etc.
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TCallback() class interface.
* @param pollrate is the desired polling rate (at the server) in milliseconds.
*
* @return A positive link handle if successful, otherwise -1. On
* failure the linkStatus property of TLink can be examined.
*
* \b Example:
*
* \include eg_attach2.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TLinkCallback
* @see de.desy.tine.client.TLink#execute
*/
public int attach(int mode,TCallback f,int pollrate)
{
return attach(mode,f,pollrate,-1);
}
public int attach(short mode,TCallback f,int pollrate)
{
return attach((int)mode,f,pollrate);
}
//private Object attachMutex = new Object();
/**
* Attaches the data sets to the created link at the given polling rate
* and signals the callback routine using the give notification parameter.
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation. The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
*
* @param mode is the tine control mode parameter, that is one of TMode.CM_POLL,
* TMode.CM_REFRESH, TMode.SINGLE, etc. in possible combination with
* TMode.CM_NETWORK or TMode.CM_CONNECT, etc.
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TCallback() class interface.
* @param pollInterval is the desired polling interval (at the server) in milliseconds.
* @param notificationId is the desired identifier to be passed to the
* supplied callback routine when it is fired.
*
* @return A positive link handle if successful, otherwise -1. On
* failure the linkStatus property of TLink can be examined.
*
* \b Example:
*
* \include eg_attach3.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TCallback
* @see de.desy.tine.client.TLink#execute
*/
public synchronized int attach(int mode,TCallback f,int pollInterval,int notificationId)
{
return _attach(mode, null, f, pollInterval, null, notificationId);
}
public int attach(short mode,TCallback f,int pollrate,int notificationId)
{
return attach((int)mode,f,pollrate,notificationId);
}
private int ensureGlobalsMode(int thisMode)
{
thisMode &= ~(TMode.CM_STREAM|TMode.CM_CONNECT);
int bmode = TMode.getBaseMode(thisMode);
if (bmode != TMode.CM_SINGLE && bmode != TMode.CM_REGISTER)
{
thisMode = (thisMode&0xfff0) | TMode.CM_GLOBAL;
}
return thisMode;
}
private static final int minPollingInterval = 10;
public static int makeValidPollingInterval(int pollingRate, int baseMode)
{
if (pollingRate <= 0) return 1000;
if (baseMode <= TMode.CM_SINGLE) return pollingRate;
if (pollingRate < minPollingInterval) return minPollingInterval;
if (pollingRate <= 100) return pollingRate;
if (pollingRate < 170) return 100;
if (pollingRate < 250) return 200;
if (pollingRate < 350) return 300;
if (pollingRate < 450) return 400;
if (pollingRate < 750) return 500;
if (pollingRate < 1000) return 1000;
return (pollingRate/1000) * 1000;
}
/**
* Attaches the data sets to the created link at the given polling rate
* and signals the callback routine using a reference to the TLink instance.
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation. The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
* This is the preferred AttachLink method to use. The Callback instantiates
* a TLinkCallback() interface which provides a TLink object as parameter.
*
* @param mode is the tine control mode parameter, that is one of TMode.CM_POLL,
* TMode.CM_REFRESH, TMode.SINGLE, etc. in possible combination with
* TMode.CM_NETWORK or TMode.CM_CONNECT, etc.
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TLinkCallback() class interface.
* @param pollrate is the desired polling rate (at the server) in milliseconds.
*
* @return A positive link handle if successful, otherwise -1. On
* failure the linkStatus property of TLink can be examined.
*
* \b Example:
*
* \include eg_attach4.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TLinkCallback
* @see de.desy.tine.client.TLink#execute
*/
public int attach(int mode,TLinkCallback f,int pollrate)
{
return attach(mode,f,pollrate,null);
}
/**
* Attaches the data sets to the created link at the given polling rate
* and signals the callback routine using a reference to the TLink instance.
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation. The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
* This is the preferred AttachLink method to use. The Callback instantiates
* a TLinkCallback() interface which provides a TLink object as parameter.
*
* @param mode is the tine control mode parameter, that is one of TMode.CM_POLL,
* TMode.CM_REFRESH, TMode.SINGLE, etc. in possible combination with
* TMode.CM_NETWORK or TMode.CM_CONNECT, etc.
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TLinkCallback() class interface.
* @param pollingInterval is the desired polling rate (at the server) in milliseconds.
* @param reference is an optional reference to any object of the caller's choosing.
* As the callback returns a reference to the original link, this extra reference can be
* used to quickly find a table entry position for example.
*
* @return A positive link handle if successful, otherwise -1. On
* failure the linkStatus property of TLink can be examined.
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TLinkCallback
* @see de.desy.tine.client.TLink#execute
*/
public synchronized int attach(int mode,TLinkCallback f,int pollingInterval,Object reference)
{
return _attach(mode, f, null, pollingInterval, reference, 0);
}
private int _attach(int mode,TLinkCallback tlcbf,TCallback tcbf,int pollingInterval,Object reference,int notificationId)
{
active = true;
if (isInsideCallback) keepActive = true;
linkStatusSource = TLink.STATUS_LOCAL;
linkStatus = 0; // be optimistic !
usrObject = reference;
devAccess &= ~(TAccess.CA_SYNC); // attach is inherently an asychronous method!
if (useAppendedAccessInConstructor &&
(devAccess & TAccess.CA_CONNECT) == TAccess.CA_CONNECT)
{ // this link flagged for CM_CONNECT transport
if ((mode & (TMode.CM_STREAM|TMode.CM_CONNECT)) == 0)
mode |= TMode.CM_CONNECT;
}
short bmode = TMode.getBaseMode(mode);
String msg = "attach link ("+linkId +") "+getFullDeviceNameAndProperty()+" : " + TMode.toString((short)mode);
if (bmode > TMode.CM_SINGLE)
{
MsgLog.log("attach",msg,0,null,1);
if (bmode == TMode.CM_DATACHANGE && dtf == null)
{
setNotificationTolerance(0, 0);
MsgLog.log("attach",getFullDeviceNameAndProperty()+": DATACHANGE mode -> will suppress null-changes",0,null,1);
}
if (bmode == TMode.CM_EVENT)
{
if (pollingInterval < 30000) pollingInterval = 30000;
}
}
else
if (TLinkFactory.debugLevel > 0) DbgLog.log("attach", msg);
if (hookObj != null)
{
if (tlcbf != null)
return hook.attach(hookObj,pollingInterval,(short)mode,tlcbf,this);
else
return hook.attach(hookObj,pollingInterval,(short)mode,tcbf,notificationId);
}
tlcb = tlcbf;
tcb = tcbf;
if (tcb != null)
{
if (notificationId >= 0)
callbackId = notificationId;
else if (callbackId <= 0)
callbackId = sub.id;
}
if (this.isBlackListed())
{
this.linkStatus = TErrorList.link_blacklisted;
tf.fireCallbackEvent("attach", this);
return -1;
}
if (this.sub == null || linkId < 0)
{
if (linkStatus == TErrorList.resources_exhausted)
MsgLog.log("attach","\nLink Table ("+tf.getLinkTable().length+" elements) is full",linkStatus,null,0);
else
MsgLog.log("attach",getFullDeviceName()+ " ["+getProperty()+"] has a null subscription <"+linkStatus+">",linkStatus,null,0);
tf.fireCallbackEvent("attach", this);
return linkStatus == 0 ? -TErrorList.code_failure : -linkStatus;
}
if (isGlobalsLink) mode = ensureGlobalsMode(mode);
if (dInput != null)
{ // link has input data
if (bmode > TMode.CM_SINGLE)
{ // don't let this change from above!
dInput.lockedMessage = "data locked by persistent link to "+getFullDeviceNameAndProperty();
dInput.isLocked = true;
}
else if (bmode == TMode.CM_SINGLE)
{ // allow new data from the input reference under some circumstances
if (dInput.dArrayLength > 0 && !dInput.isLocked) dInput.putData();
}
}
pollingInterval = makeValidPollingInterval(pollingInterval, bmode);
devTimeout = pollingInterval;
if (this.isBound()) //synchronized (this.boundTo)
{ // this link is bound to a parent !
if (!this.boundTo.active)
{ // the original link is no longer active !
this.boundTo.sub.mode = (short)mode; // must reset even if a dependent link (current could be SINGLE)
this.boundTo.sub.pollingInterval = pollingInterval;
this.boundTo.cancelledWithDependencies = true;
this.boundTo.active = true; // now it's active again
this.boundTo.devAccess = this.devAccess;
long ltime = System.currentTimeMillis();
tf.sendLinkRequest(this.boundTo);
this.boundTo.sub.linkLastTime = ltime;
this.boundTo.tlcb = this.tlcb;
this.boundTo.tcb = this.tcb;
}
else
{ // we're a dependent link to a live parent
boolean fireevent = false;
TDataType bdout = this.boundTo.getOutputDataObject();
if (bdout.getFormat() != TFormat.CF_DEFAULT)
{ // parent data is a known quantity
TDataType dout = this.getOutputDataObject();
synchronized (dout)
{
if (dout.getFormat() == TFormat.CF_DEFAULT)
{ // made it this far without knowing the type ...
dout.dFormat = bdout.dFormat;
dout.dArrayLength = mcaDevice != null ? 1 : bdout.dArrayLength;
dout.setDataObject(dout.dArrayLength,bdout.dFormat);
}
}
if (bdout.hasBeenUpdated)
{ // safe to copy data and fire the callback below
linkStatus = this.boundTo.linkStatus;
lastLinkStatus = this.boundTo.lastLinkStatus;
dout.dataCopy(bdout);
dout.setDataTimeStamp(bdout.getDataTimeStamp());
dout.setDataObject(dout.dArrayLength,bdout.dFormat);
fireevent = true;
}
MsgLog.log("attach","attaching "+getFullDeviceNameAndProperty()+" to active link "+boundTo.getFullDeviceNameAndProperty()+" (has been updated: "+bdout.hasBeenUpdated,0,null,2);
}
if (!TLinkFactory.isRichClient() || this.boundTo.mcaIndex > 0 || this.mcaIndex > 0)
{ // don't let them change horses in mid-stream ...
pollingInterval = this.boundTo.sub.pollingInterval;
mode = this.boundTo.sub.mode;
}
if (fireevent)
{ // have to fire this once here, otherwise there might be a long wait
MsgLog.log("attach",getFullDeviceNameAndProperty()+": fire early callback",0,null,2);
tf.fireCallbackEvent("attach", this);
Thread.yield();
}
bmode = TMode.getBaseMode(this.boundTo.sub.mode);
long ltime = System.currentTimeMillis();
if (TMode.getBaseMode(mode) == TMode.CM_TIMER && bmode != TMode.CM_TIMER)
{ // bound link has a higher polling hierarchy !
int pmode = (this.boundTo.sub.mode & ~(bmode)) | TMode.CM_TIMER;
MsgLog.log("attach", this.boundTo.getFullDeviceNameAndProperty()+" adjust parent polling hierarchy from "+TMode.toString(this.boundTo.sub.mode)+" to "+TMode.toString(pmode),0,null,1);
this.boundTo.sub.mode = (short)pmode;
if (pollingInterval < this.boundTo.sub.pollingInterval)
{
MsgLog.log("attach", this.boundTo.getFullDeviceNameAndProperty()+" adjust parent polling interval to "+pollingInterval+" msec",0,null,1);
this.boundTo.sub.pollingInterval = pollingInterval;
}
tf.sendLinkRequest(this.boundTo);
if (!this.boundTo.hasNotifiedOnce) dOutput.timestamp = this.boundTo.dOutput.timestamp;
this.boundTo.isLinkReassignment = true;
this.boundTo.sub.linkLastTime = ltime;
}
else
{ // must reset even if a dependent link (current could be SINGLE)
this.sub.mode = (short)mode;
this.sub.pollingInterval = pollingInterval;
if (TMode.getBaseMode(mode) > TMode.CM_SINGLE &&
pollingInterval < this.boundTo.sub.pollingInterval)
{ // up the polling rate ...
MsgLog.log("attach", this.boundTo.getFullDeviceNameAndProperty()+" adjust parent polling interval to "+pollingInterval+" msec",0,null,1);
this.boundTo.sub.pollingInterval = pollingInterval;
tf.sendLinkRequest(this.boundTo);
if (!this.boundTo.hasNotifiedOnce) dOutput.timestamp = this.boundTo.dOutput.timestamp;
this.boundTo.isLinkReassignment = true;
this.boundTo.sub.linkLastTime = ltime; // don't let the watchdog thread kick in ...
}
}
}
return this.sub.id;
}
this.sub.mode = (short)mode;
this.sub.pollingInterval = pollingInterval;
if (this.devName.indexOf("*") >= 0 &&
!TQuery.isStockProperty(this.devProperty))
{
if (!isWildcardLink)
{ // not yet so flagged ...
TWildcardLink wc = tf.getWildcardLink();
wc.tlcb = tlcbf;
wc.tcb = tcbf;
wc.parent = this;
wc.format = this.dOutput.dFormat;
/* reset these to the wildcard callback */
this.tlcb = wc.scb;
this.twcl = wc;
this.isWildcardLink = true;
this.sub.mode &= ~(TMode.CM_GROUPED|TMode.CM_SYNCGROUP); // conflicting groups! => turn it off
}
}
long ltime = System.currentTimeMillis();
// TODO: make a method setStartTime() to handle this ....
this.sub.starttime = (int)(ltime/1000);
this.sub.linkStartTime = ltime;
this.sub.linkLastTime = ltime;
if (canSendPacked()) delayEstablishLink(true);
if (tf.sendLinkRequest(this) != 0) return -1;
if (!TMode.isConnected(this.sub.mode) &&
TLinkFactory.alwaysRetry) retryOnTimeoutError = true;
if ((devAccess & TAccess.CA_NORETRY) == TAccess.CA_NORETRY)
{ // override the global settings with this
retryOnTimeoutError = false;
}
if ((devAccess & TAccess.CA_RETRY) == TAccess.CA_RETRY)
{ // override the global settings with this
retryOnTimeoutError = true;
}
if (TMode.isGrouped(this.sub.mode))
{
if (grp == null)
{
if (tlcbf != null)
grp = tf.getGroup(tlcbf);
else
grp = tf.getGroup(tcbf);
}
if (grp != null)
{ // dis-allow in case getGroup returns a null
grp.addMember(this);
if ((this.sub.mode & TMode.CM_SYNCGROUP) == TMode.CM_SYNCGROUP)
grp.setSynchronizationLevel(TLinkGroup.GRP_SYNC_STARTSYNC);
}
}
if ((mode & TMode.CM_USE_ON_ERROR) == TMode.CM_USE_ON_ERROR)
{
dError = (TDataType)dOutput.clone();
if (dError != null) useErrObject = true;
}
TLinkFactory.adjustLinkTable(this,TLinkFactory.adjustLinkTableAdd);
if (TMode.isWaiting(mode))
{
if (TMode.isGlobal(mode) && !hasObtainedStatus && srvAddr != null && srvAddr.fecAddr != null)
{ // fill in from cached globals block
if (TLinkFactory.debugLevel > 1)
DbgLog.log("TLink._attach", "expedite WAIT on globals link");
TLinkFactory.GlobalInfo gi = tf.getGlobalsInfo(srvAddr.fecAddr.fecHost);
if (gi != null && gi.hasUpdated)
{ // try to speed things along ...
//synchronized (this)
{
gi.postDataElement(this, devProperty);
tf.fireCallbackEvent("InterpretIncomingGlobalsData", this);
}
}
}
waitForLinkCompletion();
}
if (TLinkFactory.debugLevel > 0) DbgLog.log("attach","link " + linkId + " is " + (removeOnClose ? "non-persistent" : "persistent"));
return sub.id;
}
public int attach(short mode,TLinkCallback f,int pollrate)
{
return attach((int)mode,f,pollrate);
}
/**
* Attaches the data sets to the global link
* and signals the callback routine using the assigned link id.
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation. The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
* Using the receive() method assumes that the data requested are global available.
*
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TCallback() class interface.
*
* @return A positive link handle if successful, otherwise -1. On
* failure the linkStatus property of TLink can be examined.
*
* \b Example:
*
* \include eg_receive1.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TLinkCallback
* @see de.desy.tine.client.TLink#receive(TCallback f,int notificationId)
* @see de.desy.tine.client.TLink#receive(TLinkCallback f)
*/
public synchronized int receive(TCallback f)
{
return attach(TMode.CM_GLOBAL,f,1000);
}
/**
* Attaches the data sets to the global link
* and signals the callback routine using a reference to the
* given notifcation ID in the callback
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation. The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
* Using the receive() method assumes that the data requested are global available.
*
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TCallback() class interface.
* @param notificationId is the desired identifier to be passed to the
* supplied callback routine when it is fired.
*
* @return A positive link handle if successful, otherwise -1. On
* failure the linkStatus property of TLink can be examined.
*
* \b Example:
*
* \include eg_receive2.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TLinkCallback
* @see de.desy.tine.client.TLink#receive(TCallback f)
*/
public synchronized int receive(TCallback f,int notificationId)
{
return attach(TMode.CM_GLOBAL,f,1000,notificationId);
}
/**
* Attaches the data sets to the global link
* and signals the callback routine using a reference to the
* referenced TLink object.
*
* Asynchronous. This method attaches the data sets to the created link using the calling
* parameters provided at instantiation. The arriving data will automatically
* update the output data set and then fire the callback method provided.
* If the callback is not needed, a null can be passed for this parameter.
* Using the receive() method assumes that the data requested are global available.
* This is the preferred receive method to use. The Callback instantiates
* a TLinkCallback() interface which provides a TLink object as parameter.
*
* @param f is the 'callback function' to be called whenever either new data
* arrive or a change in link status occurs.
* The callback function should instantiate a class you create which
* implements the TLinkCallback() class interface.
*
* @return A positive link handle if successful, otherwise -1. On
* failure the linkStatus property of TLink can be examined.
*
* \b Example:
*
* \include eg_receive3.java
*
* @see de.desy.tine.client.TLink#TLink
* @see de.desy.tine.client.TLink#TLinkCallback
* @see de.desy.tine.client.TLink#receive(TCallback f)
*/
public synchronized int receive(TLinkCallback f)
{
return attach(TMode.CM_GLOBAL,f,1000);
}
/**
* Stops thread execution until this link has completed.
* Useful for grouped asynchronous links.
*/
public void waitForLinkCompletion()
{
int attempts = 0;
TLink lnk = this;
int dt = this.sub.pollingInterval;
if (dt < 100) dt = 100;
boolean logit = false;
int maxattemps = lnk.isGlobalsLink ? 40 : 20;
while (!this.hasObtainedStatus)
{
try
{
Thread.sleep(100);
if (++attempts > (dt/100))
{
if (this.isBound()) lnk = this.boundTo;
if (!lnk.isGlobalsLink)
{
lnk.linkStatus = TErrorList.link_timeout;
lnk.linkTimeouts++;
lnk.sub.mode |= TMode.CM_RETRY;
tf.sendLinkRequest(lnk);
}
if (attempts > maxattemps)
{ // give up
logit = true;
break;
}
}
}
catch (InterruptedException e)
{
DbgLog.log("waitForLinkCompletion"," waitForLinkCompletion : " + e);
}
}
if (logit)
{
MsgLog.log("waitForLinkCompletion", "timed out waiting for completion",TErrorList.link_timeout,null,1);
}
lnk.sub.mode &= ~(TMode.CM_WAIT);
}
/**
* Gets the array delimiter for use in the toString() method
* of associated TDataType objects.
*
* @return The delimiter in use in separating data
* variables in a string dump (default ",")
*/
public String getArrayDelimiter()
{
return arrayDelimiter;
}
/**
* Sets the array delimiter for use in the toString() method
* of associated TDataType objects.
*
* @param delimiter is the string delimiter to use in separating data
* variables in a string dump (default ",")
*/
public void setArrayDelimiter(String delimiter)
{
if (arrayDelimiter != null && arrayDelimiter.compareTo(delimiter) == 0) return;
arrayDelimiter = new String(delimiter);
this.dOutput.setArrayDelimiter(arrayDelimiter);
}
public int getTineProtocol()
{
return tineProtocol;
}
public void setTineProtocol(int protocolLevel)
{
tineProtocol = protocolLevel;
if (reqHdr != null) reqHdr.setTineProtocol(protocolLevel);
if (dInput != null) dInput.resetCounters(protocolLevel);
if (dOutput != null && tineProtocol < TTransport.DEFAULT_PROTOCOL_LEVEL) dOutput.resetCounters(protocolLevel);
if (sub != null && tineProtocol < TTransport.DEFAULT_PROTOCOL_LEVEL) sub.isLegacy = true;
}
}