Package de.desy.tine.server.equipment

Source Code of de.desy.tine.server.equipment.TEquipmentModuleFactory

/*
* Created on Nov 9, 2004
*
* To change the template for this generated file go to
* Window>Preferences>Java>Code Generation>Code and Comments
*
* CHANGES
* 2005-02-10 JW * registerStockProperties() put into constructor
* 13.7.05 PD * added getFecInformationFromFile() and overloaded method SystemInit()
*/
package de.desy.tine.server.equipment;

import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Array;
import java.net.*;
import java.nio.channels.ServerSocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;

import de.desy.tine.xmlUtils.*;
import de.desy.tine.addrUtils.*;
import de.desy.tine.bitfieldUtils.TBitfield;
import de.desy.tine.bitfieldUtils.TBitfieldRegistry;
import de.desy.tine.bitfieldUtils.TField;
import de.desy.tine.client.*;
import de.desy.tine.console.TCommandList;
import de.desy.tine.csvUtils.*;
import de.desy.tine.dataUtils.TDataTime;
import de.desy.tine.dataUtils.TDataType;
import de.desy.tine.definitions.*;
import de.desy.tine.endianUtils.Swap;
import de.desy.tine.headers.*;
import de.desy.tine.io.*;
import de.desy.tine.queryUtils.*;
import de.desy.tine.server.*;
import de.desy.tine.server.alarms.*;
import de.desy.tine.server.connections.*;
import de.desy.tine.server.devices.TDevice;
import de.desy.tine.server.devices.TDeviceList;
import de.desy.tine.server.equipment.TEquipmentModule.ACLGroup;
import de.desy.tine.server.histories.*;
import de.desy.tine.server.logger.DbgLog;
import de.desy.tine.server.logger.MsgLog;
import de.desy.tine.server.logger.TFecLog;
import de.desy.tine.server.properties.*;
import de.desy.tine.startup.*;
import de.desy.tine.stringUtils.*;
import de.desy.tine.structUtils.*;
import de.desy.tine.types.*;

/**
* Equipment module factory.
*
* The equipment module factory maintains a list of registered equipment
* modules in a running TINE server process.  It manages all access and
* scheduling of the associated dispatch and property handlers as well as
* local history and alarm access.
*
* @author duval
*
*/
/**
* @author duval
*
*/
public class TEquipmentModuleFactory
{
  public static final int FAILOVER_NONE = 0x00;
  public static final int FAILOVER_MASTER = 0x01;
  public static final int FAILOVER_SLAVE = 0x02;
  private static final String FAILOVER_MASTER_DESC = "failover MASTER";
  private static final String FAILOVER_SLAVE_DESC = "failover SLAVE";
  private static final short REMINDER = 5;
  private static final short ACK_REQUEST = (2 * REMINDER);
  private static final short HEARTBEAT = 60;
  private static final short REPLY_PENDING = 3;
  private static final int STD_CYCLE_INTERVAL = 1;
  public static final int MAXIMUM_LOCK_DURATION = 60; // seconds
  private String gFecContext;
  private String gFecName;
  private String gFecDescription = "unknown";
  private String gFecLocation = "unknown";
  private String gFecSubsystem = "TEST";
  private String gFecHardware = "none";
  private String gFecResponsible = "unknown";
  private FECAddr gFecAddr;
  private FECInfo gFecInfo;
  private InetAddress myIp = null
  protected HashMap<String,ACLGroup> gKnownGroupsList = new HashMap<String,ACLGroup>();
  private static TLinkFactory gLinkFactory = null;
  private static TLinkFactory getLinkFactory()
  {
    if (gLinkFactory == null) gLinkFactory = TLinkFactory.getInstance();
    return gLinkFactory;
  }
  private boolean gDieOnAddrInUse = true;
  public boolean getDieOnAddressInUse() { return gDieOnAddrInUse; }
  public void setDieOnAddressInUse(boolean value) { gDieOnAddrInUse = value; }
  private TEquipmentModule pendingEqm = null;
  protected void setPendingEqm(TEquipmentModule eqm) { pendingEqm = eqm; }
  private LinkedList<clnInputData> clnInputDataTable = new LinkedList<clnInputData>();
  //private LinkedList<AliasTableEntry> gAliasList = new LinkedList<AliasTableEntry>();
  private HashMap<String, AliasTableEntry> gAliasList = new HashMap<String, AliasTableEntry>();
  private static TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
  public TInitializer getInitializer() { return initializer;}
  private static TEquipmentModuleFactory instance = new TEquipmentModuleFactory();
  private TPacket sckUdp = null; // udp server socket for handling requests
  private TPacket sckNetSrv = null; // udp network service socket (responds to address-request multicasts).
  private boolean gRespondToServiceRequests = false;
  private static boolean isHeadless = java.awt.GraphicsEnvironment.isHeadless();
  public void setRespondToServiceRequests(boolean value)
  {
    if (gRespondToServiceRequests && value) return;
    gRespondToServiceRequests = value;
    if (isInitialized)
    { // called late
      int port = initializer.getNetCastPort();
      sckNetSrv = new TPacket(port);
      registerLocalServerModule();
      acceptThrd[3] = new TAcceptorThread(TTransport.NETSRV);
      acceptThrd[3].start();     
    }
  }
  public static synchronized void dbgPrint(String msg)
  {
    if (msg == null) msg = "";
    if (!isHeadless) System.out.println(msg);
    if (bucketList == null || bucketList.size() == 0) return;
    byte[] msgbytes = null;
    msg = msg + "\n";
    try
    {
      for (TContractBucket b : bucketList)
      {
        if (b.transport == TTransport.PIPE)
        {
          if (msgbytes == null) msgbytes = msg.getBytes("US-ASCII");
          b.getOutputStream().write(msgbytes);
        }
      }
    }
    catch (Exception e) {}
  }
  public boolean getRespondToServiceRequests() { return gRespondToServiceRequests; }
  private int sckRcvBufferSize = initializer.getSrvRcvBufferSize(); //0x10000;
  private int sckTimeToLive = initializer.getSckTimeToLive(); //16;
  private int sckSndBufferSize = initializer.getSrvSndBufferSize(); //0x10000;
  private ServerSocket sckTcp = null; // tcp server socket for handling requests
  private ServerSocket sckStream = null; // tcp stream socket for handling requests
  private ServerSocket sckPipe = null; // tcp pipe socket for debugging
  private static LinkedList<TContractBucket> bucketList = new LinkedList<TContractBucket>(); // do we need this ?
  private int gPortOffset = 0;
  public int getPort() { return gPortOffset; }
  private void validatePortOffset()
  {
    if (gPortOffset < 0 || gPortOffset > 55500)
    {
      TFecLog.log("assigned port offset "+gPortOffset+" is invalid!");
      TFecLog.log("Aborting server process! (Please correct this and try again)");
      System.exit(1);
    }
  }
  static final int maxNumEqmTableEntries = 16;
  private int numEqmTableEntries = 0;
  private TEquipmentModule[] eqmTable = new TEquipmentModule[maxNumEqmTableEntries];
  private int numExpiredContracts = 0;
  public TEquipmentModule[] getEquipmentModuleTable()
  {
    return Arrays.copyOf(eqmTable, numEqmTableEntries);
  }
  private static ArrayList<TContractTable> conTable = new ArrayList<TContractTable>();
  private LinkedList<TContractTable> conTableAdd = new LinkedList<TContractTable>();
  private static ArrayList<TClient> clnTable = new ArrayList<TClient>();
  private LinkedList<TClient> clnTableAdd = new LinkedList<TClient>();
  private TContractTable currentContractEntry = null;
  private int gSystemTick = 10;
  private int gStaleData = 0;
  private static int gBurstLimit = 1024;
  private static int gCycleDelay = STD_CYCLE_INTERVAL;
  public static int getCycleDelay() { return gCycleDelay; }
  public static void setCycleDelay(int value) { gCycleDelay = value; }
  private static int gBurstLimitReachedCount = 0;
  private static int gClientReconnects = 0;
  private static int gClientRetries = 0;
  private static int gContractMisses = 0;
  private static int gContractDelays = 0;
  private static int gClientMisses = 0;
  private static int gSingleLinkCount = 0;
  private long gDataTimeStamp = 0;
  private static long gDataTimeStampOffset = 0;
  private static long gSrvStartupTime = 0;
  private static long gSrvBaselineTime = 0;
  private long gAppCompilationTime = 0;
  private int gAppVersionMajor = 1;
  private int gAppVersionMinor = 0;
  private int gAppVersionRevision = 0;
  private int[] gSrvStatsBuffer = new int[16];
  private String gLastErrorString;
  private String gLastMessage;
  public static final int WRITEACCESS_TBL_SIZE = 100;
  private TWriteAccessInfo[] wrAccTbl = new TWriteAccessInfo[WRITEACCESS_TBL_SIZE];
  private int wrAccTblPtr = -1;
  private void appendWriteAccessTable(TContractTable tct)
  {
    if (tct == null || tct.clt == null || tct.clt.isEmpty()) return;
    TClient tc = ((TClientEntry)tct.clt.element()).cln;
    InetAddress ia = tc.IPaddress;   
    TWriteAccessInfo wai =
      new TWriteAccessInfo(
          ((TClientEntry)tct.clt.element()).cln.userName,
          ia != null ? ia.getHostAddress() : "",
          tc.port,
          tct.contract.eqmName,
          tct.contract.eqmProperty,tct.contract.eqmDeviceName,
          TDataTime.getDataTimeStamp());
    wrAccTblPtr = (wrAccTblPtr+1)%WRITEACCESS_TBL_SIZE;
    wrAccTbl[wrAccTblPtr] = wai;
  }
  public TContractTable awtcon = new TContractTable();
  private TClientEntry awtcln = new TClientEntry();
  public TContractTable hstcon = new TContractTable();
  private TClientEntry hstcln = new TClientEntry();
  {
    awtcln.cln = new TClient();
    awtcln.cln.userName = "AlarmWatch";
    awtcln.cln.IPaddress = myIp;
    awtcon.clt.add(awtcln);
    hstcln.cln = new TClient();
    hstcln.cln.userName = "LocalHistory";
    hstcln.cln.IPaddress = myIp;
    hstcon.clt.add(hstcln);
  }
  protected static int debugLevel = 0;
  public static void setDebugLevel(int level) { debugLevel = level; }
  public static int getDebugLevel() { return debugLevel; }
  private static int gTotalUdpPackets = 0;
  private static int gTotalNetSrvPackets = 0;
  private static int gTotalTcpPackets = 0;
  private static int gTotalStreamPackets = 0;
  private TAcceptorThread[] acceptThrd = new TAcceptorThread[5];
  private TCycleThread cycleThrd;
  private boolean gLinkTablesAccessed = false;
  private boolean gSystemExitCondition = false;
  private boolean isInsideCycle = false;
  private static boolean isInitialized = false;
  public boolean isInitialized() { return isInitialized; }
  public boolean registrationPending = false;
  private long registrationTime = 0;
  public boolean gSynchronizeContracts = true;
  public static boolean gRequireAcknowledgments = true;
  /**
   * If set to 'true' requires an acknowledgment from a client
   * if data are set out according to DATACHANGE or EVENT criteria.
   *
   * @param value is the desired setting (default = 'true')
   */
  public static void setRequireAcknowledgments(boolean value)
  {
    gRequireAcknowledgments = value;
  }
  /**
   * If set to 'true' requires an acknowledgment from a client
   * if data are set out according to DATACHANGE or EVENT criteria.
   *
   * @return the current setting (default = 'true')
   */
  public static boolean getRequireAcknowledgments()
  {
    return gRequireAcknowledgments;
  }
  public boolean gSystemPresetMemory = false;
  public static boolean gRetardSingleContractRemoval = true;
  public boolean gPutCommandsInFeclog = true;
  private static boolean gServerWaiting = false;
  private static boolean gSystemRunningStandAlone = false;
  private String[] gFecAdmins = null;
  public static String getManifestLocation()
  {
    return TSrvEntry.getAddrFileLocation();
  }
  public boolean isRunningStandAlone() { return gSystemRunningStandAlone; }
  private static int gSystemStamp = 0;
  public static int getSystemStamp() { return gSystemStamp; }
  public static void setSystemStamp(int value) { gSystemStamp = value; }
  /**
   * A server can be toggled in and out of a waiting state with this method
   *
   * During initialization it might be prudent to set the waiting state to 'true'
   * until all initialization has been completed and the server is ready to server,
   * at which point the waiting state can then be set to 'false'.
   *
   * If the server is in a waiting state, all requests will receive the error code
   * 'not initialized' as the link status.
   *
   * @param value is the wait state.
   */
  public static void setServerWaiting(boolean value) { gServerWaiting = value; }
  public static boolean getServerWaiting() { return gServerWaiting; }
  private static final short ACK_PENDING = 0x7fff;
  private static final short SUB_PENDING = 0x3fff;
  private static final short BCAST_ID = 255;
  private static final short PRP_SCHEDULE_SIGNAL = 0;
  private static final short PRP_REQUEST_SIGNAL = 1;
  private static final short PRP_CANCEL_SIGNAL = 2;
  public static final short CTR_RENEWAL = 60;
  public static final short CTR_MAXIMUM = 32767
  private static short gRenewalMultiplier = 1;
  private static short gRenewalLength = CTR_RENEWAL;
  /**
   * A server can explicitly set the contract subscription renewal length with
   * this call.
   *
   * Subscriptions generally have a set duration length of 60 at 'normal' polling
   * intervals, which is periodically renewed by the client.  Under some circumstances
   * (e.g. the server expects 'CM_EVENT' acquisition mode and knows that the
   * event interval is small) the server will want to explicitly set the duration
   * length to some larger value.
   *
   * @param value is the desired contact subscription renewal length
   *
   */
  public static void setRenewalLength(short value)
  {
    if (value < CTR_RENEWAL) value = CTR_RENEWAL;
    if (value > CTR_MAXIMUM) value = CTR_MAXIMUM;
    gRenewalLength = value;
    gRenewalMultiplier = (short)(gRenewalLength/CTR_RENEWAL);
    if (gRenewalMultiplier < 1) gRenewalMultiplier = 1;
    if (gRenewalMultiplier > 500) gRenewalMultiplier = 500;   
    TFecLog.log("set subscription renewal length to "+gRenewalLength);
  }
  public static short getRenewalLength() { return gRenewalLength; }
  private static int srvPacketMtu = initializer.getSrvPacketMtu();//1472;
  public static int getServerPacketMTU() { return srvPacketMtu; }
  private static int gMinPollingInterval = 100;
  private static int gSystemCycleDeadband = 10;
  public static int getMinimumPollingInterval() { return gMinPollingInterval; }
  public static void setMinimumPollingInterval(int value)
  {
    if (value < 10) value = 10;
    if (value > 1000) value = 1000;
    gMinPollingInterval = value;
  }
  private static final int gMaxPollingRate = 60000;
  private int gNumBkgTasks = 0;
  private int gNumEnsErrors = 0;
  private TEquipmentModuleHook tEqmHook = null;
  private FecCfg gFecXmlCfg = null;
  public FecCfg getFecXmlCfg() { return gFecXmlCfg; }
  public void setTEquipmentModuleHook(TEquipmentModuleHook hook)
  {
    tEqmHook = hook;
    // if this is called after the factory's systemInit() has been called ...
    if (isInitialized) hook.SystemInit();
  }
  public TEquipmentModuleHook getTEquipmentModuleHook() { return tEqmHook; }
  private String tRedirectedServer;
  private boolean isDeviceRedirected(String redirString,String devName)
  { // only return true if this property is not redirected for all devices
    if (redirString == null || redirString.length() == 0) return false;
    if (devName == null || devName.length() == 0) return true; // device not given
    int nslashes = (redirString.startsWith("/")) ? 3 : 2;
    String[] parts = redirString.split("/");
    if (parts.length < nslashes+1) return true; // all devices get redirected
    int idx = redirString.lastIndexOf('/');
    if (idx == -1) return true; // not given in redirection string
    int edx = redirString.indexOf('[');
    if (edx == -1) edx = redirString.length();
    String ss = redirString.substring(idx+1,edx);
    if (ss.compareTo(devName) == 0) return true;
    if (redirString.substring(idx+1,edx).compareTo(devName) == 0) return true;
    return false;
  }
  private byte[] redirectionStringToBytes(String redirString)
  { // parse strings of form "/<context>/<server>/<device>[<property>] into byte array ...
    byte[] b = new byte[TStrings.STATUS_SIZE];
    String s, ctx = "", srv, dev = "", prp = "";   
    int indx = redirString.indexOf('[');
    if (indx != -1)
    { // property given !
      if (indx == 0) return b; // invalid string
      s = redirString.substring(0,indx);
      prp = redirString.substring(indx+1);
      if ((indx=prp.indexOf(']')) == -1) return b; // invalid string
      prp = indx > 0 ? prp.substring(0,indx) : "";
    }
    else
    {
      s = redirString;
    }
    indx = s.indexOf('/');
    if (indx == 0
    { // context given, peel it off
      indx = s.substring(1).indexOf('/');
      if (indx == -1) return b; // invalid string
      ctx = s.substring(1, indx+1);
      s = s.substring(indx+2);
      indx = s.indexOf('/');
    }
    if (indx == -1)
    { // s is now of the form <server>/<device>
      srv = s;
      dev = "";
    }
    else
    {
      srv = indx > 0 ? s.substring(0,indx) : "";
      dev = s.substring(indx+1);
    }
    // this is the way it's done in release 3 ...
    int len, off = 0;
    len = ctx.length(); if (len > TStrings.CONTEXT_NAME_SIZE) len = TStrings.CONTEXT_NAME_SIZE;
    System.arraycopy(ctx.getBytes(),0,b,off,len);   
    off += TStrings.CONTEXT_NAME_SIZE;
    len = srv.length(); if (len > TStrings.EXPORT_NAME_SIZE) len = TStrings.EXPORT_NAME_SIZE;
    System.arraycopy(srv.getBytes(),0,b,off,len);   
    off += TStrings.EXPORT_NAME_SIZE;
    len = dev.length(); if (len > TStrings.DEVICE_NAME_SIZE) len = TStrings.DEVICE_NAME_SIZE;
    System.arraycopy(dev.getBytes(),0,b,off,len);   
    off += TStrings.DEVICE_NAME_SIZE;
    len = prp.length(); if (len > TStrings.PROPERTY_NAME_SIZE) len = TStrings.PROPERTY_NAME_SIZE;
    System.arraycopy(prp.getBytes(),0,b,off,len);   
    return b;
  }
  private TServerStatistics serverStatistics = new TServerStatistics(this);
  //TODO: this (cheap technique) only works because contracts are called one after the other in
  // the factory and redirected contracts would only ever be called once per client
  public int setRedirectionString(String redirectionString)
  {
    tRedirectedServer = redirectionString;
    return TErrorList.server_redirection;
  }
  public TContractTable getCurrentContractEntry() { return currentContractEntry; }
  protected void setCurrentContractEntry(TContractTable tct) { currentContractEntry = tct; }
  TPropertyList stockList = new TPropertyList(); // a list or a query fcn
  public TPropertyList getStockList() { return stockList; }
  TMetaProperties metaProps = TMetaProperties.getInstance();
  class AliasTableEntry
  {
    private String name;
    private String alias;
    AliasTableEntry(String thisName,String thisAlias)
    {
      name = thisName;
      alias = thisAlias;
    }
    public String getTag() { return name; }
    public String getAlias() { return alias; }
    public void setTag(String newTag)
    {
      if (newTag != null && newTag.length() > 0) name = newTag;
    }
  }
  /**
   * Sets a system alias pair.
   *
   * An alias can be established for any registered property or device via this
   * method call. 
   *
   * @param alias is an alias name which can be used for the registered device or property given.
   * @param name is the targeted registered device or property for which an alias is desired.
   *
   * @return 0 if successful or a tine error code.
   */
  public int setSystemAlias(String alias,String name)
  {
    return addAliasToList(name,alias);
  }
  public int addAliasToList(String name, String alias)
  {
    if (name == null || name.length() == 0) return TErrorList.argument_list_error;
    if (alias == null || alias.length() == 0) return TErrorList.argument_list_error;
    if (gAliasList.containsKey(alias))
    {
      gAliasList.get(alias).setTag(name);
    }
    else
    {
      gAliasList.put(alias,new AliasTableEntry(name,alias));
    }
    return 0;
  }
  public int getSrvAddress(NAME32[] sa, String eqmName, String expName)
  {
    if (sa == null) return TErrorList.argument_list_error;
    TEquipmentModule eqm;
    if ((eqm=getEquipmentModule(eqmName)) == null &&
        (eqm=getEquipmentModuleFromExportName(expName)) == null)
    {
      return TErrorList.non_existent_elem;
    }
    switch (sa.length)
    {     
      case 6: sa[5] = new NAME32(eqm.getSubsystem());
      case 5: sa[4] = new NAME32(eqm.getExportName())
      case 4: sa[3] = new NAME32(eqm.getLocalName());
      case 3: sa[2] = new NAME32(eqm.getContext());
      case 2: sa[1] = new NAME32(getFecName());
      case 1: sa[0] = new NAME32(""+getPort());
        break;
      default:
        return TErrorList.dimension_error;
    }
    return 0;
  }   
 
  /*
   * Register common stock properties.
   */
  void registerStockProperties()
  {
    /*
     * HOW TO ADD A STOCK PROPERTY 1. Create Stock Property (see
     * TStockProperties) 2. Add a property handler here
     */
    stockList.addProperty(TStockProperties.SRVADMINS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        NAME16[] n = new NAME16[1];
        n[0] = new NAME16("EVERYONE");
        return (short) dout.putData(n);
      }
    });
    stockList.addProperty(TStockProperties.NSTOCKPROPS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData(TStockProperties.getNumStockProperties());
      }
    });
    // Returns all known stock properties, if available or not.
    stockList.addProperty(TStockProperties.STOCKPROPS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        Set<String> pset = TStockProperties.getPropertyNames();
        String[] plst = pset.toArray(new String[0]);
        if (din != null)
        {
          String inpt = din.toString();
          LinkedList<String> lst = new LinkedList<String>();
          if (inpt.compareToIgnoreCase("FECONLY") == 0)
          {
            for (int i=0; i<plst.length; i++)
            {
              if (!TStockProperties.isFecProperty(plst[i])) continue;
              lst.add(plst[i]);
            }
            plst = lst.toArray(new String[0]);
          }
          if (inpt.compareToIgnoreCase("EQMONLY") == 0)
          {
            for (int i=0; i<plst.length; i++)
            {
              if (TStockProperties.isFecProperty(plst[i])) continue;
              lst.add(plst[i]);
            }
            plst = lst.toArray(new String[0]);           
          }
        }
        return (short) dout.putData(StringToName.stringArrayToName32(plst));
      }
    });
    stockList.addProperty(TStockProperties.STOCKPROPS_USTRING, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        boolean checkWildCard = (devName.compareTo("*") != 0 && devName.indexOf("*") != -1);
        TPropertyList lst = TStockProperties.getPropertyList();           
        String[] slst = new String[lst.countUniqueProperties()];
        lst.getPropertyNames().toArray(slst);
        if (checkWildCard) slst = StringToName.matchStringsInArray(slst,devName);
        USTRING[] ustr = new USTRING[slst.length];
        char[] cha;
        StringBuffer sb = new StringBuffer(80);
        TExportProperty prp;
        for (int i = 0; i<slst.length; i++)
        {
          ustr[i] = new USTRING();
          if ((prp = lst.getFirstProperty(slst[i])) == null) continue;
          cha = prp.getName().toCharArray();
          sb.insert(0,cha);
          for (int c=cha.length; c<32; c++) sb.insert(c,(char)0);
          cha = prp.getDescription().getText().toCharArray();
          sb.insert(32,cha);
          ustr[i].setValues(prp.getOutputSize(),0,0,prp.getOutputFormat(),new String(sb));
        }
        return (short) dout.putData(ustr);
      }
    });
    stockList.addProperty(TStockProperties.STOCKPROPS_STRUCT, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return callPropertyStockProps(devName, dout, din, devAccess);
      }
    });
    stockList.addProperty(TStockProperties.SRVSTARTTIME, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData(new Date(getServerStartTime()).toString());
      }
    });
    stockList.addProperty(TStockProperties.SRVSTARTTIME_LONG, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData((int) (getServerStartTime() / 1000));
      }
    });
    stockList.addProperty(TStockProperties.SRVBASELINE, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData(new Date(getServerBaseline()).toString());
      }
    });
    stockList.addProperty(TStockProperties.SRVBASELINE_LONG, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData((int) (getServerStartTime() / 1000));
      }
    });
    stockList.addProperty(TStockProperties.STRUCTFORMAT_INTINT, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return callPropertyStructFormat(devName, dout, din, devAccess);
      }
    });
    stockList.addProperty(TStockProperties.STRUCTFORMAT_NDD, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return callPropertyStructFormat(devName, dout, din, devAccess);
      }
    });
    stockList.addProperty(TStockProperties.STRUCTFORMAT, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return callPropertyStructFormat(devName, dout, din, devAccess);
      }
    });
    stockList.addProperty(TStockProperties.BITFIELDFORMAT, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return callPropertyBitfieldFormat(devName, dout, din, devAccess);
      }
    });
    stockList.addProperty(TStockProperties.CONTRACTS, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return callPropertyContracts(devName, dout, din, devAccess);
      }
    });
    stockList.addProperty(TStockProperties.CONNECTIONS, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return callPropertyConnections(devName, dout, din, devAccess);
      }
    });
    stockList.addProperty(TStockProperties.CLIENTS, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return callPropertyClients(devName, dout, din, devAccess);
      }
    });
    stockList.addProperty(TStockProperties.ACTIVITY, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return callPropertyActivity(devName, dout, din, devAccess);
      }
    });
    stockList.addProperty(TStockProperties.SRVDESC, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData(getFecDescription());
      }
    });
    stockList.addProperty(TStockProperties.SRVLOCATION, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData(getFecLocation());
      }
    });
    stockList.addProperty(TStockProperties.SRVALIASLIST, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (gAliasList == null) return TErrorList.not_implemented;
        int len = gAliasList.size()*2;
        if (len == 0) return TErrorList.not_defined;
        NAME32 n32[] = new NAME32[len];
        AliasTableEntry ate;
        for (int i=0; i<len/2; i++)
        {
          ate = (AliasTableEntry)gAliasList.get(i);
          n32[2*i] = new NAME32(ate.name);
          n32[2*i+1] = new NAME32(ate.alias);
        }
        return (short) dout.putData(n32);
      }
    });
    stockList.addProperty(TStockProperties.SRVLASTACCESS, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        NAME32[] n32 = new NAME32[dout.dArrayLength];
        String s = null;
        switch (dout.dArrayLength)
        {
          case 6:
            s = getLastAccessEqm();
            n32[5] = new NAME32(s != null ? s : "");
          case 5:
            n32[4] = new NAME32(TDataTime.toString(getLastAccessTime()));
          case 4:
            s = getLastAccessDev();
            n32[3] = new NAME32(s != null ? s : "");
          case 3:
            s = getLastAccessPrp();
            n32[2] = new NAME32(s != null ? s : "");
          case 2:
            n32[1] = new NAME32(getLastAccessAddress());
          case 1:
            s = getLastAccessUser();
            n32[0] = new NAME32(s != null ? s : "");
            break;
          default:
            return TErrorList.dimension_error;
        }
        return (short) dout.putData(n32);
      }
    });
    stockList.addProperty(TStockProperties.SRVLASTACCESS_LONG, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData((int)(getLastAccessTime()/100));
      }
    });
    stockList.addProperty(TStockProperties.SRVSTATS, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData(getServerStats());
      }
    });
    stockList.addProperty(TStockProperties.SRVSETTINGS, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData(getServerSettings());
      }
    });
    stockList.addProperty(TStockProperties.SRVRESET, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return TErrorList.not_implemented;
      }
    });
    stockList.addProperty(TStockProperties.SRVEXIT, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (devAccess.isWrite())
        {
          int[] level = new int[]{0};
          if (din != null) din.getData(level);
          TFecLog.log("Server exit (level "+level[0]+") from remote caller");
          shutdown(level[0]);
          return 0;
        }
        return TErrorList.illegal_read_write;
      }
    });
    stockList.addProperty(TStockProperties.SRVOS, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData(TStockProperties.getStockSrvOS());
      }
    });
    stockList.addProperty(TStockProperties.SRVVERSION, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData(TStockProperties.getStockSrvVersion());
      }
    });
    stockList.addProperty(TStockProperties.SRVCMDLINE, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData(TStockProperties.getStockSrvStartup());
      }
    });
    stockList.addProperty(TStockProperties.SRVCWD, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData(TStockProperties.getStockSrvCwd());
      }
    });
    stockList.addProperty(TStockProperties.SRVPID, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData(TStockProperties.getStockSrvPid());
      }
    });
    stockList.addProperty(TStockProperties.DEBUGLEVEL, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc = 0;
        short[] dbglvl = new short[1];
        if (din != null && devAccess.isWrite())
        {
          if  ((cc=din.getData(dbglvl)) == 0)
          {
            debugLevel = dbglvl[0];
            TLinkFactory.setOutputDebugLevel(debugLevel);
          }
        }
        if (cc == 0 && dout != null)
        {
          dbglvl[0] = (short)TLinkFactory.getOutputDebugLevel();
          cc = dout.putData(dbglvl);
        }
        return cc;
      }
    });
    stockList.addProperty(TStockProperties.LOGCOMMANDS, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc = 0;
        short[] logcmds = new short[1];
        if (din != null && devAccess.isWrite())
        {
          if  ((cc=din.getData(logcmds)) == 0)
            gPutCommandsInFeclog = logcmds[0] != 0 ? true : false;
        }
        if (cc == 0 && dout != null)
        {
          logcmds[0] = (short)(gPutCommandsInFeclog ? -1 : 0);
          cc = dout.putData(logcmds);
        }
        return cc;
      }
    });
    stockList.addProperty(TStockProperties.MESSAGE, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc = 0;
        char[] msg = new char[128];
        if (din != null && devAccess.isWrite())
        {
          if  ((cc=din.getData(msg)) == 0)
          {
            gLastMessage = new String(msg).substring(0, din.dCompletionLength);
            String s = "MSG: " + gLastMessage;
            System.out.println(s);
            TFecLog.log(s);
            if (gLastMessage.contains(" in use by "))
            {
              if (gDieOnAddrInUse)
              {
                TFecLog.log("stopping server process (as per configuration: TEquipmentModuleFactory.setDieOnAddressInUse(false) to prevent this)");  
                System.exit(1);
              }
            }
          }
        }
        if (cc == 0 && dout != null)
        {
          cc = dout.putData(gLastMessage);
        }
        return cc;
      }
    });
    stockList.addProperty(TStockProperties.LOGFILE, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (dout.getFormat() != TFormat.CF_TEXT) return TErrorList.illegal_format;
        int nlines = dout.getArrayLength()/80;
        return (short) dout.putData(TFecLog.getLines(nlines));
      }
    });
    stockList.addProperty(TStockProperties.ENSADDR, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (!TFormat.isSimpleString(dout.getFormat())) return TErrorList.illegal_format;
        String[] lst = TSrvEntry.getKnownENSes();
        return (short) dout.putData(lst);
      }
    });
    stockList.addProperty(TStockProperties.MANIFEST, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (dout.getFormat() != TFormat.CF_TEXT) return TErrorList.illegal_format;
        int nlines = dout.getArrayLength()/80;
        String mffp = TEquipmentManifest.getFilePath();
        String mfFileName = mffp + File.separator + "fecmf.csv";
        return (short) dout.putData(TFecLog.getLines(mfFileName,nlines));
      }
    });
    stockList.addProperty(TStockProperties.MANIFESTPATH, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        if (dout.getFormat() != TFormat.CF_TEXT) return TErrorList.illegal_format;
        String mffp = TEquipmentManifest.getFilePath();
        return (short) dout.putData(mffp);
      }
    });
    stockList.addProperty(TStockProperties.LOGDEPTH, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        int cc = 0;
        int[] depth = new int[1];
        if (din != null && devAccess.isWrite())
        {
          if  ((cc=din.getData(depth)) == 0)
          {
            TFecLog.setLogDepth(depth[0]);
          }
        }
        if (cc == 0 && dout != null)
        {
          depth[0] = TFecLog.getLogDepth();
          cc = dout.putData(depth);
        }
        return cc;
      }
    });
    stockList.addProperty(TStockProperties.STRUCTURES, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        boolean includeKeys = false, includeSystem = false;
        if (dout.getFormat() == TFormat.CF_NAME64) includeKeys = true;
        if (devName.compareToIgnoreCase("ALL") == 0) includeSystem = true;
        String[] tags = TStructRegistry.getRegisteredStructureTags(includeKeys, includeSystem);
        if (tags == null || tags.length == 0)
        {
          dout.dArrayLength = 0;
          return 0;
        }
        return dout.putData(tags);
      }
    });
    stockList.addProperty(TStockProperties.NSTRUCTURES, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return dout.putData(TStructRegistry.numRegisteredStructs);
      }
    });
    stockList.addProperty(TStockProperties.APPDATE_STRING, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData(new Date(getAppCompilationTime()).toString());
      }
    });
    stockList.addProperty(TStockProperties.APPDATE, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData((int) (getAppCompilationTime() / 1000));
      }
    });
    stockList.addProperty(TStockProperties.APPVERSION, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData(getAppVersion());
      }
    });
    stockList.addProperty(TStockProperties.SRVLOGFILES, new TPropertyHandler()
    {
      public int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        return (short) dout.putData(StringToName.stringArrayToName64(TFecLog.getFiles()));
      }
    });
    stockList.addProperty(TStockProperties.SRVLOGFILE, new TPropertyHandler()
    {
      protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
      {
        String tgtLogFile = null;
        if (devName.length() > 0 && !devName.startsWith("#"))
        {
          tgtLogFile = devName;
          if (!TFecLog.isAllowedLogFileAccess(tgtLogFile)) return TErrorList.not_allowed;
        }
        else
        {
          tgtLogFile = "fec.log.0";         
        }
        if (din != null && din.getArrayLength() > 0)
        {
          if (devAccess.isWrite())
          {
            char[] txt = new char[din.getArrayLength()];
            din.getData(txt);
            TFecLog.writeTextFile(tgtLogFile, txt);
          }
        }
        if (dout.getFormat() != TFormat.CF_TEXT) return TErrorList.illegal_format;
        int nlines = dout.getArrayLength()/80;
        return (short) dout.putData(TFecLog.getLines(tgtLogFile,nlines));
      }
    });

  }
  private short callPropertyActivity(String devName, TDataType dout, TDataType din,TAccess devAccess)
  {
    short cc = 0;
    ByteArrayOutputStream bsout = new ByteArrayOutputStream(1024);
    TDataOutputStream dsout = new TDataOutputStream(bsout);
    try
    {
      serverStatistics.setNumTotalContracts((short)(conTable.size()));
      serverStatistics.setNumTotalClients((short)clnTable.size());
      serverStatistics.setNumTargetClients((short)getCurrentContractEntry().eqm.numClients);
      serverStatistics.setNumTargetContracts((short)getCurrentContractEntry().eqm.numContracts);
      serverStatistics.setNumBkgTsks((short)gNumBkgTasks);
      serverStatistics.setNumConnections((short)getLinkFactory().getNumberActiveLinks());
      serverStatistics.setNumConnectionTimeouts(getLinkFactory().getTotalLinkTimeouts());
      serverStatistics.setNumConnectionArrivals(getLinkFactory().getTotalConnectionArrivals());
      serverStatistics.setNumUdpPkts(gTotalUdpPackets);
      serverStatistics.setNumTcpPkts(gTotalTcpPackets);
      serverStatistics.setSystemPollingRate(gSystemCycleDeadband);
      serverStatistics.writeData(dsout);
      dsout.close();
      cc = (short) dout.putData(bsout.toByteArray(),TServerStatistics.getStructDescription().getTagName());
    }
    catch (IOException e)
    {
      cc = TErrorList.code_failure;
      MsgLog.log("callPropertyActivity",e.toString(),TErrorList.code_failure,e,0);                         
    }
    return cc;
  }
  private short callPropertyStockProps(String devName, TDataType dout, TDataType din,
      TAccess devAccess)
  {
    LinkedList<TExportProperty> matchingProps;
    ByteArrayOutputStream bsout = new ByteArrayOutputStream(1024);
    TDataOutputStream dsout = new TDataOutputStream(bsout);
    Iterator<TExportProperty> it;

    if (dout.getTag().compareTo("PRPQSr4") == 0 || (dout.getArrayLength() % TPropertyQuery.sizeInBytes) == 0)
    {
      if (din != null)
      { // target property specified ...
        String tgt = din.toString();
        tgt = tgt.replace('\n', (char) 0).trim();
        matchingProps = TStockProperties.getPropertyList().getEqualProperties(tgt);
        if (matchingProps == null || matchingProps.size() == 0) return TErrorList.not_exported;
        // fill in the targeted property info
        try
        {
          TPropertyQuery pqs = null;
          int noverloads = matchingProps.size(); // n.b. NOT matchingProps.size() - 1 !
          for (it = matchingProps.iterator(); it.hasNext();)
          {
            pqs = new TPropertyQuery((TExportProperty) it.next(), noverloads);
            pqs.writeData(dsout);
          }
          dsout.close();
          return (short) dout.putData(bsout.toByteArray(), "PRPQSr4");
        }
        catch (Exception e)
        {
          MsgLog.log("callPropertyStockProps",e.toString(),TErrorList.code_failure,e,0);                         
          return TErrorList.code_failure;
        }
      }
      else
      { // want the whole list ...
        TPropertyList lst = TStockProperties.getPropertyList();
        String[] slst = new String[lst.countUniqueProperties()];
        lst.getPropertyNames().toArray(slst);
        TPropertyQuery[] pqs = new TPropertyQuery[slst.length];
        if (slst.length == 0) return 0;
        byte[] tba = new byte[slst.length * XPropertyQuery.sizeInBytes];
        TExportProperty prp;
        for (int i=0; i<slst.length; i++)
        {
          prp = lst.getFirstProperty(slst[i]);
          pqs[i] = new TPropertyQuery(prp,1);
          System.arraycopy(pqs[i].toByteArray(), 0, tba, i * TPropertyQuery.sizeInBytes,
              XPropertyQuery.sizeInBytes);
        }
        return (short) dout.putData(tba, "PRPQSr4");
      }
    }
    if (dout.getTag().compareTo("XPQS") == 0 || (dout.getArrayLength() % XPropertyQuery.sizeInBytes) == 0)
    {
      if (din != null)
      { // target property specified ...
        String tgt = din.toString();
        tgt = tgt.replace('\n', (char) 0).trim();
        matchingProps = TStockProperties.getPropertyList().getEqualProperties(tgt);
        if (matchingProps == null || matchingProps.size() == 0) return TErrorList.not_exported;
        // fill in the targeted property info
        try
        {
          XPropertyQuery xpq = null;
          for (it = matchingProps.iterator(); it.hasNext();)
          {
            xpq = new XPropertyQuery((TExportProperty) it.next(), matchingProps.size() - 1);
            xpq.writeData(dsout);
          }
          dsout.close();
            return (short) dout.putData(bsout.toByteArray(), "XPQS");
        }
        catch (Exception e)
        {
          MsgLog.log("callPropertyStockProps",e.toString(),TErrorList.code_failure,e,0);                         
          return TErrorList.code_failure;
        }
      }
      else
      { // want the whole list ...
        TPropertyList lst = TStockProperties.getPropertyList();
        String[] slst = new String[lst.countUniqueProperties()];
        lst.getPropertyNames().toArray(slst);
        XPropertyQuery[] xpq = new XPropertyQuery[slst.length];
        if (slst.length == 0) return 0;
        byte[] tba = new byte[slst.length * XPropertyQuery.sizeInBytes];
        TExportProperty prp;
        for (int i=0; i<slst.length; i++)
        {
          prp = lst.getFirstProperty(slst[i]);
          xpq[i] = new XPropertyQuery(prp,1);
          System.arraycopy(xpq[i].toByteArray(), 0, tba, i * XPropertyQuery.sizeInBytes,
              XPropertyQuery.sizeInBytes);
        }
        return (short) dout.putData(tba, "XPQS");
      }
    }
    return TErrorList.illegal_format;
  }
  private short callPropertyStructFormat(String devName, TDataType dout, TDataType din, TAccess devAccess)
  {
    if( din == null //added by Karol, 18.02.2008
    {
        MsgLog.log("callPropertyStructFormat", "callPropertyStructFormat, din == null",TErrorList.code_failure,null,0);
        return TErrorList.code_failure;
    }
    short cc = 0;
    String tag = din.toString();
    tag = tag.replace('\n', (char) 0).trim();
    NAME64DBLDBL[] structData = TStructRegistry.getStructureRegistration(tag);
    if (structData != null && structData.length > 0)
    {
      switch (dout.getFormat())
      {
        case TFormat.CF_INTINT:
          INTINT[] ii = new INTINT[structData.length];
          for (int i=0; i<structData.length; i++)
            ii[i] = new INTINT((int)structData[i].d1val,(int)structData[i].d2val);
          cc = (short) dout.putData(ii);
          break;
        case TFormat.CF_NAME16II:
          cc = (short) dout.putData(structData);
          NAME16II[] sd16 = new NAME16II[structData.length];
          for (int i=0; i<structData.length; i++)
            sd16[i] = new NAME16II(structData[i].name,(int)structData[i].d1val,(int)structData[i].d2val);
          cc = (short) dout.putData(sd16);
          break;
        case TFormat.CF_NAME64DBLDBL:
          cc = (short) dout.putData(structData);
          break;
        default:
          cc = TErrorList.illegal_format;
      }
    }
    else
    {
      cc = TErrorList.invalid_structure_tag;
    }
    return cc;
  }
  private short callPropertyBitfieldFormat(String devName, TDataType dout, TDataType din, TAccess devAccess)
  {
    if( din == null //added by Karol, 18.02.2008
    {
        return TErrorList.argument_list_error;
    }
    short cc = 0;
    String tag = din.toString();
    tag = tag.replace('\n', (char) 0).trim();
    TBitfield tbf = TBitfieldRegistry.getBitfield(tag);
    if (tbf == null) return TErrorList.invalid_structure_tag;
    TField[] tbff = tbf.getFields();
    NAME16I[] ni = new NAME16I[tbff.length+1];
    for (int i=0; i<tbff.length; i++)
      ni[i] = new NAME16I(tbff[i].getName(),tbff[i].getBitmask());
    ni[tbff.length] = new NAME16I(tag,tbf.getFormat());
    switch (dout.getFormat())
    {
      case TFormat.CF_NAME16I:
        cc = (short) dout.putData(ni);
        break;
      case TFormat.CF_NAME64DBL:
        // TODO: let the registry return NAME64DBL
        NAME64DBL[] ni64 = new NAME64DBL[ni.length];
        for (int i=0; i<ni.length; i++)
          ni64[i] = new NAME64DBL(ni[i].name,ni[i].ival);
        cc = (short) dout.putData(ni64);
        break;
      default:
        cc = TErrorList.illegal_format;
    }
    return cc;
  }
  protected short callWrAccessTableInfo(String eqmName, String devName, TDataType dout, TDataType din,TAccess devAccess)
  {
    short cc = 0;
    String dTag = dout.getTag();
    if (dTag.compareTo("WRACCTBL") != 0)
    {
      return TErrorList.invalid_structure_tag;
    }
    ArrayList<TWriteAccessInfo> lst = new ArrayList<TWriteAccessInfo>();
    try
    {
      int idx = wrAccTblPtr;
      int ncmds = 0;
      if (idx < 0) idx = 0; // there are no commands in the list
      for (int i=0; i<WRITEACCESS_TBL_SIZE; i++)
      {
        if (wrAccTbl[idx] == null) break;
        if (wrAccTbl[idx].getTime() == 0) break;
        if (wrAccTbl[idx].getEqm().compareTo(eqmName) == 0)
        {
          lst.add(wrAccTbl[idx]);
        }
        idx = (idx-1+WRITEACCESS_TBL_SIZE)%WRITEACCESS_TBL_SIZE;
      }
      if ((ncmds = lst.size()) > 0)
      {
        TWriteAccessInfo[] wai = lst.toArray(new TWriteAccessInfo[0]);
        cc = (short)dout.putData(wai);
      }
      dout.setArrayLength(ncmds);
    }
    catch (Exception e)
    {
      cc = TErrorList.code_failure;
      MsgLog.log("callWrAccessTableInfo",e.toString(),TErrorList.code_failure,e,0);                         
    }
    return cc;
  }
  private short callPropertyConnections(String devName, TDataType dout, TDataType din, TAccess devAccess)
  {
    short cc = 0;
    String dTag = dout.getTag();
    if (dTag.compareTo("CONTBLr4") != 0)
    {
      return TErrorList.invalid_structure_tag;
    }
    TLink[] lnks = getLinkFactory().getLinkTable();
    try
    {
      int n = 0;
      int nlnks = getLinkFactory().getNumberTLinksInTable();
      TLink lnk;
      TConnectionStruct tcs;
      ArrayList<TConnectionStruct> cal = new ArrayList<TConnectionStruct>();
      for (int i=0; i<nlnks; i++)
      {
        lnk = lnks[i];
        if (lnk == null) continue;
        if (!lnk.isActive()) continue;
       
        tcs = new TConnectionStruct();
        tcs.setContext(lnk.cntName);
        tcs.setExpName(lnk.getDeviceServer());
        tcs.setPrpName(lnk.devProperty);
        tcs.setDevName(lnk.devName);
        tcs.setAccess(lnk.devAccess);
        tcs.setCounter(lnk.linkCounter);
        tcs.setDtimestamp(0);
        tcs.setDtimestampMSEC(0);
        tcs.setEqm(lnk.getEquipmentModuleName());
        tcs.setLinkStatus((short)lnk.getLinkStatus());
        tcs.setMode(lnk.getSubscription().mode);
        tcs.setPollRate(lnk.getSubscription().pollingInterval);
        if (lnk.dInput != null)
        {
          tcs.setFormatIn(lnk.dInput.dFormat);
          tcs.setSizeIn(lnk.dInput.dArrayLength);
          tcs.setStrTagIn(lnk.dInput.getTag());         
        }
        else
        {
          tcs.setFormatIn(TFormat.CF_NULL);
          tcs.setSizeIn(0);
          tcs.setStrTagIn("");         
        }
        if (lnk.dOutput != null)
        {
          tcs.setFormatOut(lnk.dOutput.dFormat);
          tcs.setSizeOut(lnk.dOutput.dArrayLength);
          tcs.setStrTagOut(lnk.dOutput.getTag());
        }
        else
        {
          tcs.setFormatOut(TFormat.CF_NULL);
          tcs.setSizeOut(0);
          tcs.setStrTagOut("");         
        }
        cal.add(tcs);
        n++;
      }
      TConnectionStruct[] tc = cal.toArray(new TConnectionStruct[0]);
      dout.putData(tc);
    }
    catch (Exception e)
    {
      cc = TErrorList.code_failure;
      MsgLog.log("callPropertyConnections",e.toString(),TErrorList.code_failure,e,0);                         
    }
    return cc;
  }
  private short callPropertyContracts(String devName, TDataType dout, TDataType din, TAccess devAccess)
  {
    short cc = 0;
    String dTag = dout.getTag();
    if (dTag.length() == 0)
    {
      int ssz = TStructRegistry.getSizeInBytes("CONQS");
      if ((dout.getArrayLength() % ssz) != 0)
      {
        return TErrorList.invalid_structure_size;
      }
      dTag = "CONQS";
    }
    if (dTag.compareTo("CONQS") != 0 && dTag.compareTo("CTQSr4") != 0)
    {
      return TErrorList.invalid_structure_tag;
    }
    // conTable is already synchronized in doScheduler
    Iterator<TContractTable> it = conTable.iterator();
    ByteArrayOutputStream bsout = new ByteArrayOutputStream(1024);
    TDataOutputStream sdout = new TDataOutputStream(bsout);
    TContractTable contract = null;
    try
    {
      while (it.hasNext())
      {
        contract = (TContractTable) it.next();
        contract.writeData(dTag,sdout);
      }
      sdout.close();
      dout.putData(bsout.toByteArray(), dTag);
    }
    catch (Exception e)
    {
      cc = TErrorList.code_failure;
      MsgLog.log("callPropertyContracts",e.toString(),TErrorList.code_failure,e,0);                         
    }
    return cc;
  }
  private short callPropertyClients(String devName, TDataType dout, TDataType din, TAccess devAccess)
  {
    short cc = 0;
    synchronized (clnTable)
    {
      Iterator<TClient> it = clnTable.iterator();
      ByteArrayOutputStream bsout = new ByteArrayOutputStream(1024);
      TDataOutputStream sdout = new TDataOutputStream(bsout);
      TClient element = null;
      try
      {
        while (it.hasNext())
        {
          element = (TClient) it.next();
          element.writeData(sdout);
        }
        sdout.close();
        dout.putData(bsout.toByteArray(), "CLNQS");
      }
      catch (Exception e)
      {
        cc = TErrorList.code_failure;
        MsgLog.log("callPropertyClients",e.toString(),TErrorList.code_failure,e,0);                         
      }
    }
    return cc;
  }
  private InetAddress mcastInetAddress = null;
  private InetAddress getMCastAddr()
  {
    if (mcastInetAddress != null) return mcastInetAddress;
    try
    {
      String myip = TInitializerFactory.getInstance().getInitializer().getMyIpAddr();
      String ip = TTransport.getMCastGroup(myip);
      mcastInetAddress = InetAddress.getByName(ip);
      return mcastInetAddress;
    }
    catch (UnknownHostException e)
    {
      return null;
    }
  }
  private InetAddress getBCastAddr(InetAddress ia)
  {
    String strAddr = ia.getHostAddress();
    strAddr = strAddr.substring(0,strAddr.lastIndexOf('.')).concat("255");
    try
    {
      return InetAddress.getByName(strAddr);
    }
    catch (UnknownHostException e)
    {
      return null;
    }
  }
  /**
   * Finds the best polling interval which satisfies 2 different client requests.
   * For instance, if client A wants an interval of 2000 msecs and client B wants
   * an interval of 3000 msecs, then polling every 1000 msecs can satisfy both.
   *
   * @param p1 is polling interval number 1
   * @param p2 is polling interval number 2
   * @return a polling interval which can satisfy the given intervals
   */
  private int findpoll(int p1, int p2)
  {
    int p3;
    if (p1 < gMinPollingInterval) return p2 <= gMinPollingInterval ? gMinPollingInterval : p2;
    if (p2 < gMinPollingInterval || p1 == p2) return p1;
    if (p2 < p1)
      while ((p3 = p1 % p2) >= gMinPollingInterval)
      {
        p1 = p2;
        p2 = p3;
      }
    else if (p1 < p2) while ((p3 = p2 % p1) >= gMinPollingInterval)
    {
      p2 = p1;
      p1 = p3;
    }
    return p1 > p2 ? p2 : p1;
  }
  /**
   * TODO keep bcast nets list in factory (not specific to an eqm); follow isMemberControlNets
   * logic
   *
   * @param tc the Tine Client to check against the list
   * @return
   */
  private boolean isMemberBcastNets(TClient tc)
  {
    if (debugLevel > 0) DbgLog.log("IsMemberBcastNets","non yet implemented");
    return true;
  }
  private boolean isMemberControlNets(TEquipmentModule eqm, TClient tc)
  {
    if (eqm.gRejectAllNets) return false;
    return tc.isMemberControlNets(eqm.gRegisteredNetsList);
  }
  public boolean isMemberGroupsList(LinkedList<String> grplst,String userName)
  {
    if (userName == null) return false;
    if (grplst != null && grplst.size() > 0)
    { // there's a groups list -> run thru it
      Iterator<String>it = grplst.iterator();
      String grp;
      ACLGroup aclgrp;
      try
      {
        while (it.hasNext())
        {
          grp = it.next();
          aclgrp = gKnownGroupsList.get(grp);
          if (aclgrp.members.contains(userName)) return true;
        }
      }
      catch (Exception e)
      {
        e.printStackTrace();
        MsgLog.log("isMemberGroupsList", e.getMessage(),TErrorList.code_failure,e,0);    
      }
    }
    return false;   
  }
  private boolean isMemberUsersList(TEquipmentModule eqm, String userName)
  {
    if (eqm.gRegisteredUsersList == null ||
        eqm.gRegisteredUsersList.size() == 0) return true; // everyone is !
    if (eqm.gRegisteredUsersList.contains(userName.toUpperCase())) return true;
    if (isMemberGroupsList(eqm.gRegisteredGroupsList,userName)) return true;
    return false;
  }
  private boolean isMemberPropertyAclList(TEquipmentModule eqm, TClient tc, String prpName)
  {
    try
    {
      TExportProperty txp = eqm.getPropertyList().getFirstProperty(prpName);
      return txp.isMemberUsersList(tc) && txp.isMemberControlNets(tc);
    }
    catch (Exception ignore) {}
    return true;
  }
  private boolean isMemberDeviceAclList(TEquipmentModule eqm, TClient tc, String devName)
  {
    try
    {
      TDevice dev = eqm.getDeviceList().getDevice(devName);
      return dev.isMemberUsersList(tc) && dev.isMemberControlNets(tc);
    }
    catch (Exception ignore) {}
    return true;
  }
  private boolean isMemberLockSet(TEquipmentModule eqm, TClient tc)
  {
    if (eqm.accessLock.clientCanWrite(tc)) return true;
    return false;
  }
  /**
   * Find the equipment module associated with the contract request.
   * TeqmFactory manages all registered equipment modules, and in most cases
   * this is a list of 1.  The value of numEqmTableEntries very rarely is
   * as large as 5 or 6, so a simple name check will always yield fast results.
   *
   * @param tc is the contract for which the associated equipment module is
   * desired.
   * @return the equipment module found or a null pointer.
   */
  private TEquipmentModule LocateEquipmentModule(TContract tc)
  {
    for (int i = 0; i < numEqmTableEntries; i++)
    {
      if (eqmTable[i] == null) continue;
      if (eqmTable[i].getModuleName().length() == 0) continue;
      if (eqmTable[i].getModuleName().compareTo(tc.eqmName) != 0) continue;
      return eqmTable[i];
    }
    return null;
  }
  private boolean isNewClient = false;
  /**
   * Finds a client entry within the contract table for the client
   * address given
   *
   * "Client Entries" and "Contract Entries" are two different things.  Furthermore,
   * a Client Entry is composed of a Contract-independent Client Address plus a
   * Contract-specific Client Status.  A "Contract Entry" is composed of a Client-
   * independent Contract plus a client entry list.  This routine checks to see if
   * the input Client Address is found within the client entry list belonging to the
   * input contract entry.  If not, it creates a new Client Entry based on the
   * input client address and appends this entry to the contract table's list.  
   * It also increments counters on the Client Entry (number of contracts the client
   * has) and on the Contract Entry (number of clients the contract is servicing).
   *
   * @param tct is the Contract Table Entry to be scanned
   * @param tc is the Client Address for which the Client Entry is desired
   *
   * @return a TClientEntry object
   */
  private TClientEntry LocateClientInContract(TContractTable tct, TClient tc)
  {
    TClientEntry tce = null;
    Iterator<TClientEntry> li = tct.clt.iterator();
    while (li.hasNext())
    { // scan thru the contract's current client list
      tce = (TClientEntry) li.next();
      if (tce.cln.equals(tc))
      { // client is already in this contract's client list
        if (tc.ncontracts == 0)
        { // client has no contracts (?) << should not be possible !
          MsgLog.log("LocateClientInContract","found client in contract list, but client has no contracts!",TErrorList.code_failure,null,0);
          tct.pending = 0;
          if (tct.nclients == 0)
          { // this cannot possibly happen !?
            MsgLog.log("LocateClientInContract","contract list claims no clients but has a non-zero sized list!",TErrorList.code_failure,null,0);
            tct.nclients++;
          }
          tc.ncontracts++;
          tc.addContract(tct);
          if (tc.tineProtocol == 0)
          {
            tc.tineProtocol = TTransport.DEFAULT_PROTOCOL_LEVEL;
          }
        }
        if (debugLevel > 2)
          DbgLog.log("LocateClientInContract","found client "+
              tc.IPaddress.getHostAddress()+" "+tc.userName+" in contract list");
        isNewClient = false;
        return tce;
      }
    }
    tce = new TClientEntry();
    tce.cln = tc;
    tce.cln.ncontracts++;
    tce.cln.addContract(tct);
    tce.sts = new TClientStatus(tc.inetProtocol);
    tce.sts.statusCode = (short) tct.returnCode;
    tce.sts.counter = 1;
    tct.clt.add(tce);
    tct.nclients++;
    if (debugLevel > 2)
      DbgLog.log("LocateClientInContract","added client "+
          tc.IPaddress.getHostAddress()+" "+tc.userName+
          " to contract list (contract has "+tct.nclients+" clients");
    isNewClient = true;
    return tce;
  }
  private TContractTable LocateContractInList(TContract con, TDataType din, boolean[] isNew)
  {
    TContractTable tct = null;
    int freeContractID = 1;
    isNew[0] = false;
    synchronized (conTable)
    {
      for (int i = 0; i < conTable.size(); i++)
      {
        tct = (TContractTable) conTable.get(i);
        if (tct.contractID == freeContractID) freeContractID++;
        if (!tct.contract.equals(con)) continue;
        if (tct.din != null && !tct.din.equals(din)) continue;
        if (debugLevel > 2) DbgLog.log("LocateContractInList","found contract in list");
        return tct;
      }
    }
    synchronized (conTableAdd)
    {
      Iterator<TContractTable> li = conTableAdd.iterator();
      while (li.hasNext())
      {
        tct = (TContractTable) li.next();
        if (tct.contractID == freeContractID) freeContractID++;
        if (!tct.contract.equals(con)) continue;
        if (tct.din != null && !tct.din.equals(din)) continue;
        if (debugLevel > 2) DbgLog.log("LocateContractInList","found contract in list");
        return tct;
      }
      if ((tct = new TContractTable()) != null)
      {
        int sizeOut = con.dataSizeOut;
        String tagOut = con.dataTagOut;
        if (TFormat.isAdjustableLength(con.dataFormatOut))
        {
          try { sizeOut = Integer.parseInt(tagOut); } catch (Exception ignore) {}
          //tagOut = "";
        }
        if (sizeOut == 0) con.dataFormatOut = (byte)TFormat.CF_NULL; // make sure of this
        tct.contract = con;
        tct.contractID = freeContractID;
        tct.din = din;
        tct.dout = new TDataType(sizeOut, con.dataFormatOut);
        tct.dout.setTag(tagOut);
        tct.dout.setMtu(srvPacketMtu);
        tct.drb = new TDataType(sizeOut, con.dataFormatOut);
        tct.drb.setTag(tagOut);
        tct.drb.setMtu(srvPacketMtu);
        tct.eqm = LocateEquipmentModule(con);
        if (debugLevel > 2) DbgLog.log("LocateContractInList","added contract to list (ID = " + tct.contractID + ")");
        conTableAdd.add(tct);
        isNew[0] = true;
      }
    }
    return tct;
  }
  private TClient LocateClientInList(InetAddress ia, int port)
  {
    TClient tc = null;
    if (debugLevel > 3)
      DbgLog.log("LocateClientInList","incoming request from client " + ia.getHostAddress());
    synchronized (clnTableAdd)
    { // first check the not yet appended list ...
      Iterator<TClient> li = clnTableAdd.iterator();
      while (li.hasNext())
      {
        tc = (TClient) li.next();
        if (!tc.IPaddress.equals(ia)) continue;
        if (tc.port != port) continue;
        if (debugLevel > 2)
          DbgLog.log("LocateClientInList","found client (not yet appended) " + ia.getHostAddress() + " in client list");
        return tc;       
      }
      synchronized (clnTable)
      { // now check the working list
        for (int i = 0; i < clnTable.size(); i++)
        {
          tc = (TClient) clnTable.get(i);
          if (!tc.IPaddress.equals(ia)) continue;
          if (tc.port != port) continue;
          tc.setTimeAppendedToList(System.currentTimeMillis());
          if (debugLevel > 2)
            DbgLog.log("LocateClientInList","found client " + ia.getHostAddress() + " in client list");
          return tc;
        }
      }
      if ((tc = new TClient()) != null)
      { // seems to be a new one ...
        tc.IPaddress = ia;
        tc.port = port;
        tc.setTimeAppendedToList(System.currentTimeMillis());
        clnTableAdd.add(tc);
        if (debugLevel > 2)
            DbgLog.log("LocateClientInList","add client " + ia.getHostAddress() + " to client list");
      }
      else
      {
        throw new RuntimeException("code error : cannot make new instance of TClient!");
      }
    }
    return tc;
  }
  private void factoryStartup()
  {
    gSystemExitCondition = false;
    debugLevel = 0;
    try { debugLevel = Integer.parseInt(System.getProperty("debug.level", "0")); }
    catch (Exception ignore) {}
    cycleThrd = new TCycleThread();
    cycleThrd.start();
    acceptThrd[0] = new TAcceptorThread(TTransport.UDP);
    acceptThrd[0].start();
    acceptThrd[1] = new TAcceptorThread(TTransport.TCP);
    acceptThrd[1].start();
    acceptThrd[2] = new TAcceptorThread(TTransport.STREAM);
    acceptThrd[2].start();
    acceptThrd[4] = new TAcceptorThread(TTransport.PIPE);
    acceptThrd[4].start();
    this.registerStockProperties();
    getAliasListFromFile();   
  }
  private TEquipmentModuleFactory()
  {
    factoryStartup();
  }
  public long getServerStartTime()
  {
    return gSrvStartupTime;
  }
  public long getServerBaseline()
  {
    return gSrvBaselineTime;   
  }
  public void resetServerBaseline()
  {
    gSrvBaselineTime = System.currentTimeMillis();
  }
  public static long getAvailableDiskSpaceInBlocks(String path)
  {
    if (path == null) return 0;
    long availBlocks = 0;
    try
    {
      File f = new File(path);
      long fs = f.getFreeSpace();
      availBlocks = fs/1000;
    }
    catch (Exception ignore) {};
    return availBlocks;
  }
  private class MinDiskSpaceTblEntry
  {
    String path;
    long minFreeBlks;
  }
  private long lastCheckFreeBlocks = 0;
  private ArrayList<MinDiskSpaceTblEntry> minDiskSpaceTbl = new ArrayList<MinDiskSpaceTblEntry>();
  private void checkFreeBlocks()
  {
    long freeblks;
    int i, code = TErrorList.warn_disk_space;
    TEquipmentModule em = null;
    for (i=0; i<numEqmTableEntries; i++)
    {
      if ((em=eqmTable[i]) == null) continue;
      break;
    }
    if (em == null) return;
    if (srvCycleTime < lastCheckFreeBlocks + 60000) return;
    lastCheckFreeBlocks = srvCycleTime;
    String txt;
    MinDiskSpaceTblEntry te = null;
    TDevice td = null;
    for (i=0; i<minDiskSpaceTbl.size(); i++)
    {
      te = minDiskSpaceTbl.get(i);
      if (te.minFreeBlks <= 0) continue;
      td = em.getDeviceList().getDevice(0);
      td.clearAlarm(TErrorList.warn_disk_space);
      td.clearAlarm(TErrorList.low_disk_space);
      freeblks = getAvailableDiskSpaceInBlocks(te.path);
      if (freeblks < te.minFreeBlks)
      {
        txt = te.path+" "+freeblks+" kb";
        if (freeblks < te.minFreeBlks/10) code = TErrorList.low_disk_space;
        code = TAlarm.encodeDiskSpaceAlarm(i,code);
        td.setAlarm(code,txt.getBytes());
      }
    }
  }
  /**
   * Add given path to the automatic alarm generation tables for low
   * disk space
   *
   * A server can instruct the local alarm subsystem to automatically generate
   * low disk space warnings and alarms by making use of this method.
   *
   * @param path is the path containing the disk drive or device mount which is to
   * be monitored for available disk space
   * @param minFreeBlocks is the minimum number of free blocks (1000 byte units)
   * which must be available. If the available disk space goes below this value a
   * 'warn_disk_space' alarm is issued.  If the available disk space goes below 10
   * percent of this value a 'low_disk_space' alarm is issued.
   * 
   * @return 0 upon success or a TINE error code.
   *
   * For example:
   *
   *   TEquipmentModuleFactory.getInstance().SetFreeBlocksAlarmThreshold("L:/history", 50000000L);
   *  
   * will cause disk space alarms to occur if the drive "L:" (windows system) shows fewer than
   * 50000000 blocks (i.e. 50000000000 bytes) available.
   */
  public int SetFreeBlocksAlarmThreshold(String path, long minFreeBlocks)
  {
    MinDiskSpaceTblEntry te = null;
    for (int i=0; i<minDiskSpaceTbl.size(); i++)
    {
      te = minDiskSpaceTbl.get(i);
      if (te.path.compareToIgnoreCase(path) == 0) break;
      te = null;
    }
    if (te == null)
    {
      te = new MinDiskSpaceTblEntry();
      te.path = path;
      minDiskSpaceTbl.add(te);
    }
    te.minFreeBlks = minFreeBlocks;
    return 0;
  }
  private static int gAveBusyTime = 0;
  public static int getAveBusyTime() { return gAveBusyTime; }
  public int[] getServerStats()
  {
    //TODO: put in the cyclecounts logic ...
    gSrvStatsBuffer[0] = gAveBusyTime; // ave busy time
    gSrvStatsBuffer[1] = 0; // gCycleCounts
    gSrvStatsBuffer[2] = 0; // gMaxCycleCounts
    gSrvStatsBuffer[3] = gSingleLinkCount;
    gSrvStatsBuffer[4] = gClientMisses;
    gSrvStatsBuffer[5] = gClientReconnects;
    gSrvStatsBuffer[6] = gClientRetries;
    gSrvStatsBuffer[7] = gContractMisses;
    gSrvStatsBuffer[8] = gContractDelays;
    gSrvStatsBuffer[9] = gBurstLimitReachedCount;
    gSrvStatsBuffer[10] = (int)gDataTimeStampOffset;
   
    return gSrvStatsBuffer;
  }
  public TServerSettings getServerSettings()
  {
    TServerSettings tss = new TServerSettings();
    tss.setClnRecvBuffers(TLinkFactory.getSckRcvBufferSize());
    tss.setConRenewalLength(CTR_RENEWAL);
    tss.setLnkTblCapacity(TLinkFactory.getMaximumNumberOfLinks());
    tss.setMinPollInterval(getMinimumPollingInterval());
    tss.setRetryOnTimeout(TLinkFactory.alwaysRetry?1:0);
    tss.setSrvBurstCycleDelay(gCycleDelay);
    tss.setSrvBurstLimit(gBurstLimit);
    tss.setSrvPacketMTU(srvPacketMtu);
    tss.setSrvRecvBuffers(sckRcvBufferSize);
    tss.setSrvRetardRemoval(gRetardSingleContractRemoval?1:0);
    tss.setSrvSendBuffers(sckRcvBufferSize);
    tss.setUseWdogLinks(gLinkFactory.getAutoLinkWatchdogs()?1:0);
    tss.setUseLoopbackAddr(1);
   
    return tss;
  }
  public long getAppCompilationTime()
  {
    return gAppCompilationTime;
  }
  public String getAppVersion()
  {
    StringBuffer appv = new StringBuffer(32);
    appv.append("" + gAppVersionMajor);
    if (gAppVersionMinor < 10appv.append(".0" + gAppVersionMinor);
    else appv.append("." + gAppVersionMinor);
    if (gAppVersionRevision < 10) appv.append(".000" + gAppVersionRevision)
    else if (gAppVersionRevision < 100) appv.append(".00" + gAppVersionRevision)
    else if (gAppVersionRevision < 1000) appv.append(".0" + gAppVersionRevision)
    else appv.append("." + gAppVersionRevision);
    return appv.toString()
  }
  public int getAppVersionMajor() { return gAppVersionMajor; }
  public int getAppVersionMinor() { return gAppVersionMinor; }
  public int getAppVersionRevision() { return gAppVersionRevision; }
  public void setAppVersionMajor(int major) { gAppVersionMajor = major; }
  public void setAppVersionMinor(int minor) { gAppVersionMinor = minor; }
  public void setAppVersionRevision(int revision) { gAppVersionRevision = revision; }
  public static synchronized TEquipmentModuleFactory getInstance()
  {
    if (instance == null) instance = new TEquipmentModuleFactory();
    return instance;
  }
  public int setFecName(String name)
  {
    if (name.length() <= TStrings.FEC_NAME_SIZE)
    {
      gFecName = new String(name);
    }
    else
    {
      gFecName = new String(name.substring(0, TStrings.FEC_NAME_SIZE));
      TFecLog.log("FEC name "+name+" too long; truncating to "+gFecName);
      TStrings.validateFecName(gFecName);
    }
    return 0;
  }
  public String getFecName() { return gFecName; }
  public int setFecContext(String context)
  {
    TStrings.validateContextName(context);
    gFecContext = new String(context);
    return 0;
  }
  public String getFecContext() { return gFecContext; }
  public void setFecDescription(String description) { if (description != null) gFecDescription = description; }
  public String getFecDescription() { return gFecDescription; }
  public void setFecLocation(String location) { if (location != null) gFecLocation = location; }
  public String getFecLocation() { return gFecLocation; }
  public void setFecSubsystem(String subsystem)
  {
    if (subsystem != null)
    {
      TStrings.validateSubSystemName(subsystem);
      gFecSubsystem = subsystem;
    }
  }
  public String getFecSubsystem() { return gFecSubsystem; }
  public void setFecHardware(String hardware) { if (hardware != null) gFecHardware = hardware; }
  public String getFecHardware() { return gFecHardware; }
  public void setFecResponsible(String responsible) { if (responsible != null) gFecResponsible = responsible; }
  public String getFecResponsible() { return gFecResponsible; }
  public double getLastAccessTime()
  {
    if (wrAccTblPtr < 0 || wrAccTbl[wrAccTblPtr] == null) return 0;
    return wrAccTbl[wrAccTblPtr].getTime();
  }
  public String getLastAccessUser()
  {
    if (wrAccTblPtr < 0 || wrAccTbl[wrAccTblPtr] == null) return "";
    return wrAccTbl[wrAccTblPtr].getUser();
  }
  public String getLastAccessAddress()
  {
    if (wrAccTblPtr < 0 || wrAccTbl[wrAccTblPtr] == null) return "";
    String addr = wrAccTbl[wrAccTblPtr].getAddress();
    if (wrAccTbl[wrAccTblPtr].port != 0) addr += ":"+wrAccTbl[wrAccTblPtr].port;
    return addr;
  }
  public String getLastAccessEqm()
  {
    if (wrAccTblPtr < 0 || wrAccTbl[wrAccTblPtr] == null) return "";
    return wrAccTbl[wrAccTblPtr].getEqm();
  }
  public String getLastAccessPrp()
  {
    if (wrAccTblPtr < 0 || wrAccTbl[wrAccTblPtr] == null) return "";
    return wrAccTbl[wrAccTblPtr].getProperty();
  }
  public String getLastAccessDev()
  {
    if (wrAccTblPtr < 0 || wrAccTbl[wrAccTblPtr] == null) return "";
    return wrAccTbl[wrAccTblPtr].getDevice();
  }
  public String makeModuleName(String suggestedModuleName)
  {
    String eqmName;
    int n;
    eqmName = suggestedModuleName;
    if (eqmName.length() > 6)
    {
      eqmName = eqmName.substring(0,6);
      TFecLog.log("Module Name "+suggestedModuleName+" too long; truncating to "+eqmName);
    }
    TStrings.validateLocalName(eqmName);
    for (n=0; n < numEqmTableEntries && eqmTable[n].getModuleName().compareTo(eqmName) != 0; n++);
    if (n == numEqmTableEntries) return eqmName;
    DbgLog.log("makeModuleName","equipment module name conflict !");
    DbgLog.log("makeModuleName","Please use a different name for " + suggestedModuleName + " and try again!");
    return null;
  }
  public int addEquipmentBackgroundTask(TEquipmentBackgroundTask tbkg)
  {
    TBackgroundThread tbt = new TBackgroundThread(tbkg);
    if (tbt == null) return -1;
    TFecLog.log("add Equipment Background task " + tbkg.getClass().getSimpleName());
    tbkg.startup();
    tbt.start();
    gNumBkgTasks++;
    return 0;
  }
  public TEquipmentModule getEquipmentModule(String eqmName)
  {
    if (eqmName == null || eqmName.length() == 0) return null;
    String eqm;
    for (int i = 0; i<numEqmTableEntries; i++)
    {
      if ((eqm=eqmTable[i].getLocalName()) == null) continue; // should not be possible
      if (eqm.compareTo(eqmName) == 0) return eqmTable[i];
    }
    return null;
  }
  public TEquipmentModule getEquipmentModuleFromExportName(String expName)
  {
    if (expName == null || expName.length() == 0) return null;
    String exp;
    for (int i = 0; i<numEqmTableEntries; i++)
    {
      if ((exp=eqmTable[i].getExportName()) == null) continue;
      if (exp.compareTo(expName) == 0) return eqmTable[i];
    }
    return null;
  }
  public int addEquipmentModule(TEquipmentModule teqm)
  {
    int i;
    for (i = 0; i < numEqmTableEntries; i++)
    {
      if (eqmTable[i] == teqm) return i;
    }
    if (i < maxNumEqmTableEntries)
    {
      TFecLog.log("add Equipment Module (" + i + ") " + teqm.getLocalName());
      eqmTable[i] = teqm;
      if (teqm.getLocalName().length() > 0) registrationPending = true;
      numEqmTableEntries++;
      return i;
    }
    return -1;
  }
  private int maxDeadTime(short mode, int pollingRate)
  {
    if (mode == TMode.CM_DATACHANGE) return HEARTBEAT + pollingRate;
    return 2 * pollingRate;
  }
  private byte[] deliveryPayload = new byte[TPacket.MAX_DATAGRAM_SIZE];
  private void resetPollingInterval(TContractTable tct)
  {
   
    Iterator<TClientEntry> itr = tct.clt.iterator();
    TClientEntry tce = null;
    int p = 1000;
    while (itr.hasNext())
    {
      if ((tce=itr.next()) == null) continue;
      if (TMode.getBaseMode(tce.sts.mode) <= TMode.CM_SINGLE) continue;
      p = findpoll(tce.sts.PollingRate, p);
    }
    tct.pollingRate = p;   
  }
  private int doDelivery(TContractTable tgtCon,TClient tgtCln)
  {
    int i, k, n, extptr, extsize, msgsize, stepsize, packetmtu,ipos;
    int fsize, dsize, dptr;
    boolean passed = false, snddata = false, actionTaken = false, isLegacy = false, isStream = false;
    boolean swallowIt = false;
    int totalsize = 0, basemode;
    TContractTable tct = null;
    TClientEntry tce = null;
    TClient tc = null;
    TPHdr pHdr = null;
    byte[] payload = deliveryPayload;
    byte[] streamBytes = null; // for use in stream transfer only
    byte[] statusBytes = null; // for use in stream transfer only
    long t_now = System.currentTimeMillis();
    int pkts_sent = 0;
    int hdrsize, extlimit;
    int npkts=0,ncnts=0;
    TContractTable[] ctbl = null;
    if (debugLevel > 0 && clnTable.size() == 0)
        DbgLog.log("doDelivery","no client table in deliver data !");
    for (k=0, ipos=0; k < clnTable.size(); k++)
    { // run thru the client table; collect outgoing data for each
      if ((tc=clnTable.get(k)) == null) continue;
      if (tgtCln != null && tc != tgtCln) continue;
      isLegacy = tc.tineProtocol < 6;
      hdrsize = isLegacy ? TPHdr.hdrSizeInBytesLegacy : TPHdr.hdrSizeInBytes;
      extlimit = isLegacy ? TStrings.STATUS_SHORTSIZE : TStrings.STATUS_SIZE;
      isStream = TTransport.isStream(tc.inetProtocol);
      passed = true;
      totalsize = 2;
      ctbl = tc.getContractList();
      if (ctbl == null || ctbl.length == 0)
      {
        if (t_now > tc.getTimeAppendedToList() + TClient.ALLOWED_DANGLE_TIME)
        {
          MsgLog.log("doDelivery","remove dangling client " + tc.userName,TErrorList.non_existent_client,null,1);
          removeClient(k--);
        }
        continue;
      }     
      for (i=ipos, n=0; i < ctbl.length && n < tc.ncontracts; i++)
      { // run thru this client's contract list
        tct = ctbl[i];
        if (tgtCon != null && tgtCon != tct) continue;
        Iterator<TClientEntry> li = tct.clt.iterator();
        boolean found = false;
        while (li.hasNext())
        { // this looks like a double check ?
          tce = li.next();
          if (tc.equals(tce.cln)) { found = true; break; }
        }
        if (!found)
        { // no match (does this ever happen?)
          tct.pending = 0;
          continue;
        }
        if (TTransport.isConnected(tc.inetProtocol) && tc.output == null) tce.sts.counter = 0;
        n++; // running count of a client's contracts (maintained independent of client array list?)
        snddata = true;
        extptr = extsize = 0;
        basemode = tce.sts.mode;
        if (isServiceRequest(tct.contract.eqmName) &&
            tce.sts.statusCode != 0)
        { // service request will only answer 'ok' if the server export matches !
          swallowIt = true;
        }
        if (tce.sts.Stale != 0)
        { // marked as stale
          if (isStream)
          { // CM_STREAM has a different delivery strategy ... 
            tce.sts.mtu = tce.sts.BytesRemaining + extlimit;
            tce.sts.nextDataSize = tce.sts.BytesRemaining;
            tce.sts.nextDataPtr = 0;
          }
          actionTaken = true;
          stepsize = hdrsize;
          packetmtu = 2 + stepsize + tce.sts.mtu;
          snddata = (tce.sts.statusCode == 0) || (tce.sts.statusCode & TErrorList.CE_SENDDATA) != 0;
          if (snddata)
          { // definitely data going back (or success) ...
            dsize = tce.sts.nextDataSize;
            dptr = tce.sts.nextDataPtr;
          }
          else
          { // error code returned
            extsize = extlimit;
            dsize = extsize;
            dptr = 0;
          }
          if ((msgsize = dsize + stepsize) > packetmtu)
          { // cannot pack this into this delivery packet ...
            tce.sts.counter = 0;
            continue;
          }
          if ((totalsize + msgsize) > packetmtu)
          { // msgsize too big to fit in this packet !
            passed = false; // this one doesn't pass -> break out and deliver what we've got
            ipos = i;
            break;
          }
          else
          {
            ncnts++;
            pHdr = new TPHdr(msgsize, tce, tct);
            System.arraycopy(pHdr.dBuffer.toByteArray(), 0, payload, totalsize, stepsize);
            fsize = TFormat.formatSizeOf(tct.contract.dataFormatOut);
            if (snddata && fsize > 0)
            { // data going back ...
              if (tct.contract.dataFormatOut == TFormat.CF_MDA) fsize = TFormat.getCarriedFormatSize(tct.contract.dataTagOut);
              if ((tce.sts.statusCode & 0xfff) != 0)
              { // and a status code going back ...
                int tdsiz = tct.contract.dataSizeOut * fsize + TFormat.getFormatHeaderSize(tct.contract.dataFormatOut);
                extsize = (int) (tce.sts.nextDataPtr + tce.sts.nextDataSize - tdsiz);
                // shouldn't be possible ...
                if (extsize > extlimit) extsize = extlimit;
                if (extsize > dsize)
                { // err string remainder
                  extsize = dsize;
                  extptr = extlimit - extsize;
                }
                if (extsize > 0) dsize -= (short)extsize; else extsize = 0;
                if (isStream) { extsize = extlimit; extptr = 0; }
              }
              if (dsize > 0)
              { // dsize = size in bytes of the data going back ...
                byte[] b = tct.dout.getDataBuffer(); // returns byte array of ALL the bytes
                // (each time for each block => TODO: improve this!)
                if (isStream)
                { // don't try to double-buffer this any more than necessary !
                  streamBytes = b;
                }
                else
                { // normal case ...
                  int thissize = b.length - dptr;
                  if (thissize < 0) thissize = 0;
                  if (dsize > thissize) dsize = thissize;
                  try
                  {
                    if (b != null && dsize > 0)
                      System.arraycopy(b, dptr, payload, totalsize + stepsize, dsize);
                  }
                  catch (ArrayIndexOutOfBoundsException e)
                  {
                    e.printStackTrace();
                    MsgLog.log("doDelivery","b len " + b.length + " dptr " + dptr +
                      " payload " + payload.length + " offset " + totalsize + stepsize + " size " + dsize,TErrorList.dimension_error,e,0);
                  }
                }
              }
            }
          }
          if (passed)
          { // everything's okay so far ...
            totalsize += msgsize;
            if (extsize > 0)
            { // insert err string
              byte b[] = null;
              if (tce.sts.statusCode == TErrorList.server_redirection)
              { // the property handler has handled the call and is returning this information!
                gLastErrorString = tct.compString;
                tce.sts.counter = 1;
                b = redirectionStringToBytes(tct.compString);
              }
              else if ((tce.sts.statusCode & 0xfff) < TErrorList.errorString.length)
              {
                gLastErrorString = TErrorList.getErrorString(tce.sts.statusCode & 0xff);
                b = gLastErrorString.getBytes();
              }
              else if (tct.compString != null)
              {
                gLastErrorString = tct.compString;
                b = gLastErrorString.getBytes();
              }
              if (b == null) b = "error unknown".getBytes();
              if (isStream)
              {
                statusBytes = b;
              }
              else
              { // only copy size of gLastErrorString (not extsize !)            
                Arrays.fill(payload, totalsize-extsize, totalsize-1, (byte)0);
                System.arraycopy(b, extptr, payload, totalsize - extsize,b.length);
              }
            }
            if (debugLevel > 0)
            {
              boolean dbgIt = (debugLevel > 2) || (TMode.getBaseMode(tce.sts.mode) <= TMode.CM_SINGLE);
              if (debugLevel > 1 && tce.sts.blknum == tce.sts.numblks) dbgIt = true;
              if (dbgIt)
              {
                String dbgstr = "["+i+"] ("+tct.contract.eqmName+") "+
                  tct.contract.eqmDeviceName+" "+tct.contract.eqmProperty+
                  " " + totalsize + " bytes to " + tce.cln.userName +
                  " @ " + tce.cln.IPaddress.getHostAddress()+":"+tce.cln.IPport +
                  " (" + tce.sts.counter + ") <" + tce.sts.statusCode + ">";
                DbgLog.log("doDelivery",dbgstr);
              }
            }
            if (!snddata || tce.sts.numblks == tce.sts.blknum)
            { // data block finished and/or error code
              if (debugLevel > 2) DbgLog.log("doDelivery","data delivery finished");
              tce.sts.Stale = 0;
              if (tce.sts.counter > 0) tce.sts.counter--;
              if (tce.sts.mode == TMode.CM_CANCEL) tce.sts.counter = 0;
              tce.sts.blknum = 1;
              tce.sts.nextDataPtr = 0;
              tce.sts.BytesRemaining = tce.sts.BytesTotal;
              if (tct.pending > 0) tct.pending--;
              tct.delivered++;
              if ((t_now - tce.sts.lasttime) > maxDeadTime(tce.sts.mode,tce.sts.PollingRate))
              {
                tce.sts.misses++;
                gClientMisses++;
              }
            }
            else
            { // just send out this piece ...
              tce.sts.blknum++;
              tce.sts.nextDataPtr += tce.sts.nextDataSize;
              tce.sts.BytesRemaining = tce.sts.BytesTotal - tce.sts.nextDataPtr;
              gStaleData++;
            }
            tce.sts.nextDataSize = tce.sts.mtu < tce.sts.BytesRemaining ?
                                   tce.sts.mtu : tce.sts.BytesRemaining;
          }
        }
        if (passed && tce.sts.counter <= 0)
        {
          if (debugLevel > 1)
            DbgLog.log("doDelivery","remove " + tce.cln.userName + " from contract "
                + tct.contract.eqmProperty);
          actionTaken = true;
          tce.cln.ncontracts--;
          tce.cln.rmvContract(tct);
          tct.clt.remove(tce);
          tct.nclients--;
          n--;
          resetPollingInterval(tct);
          if (debugLevel > 1)
            DbgLog.log("doDelivery"," contract " + tct.contract.eqmProperty + " now has "
                + tct.nclients + " clients");
        }
        if (passed && tct.nclients == 0)
        {
          actionTaken = true;
          if (gRetardSingleContractRemoval && basemode == TMode.CM_SINGLE)
          {
            if (debugLevel > 1)
              DbgLog.log("doDelivery","mark contract " + tct.contract.eqmProperty + " as expired");
            tct.expired = true;
            tct.contract.dataAccess =
              TAccess.removeBits(tct.contract.dataAccess,(short)(TAccess.CA_FIRST | TAccess.CA_LAST));
            numExpiredContracts++;
          }
          else
          {
            if (debugLevel > 1) DbgLog.log("doDelivery","remove contract " + tct.contract.eqmProperty);
            removeContract(tct);
          }
        }
        if (isStream) break;
      }
      if (totalsize > 2 && tc != null)
      { // something ready to send out
        actionTaken = true;
        //System.arraycopy(Swap.ShortToBytes(totalsize), 0, payload, 0, 2);
        // this is quicker :
        payload[0] = (byte) (totalsize & 0xff);
        payload[1] = (byte) ((totalsize >> 8) & 0xff);
        if (isStream)
        {
          payload[2] (byte) ((totalsize >> 16) & 0xff);
          payload[3] (byte) ((totalsize >> 24) & 0xff);
        }
        if (!swallowIt) sendToPeer(tc, payload, totalsize, streamBytes, statusBytes);
        streamBytes = statusBytes = null;
        pkts_sent++;
        npkts++;
        if (debugLevel > 2) DbgLog.log("doDelivery","client "+k+" sent packet "+npkts+" ncontracts: "+ncnts);
      }
      ncnts=0;
      if (passed && tc.ncontracts == 0)
      {
        if (debugLevel > 1) DbgLog.log("doDelivery","remove client " + tc.userName);
        removeClient(k--);
      }
      if (!passed)
      { // still data to send to this client => back up (cancel the increment on k)
        k--;
      }
      else
      { // finished with this client, reset counters ...
        ipos = 0;
        npkts = 0;
      }
    }
    if (gStaleData > 0) gStaleData--; else gStaleData = 0;
    if (!actionTaken || pkts_sent == 0)
    {
      if (debugLevel > 2)
        DbgLog.log("doDelivery",actionTaken ? "no packets sent in doDelivery()" : "empty pass through doDelivery()");
      gStaleData = 0;
    }
    if (debugLevel > 1) DbgLog.log("doDelivery","sent "+pkts_sent+" packets in "+(System.currentTimeMillis()-t_now)+" msecs");
    return gStaleData;
  }
  private void sendToPeer(TClient tc, byte[] payload, int length, byte[] streamBytes,byte[] statusBytes)
  {
    if (debugLevel > 1)
        DbgLog.log("sendToPeer",tc.IPaddress.getHostAddress() + ":" + tc.port);
    try
    {
      switch (tc.inetProtocol)
      {
        case TTransport.UDP:
          if (sckUdp == null) return; // socket not yet available ?
          sckUdp.dpOut = new DatagramPacket(payload, length, tc.IPaddress, tc.port);
          sckUdp.getSocket().send(sckUdp.dpOut);
          break;
        case TTransport.TCP:
          byte[] buf = new byte[length];
          System.arraycopy(payload,0,buf,0,length);
          tc.output.write(buf); // more efficient ?
          break;
        case TTransport.STREAM:
          int hdrlen = length;
          if (streamBytes != null) hdrlen -= streamBytes.length;
          if (statusBytes != null) hdrlen -= statusBytes.length;
          tc.output.write(payload,0,hdrlen);
          if (streamBytes != null) tc.output.write(streamBytes);
          if (statusBytes != null) tc.output.write(statusBytes);
          break;
      }
    }
    catch (IOException e)
    {
      MsgLog.log("sendToPeer",e.toString(),TErrorList.net_write_error,e,0);     
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("sendToPeer",e.toString(),TErrorList.net_write_error,e,0);
      // TODO: TCP -> close the socket if the endpoint has closed !!
    }
  }
  private void removeClient(int i)
  {
    synchronized (clnTable)
    {
      clnTable.remove(i);
      serverStatistics.setNumTotalClients((short) clnTable.size());
    }
  }
  private void removeContract(int i)
  {
    conTable.remove(i);
  }
  private void removeContract(TContractTable tct)
  {
    conTable.remove(tct);
    serverStatistics.setNumTotalContracts((short) conTable.size());
  }
  private void addContract(TContractTable tct)
  {
    synchronized (conTable)
    {
      conTable.add(tct);
    }       
  }
  private long rejectEarlierThan = 0;
  private final static int FLUSH_WINDOW = 5000;
  private boolean gFlushContractTable = false;
  public void flushContractTable()
  {
    if (gIsInsideScheduler)
    {
      gFlushContractTable = true;
      return;
    }
    rejectEarlierThan = System.currentTimeMillis() + FLUSH_WINDOW;
    synchronized (conTable)
    {
      for (int i=0; i<conTable.size(); i++)
      {
        removeContract(i);       
      }
    }
    serverStatistics.setNumTotalContracts((short)0);
    synchronized (clnTable)
    {
      for (int i=0; i < clnTable.size(); i++)
      {
        clnTable.remove(i);
      }
    }
    serverStatistics.setNumTotalClients((short)0);
  }
  public static void dumpClientsInContract(TContractTable tct)
  {
    Iterator<TClientEntry> itr = tct.clt.iterator();
    TClientEntry tce = null;
    String s = "clients: ";
    while (itr.hasNext())
    {
      if ((tce=itr.next()) == null) continue;
      s += "\t"+tce.cln.userName+" (" +
           tce.cln.IPaddress.getHostAddress() +
           ") @ "+tce.sts.PollingRate+ " msec\n";
    }
    dbgPrint(s);
  }
  public static void dumpStats()
  {
    dbgPrint("\nCurrent System Statistics");
    dbgPrint("Running since       : "+ TDataTime.toString(gSrvStartupTime));
    dbgPrint("Baseline            : "+ gSrvBaselineTime);
    dbgPrint("Registered clients  : "+ clnTable.size());
    dbgPrint("Registered contracts: "+ conTable.size());
    dbgPrint("Total UDP reqs      : "+ gTotalUdpPackets+" pkts");
    dbgPrint("Total TCP reqs      : "+ gTotalTcpPackets+" pkts");
    dbgPrint("Total STREAM reqs   : "+ gTotalStreamPackets+" pkts");
    dbgPrint("Total NETWORK reqs  : "+ gTotalNetSrvPackets+" pkts");
    dbgPrint("Contract misses     : "+ gContractMisses);
    dbgPrint("Contract delays     : "+ gContractDelays);
    dbgPrint("Client misses       : "+ gClientMisses);
    dbgPrint("Client reconnects   : "+ gClientReconnects);
    dbgPrint("Client retries      : "+ gClientRetries);   
    dbgPrint("Synchronous calls   : "+ gSingleLinkCount);
    dbgPrint("Bursts              : "+ gBurstLimitReachedCount);
    dbgPrint("current time offset : "+ gDataTimeStampOffset+" msec");
    dbgPrint("CPU usage           : "+ gAveBusyTime+" percent");   
  }
  public static void dumpContractTable()
  {
    dbgPrint("\nCurrent Contract Table");
    synchronized (conTable)
    {
      TContractTable tct;
      for (int i=0; i<conTable.size(); i++)
      {
        tct = conTable.get(i);
        dbgPrint(tct.toString());
        dumpClientsInContract(tct);
      }
    }
    dbgPrint("\n(inside scheduler : "+gIsInsideScheduler+")");
    dbgPrint("");
  }
  public static void dumpClientTable()
  {
    //System.out.println("\nCurrent Client Table");
    dbgPrint("\nCurrent Client Table\n");
    synchronized (clnTable)
    {
      TClient tc;
      for (int i=0; i<clnTable.size(); i++)
      {
        tc = clnTable.get(i);
        //System.out.println(tc.toString());
        dbgPrint(tc.toString());
      }
    }
    //System.out.println("");   
    dbgPrint("");
  }
  public static void dumpModules()
  {
    if (instance == null) return;
    TEquipmentModule eqm;
    dbgPrint("Module     \tExport Name \tContext     \tSubsystem");
    for (int i = 0; i<instance.numEqmTableEntries; i++)
    {
      if ((eqm=instance.eqmTable[i]) == null) continue;
      if (eqm.getLocalName() == null) continue; // should not be possible
      dbgPrint(eqm.getLocalName()+"     \t"+eqm.getExportName()+" \t"+
          eqm.getContext()+"      \t"+eqm.getSubsystem());
    }   
  }
  public static void dumpHistories()
  {
    if (instance == null) return;
    TEquipmentModule eqm;
    for (int i = 0; i<instance.numEqmTableEntries; i++)
    {
      if ((eqm=instance.eqmTable[i]) == null) continue;
      if (eqm.getLocalName() == null) continue; // should not be possible
      if (eqm.gLclHstList.isEmpty()) continue;
      dbgPrint("Registered local histories for module: "+eqm.getLocalName());
      for (THistoryRecord hst : eqm.gLclHstList)
      {
        dbgPrint(hst.dump());
      }
    }   
  } 
  public static void dumpUserLists()
  {
    if (instance == null) return;
    TEquipmentModule eqm;
    NAME16[] usrs;
    for (int i = 0; i<instance.numEqmTableEntries; i++)
    {
      if ((eqm=instance.eqmTable[i]) == null) continue;
      if (eqm.getLocalName() == null) continue; // should not be possible
      dbgPrint(eqm.getLocalName()+" allowed users");
      if ((usrs = eqm.getRegisteredUsers()) == null || usrs.length == 0)
      {
        dbgPrint("\t open to all users");
        continue;
      }
      for (int u=0; u < usrs.length; u++)
      {
        dbgPrint("\t "+usrs[u].getName());
      }
    }   
    dbgPrint("");
  }
  public static void dumpNetsLists()
  {
    if (instance == null) return;
    TEquipmentModule eqm;
    NAME16[] nets;
    for (int i = 0; i<instance.numEqmTableEntries; i++)
    {
      if ((eqm=instance.eqmTable[i]) == null) continue;
      if (eqm.getLocalName() == null) continue; // should not be possible
      dbgPrint(eqm.getLocalName()+" allowed network addresses");
      if ((nets = eqm.getRegisteredNets()) == null || nets.length == 0)
      {
        dbgPrint("\t open to all addresses");
        continue;
      }
      for (int u=0; u < nets.length; u++)
      {
        dbgPrint("\t "+nets[u].getName());
      }
    }   
    dbgPrint("");
  }
  public static void dumpProperties(String eqmName)
  {
    TEquipmentModule eqm;
    for (int i = 0; i<instance.numEqmTableEntries; i++)
    {
      if ((eqm=instance.eqmTable[i]) == null) continue;
      if (eqm.getLocalName().compareToIgnoreCase(eqmName) != 0) continue;
      eqm.dumpProperties();
      return;
    }
    dbgPrint(eqmName+" is not a registered equipment module");
  }
  public static void dumpDevices(String eqmName)
  {
    TEquipmentModule eqm;
    for (int i = 0; i<instance.numEqmTableEntries; i++)
    {
      if ((eqm=instance.eqmTable[i]) == null) continue;
      if (eqm.getLocalName().compareToIgnoreCase(eqmName) != 0) continue;
      eqm.dumpDevices();
      return;
    }
    dbgPrint(eqmName+" is not a registered equipment module");
  }
  public static void dumpDeadbands(String eqmName)
  {
    TEquipmentModule eqm;
    for (int i = 0; i<instance.numEqmTableEntries; i++)
    {
      if ((eqm=instance.eqmTable[i]) == null) continue;
      if (eqmName != null && eqmName.length() > 0 &&
          eqm.getLocalName().compareToIgnoreCase(eqmName) != 0) continue;
      eqm.dumpDeadbands();
      return;
    }
    dbgPrint(eqmName+" is not a registered equipment module");
  }
  private void updateTables()
  {
    TContractTable tct = null;
    TClient tc = null;
    synchronized (clnTableAdd)
    {
      if (clnTableAdd.size() > 0) synchronized (clnTable)
      { // add any new clients to the client table
        Iterator<TClient> li = clnTableAdd.iterator();
        while (li.hasNext())
        {
          tc = (TClient) li.next();
          tc.setTimeAppendedToList(System.currentTimeMillis());
          clnTable.add(tc);
        }
        clnTableAdd.clear();
      }
    }
    serverStatistics.setNumTotalClients((short) clnTable.size());
    synchronized (conTableAdd)
    { // add any new contract to the contract table
      if (conTableAdd.size() > 0) synchronized (conTable)
      {
        Iterator<TContractTable> li = conTableAdd.iterator();
        while (li.hasNext())
        {
          tct = (TContractTable) li.next();
          conTable.add(tct);
        }
      }
      conTableAdd.clear();
    }
    serverStatistics.setNumTotalContracts((short) conTable.size());
    for (int i=0; i<clnTable.size(); i++)
    { // make sure this is also synchronized
      tc = (TClient) clnTable.get(i);
      tc.appendAddedContracts();
    }
  }
  private void assertClientTableConsistent(TContractTable tct)
  {
    try
    {
      if (tct.clt.isEmpty())
      {
        tct.nclients = 0;
        return;
      }
      Iterator<TClientEntry> li = tct.clt.iterator();
      TClientEntry tce;
      int i, clnTblSize = clnTable.size();
      while (li.hasNext())
      {
        tce = (TClientEntry)li.next();
        for (i=0; i<clnTblSize; i++)
        {
          if (tce.cln.equals(clnTable.get(i))) break;
        }
        if (i == clnTblSize)
        { // wasn't found
          MsgLog.log("assertClientTableConsistent", "remove phantom client "+tce.cln.toString()+" from "+tct.contract.getNameTag(),0,null,1);
          li.remove();
          if (tct.nclients > 0) tct.nclients--;
        }
      }
      tct.nclients = tct.clt.size();
    }
    catch (Exception e)
    {
      MsgLog.log("assertClientTableConsistent",e.toString(),TErrorList.code_failure,e,0);                         
    };
  }
  private static boolean gIsInsideScheduler = false;
  public boolean isInsideScheduler() { return gIsInsideScheduler; }
  /**
   *
   * @param tct is the contract to process
   * @return true if contract should be removed
   */
  private boolean processContract(TContractTable tct)
  {
    TClientEntry tce = null;
    int cc = 0, datasize = 0, msec = 0, dss, dus;
    boolean isScheduled=false, isCanceled=false, stale=false, dataChanged=false, hasData=false, clockAdjusted=false;
    long dts, delta_t = 0, t_now = System.currentTimeMillis(), t_stamp = 0;

    if (debugLevel > 3)
      DbgLog.log("processContract","looking at contract " + tct.contract.toString() + " with "
          + tct.nclients + " clients");
    if (tct.clt.isEmpty() && !tct.expired)
    {
      MsgLog.log("processContract","contract "+tct.contract.getNameTag()+" has no clients",0,null,0);
      if (TAccess.isLast(tct.contract.dataAccess))
      { // going out of scope
        doContract(tct);
      }
      tct.expired = true;
      tct.pending = 0;
      return true;
    }
    if (tct.pollingRate <= 0) tct.pollingRate = 1000; // sanity check
    if (tct.expired) tct.isStale = false; // not allowed to be stale
    assertClientTableConsistent(tct);
    isScheduled = false;
    if (tct.isStale) gStaleData++;
    cc = 0;
    datasize = tct.contract.getOutputDataSize();
    if ((delta_t = t_now - tct.lasttime) < 0)
    { // system clock has been set back !
      tct.lasttime = t_now;
      clockAdjusted = true;
      synchronized (TDataTime.syncMutex)
      {
        long tsOffset = TDataTime.getDataTimeStampOffset();
        if (tsOffset != 0 && applyTsOffsetIfNeeded)
        { // is receiving TINE time updates and has an offset (probably negative)
          tsOffset -= delta_t;
          TDataTime.setDataTimeStampOffset(tsOffset);
          TFecLog.log("synchronization correction (applied): system clock set back "+delta_t+" msec -> adjust data timestamp offset to "+tsOffset);
          applyTsOffsetIfNeeded = false;
        }
      }
      delta_t = (long) tct.pollingRate; // make sure we fall thru the next check
    }
    if (delta_t <= (long) tct.pollingRate - gSystemTick) return false;
    if (debugLevel > 2) DbgLog.log("processContract","contract "+tct.contract.getNameTag()+" needs attention : polling interval " + tct.pollingRate +  " last accessed : " + tct.lasttime);
    if (tct.expired)
    {
      if (delta_t > 3 * tct.pollingRate)
      {
        if (debugLevel > 1) DbgLog.log("processContract","removing expired contract "+tct.contract.getNameTag());
        return true;
      }
      if (tct.clt.size() == 0) return false;
    }
    if (tct.isProcessing)
    { // scheduling several properties will also run thru contract lists several times ....
      if (tct.eqm != null && tct.eqm.prpSigHdlrList.size() > 0)
        tct.eqm.sendPropertySignal(tct.contract.eqmProperty, tct.contract, TPropertySignal.PS_PENDING, TErrorList.not_ready);
      return false;
    }
    if (delta_t < gMaxPollingRate && delta_t >= 2 * tct.pollingRate)
    {
      tct.misses++;
      gContractMisses++;
      MsgLog.log("processContract","missed schedule on "+tct.contract.getNameTag()
        +" : "+delta_t+" vs "+tct.pollingRate,0,null,1);
      if (tct.eqm != null && tct.eqm.prpSigHdlrList.size() > 0)
        tct.eqm.sendPropertySignal(tct.contract.eqmProperty, tct.contract, TPropertySignal.PS_LATE, tct.compStatus);
    }
    if (tct.pending > 0 && tct.clt.size() > 0)
    { // still sending out the last round
      if (debugLevel > 2) DbgLog.log("processContract","still sending "+tct.contract.getNameTag()+" to clients");
      tct.delays++;
      gContractDelays++;
      if (gStaleData == 0) gStaleData++;
      Iterator<TClientEntry> li = tct.clt.iterator();
      while (li.hasNext())
      {
        tce = (TClientEntry) li.next();
        tce.sts.Stale |= (short) TTransferCodes.CX_RETRY;
      }
      if (tct.eqm != null && tct.eqm.prpSigHdlrList.size() > 0)
        tct.eqm.sendPropertySignal(tct.contract.eqmProperty, tct.contract, TPropertySignal.PS_PENDING, tct.compStatus);
      return false;
    }
    tct.isProcessing = true;
    if (tct.starttime == 0) tct.starttime = t_now;
    t_stamp = t_now;
    msec = (int) (t_now % 100);
    // Synchronize on 100 msec boundaries if possible
    if (gSynchronizeContracts && (tct.pollingRate % 100 == 0)
        && msec < (100 - (int) gSystemTick)) t_stamp = t_now - msec;
    dts = gDataTimeStamp = t_now + gDataTimeStampOffset;
    dss = gSystemStamp;
    dus = 0;
    if (tct.nclients == 1)
    { // one client left
      tce = (TClientEntry) tct.clt.element();
      if (tce.sts.counter == 1 && !tct.isRepeat)
      { // dangling client going out of scope ?
        tct.contract.dataAccess |= TAccess.CA_LAST;
      }
    }
    hasData = true; // be optimistic !
    if (debugLevel > 2) DbgLog.log("processContract","processing contract "+tct.contract.getNameTag());
    switch (cc = doContract(tct))
    {
      case 0:
        break;
      case TErrorList.not_ready:
        // call returned not_ready or still not_signalled
        tct.contract.dataAccess =
          TAccess.removeBits(tct.contract.dataAccess,TAccess.CA_LAST);
        tct.isRepeat = true;
        if ((t_now - tct.starttime) < tct.pollingRate + 500)
        { // don't send out
          tct.setStale((byte) 0);
          tct.pending = 0;
          tct.isProcessing = false;
          return false;
        }
        break;
      case TErrorList.server_redirection:
        // the property handler is handling the call and has filled in tRedirectedServer
        if (tRedirectedServer == null) cc = TErrorList.code_failure;
        else tct.compString = new String(tRedirectedServer);
      default:
        if ((cc & 0xf000) != 0)
        { // CE_RENEGOTIATE or CE_SENDDATA
          int rc = 0;
          if ((rc = tct.renegotiateContract((short) (cc & 0xfff))) != 0)
            cc = rc;
          else
            cc &= ~TErrorList.CE_RENEGOTIATE;
          datasize = tct.contract.getOutputDataSize();
        }
        else
        {
          hasData = false;
        }
    }
    if ((tct.contract.dataAccess & TAccess.CA_WRITE) == TAccess.CA_WRITE )
    {
      appendWriteAccessTable(tct);
    }
    if (hasData)
    {
      if (debugLevel > 3) DbgLog.log("processContract","Contract "+tct.contract.getNameTag()+" has data");
      dts = tct.drb.dTimestamp;
      dss = tct.drb.sysDataStamp;
      dus = tct.drb.usrDataStamp;
      if (datasize > 0 && !tct.dout.equals(tct.drb))
      { // Data have changed; the cached data are dirty
        tct.dout.copy(tct.drb);
        stale = dataChanged = true;
      }
      else
      {
        if (t_now - tct.lastmarked > (HEARTBEAT - 1) * 1000) stale = true;
        dataChanged = false; // Cached data have not changed
      }
    }
    if (cc != 0) stale = true;
    if (stale) tct.lastmarked = t_stamp; // Last marked as Stale
    if (debugLevel > 2)
        DbgLog.log("processContract","contract data is " + (stale ? "stale" : "not stale"));
    if (tct.lasttime == PRP_SCHEDULE_SIGNAL) isScheduled = true;
    if (tct.lasttime == PRP_CANCEL_SIGNAL)
    {
      stale = true;
      isCanceled = true;
    }
    if (cc == TErrorList.not_ready)
    { // was 'not_ready' long enough ! -> send 'busy' back to caller
      cc = TErrorList.operation_busy;
      MsgLog.log("processContract", tct.contract.getNameTag()+" : 'not ready' time limit exceeded -> return operation_busy",cc,null,0);
    }
    else
    { // normal case, call is finished ...
      tct.starttime = 0;
      if (!isCanceled) tct.lasttime = t_stamp; // Last polled
      tct.compStatus = TErrorList.not_signalled;
    }
    tct.sysstamp = dss;
    tct.dtimestamp = dts;
    tct.usrstamp = dus;
    tct.returnCode = cc;
    Iterator<TClientEntry> li = tct.clt.iterator();
    while (li.hasNext())
    {
      tce = (TClientEntry) li.next();
      tce.sts.statusCode = (short) cc;
      if (isScheduled)
      {
        tce.sts.lasttime = PRP_SCHEDULE_SIGNAL;
        tce.sts.Stale |= (short)TTransferCodes.CX_EVENT;
      }
      else if (clockAdjusted) tce.sts.lasttime = tct.lasttime;
      if (cc != 0) tce.sts.Stale |= (short)TTransferCodes.CX_ERROR; // stale because of error code
      if (tce.sts.mode == TMode.CM_CANCEL)
      {
        tce.sts.Stale = 0;
        tce.sts.counter = 0;
      }
      else if (tce.sts.mode == TMode.CM_DATACHANGE || tce.sts.mode == TMode.CM_EVENT)
      {
        boolean requireAck = gRequireAcknowledgments && (tce.cln.inetProtocol == TTransport.UDP);
        if (stale)
        { // stale because of data change or system heartbeat
          tce.sts.Stale |= (short)(dataChanged ? TTransferCodes.CX_STALE : TTransferCodes.CX_HEARTBEAT);
          // force acknowledgment
          if (dataChanged && requireAck) tce.sts.counter = ACK_REQUEST;
        }
      }
      else // honor polling rate
      { // stale because of polling rate ?
        if ((t_now - tce.sts.lasttime) > tce.sts.PollingRate - gSystemTick)
        {
          tce.sts.Stale |= (short)TTransferCodes.CX_TIMER;
        }
      }
      if (tce.sts.Stale > 0) // client for this contract is to be refreshed
      {
        if (debugLevel > 2) DbgLog.log("doScheduler","refresh client with new data");
        tct.pending++;
        tce.sts.lasttime = t_stamp;
        if (gStaleData == 0) gStaleData++; // ensure pass thru doDelivery
      }
      if (tce.sts.mode > TMode.CM_SINGLE && tce.sts.counter == ACK_REQUEST)
      { // get serious until an acknowledgment comes
        tce.sts.mode = ACK_PENDING; // reset by renewal
        tce.sts.PollingRate = tct.pollingRate;
      }
      if (tce.sts.mode == TMode.CM_CANCEL)
      { // ensure pass thru doDelivery so that it's removed !
        gStaleData++;
      }
    }
    tct.isProcessing = false;
    return false
  }
  private boolean applyTsOffsetIfNeeded = true;
  private synchronized void doScheduler(TContractTable tgtCon,TClient tgtCln)
  {
    TContractTable tct = null;
    gIsInsideScheduler = true;
    try
    {
      long t_now = System.currentTimeMillis();
     
      updateTables();
     
      synchronized (conTable)
      {
        boolean markedStale = false;
        boolean sendSignal = false;
        Iterator<TContractTable> ctli = conTable.iterator();
        applyTsOffsetIfNeeded = true;
        if (tgtCon != null)
        { // target contract table entry given
          if (tgtCon.isStale) markedStale = true;
          if (tgtCon.eqm != null && tgtCon.eqm.prpSigHdlrList.size() > 0) sendSignal = true;
          processContract(tgtCon);         
        }
        else while (ctli.hasNext())
        { // run thru the whole list
          tct = ctli.next();
          if (tct.isStale) markedStale = true;
          if (tct.eqm != null && tct.eqm.prpSigHdlrList.size() > 0) sendSignal = true;
          if (processContract(tct)) ctli.remove();
        }
        if (debugLevel > 2)
        {
          if (conTable.size() > 0)
            DbgLog.log("doScheduler","processed "+conTable.size()+" contracts in "+(System.currentTimeMillis()-t_now)+" msecs");
        }
        for (int i = 0; gStaleData > 0 && i < gBurstLimit; i++)
        { // send data to clients ...
          doDelivery(tgtCon,tgtCln);
        }
        if (gStaleData > 0)
        { // caught by burst limit ...
          if (debugLevel > 2) DbgLog.log("doScheduler","deliverData() : burst limit exceeded");
          gBurstLimitReachedCount++;
        }
        if (markedStale || sendSignal)
        { // marked stale via getRequest : clear the flag
          if (tgtCon != null)
          { // target given
            if (markedStale) tgtCon.isStale = false;
            if (sendSignal && tgtCon.delivered > 0) signalDelivery(tgtCon);
            tgtCon.delivered = 0;
          }
          else
          { // run thru the whole list
            ctli = conTable.iterator();
            while (ctli.hasNext())
            {
              tct = ctli.next();
              if (markedStale) tct.isStale = false;
              if (sendSignal && tct.delivered > 0) signalDelivery(tct);
              tct.delivered = 0;
            }
          }
        }
      } // synchronized
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("doScheduler", "unhandled exception "+e.toString(),TErrorList.code_failure,e,0);
    }
    gIsInsideScheduler = false;
    if (gFlushContractTable)
    { // in case some dispatch called it ...
      flushContractTable();
      gFlushContractTable = false;
    }
    return;
  }
  private void signalDelivery(TContractTable tct)
  {
    if (tct.eqm != null && tct.eqm.prpSigHdlrList.size() > 0)
      tct.eqm.sendPropertySignal(tct.contract.eqmProperty, tct.contract, TPropertySignal.PS_SENT, tct.returnCode);
  }
  private boolean isWildcardContract(String deviceString)
  {
    if (deviceString == null) return false;
    return deviceString.indexOf('*') != -1;
  }
  private int doContractWithWildcardDevice(TContractTable tct)
  {
    TDataType drdb, dnull;
    int start = 0, len = 0, mask = 0;
    boolean isCont = false, chkMask = false, chkOffline = false;
    int cc = TErrorList.not_allowed, i, k;
    TAccess acc = new TAccess(tct.contract.dataAccess);
    int devlistsize = 0;  
    String[] devlist = null;
    int atype;
    NAME16FI[] nfi;
    NAME16II[] nii;
    TDevice dev;

    TEquipmentModule eqm = tct.eqm;
    TDeviceList dlst = eqm.getDeviceList();
    String devprp = tct.contract.eqmProperty;
    String devnam = tct.contract.eqmDeviceName;
    TExportProperty prp = eqm.propertyList.getFirstProperty(devprp);
    if ((atype=prp.getDescription().getArrayType()) == TArrayType.AT_SPECTRUM)
      return TErrorList.not_allowed;
    if (tct.dout == null || tct.dout.dArrayLength < 1) return TErrorList.not_allowed;
    if (!eqm.isDeviceSetLocal(devnam,devprp)) return TErrorList.data_not_local;

    if (prp.getDeviceList() != null)
    {
      devlistsize = prp.getDeviceList().size();
      devlist = new String[devlistsize];
      prp.getDeviceList().toArray(devlist);
    }
    if (devlistsize == 0)
    { // normal state of affairs: use the registered device list
      devlist = dlst.getDeviceNameList();
      devlistsize = devlist.length;
      chkOffline = true;
      if (tct.din != null && tct.din.dArrayLength == 1 && tct.din.dFormat == TFormat.CF_INT32)
      { // appears to be an input device mask
        int[] msk = new int[1];
        tct.din.getData(msk);
        mask = msk[0];
        if (mask != 0) chkMask = true;
      }
    }
    if ((atype & TArrayType.AT_CHANNEL) == TArrayType.AT_CHANNEL)
    {
      len = devlistsize;
      int endpoints[] = StringToName.getContiguousEndpoints(devlist,devnam);
      if (endpoints != null)
      {
        isCont = true;
        start = endpoints[0];
        len = endpoints[1] - endpoints[0] + 1;
      }
    }
    WildcardMatch wc = new WildcardMatch(devnam);
    dnull = new TDataType();
    switch (tct.dout.dFormat)
    {
      case TFormat.CF_NAME16DBLDBL:
      case TFormat.CF_NAME32DBLDBL:
      case TFormat.CF_NAME64DBLDBL:
      case TFormat.CF_USTRING:
      case TFormat.CF_NAME16FI:
        if (isCont)
        {
          int dsiz = tct.contract.dataSizeOut > len ? len : tct.contract.dataSizeOut;
          nfi = new NAME16FI[dsiz];
          float[] frb = new float[dsiz];
          drdb = new TDataType(frb);
          cc = tct.eqm.callProperty(devprp, devlist[start], drdb, dnull, acc);
          if (cc != 0) return cc;
          for (i=0, k=0; i<drdb.dArrayLength; i++)
          {
            dev = dlst.getDevice(i);
            if (chkMask) if (dev == null || !dev.isMaskSet(mask)) continue;
            if (chkOffline) if (dev == null || dev.isOffline()) continue;
            nfi[k] = new NAME16FI(devlist[start+i],0,frb[i]);
            k++;
          }
          tct.drb.dArrayLength = k;
        }
        else
        {
          int dsiz = tct.contract.dataSizeOut;
          nfi = new NAME16FI[dsiz];
          float[] frb = new float[1];
          drdb = new TDataType(frb);
          for (i=0, k=0; i<devlistsize && k<tct.dout.dArrayLength; i++)
          {
            if (!wc.matches(devlist[i])) continue;
            dev = dlst.getDevice(i);
            if (chkMask) if (dev == null || !dev.isMaskSet(mask)) continue;
            if (chkOffline) if (dev == null || dev.isOffline()) continue;
            cc = tct.eqm.callProperty(devprp, devlist[i], drdb, dnull, acc);
            if (cc == TErrorList.server_redirection) return TErrorList.data_not_local;
            nfi[k] = new NAME16FI(devlist[i],cc,frb[0]);
            k++;
          }
          tct.drb.dArrayLength = k;
        }
        tct.drb.putData(nfi);
        return 0;
      case TFormat.CF_NAME16II:
        if (isCont)
        {
          int dsiz = tct.contract.dataSizeOut > len ? len : tct.contract.dataSizeOut;
          nii = new NAME16II[dsiz];
          int[] irb = new int[dsiz];
          drdb = new TDataType(irb);
          cc = tct.eqm.callProperty(devprp, devlist[start], drdb, dnull, acc);
          if (cc != 0) return cc;
          for (i=0,k=0; i<drdb.dArrayLength; i++)
          {
            dev = dlst.getDevice(i);
            if (chkMask) if (dev == null || !dev.isMaskSet(mask)) continue;
            if (chkOffline) if (dev == null || dev.isOffline()) continue;
            nii[k] = new NAME16II(devlist[start+i],0,irb[i]);
            k++;
          }
          tct.drb.dArrayLength = k;
        }
        else
        {
          int dsiz = tct.contract.dataSizeOut;
          nii = new NAME16II[dsiz];
          int[] irb = new int[1];
          drdb = new TDataType(irb);
          for (i=0, k=0; i<devlistsize && k<tct.dout.dArrayLength; i++)
          {
            if (!wc.matches(devlist[i])) continue;
            cc = tct.eqm.callProperty(devprp, devlist[i], drdb, dnull, acc);
            if (cc == TErrorList.server_redirection) return TErrorList.data_not_local;
            nii[k] = new NAME16II(devlist[i],irb[0],cc);
            k++;
          }
          tct.drb.dArrayLength = k;
        }
        tct.drb.putData(nii);
        return 0;
      default:
        if (tct.dout.dFormat <= TFormat.CF_NAME32 && devnam.compareTo("*") == 0)
        { /* primitive format allowed in this case */
          if (isCont)
          {
            if (tct.drb.dArrayLength > len) tct.drb.dArrayLength = len;
            return tct.eqm.callProperty(devprp, devlist[0], tct.drb, dnull, acc);
          }
          drdb = new TDataType(1,tct.dout.dFormat);
          for (i=0; i<devlistsize && i<tct.dout.dArrayLength; i++)
          {
            cc = tct.eqm.callProperty(devprp, devlist[i], drdb, dnull, acc);
            if (cc != 0) break;
          }
          if (cc == TErrorList.server_redirection) return TErrorList.data_not_local;
          tct.drb.dArrayLength = i;
          tct.drb.putData(drdb);
          return cc;
        }
    }
    return cc;
  }
  private int doContractWithWildcardProperty(TContractTable tct)
  {
    LinkedList<String> lst = null;
    TDataType drdb;
    int cc = TErrorList.not_allowed, i, k, devnr;
    TAccess acc = new TAccess(tct.contract.dataAccess);
    int prplistsize = 0;  
    String[] prplist = null;
    NAME32DBLDBL[] ndd;
  
    TEquipmentModule eqm = tct.eqm;
    String devprp = tct.contract.eqmProperty;
    String devnam = tct.contract.eqmDeviceName;
    TExportProperty prp;
    if (tct.dout == null || tct.dout.dArrayLength < 1) return TErrorList.not_allowed;
    if ((devnr=eqm.getDeviceList().getDeviceNumber(tct.contract.eqmDeviceName)) < 0)
      return TErrorList.illegal_equipment_number;

    lst = eqm.getDeviceList().getDevice(devnr).getPropertyList();
    if (lst != null)
    {
      prplistsize = lst.size();
      prplist = new String[prplistsize];
      lst.toArray(prplist);
    }
    else
    {
      if (eqm.gPropertyNameList == null)
      { // just do this once
        if ((lst = eqm.getPropertyNames()) == null) return TErrorList.code_failure;
        prplistsize = lst.size();
        prplist = new String[prplistsize];
        lst.toArray(prplist);
        eqm.gPropertyNameList = prplist;
      }
      prplistsize = eqm.gPropertyNameList.length;
      prplist = eqm.gPropertyNameList;
    }
   
    WildcardMatch wc = new WildcardMatch(devprp);

    switch (tct.dout.dFormat)
    {
      case TFormat.CF_NAME16DBLDBL:
      case TFormat.CF_NAME32DBLDBL:
      case TFormat.CF_NAME64DBLDBL:
      case TFormat.CF_USTRING:
      case TFormat.CF_NAME16FI:
      case TFormat.CF_NAME16II:
        int dsiz = tct.contract.dataSizeOut;
        ndd = new NAME32DBLDBL[dsiz];
        double[] drb = new double[1];
        drdb = new TDataType(drb);
        for (i=0, k=0; i<prplistsize && k<tct.dout.dArrayLength; i++)
        {
          if (!wc.matches(prplist[i])) continue;
          prp = eqm.propertyList.getFirstProperty(prplist[i]);
          if (prp.getDescription().getArrayType() == TArrayType.AT_SPECTRUM) continue;
          cc = tct.eqm.callProperty(prplist[i], devnam, drdb, tct.din, acc);
          if (cc == TErrorList.server_redirection) return TErrorList.data_not_local;
          ndd[k] = new NAME32DBLDBL(prplist[i],drb[0],cc);
          k++;
        }
        tct.drb.dArrayLength = k;
        tct.drb.putData(ndd);
        return 0;
      default:
        break;
    }
    return cc;
  }
  private int validateEqmReturnCode(int cc)
  {
    switch (cc)
    {
      case TErrorList.invalid_interval:
      case TErrorList.illegal_protocol:
      case TErrorList.get_subscription_id:
      case TErrorList.mcast_access_required:
      case TErrorList.property_is_mca:
        return TErrorList.not_applicable;
      default:
        return cc;
    }
  }
  private int doContract(TContractTable tct)
  {
    if (tct.expired) return tct.returnCode;
    int cc = TErrorList.non_existent_elem; // return code
    String devnam = new String(tct.contract.eqmDeviceName);
    String devprp = new String(tct.contract.eqmProperty);
    short lclAccess = tct.contract.dataAccess;
    if (tct.isRepeat) lclAccess |= TAccess.CA_REPEAT;
    TAccess acc = new TAccess(lclAccess);
    int datasize = tct.contract.getOutputDataSize();
    boolean isRegisteredProperty = false;
    boolean isStaticProperty = false;
    boolean isSaveRestore = false;
    TExportProperty rp = null; // registered property
    int gate = 0, dsize = tct.contract.dataSizeOut;
    int gateType = TMetaProperties.GATE_NONE;
    if (TFormat.isAdjustableLength(tct.contract.dataFormatOut))
    {
      try { dsize = Integer.parseInt(tct.contract.dataTagOut); }
      catch (Exception ignore) {}
    }
    tct.dout.dArrayLength = tct.drb.dArrayLength = dsize;
    tct.drb.dTimestamp = gDataTimeStamp;
    tct.drb.sysDataStamp = gSystemStamp;
    if (debugLevel > 2)
      DbgLog.log("doContract","process " + tct.contract.eqmName + "/" +
        tct.contract.eqmDeviceName + "[" + tct.contract.eqmProperty + "]");
    currentContractEntry = tct;
    tct.compString = null;
    if (tct.contract.extStringSize > 0)
    { // long device name !
      devnam = tct.contract.getExtendedDeviceName();
    }
    if (devprp.indexOf("*") != -1)
    {
      if (TFormat.isSimpleFormat(tct.contract.dataFormatOut))
      {
        devnam = devprp;
        devprp = new String("PROPERTIES");
      }
      else
      {
        isRegisteredProperty = true; // wildcard call
      }
    }
    if (stockList.hasProperty(devprp))
    { // Check common stock properties
      if (debugLevel > 1) DbgLog.log("doContract","Common Stock Property found: " + tct.contract.eqmProperty);
      gDataTimeStamp = gSrvStartupTime + gDataTimeStampOffset;
      if (datasize > 0) tct.drb.dataFill(0);
      cc = stockList.callPropertyHandler(devprp, devnam, tct.drb, tct.din, acc);
    }
    else if (tct.eqm == null)
    {
      DbgLog.log("doContract","No Equipment Module found.");
      cc = TErrorList.non_existent_elem;
    }
    else if (tct.eqm.hasStockProperty(devprp))
    { // check stock properties of equipment module ...
      if (debugLevel > 1) DbgLog.log("doContract","Stock Property found: " + tct.contract.eqmProperty);
      gDataTimeStamp = gSrvStartupTime + gDataTimeStampOffset;
      if (datasize > 0) tct.drb.dataFill(0);
      cc = tct.eqm.callStockProperty(devprp, devnam, tct.drb, tct.din, acc);
    }
    else if (tct.eqm.propertyList.hasProperty(devprp) == false)
    { // not a registered property; is it a meta-property ?
      gateType = TMetaProperties.getGateType(tct.contract.eqmProperty);
      if ((gate=TMetaProperties.getGate(tct.contract.eqmProperty)) != 0)
      { // appears to be gated
        if (gateType == TMetaProperties.GATE_ARRAY)
        { // then the property MUST be an MCA !
          devprp = TMetaProperties.getTargetProperty(tct.contract.eqmProperty);
          TExportProperty p = tct.eqm.propertyList.getFirstProperty(devprp);
          if (p == null ||
              p.isMultiChannelArray() == false ||
              p.getDeviceList() != null)
          { // don't allow this !
            gateType = TMetaProperties.GATE_NONE;
            gate = 0;
          }
        }
      }
      if (gateType == TMetaProperties.GATE_NONE)
      { // definitely not gated
        if (debugLevel > 1) DbgLog.log("doContract","Meta Property found: " + tct.contract.eqmProperty);
        cc = tct.eqm.callMetaProperty(devprp, devnam, tct.drb, tct.din, acc);
        isStaticProperty = TMetaProperties.isStatic(tct.contract.eqmProperty);       
      }
      else
      { // is gated after all ...
        if (debugLevel > 1) DbgLog.log("doContract","Gated Property found: " + tct.contract.eqmProperty);
        devprp = TMetaProperties.getTargetProperty(tct.contract.eqmProperty);
        isRegisteredProperty = true;
      }
    }
    else
    { // a registered property, give the 'go-ahead'
      if (debugLevel > 1) DbgLog.log("doContract","registered Property found: " + tct.contract.eqmProperty);
      devprp = tct.contract.eqmProperty;
      isRegisteredProperty = true;
      rp = tct.eqm.propertyList.getFirstProperty(devprp);
      if (rp != null)
      {
        if (rp.isStatic) isStaticProperty = true;
        if (rp.isSaveAndRestore) isSaveRestore = true;
      }
    }
    boolean isIdle = tct.eqm.isIdleState();
    if (isRegisteredProperty)
    { // Call Equipment module's properties
      if (isIdle)
      { // server is marked idle -> jump out
        gate = 0;
        cc = TErrorList.server_idle;
      }
      else if (isWildcardContract(devnam))
      {
        if (debugLevel > 1) DbgLog.log("doContract","wildcard device: "+tct.contract.eqmDeviceName+" ["+tct.contract.eqmProperty+"]");
        cc = doContractWithWildcardDevice(tct);
      }
      else if (isWildcardContract(devprp))
      {
        if (debugLevel > 1) DbgLog.log("doContract","wildcard property: "+ tct.contract.eqmProperty);
        cc = doContractWithWildcardProperty(tct);
      }
      else
      {
        if (debugLevel > 1)
          DbgLog.log("doContract","call property handler for "+tct.contract.eqmName+"/"+tct.contract.eqmDeviceName+"["+tct.contract.eqmProperty+ "]");
        cc = tct.eqm.callProperty(devprp, devnam, tct.drb, tct.din, acc);
      }
      if (!isIdle && tct.eqm.prpSigHdlrList.size() > 0)
        tct.eqm.sendPropertySignal(devprp, tct.contract, TPropertySignal.PS_CALLED, cc);
      cc = validateEqmReturnCode(cc);
      if (acc.isWrite() && TErrorList.isLinkSuccess(cc) && isSaveRestore)
      {
        int devnr = tct.eqm.getDeviceNumber(devnam, devprp);
        if (devnr >= 0 && rp != null)
        {
          TDataType[] srdt = rp.getSaveRestoreDataSet();
          if (srdt != null && devnr < srdt.length)
          {
            int limit = 1;
            try
            { // if we're here, then the property duly exists and should not throw any exceptions
              if (TFormat.isAllowedSaveAndRestoreTraceArray(srdt[devnr].dFormat))
              { // make some adjustments ...
                limit = tct.eqm.getPropertyList().getFirstWriteProperty(devprp).getInputSize();
              }
              if (srdt[devnr].dFormat == TFormat.CF_STRUCT)
              {
                limit = TStructRegistry.getSizeInBytes(srdt[devnr].getTag());
              }
            }
            catch (Exception e)
            {
              MsgLog.log("doContract", "save/restore array property",TErrorList.code_failure,e,0);
            };
            tct.din.getData(srdt[devnr].getDataObject(),limit,0);
            tct.eqm.savePropertyValues(devprp, devnam, srdt[devnr]);
          }
        }
      }
      if (gate > 0)
      {
        gateOutputData(tct.eqm.deviceList,tct.drb,gate,gateType);
      }
    }
    if (tct.eqm != null && tct.eqm.propertyList == null)
    {
      if (debugLevel > 1) DbgLog.log("doContract","Module has no property list");
    }
    if (tct.dout.dArrayLength > tct.drb.dArrayLength)
    {
      if (!gSystemPresetMemory)
      { // zero out the remainder for later data comparisons
        tct.dout.dataFill(0, tct.drb.dArrayLength);
      }
      tct.dout.dArrayLength = tct.drb.dArrayLength;
    }
    tct.dout.dTimestamp = tct.drb.dTimestamp;
    tct.dout.sysDataStamp = tct.drb.sysDataStamp;
    tct.dout.usrDataStamp = tct.drb.usrDataStamp;
    if ((tct.contract.dataAccess & TAccess.CA_FIRST) != 0)
      tct.contract.dataAccess = TAccess.removeBits(tct.contract.dataAccess,TAccess.CA_FIRST);
    if (cc < 0) cc = TErrorList.ambiguous;
    if (cc != TErrorList.not_ready) tct.isRepeat = false;
    if (isStaticProperty && TErrorList.isLinkSuccess(cc))
    {
      TClientEntry[] clns = tct.clt.toArray(new TClientEntry[0]);
      for (TClientEntry c : clns)
        if (TMode.getBaseMode(c.sts.mode) > TMode.CM_SINGLE)
          cc = TErrorList.CE_SENDDATA + TErrorList.information_static;
    }   
    // record possible new data output length
    tct.dataSizeOut = tct.drb.dArrayLength;
    tct.drb.dCompletionLength =  tct.drb.dArrayLength;  // one of the copy constructors will want this ?
    if (tct.contract.dataSizeOut != tct.drb.dArrayLength)
    {
      cc |= TErrorList.CE_RENEGOTIATE;
    }
    if (TFormat.isAdjustableLength(tct.contract.dataFormatOut)) cc |= TErrorList.CE_RENEGOTIATE;
    tct.compStatus = cc;
    currentContractEntry = null;
    if (!isIdle && tct.eqm != null && tct.eqm.prpSigHdlrList.size() > 0)
      tct.eqm.sendPropertySignal(devprp, tct.contract, TPropertySignal.PS_PROCESSED, cc);
    return cc;
  }
  private void gateOutputData(TDeviceList devlst,TDataType dout, int gate, int gateType)
  {
    int i, n=0;
    boolean gateBoolean = (gateType == TMetaProperties.GATE_BOOLEAN);
    boolean gateOffline = (gateType == TMetaProperties.GATE_ARRAY) && (gate == -1);
    boolean gateArray = (gateType == TMetaProperties.GATE_ARRAY) && (gate > 0);
    boolean gateData = true;
    Object hDataObject = dout.getDataObject();
    int size = dout.getArrayLength();
    TDevice dev;
    if (gateOffline || gateArray)
    { // need to have a valid device list
      if (devlst == null) return;
      if (devlst.getNumberOfDevices() != size) return;
      gateData = false;
    }
    switch (dout.getFormat())
    {
      case TFormat.CF_BYTE:
      {
        byte[] data = (byte[])hDataObject;
        for (i=0; i<size; i++)
        {
          if ((dev=devlst.getDevice(i)) == null) continue;
          if (gateOffline && dev.isOffline()) continue;
          if (gateArray && !dev.isMaskSet(gate)) continue;
          if (i > n) data[n] = data[i];
          if (gateData) data[n] &= gate;
          if (gateBoolean) data[n] = (byte)(data[n] == (byte)gate ? 1 : 0);
          n++;
        }
        if (n < size) dout.setArrayLength(n);
        dout.putData(data);
        break;
      }
      case TFormat.CF_INT16:
      {
        short[] data = (short[])hDataObject;
        for (i=0; i<size; i++)
        {
          if ((dev=devlst.getDevice(i)) == null) continue;
          if (gateOffline && dev.isOffline()) continue;
          if (gateArray && !dev.isMaskSet(gate)) continue;
          if (i > n) data[n] = data[i];
          if (gateData) data[n] &= gate;
          if (gateBoolean) data[n] = (short)(data[n] == (short)gate ? 1 : 0);
          n++;
        }
        if (n < size) dout.setArrayLength(n);
        dout.putData(data);
        break;
      }
      case TFormat.CF_INT32:
      {
        int[] data = (int[])hDataObject;
        for (i=0; i<size; i++)
        {
          if ((dev=devlst.getDevice(i)) == null) continue;
          if (gateOffline && dev.isOffline()) continue;
          if (gateArray && !dev.isMaskSet(gate)) continue;
          if (i > n) data[n] = data[i];
          if (gateData) data[n] &= gate;
          if (gateBoolean) data[n] = (int)(data[n] == (int)gate ? 1 : 0);
          n++;
        }
        if (n < size) dout.setArrayLength(n);
        dout.putData(data);
        break;
      }
      default:
        if (!gateArray) break;
        Object data = hDataObject;
        if (!data.getClass().isArray()) break;
        for (i=0; i<size; i++)
        {
          if ((dev=devlst.getDevice(i)) == null) continue;
          if (gateOffline && dev.isOffline()) continue;
          if (gateArray && !dev.isMaskSet(gate)) continue;
          if (i > n) Array.set(data, n, Array.get(data, i));
          n++;
        }
        if (n < size) dout.setArrayLength(n);
        dout.putData(data);
        break;
       
    }
  }
  public static TClient getCaller()
  {
    TContractTable tct = getInstance().getCurrentContractEntry();
    return tct != null ? ((TClientEntry)tct.clt.element()).cln : null;
  }  
  public static TClientEntry[] getClientList()
  {
    TContractTable tct = getInstance().getCurrentContractEntry();
    return tct != null ? ((TClientEntry[])tct.clt.toArray(new TClientEntry[0])) : null;
  }
  private class clnInputData
  {
    TDataType din;
    TClient tc;
    int id;
    int starttime;
  }
  private boolean assertRedirectionValid(String rdr)
  {
    int indx = rdr.indexOf('/');
    String cntName = gFecContext, expName = null;
    switch (indx)
    {
      case -1: // just the server name ?
        expName = rdr;
        break;
      case  0: // context + server name (+ device name)
        rdr = rdr.substring(1);
        if ((indx=rdr.indexOf('/')) == -1) break;
        cntName = rdr.substring(0,indx);
        rdr = rdr.substring(indx+1);
        indx = rdr.indexOf('/');
      default:
        expName = indx > 0 ? rdr.substring(0,indx) : rdr;
        break;
    }
    if (expName == null) return false;
    for (int i = 0; i<numEqmTableEntries; i++)
    {
      if (eqmTable[i].getContext().compareToIgnoreCase(cntName) != 0) continue;
      if (eqmTable[i].getExportName().compareToIgnoreCase(expName) != 0) continue;
      return false; // oops!  redirecting to myself !
    }
    return true;
  }
  private boolean isMcaElement(TEquipmentModule eqm, TExportProperty txp, TSubscription ts,int[] indexAndSize)
  {
    if (ts.contract.dataSizeOut != 1) return false;
    if (TAccess.isWrite(ts.contract.dataAccess)) return false;
    if (txp.getOutputSize() < 2) return false;
    if (!TArrayType.isChannel(txp.getDescription().getArrayType())) return false;
    indexAndSize[0] = eqm.getDeviceNumber(ts.contract.eqmDeviceName, ts.contract.eqmProperty);
    if (indexAndSize[0] < 0) return false;
    indexAndSize[1] = txp.getOutputSize();
    return true;
  }
  private byte[] getRedirectionStringFromProperty(TEquipmentModule eqm, String dev, String prp)
  {
    TPropertyDescription dsc = eqm.propertyList.getFirstProperty(prp).getDescription();
    String rdirStr = dsc.getRedirection();
    if (rdirStr != null && isDeviceRedirected(rdirStr,dev))
    { // registered as a redirected property !
      if (assertRedirectionValid(rdirStr))
      {
        return redirectionStringToBytes(rdirStr);
      }
      else
      {
        dsc.setRedirection(null);
        TFecLog.log("redirection to "+rdirStr+" is the local process : rejected !");
      }       
    }
    return null;
  }
  protected void lockToExclusiveRead(TClient tc)
  {
    TContractTable tct = null;
    TClientEntry tce = null;
    for (int i = 0; i < conTable.size(); i++)
    {
      tct = (TContractTable) conTable.get(i);
      if (tct.expired) continue;
      if ((tct.eqm.hasExclusiveRead(tct.contract.eqmProperty,true)))
      {
        Iterator<TClientEntry> it = tct.clt.iterator();
        while (it.hasNext())
        {
          tce = it.next();
          if (!isMemberLockSet(tct.eqm, tce.cln))
          {
            tce.sts.counter = 1;
          }
        }
      }
    }
  }
  int assertIsPastDeadband(TEquipmentModule eqm,String tgtProperty,short access,TClient tc)
  {
    TEquipmentModule.PrpDbaItem dba = eqm.getPrpDbaItem(tgtProperty);
    long ts = System.currentTimeMillis();
    if (dba != null)
    {
      if ((dba.access & access) != 0) return 0;
      int dt = (int)(ts - dba.lastAccess);
      if (dt < dba.deadband) return TErrorList.operation_busy;
      if (dba.usr.compareToIgnoreCase(tc.userName) != 0 ||
          dba.addr != tc.IPaddress)
      { // different client as last access: check against MAXIMUM_LOCK_DURATION
        if (dt < MAXIMUM_LOCK_DURATION) return TErrorList.access_locked;
      }
      dba.usr = tc.userName;
      dba.addr = tc.IPaddress;
      dba.lastAccess = ts;
    }
    return 0;
  }
  private boolean isAllowedPersistentWriteLinks = false;
  public void allowPersistentWriteLinks(boolean value)
  {
    isAllowedPersistentWriteLinks = value;
  }
  public boolean isAllowedPersistentWriteLinks()
  {
    return isAllowedPersistentWriteLinks;
  }
  private String[] untaggedTag = new String[1];
  private short isIllegalProperty(TClient tc, TSubscription ts,byte[] errorData)
  { // note: wildcard properties such as "PROP*" no longer allowed !
    TEquipmentModule eqm = LocateEquipmentModule(ts.contract);
    String tgtProperty = ts.contract.eqmProperty;
    String tgtDevice = ts.contract.eqmDeviceName;
    String rdirStr = null;
    boolean chkFullPropertyForRedirection = false;
    boolean isMetaPrp = TMetaProperties.isMetaProperty(ts.contract.eqmProperty);
    if (eqm == null)
    { // subscription must point to something being managed
      if (TStockProperties.isFecProperty(tgtProperty)) return 0;
      return TErrorList.non_existent_elem;
    }
    if (!eqm.hasInitialized) return TErrorList.not_initialized;
    TPropertyList pl = eqm.getPropertyList();
    if (pl == null) return TErrorList.non_existent_property;
    if (pl.requiresNetworkAccess(ts.contract.eqmProperty))
    {
      if (!TMode.isNetwork(ts.mode)) return TErrorList.mcast_access_required;
    }
    if (pl.requiresAsynchronousAccess(ts.contract.eqmProperty))
    {
      if (TMode.isSynchronous(ts.mode) && (ts.contract.dataAccess & TAccess.CA_SYNC) == 0)
        return TErrorList.async_access_required;
    }
    if (isMetaPrp)
    {
      tgtProperty = TMetaProperties.getTargetProperty(ts.contract.eqmProperty);
      if (pl.hasProperty(ts.contract.eqmProperty))
        chkFullPropertyForRedirection = true;
    }
    boolean isWrite = TAccess.isWrite(ts.contract.dataAccess);
    if (isWrite)
    {
      if (isWildcardContract(tgtDevice)) return TErrorList.not_allowed;
      if (isWildcardContract(tgtProperty)) return TErrorList.not_allowed;
    }
    // check if device is categorically redirected ...
    TDevice td = eqm.getDeviceList().getDevice(tgtDevice);
    if (td != null && (rdirStr=td.getRedirection()) != null)
    {
      byte[] rdb = redirectionStringToBytes(rdirStr);
      System.arraycopy(rdb,0,errorData,0,rdb.length);
      return TErrorList.server_redirection;     
    }
    // check if registered
    if (chkFullPropertyForRedirection)
    { // this call is for a meta property but the meta-property itself can be redirected!
      byte rdb[] = getRedirectionStringFromProperty(eqm,ts.contract.eqmDeviceName,ts.contract.eqmProperty);
      if (rdb != null)
      {
        System.arraycopy(rdb,0,errorData,0,rdb.length);
        return TErrorList.server_redirection;             
      }
    }
    if (pl.hasProperty(tgtProperty) == true)
    { // check for property redirection
      byte rdb[] = getRedirectionStringFromProperty(eqm,ts.contract.eqmDeviceName,tgtProperty);
      if (rdb != null)
      {
        System.arraycopy(rdb,0,errorData,0,rdb.length);
        return TErrorList.server_redirection;             
      }
      if (ts.pollingInterval < gMinPollingInterval &&
          TMode.getBaseMode(ts.mode) > TMode.CM_SINGLE)
      {
        try
        {
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          DataOutputStream dis = new DataOutputStream(baos);
          dis.writeInt(gMinPollingInterval);
          System.arraycopy(baos.toByteArray(), 0, errorData, 0, 4);
          MsgLog.log("isIllegalProperty", "renegotiate contract with caller",TErrorList.invalid_interval,null,1);
          return TErrorList.invalid_interval;
        }
        catch (IOException e)
        {
          MsgLog.log("isIllegalProperty", e.toString(),TErrorList.invalid_interval,e,0);
        }       
      }     
      if (!isMetaPrp)
      { // then this is a registered property
        TExportProperty txp = pl.getFirstProperty(ts.contract.eqmProperty);
        if (txp == null) return TErrorList.illegal_property;
        if (pl.enforcesOutput(ts.contract.eqmProperty) && !isWrite)
        { // must check output length and type
          if (txp.getOutputSize() != ts.contract.dataSizeOut ||
              txp.getOutputFormat() != ts.contract.dataFormatOut)
          {
            int[] sizAndFmt = new int[2];
            sizAndFmt[0] = txp.getOutputSize();
            sizAndFmt[1] = TFormat.makeStdFormatCode(txp.getOutputFormat());
            try
            {
              Arrays.fill(errorData, 0, errorData.length, (byte)0);
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
              DataOutputStream dis = new DataOutputStream(baos);
              dis.writeInt(sizAndFmt[0]); dis.writeInt(sizAndFmt[1]);
              System.arraycopy(baos.toByteArray(), 0, errorData, 0, 8);
              MsgLog.log("isIllegalProperty", "enforce data length and format",TErrorList.invalid_datarequest,null,1);
              return TErrorList.invalid_datarequest;
            }
            catch (IOException e)
            {
              MsgLog.log("isIllegalProperty", e.toString(),TErrorList.code_failure,e,0);
              return TErrorList.code_failure;
            }           
          }
        }
        if (TMode.getBaseMode(ts.mode) > TMode.CM_SINGLE)
        { // a polling link, check WRITE access conditions ...
          if (isWrite && !isAllowedPersistentWriteLinks)
          {
            return TErrorList.illegal_mode;
          }
          // check for MCA property ...
          int[] idxAndSiz = new int[2];
          if (isMcaElement(eqm,txp,ts,idxAndSiz) && tc.revisionId > 10)
          { // introduce in version 4.0.11
            if (debugLevel > 1) DbgLog.log("isIllegalProperty", "mca element "+idxAndSiz[0]+" from "+idxAndSiz[1]+" is being accessed");
            try
            {
              Arrays.fill(errorData, 0, errorData.length, (byte)0);
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
              DataOutputStream dis = new DataOutputStream(baos);
              dis.writeInt(idxAndSiz[0]); dis.writeInt(idxAndSiz[1]);
              System.arraycopy(baos.toByteArray(), 0, errorData, 0, 8);
              String dev = eqm.deviceList.getDevice(0).getName();
              if (dev == null) dev = "#0";
              byte[] b = dev.getBytes();
              System.arraycopy(b, 0, errorData, 8, b.length);
              MsgLog.log("isIllegalProperty", "renegotiate contract with caller",TErrorList.property_is_mca,null,1);
              return TErrorList.property_is_mca;
            }
            catch (IOException e)
            {
              MsgLog.log("isIllegalProperty", e.toString(),TErrorList.code_failure,e,0);
              return TErrorList.code_failure;
            }
          }
          if (TArrayType.isChannel(txp.getDescription().getArrayType()) &&
              ts.contract.dataSizeOut > 1 && txp.getMcaValidFloor() > ts.starttime)
          {
            MsgLog.log("isIllegalProperty","send reset mca property reset signal to caller for"+
                ts.contract.eqmName+" "+ts.contract.eqmProperty,TErrorList.reset_mca_property,null,1);
            return TErrorList.reset_mca_property;
          }         
        }
      }
    }
    else if (pl.isBitfieldProperty(tgtProperty, null, untaggedTag))
    {
      byte rdb[] = untaggedTag[0].getBytes();
      System.arraycopy(untaggedTag[0].getBytes(),0,errorData,0,rdb.length);
      return TErrorList.has_bitfield_tag;                   
    }
    else if (pl.isStructProperty(tgtProperty, null, untaggedTag))
    {
      byte rdb[] = untaggedTag[0].getBytes();
      System.arraycopy(untaggedTag[0].getBytes(),0,errorData,0,rdb.length);
      return TErrorList.has_structure_tag;                   
    }
    else
    { // check if stock
      if (tgtProperty.indexOf("*") == -1 && // mapped as "PROPERTIES"
          stockList.hasProperty(tgtProperty) == false &&
          eqm.stockList.hasProperty(tgtProperty) == false) return TErrorList.illegal_property;
    }
    boolean logit = true;
    boolean chkAccess = isWrite;
    if (!chkAccess && eqm.hasExclusiveRead(tgtProperty, false))
    {
      chkAccess = true; logit = false;
    }
    if (tgtProperty.compareTo("MESSAGE") == 0) chkAccess = false;
    if (chkAccess)
    { // check if WRITE (and not MESSAGE)
      if (tgtDevice == null || tgtDevice.length() == 0) tgtDevice = "(no device)";
      String cmdString = "COMMAND ("+eqm.getLocalName()+")/"+tgtDevice+" "+tgtProperty + " called by " + tc.userName +
                         " from addr "+tc.IPaddress.getHostAddress()+":"+tc.IPport+" : ";
      if (!isMemberUsersList(eqm,tc.userName))
      {
        return signalAccess(eqm,tgtProperty,TErrorList.access_denied,logit,cmdString,"user refused access");
      }
      if (!isMemberControlNets(eqm,tc))
      {
        return signalAccess(eqm,tgtProperty,TErrorList.access_denied,logit,cmdString,"network refused access");
      }
      if (!isMemberPropertyAclList(eqm,tc,tgtProperty))
      {
        return signalAccess(eqm,tgtProperty,TErrorList.access_denied,logit,cmdString,"property refused user access");
      }
      if (!isMemberDeviceAclList(eqm,tc,tgtDevice))
      {
        return signalAccess(eqm,tgtProperty,TErrorList.access_denied,logit,cmdString,"device refused user access");
      }
      if (tgtProperty.compareTo("ACCESSLOCK") != 0 && !isMemberLockSet(eqm,tc))
      {
        return signalAccess(eqm,tgtProperty,TErrorList.access_locked,logit,cmdString,"access lock refused access");
      }
      if (logit && gPutCommandsInFeclog) TFecLog.log(cmdString + "accepted");
    }
    if (pl.getAccessDeadband(tgtProperty) > 0)
    { // gotta check the deadband ...
      short cc;
      if ((cc=(short)assertIsPastDeadband(eqm,tgtProperty,ts.contract.dataAccess,tc)) != 0)
        return cc;
    }
    if ((eqm.hasExclusiveRead(tgtProperty,true)) && !isMemberLockSet(eqm, tc))
    {
      return signalAccess(eqm,tgtProperty,TErrorList.access_locked,false,"","");
    }
    if (eqm.prpSigHdlrList.size() > 0)
      eqm.sendPropertySignal(tgtProperty, null, TPropertySignal.PS_ACCESS, 0);
    return 0;
  }
  private short signalAccess(TEquipmentModule eqm,String prp,short code, boolean log, String cmd, String txt)
  {
    if (log) TFecLog.log(cmd + txt);   
    if (eqm.prpSigHdlrList.size() > 0)
      eqm.sendPropertySignal(prp, null, TPropertySignal.PS_ACCESS, code);
    return code;
  }
  private short fixOutputDataTypeAndSize(TSubscription ts)
  {
    TEquipmentModule eqm = LocateEquipmentModule(ts.contract);
    if (eqm == null) return TErrorList.non_existent_elem;
    short df = ts.contract.dataFormatOut >= 0 ?
               ts.contract.dataFormatOut : (short)(256 + ts.contract.dataFormatOut);
    if (df == TFormat.CF_DEFAULT)
    { // caller wants default settings
      int fmtsize = 0;
      int datasize = 0;
      boolean isMca = false;
      TExportProperty prop = eqm.propertyList.getFirstProperty(ts.contract.eqmProperty);
      if (prop == null) prop = eqm.stockList.getFirstProperty(ts.contract.eqmProperty);
      if (prop == null) prop = stockList.getFirstProperty(ts.contract.eqmProperty);
      TPropertyDescription tpd = prop != null ? prop.getDescription() : null;
      if (tpd != null && TArrayType.isChannel(tpd.getArrayType()))
      { // help out the doocs panels ...
        isMca = true;       
      }
      if (TMetaProperties.isMetaProperty(ts.contract.eqmProperty))
      {
        if (ts.contract.eqmProperty.contains("@"))
        {
          String p = TMetaProperties.getTargetProperty(ts.contract.eqmProperty);
          prop = eqm.propertyList.getFirstProperty(p);
          fmtsize = TFormat.formatSizeOf(prop.getOutputFormat());
        }
        else
        {
          ts.contract.dataFormatOut = (byte) TMetaProperties.getSuggestedFormat(ts.contract.eqmProperty);
          fmtsize = TFormat.formatSizeOf(ts.contract.dataFormatOut);
          datasize = TMetaProperties.getSuggestedDataSize(ts.contract.eqmProperty);
        }
      }     
      if (prop == null) return TErrorList.illegal_property;
      if (isWildcardContract(ts.contract.eqmDeviceName) && TFormat.isNumberFormat(prop.getOutputFormat()))
      { // an allowable wild-card contract ...
        ts.contract.dataFormatOut = TFormat.CF_USTRING;
        fmtsize = TFormat.formatSizeOf(TFormat.CF_USTRING);
        int ndevs = eqm.getDeviceList().getNumberOfDevices();
        if (ts.contract.dataSizeOut < ndevs * fmtsize) return TErrorList.buffer_too_small;
        ts.contract.dataSizeOut = ndevs;
        return 0;
      }
      if (fmtsize == 0)
      { // not a meta property
        ts.contract.dataFormatOut = (byte) prop.getOutputFormat();
        fmtsize = TFormat.formatSizeOf(prop.getOutputFormat());
        if (ts.contract.dataFormatOut == TFormat.CF_MDA)
        {
          fmtsize = TFormat.getCarriedFormatSize(prop.getOutputTag());
          ts.contract.dataTagOut = TFormat.toString(ts.contract.dataFormatOut);
        }
      }
      if (datasize == 0)
      {
        datasize = (isMca ? 1 : prop.getOutputSize()) * fmtsize;
      }
      if (datasize == 0) return TErrorList.illegal_data_size;
      int hdrsize = TFormat.getFormatHeaderSize(ts.contract.dataFormatOut);
      datasize += hdrsize;
      // EqpSizeOut is in bytes ...
      if (ts.contract.dataSizeOut < datasize) return TErrorList.buffer_too_small;
      ts.contract.dataSizeOut = datasize;
      if (fmtsize != 0)
      {
        ts.contract.dataSizeOut -= hdrsize;
        ts.contract.dataSizeOut -= ts.contract.dataSizeOut % fmtsize;
        ts.contract.dataSizeOut /= fmtsize;
      }
    }
    return 0;
  }
  private void shrinkInputDataTypeRepository()
  {
    clnInputData cip = null;
    for (int i = 0; i < clnInputDataTable.size(); i++)
    {
      cip = (clnInputData) clnInputDataTable.get(i);
      if (cip.din.blksin == cip.din.numblks)
      {
        clnInputDataTable.remove(i--);
      }
    }
  }
  private TDataType getInputDataTypeRepository(TClient tc, TSubscription ts)
  {
    int sizeIn = ts.contract.dataSizeIn;
    String tagIn = ts.contract.dataTagIn;
    if (TFormat.isAdjustableLength(ts.contract.dataFormatIn))
    {
      try
      {
        sizeIn = Integer.parseInt(tagIn);
        tagIn = "";
      }
      catch (Exception ignore) {};
    }
    if (ts.numblks == 1)
    { // just this one ...
      TDataType tdt;
      tdt = new TDataType(sizeIn, ts.contract.dataFormatIn);
      tdt.setTag(tagIn);
      return tdt;
    }
    if (debugLevel > 1) DbgLog.log("getInputDataTypeRepository","in-coming long datagram ...");
    clnInputData cip = null;
    for (int i = 0; i < clnInputDataTable.size(); i++)
    {
      cip = (clnInputData) clnInputDataTable.get(i);
      if (!cip.tc.IPaddress.equals(tc.IPaddress)) continue;
      if (cip.id != ts.id) continue;
      if (cip.starttime != ts.starttime) continue;
      if (debugLevel > 1) DbgLog.log("getInputDataTypeRepository","found it in list ...");
      return cip.din;
    }
    cip = new clnInputData();
    cip.id = ts.id;
    cip.starttime = ts.starttime;
    cip.tc = tc;
    cip.din = new TDataType(sizeIn, ts.contract.dataFormatIn,ts.numblks);
    cip.din.setTag(tagIn);
    clnInputDataTable.add(cip);
    if (debugLevel > 1) DbgLog.log("getInputDataTypeRepository","add it to list ...");
    return cip.din;
  }
  public String getNameFromAlias(String alias)
  {
    if (gAliasList == null) return null;
    if (gAliasList.containsKey(alias)) return gAliasList.get(alias).getTag();
    return null;
  }
  public String getAliasFromName(String name)
  {
    if (gAliasList == null) return null;
    AliasTableEntry ate;

    Iterator<AliasTableEntry> itr = gAliasList.values().iterator();
    while (itr.hasNext())
    {
      ate = itr.next();
      if (ate.name.compareToIgnoreCase(name) == 0) return ate.alias;
      if (TMetaProperties.isMetaProperty(ate.name))
      { // there's an alias for a meta property ...
        if (TMetaProperties.getTargetProperty(ate.name).compareToIgnoreCase(name) == 0)
          return ate.alias;
      }
    }
    return null;
  }
  private static boolean[] isNewContract = new boolean[1];
  private byte[] requestBytes = new byte[256];
  private boolean canSendSingleDelivery = true;
  /**
   * Sets ability to handle synchronous requests in an 'express' manner
   *
   * If set to 'true' (default) then incoming synchronous requests are handled immediately
   * without scanning an collating the current contract table
   *
   * @param value is the desired setting (default = true)
   * @see getSendSingleDelivery()
   */
  public void setSendSingleDelivery(boolean value) { canSendSingleDelivery = value; }
  /**
   * Gets the current setting for the ability to handle synchronous requests in an 'express' manner
   *
   * If set to 'true' (default) then incoming synchronous requests are handled immediately
   * without scanning an collating the current contract table
   *
   * @return the current setting
   */
  public boolean getSendSingleDelivery() { return canSendSingleDelivery; }
  private void getRequest(TClient tcaller, byte[] data)
  {
    Arrays.fill(requestBytes,(byte)0);
    byte[] d = requestBytes;
    TDataType din = null;
    int offset = 0, datasize = 0;
    int renewalMultiplier;
    int cc = 0, nreqs = 0;
    short bmode;
    boolean isTrueMCast = false;
    boolean isLegacy = false;
    boolean sendSingleDelivery = false;
    boolean chkResendData = false;
    boolean resendCurrentData = false;
    TClientEntry tce = null;
    TClient tc = tcaller;
    String astr = null;
    if (srvCycleTime < rejectEarlierThan) return;
    // peel off the header
    TReqHdr hdr = new TReqHdr(data, 0, TReqHdr.hdrSizeInBytes);
    tc.userName = hdr.getUserName();
    int tineProtocol = hdr.getProtocol();   
    if (tineProtocol < 5)
    {
      SendMessageToCaller(tc, null, TErrorList.illegal_protocol,null);
    }
    tc.tineProtocol = (short)tineProtocol;
    tc.revisionId = hdr.getRevisionId();
    if (tineProtocol < 6) isLegacy = true;
    int totalsize = hdr.getTotalSizeInBytes();
    int hdrSize = isLegacy ? TSubscription.hdrSizeInBytesLegacy : TSubscription.hdrSizeInBytes;
    int conSize = isLegacy ? TContractP5.hdrSizeInBytes : TContract.hdrSizeInBytes;
    offset += TReqHdr.hdrSizeInBytes;
    if (debugLevel > 3) DbgLog.log("getRequest",hdr.toString());
    TContractTable tct = null;
    long t_now = System.currentTimeMillis();
    while (offset < totalsize)
    { // peel off the TSubscription
      nreqs++; resendCurrentData = false;
      TSubscription sub = new TSubscription(tineProtocol, data, offset, hdrSize);
      tc = tcaller; // reset this in case of packed network requests
      bmode = TMode.getBaseMode(sub.mode);
      // Is the caller using alias names ?
      if ((astr=getNameFromAlias(sub.contract.eqmDeviceName)) != null) sub.contract.eqmDeviceName = astr;
      if ((astr=getNameFromAlias(sub.contract.eqmProperty)) != null) sub.contract.eqmProperty = astr;
      offset += hdrSize + conSize + sub.contract.extStringSize;
      if (debugLevel > 0 && sub.numblks == sub.blknum)
      {
        if (debugLevel > 2)
          DbgLog.log("getRequest",sub.toString());
        else
        {
          String msg = sub.contract.eqmName+" "+sub.contract.eqmDeviceName+" "+
            sub.contract.eqmProperty+" "+sub.pollingInterval+" msec "+
            "("+sub.numblks+" blks) "+TMode.toString(sub.mode);
          DbgLog.log("getRequest", msg);
        }
      }
      // prepare incoming data ...
      if ((datasize = sub.getInputDataSize()) > 0)
      {
        din = getInputDataTypeRepository(tc, sub);
        if (din.update(data, offset, datasize, sub.blknum, sub.blkid))
        {
          din.bytesin += datasize;
        }
        offset += datasize + (datasize % 2);
        if (din.blksin < din.numblks)
        {
          if (debugLevel > 2)
            DbgLog.log("getRequest","input data set "+din.blksin+" of "+din.numblks);
          continue;
        }
        if (din.numblks > 1 && debugLevel > 1)
          DbgLog.log("getRequest","long input data set ("+din.numblks+" blocks) complete");
        // when finished ...
        din.resetBuffersReady();
        din.getData();
        if (debugLevel > 3) DbgLog.log("getRequest","input data : \n" + din.toString());
      }
      // requested output data ...
      if ((cc=fixOutputDataTypeAndSize(sub)) != 0)
      { // default format ?
        SendMessageToCaller(tc, sub, cc, null);
        continue;
      }
      // is contract in list ? if no -> add to list
      tct = LocateContractInList(sub.contract, din, isNewContract);
      // finished with TDataType repository entry ?
      shrinkInputDataTypeRepository();
      if (tct == null)
      { // no contract table entry !!
        SendMessageToCaller(tc, sub, TErrorList.resources_exhausted, null);
        continue;
      }
      if (gServerWaiting)
      { // is the server flagged as 'waiting?'
        SendMessageToCaller(tc, sub, TErrorList.not_initialized, d);
        continue;       
      }
      if ((cc=isIllegalProperty(tc, sub, d)) != 0)
      { // can caller access the property ?
        SendMessageToCaller(tc, sub, cc, d);
        continue;
      }
      if (tct.expired && ((sub.mode & TMode.CM_RETRY) == 0))
      { // is a new link attaching to an expired contract ?
        tct.lasttime = 0;
        tct.expired = false;
        tct.pollingRate = 0;
        isNewContract[0] = true;
        if (numExpiredContracts > 0) numExpiredContracts--;
        if (debugLevel > 1)
          DbgLog.log("getRequest",tct.contract.eqmName+"/"+tct.contract.eqmDeviceName+"["+tct.contract.eqmProperty+ "] attaching to expired contract");
      }
      if ((sub.mode & TMode.CM_MCAST) != 0)
      { // is broadcast or multicast request ?
        if (TAccess.isWrite(sub.contract.dataAccess) || sub.contract.dataSizeOut == 0)
        {
          SendMessageToCaller(tc, sub, TErrorList.illegal_mode, d);
          continue;                 
        }
        isTrueMCast = true;
        sub.mode = bmode; // strip off
        if (sub.mode == TMode.CM_CANCEL) continue; // don't do anything ...
        if (isTrueMCast || isMemberBcastNets(tc))
        { // send subscription ID to caller
          ByteArrayOutputStream boas = new ByteArrayOutputStream(2);
          DataOutputStream dos = new DataOutputStream(boas);
          try
          {
            dos.writeShort((short) tct.contractID);
          }
          catch (Exception e)
          {
            e.printStackTrace();
            MsgLog.log("getRequest",e.toString(),TErrorList.code_failure,e,0);                   
          }
          byte[] subid = boas.toByteArray();
          Swap.Bytes(subid, TFormat.CF_SHORT);
          SendMessageToCaller(tc, sub, TErrorList.get_subscription_id, subid);
          // add network to client list and replace cln
          InetAddress ia = isTrueMCast ? getMCastAddr() : getBCastAddr(tc.IPaddress);
          int port = initializer.getMCastPort();
          //tc.port = initializer.getMCastPort();
          tc = LocateClientInList(ia, port);
          tc.inetProtocol = tcaller.inetProtocol;
          tc.userName = "NETWORK";
          sub.id = (short) tct.contractID;
          sub.starttime = BCAST_ID;
        }
      }
      // is client in contractList?
      tce = LocateClientInContract(tct, tc);
      if (tce == null)
      {
        SendMessageToCaller(tc, sub, TErrorList.resources_exhausted, null);
        continue;
      }
      // fill in renewed fields ...    
      tce.sts.reset(sub);
      renewalMultiplier = TSubscription.getRenewalMultiplier(gRenewalMultiplier, sub.pollingInterval);
      if (sub.isLegacy && renewalMultiplier > 4) renewalMultiplier = 4;
      if (tce.sts.mode != ACK_PENDING)
      { // Force Contract execution
        chkResendData = ((tce.sts.counter == 0 && sub.starttime != BCAST_ID) ||
            bmode == TMode.CM_DATACHANGE ||
            bmode == TMode.CM_EVENT);
        if (tct.nclients > 1)
        { // client is attaching to a contract
          long dt = t_now - tct.lasttime;
          if (bmode == TMode.CM_SINGLE && dt <= sub.pollingInterval)
          { // have data within the desired timeout -> use them
            chkResendData = true;
          }
          if (bmode == TMode.CM_TIMER && dt < (sub.pollingInterval-50) && sub.starttime != BCAST_ID)
          { // will be at least 50 msec before I look again !
            chkResendData = true;
          }
        }
        // update counters ? ...
        if (sub.mode != TMode.CM_CANCEL && sub.mode != TMode.CM_DATACHANGE
            && tce.sts.counter > ACK_REQUEST * renewalMultiplier
            && tce.sts.counter < CTR_RENEWAL * renewalMultiplier)
        {
          tce.sts.reconnects++;
          gClientReconnects++;
          MsgLog.log("getRequest", tce.cln.userName+" is reconnecting to "+tct.contract.eqmName+"/"+tct.contract.eqmDeviceName+"["+tct.contract.eqmProperty+ "]",0,null,1);
        }
      }
      if (chkResendData)
      { /* send out the last acquired data */
        boolean contractValid = tce.sts.counter < (short)(CTR_RENEWAL*renewalMultiplier-1);
        if (!isNewClient && tce.sts.counter == (short)(CTR_RENEWAL*renewalMultiplier))
        { // possible ungraceful exit and restart of remote client in DATACHANGE/EVENT mode
          contractValid = true;
        }
        if (tct.returnCode != TErrorList.not_posted && contractValid)
        { /* contract return code can be trusted */
          tct.isStale = true; // p.d. 18.1.11 (seems to be needed for express delivery).
          resendCurrentData = true;
          tce.sts.Stale |= (short)TTransferCodes.CX_ATTACH; //CX_ATTACH;
          tce.sts.lasttime = System.currentTimeMillis();
          if (tct.dataSizeOut < tct.contract.dataSizeOut)
            tct.renegotiateContract((short)tct.compStatus);
        }
      }     
      // is retry ?
      if ((sub.mode & TMode.CM_RETRY) != 0)
      { // if expired, DeliverData() will send the last payload again
        tce.sts.Stale |= (short)TTransferCodes.CX_RETRY;
        tct.isStale = true;
        tce.sts.isRetry = true;
        gClientRetries++;
        sub.mode = TMode.getBaseMode(sub.mode);
        MsgLog.log("getRequest",tce.cln.userName+" is retrying "+tct.contract.eqmName+"/"+tct.contract.eqmDeviceName+"["+tct.contract.eqmProperty+ "] reqeust",0,null,1);
        if (tct.eqm != null && tct.eqm.prpSigHdlrList.size() > 0)
          tct.eqm.sendPropertySignal(tct.contract.eqmProperty, tct.contract, TPropertySignal.PS_RETRY, tct.compStatus);
      }
      if (tct.nclients == 0)
      { // just turned off, turn it back on (client must have still been in the local list)
        MsgLog.log("getRequest", "contract "+tct.contract.getNameTag()+" claims to have no clients",TErrorList.code_failure,null,0);
        tct.clt.add(tce);
        tct.nclients++;
        tct.contract.dataAccess =
          TAccess.removeBits(tct.contract.dataAccess,TAccess.CA_LAST);
        assertClientTableConsistent(tct);
      }
      tct.contract.dataAccess =
        TAccess.removeBits(tct.contract.dataAccess,TAccess.CA_REPEAT);// don't allow this from the caller
      if (isNewContract[0]) tct.contract.dataAccess |= TAccess.CA_FIRST; // turn on the FIRST bit
      // switch on mode
      sub.pollingInterval = TLink.makeValidPollingInterval(sub.pollingInterval, TMode.getBaseMode(sub.mode));
      int SubsciptionPacketOffset = 0; // for staggering the POLLING guys ...
      if (tct.pollingRate == 0) tct.pollingRate = sub.pollingInterval;
      switch (tce.sts.mode = sub.mode)
      {
        case TMode.CM_SINGLE:
          gSingleLinkCount++;
          tce.sts.counter = 1;
          if (!(tct.expired && tce.sts.isRetry))
          { // normal case ...
            if (!resendCurrentData) tct.lasttime = PRP_REQUEST_SIGNAL;
          }
          else 
          { // a RETRY marked as expired should not be called
            tct.lasttime = System.currentTimeMillis();
            if (tct.dataSizeOut < tct.contract.dataSizeOut)
              tct.renegotiateContract((short)tct.compStatus);
          }
          if (tce.sts.PollingRate == 0) tce.sts.PollingRate = sub.pollingInterval;
          if (tct.pollingRate == 0) tct.pollingRate = tce.sts.PollingRate;
          if (tct.nclients == 1)
          { // turn on LAST bit
            tct.contract.dataAccess |= TAccess.CA_LAST;
          }
          sendSingleDelivery = canSendSingleDelivery;
          break;
        case TMode.CM_TIMER:
          SubsciptionPacketOffset = (int)(100.0 * Math.random());
          if (sub.pollingInterval > 2000) SubsciptionPacketOffset /= 10;
        case TMode.CM_DATACHANGE:
        case TMode.CM_EVENT:
          tce.sts.counter = (short) (CTR_RENEWAL * renewalMultiplier + SubsciptionPacketOffset);
          tct.pollingRate = findpoll(sub.pollingInterval, tct.pollingRate);
          break;
        default:
        case TMode.CM_CANCEL:
          if (tce.cln.userName.compareTo("NETWORK") == 0) break;
          if (tct.nclients == 1)
          {
            if (isNewContract[0])
            { // canceling a brand new contract makes no sense !
              SendMessageToCaller(tc, sub, TErrorList.invalid_link, null);
              continue;             
            }
            if (tce.sts.counter > 0) // was active (turn on the LAST bit
                tct.contract.dataAccess |= TAccess.CA_LAST;
            tct.lasttime = PRP_CANCEL_SIGNAL;
            tct.pending = 0;
          }
          tce.sts.counter = 0;
          break;
      }
    }
    if (nreqs > 1) sendSingleDelivery = false;
    if (sendSingleDelivery && tct != null)
    {
      if (debugLevel > 1) DbgLog.log("getRequest", "send synchronous express contract "+
          tct.contract.eqmDeviceName+ " "+tct.contract.eqmProperty+
          " (call dispatch: "+(resendCurrentData?"NO":"YES")+")");
      doScheduler(tct, tcaller);
    }
  }
  private void validateContextAndSubsystem(TEquipmentModule eqm)
  {
    String ctx = eqm.getContext();
    String sub = eqm.getSubsystem();
    String s;
    int idx;
    if (ctx == null)
    {
      eqm.setCompletionString("TEST");
      TFecLog.log("Context not given: set to \"TEST\"");
      return;
    }
    if ((idx=ctx.indexOf('.')) == -1) return;
    s = ctx.substring(idx+1);
    if (s.endsWith("TEST") || s.endsWith("SIM")) return;
    if (sub == null || sub.length() == 0)
    {
      sub = s;
      ctx = ctx.substring(0, idx);
    }
    if (sub.length() == 0) sub = gFecSubsystem;
    if (ctx.compareTo(eqm.getContext()) != 0)
    {
      TFecLog.log("given context " + eqm.getContext() +
          " will be treated as the " + sub + " subsystem decorated context " + ctx);
    }
  }
  private int _sndExportToEns(TEquipmentModule eqm,TDataType dout,TDataType din)
  {
    if (debugLevel > 2)
      DbgLog.log("SendRegisteredExportToENS","Using ENS : " + TSrvEntry.currentConfiguredNameServerTag);
    synchronized (getLinkFactory().ensMutex)
    {
      try
      {
        TLink expLnk = new TLink(TSrvEntry.currentConfiguredNameServerTag,"EXPORT",dout,din,TAccess.CA_WRITE);
        return expLnk.execute(500, true);
      }
      catch (Exception e)
      {
        eqm.isRegistered = true;
        MsgLog.log("SendRegisteredExportToENS", "Cannot get link to ENS: "+e.getMessage(),TErrorList.link_error,e,0);
        return TErrorList.address_unknown;
      }
    }
   
  }
  private TDataType _prepExportDataType(TEquipmentModule eqm,String exp,FECInfo inf,boolean legacyEns)
  {
    SrvAddr da = legacyEns ? new SrvAddr(gFecAddr.toFECAddrIPv4()) : new SrvAddr(gFecAddr);
    da.expName = exp;
    da.eqmContext = eqm.getContext();
    da.eqmName = eqm.getModuleName();
    da.subSystem = eqm.getSubsystem();
    if (gSystemRunningStandAlone)
    {
      TSrvEntry.addAddressToCacheFile(new SrvAddr(gFecAddr.getFecName(),
          FECAddr.getLoopBackAddr(),gFecAddr.getPortOffset(),eqm.getModuleName(),
          exp,eqm.getContext()));
      return null;
    }
    // prepare it and send it out: devAddr-- + FecAddr + FecInfo ...
    int siz = legacyEns ? SrvAddr.legacySizeInBytes : SrvAddr.sizeInBytes;
    siz += FECInfo.getSizeInBytes();
    byte[] expAddr = new byte[siz];
    int cpsize = legacyEns ? SrvAddr.legacySizeInBytes : SrvAddr.sizeInBytes;
    System.arraycopy(da.toByteArray(legacyEns), 0, expAddr, 0, cpsize);
    System.arraycopy(inf.toByteArray(), 0, expAddr, cpsize, FECInfo.getSizeInBytes());
    TDataType din = new TDataType(expAddr, "");
    if (debugLevel > 0)
    {
      DbgLog.log("SendRegisteredExportToENS","fec -> " + gFecAddr.toString());
      DbgLog.log("SendRegisteredExportToENS","dev -> " + da.toString());
      DbgLog.log("SendRegisteredExportToENS","fec info -> " + inf.toString());
    }
    return din;
  }
  private void SendRegisteredExportToENS(TEquipmentModule eqm)
  {
    SendRegisteredExportToENS(eqm,false);
  }
  private void SendRegisteredExportToENS(TEquipmentModule eqm,boolean isMaster)
  {
    if (eqm == null) return;
    String eqmName = eqm.getLocalName();
    if (eqmName == null || eqmName.length() == 0)
    {
      TFecLog.log("attempt to register equipment module with empty local name !");
      eqm.isRegistered = true;
      eqm.registerAsMaster = false;
      return;
    }
    String srvName = eqm.getExportName();
    if (srvName == null || srvName.length() == 0)
    {
      TFecLog.log("attempt to register equipment module with empty export name !");
      eqm.isRegistered = true;
      eqm.registerAsMaster = false;
      return;
    }
    if (gFecName == null) return; // not yet known !
    String lbip = FECAddr.isIPv6Site() ? FECAddr.LOOPBACKADDR6 : FECAddr.LOOPBACKADDR4;
    if (gFecAddr == null)
    {
      new TSrvEntry();
      gFecAddr = new FECAddr();
      gFecAddr.setFecName(gFecName);
      String ip = gSystemRunningStandAlone ? lbip : TInitializerFactory.getInstance().getInitializer().getMyIpAddr();
      gFecAddr.setIpAddr(ip);
      gFecAddr.setPortOffset(gPortOffset);
    }
    if (gFecInfo == null)
    {
      String floc = System.getProperty("fec.location");
      if (floc == null) System.getenv("FEC_LOCATION");
      if (floc != null && floc.length() > 0) setFecLocation(floc);
      gFecInfo = new FECInfo("JAVA",getFecDescription(),getFecLocation(),
          getFecHardware(),getFecResponsible());
    }
    FECInfo inf = gFecInfo;
    if (isMaster)
    { // secondary registration as a failover master
      if (!eqm.isRegistered)
      {
        TFecLog.log("premature attempt to register equipment module as master!");
        return;
      }
      String dsc = eqm.getFailoverType() == TEquipmentModuleFactory.FAILOVER_MASTER ?
                   FAILOVER_MASTER_DESC : FAILOVER_SLAVE_DESC;
      inf = new FECInfo("JAVA",dsc,getFecLocation(),getFecHardware(),getFecResponsible());
      srvName = eqm.getMaster();
    }
    if (eqm.getContext() == null || eqm.getContext().length() == 0)
    { // fill this in if it hasn't been set ...
      eqm.setContext(getFecContext());
    }
    String ctxName = eqm.getContext();
    String subsys = System.getenv(eqm.getLocalName()+"_SUBSYSTEM");
    if (subsys != null && subsys.length() > 0) eqm.setSubsystem(subsys);
    if (eqm.getSubsystem() == null || eqm.getSubsystem().length() == 0)
    { // fill this in if it hasn't been set ...
      eqm.setSubsystem(getFecSubsystem());
    }
    validateContextAndSubsystem(eqm);
   
    if (TSrvEntry.currentConfiguredNameServerTag != null)
    { // this should always point to the primary ENS
      TDataType dout = new TDataType();
      TDataType din = _prepExportDataType(eqm,srvName,inf,false);
      if (din == null) return; // stand-alone ? -> we're done
      int rc = _sndExportToEns(eqm,dout,din);
      if (rc == TErrorList.illegal_data_size)
      {
        din = _prepExportDataType(eqm,srvName,inf,true);
        rc = _sndExportToEns(eqm,dout,din);
      }
      String subj = "FEC "+getFecName()+", Server "+srvName+"("+eqmName+")";
      if (rc == 0)
      {
        eqm.isRegistered = true;
        if (isMaster) eqm.registerAsMaster = false;
        TFecLog.log(subj+" registered with equipment name server");
        TFecLog.log("Context is " + ctxName);
        gNumEnsErrors = 0;
      }
      else
      {
        if (gNumEnsErrors++ > 30) eqm.isRegistered = true;
        TFecLog.log(subj+" failed to register with equipment name server "+
            TSrvEntry.currentConfiguredNameServerTag+" : "+TErrorList.getErrorString(rc));
      }
    }
    else
    {
      DbgLog.log("SendRegisteredExportToENS","No configured ENS !");
    }
    // update the manifest
    TEquipmentManifest.update(eqm);
  }
  private void joinEnsGroup(TEquipmentModule eqm)
  {
    if (gFecName == null) return; // not yet known !
    if (eqm == null) return; // huh ?
    if (!eqm.isRegistered) return;
    String grp = eqm.getGroupName();
    if (grp == null || grp.length() == 0)
    {
      eqm.grpRegistered = true; // mark it
      return;
    }
    if (eqm.grpRegistered) return;
    String exp = eqm.getExportName();
    if (exp == null)
    { // this can't work out !
      eqm.grpRegistered = true; // mark it
      return;
    }
    String cnt = eqm.getContext(); if (cnt == null) cnt = new String("");
    String sub = eqm.getSubsystem();
    if (sub == null || sub.length() == 0)
    { // fill this in if it hasn't been set ...
      sub = eqm.getEquipmentModuleFactory().getFecSubsystem();
    }
    String prefix = eqm.getGroupDevicePrefix();
    if (prefix == null) prefix = "";
    String postfix = eqm.getGroupDevicePostfix();
    if (postfix == null) postfix = "";
    NAME32[] n32 = new NAME32[7];
    n32[0] = new NAME32(exp);
    n32[1] = new NAME32(grp);
    n32[2] = new NAME32(cnt);
    n32[3] = new NAME32(String.valueOf(eqm.getGroupIndex()));
    n32[4] = new NAME32(sub);
    n32[5] = new NAME32(prefix);
    n32[6] = new NAME32(postfix);
    TDataType din = new TDataType(n32);
    TDataType dout = new TDataType();
    if (debugLevel > 0)
    {
      String dbgstr = "/"+cnt+"/"+exp+" -> ";
      DbgLog.log("joinEnsGroup",dbgstr+"joining group "+grp+" (index "+eqm.getGroupIndex()+")");
      if (prefix.length() > 0) DbgLog.log("joinEnsGroup","device prefix: "+prefix);
      if (postfix.length() > 0) DbgLog.log("joinEnsGroup","device postfix: "+postfix);
    }
    // this should always point to the primary ENS
    if (TSrvEntry.currentConfiguredNameServerTag != null)
    {
      TLink expLnk = new TLink("/SITE/GENS","JOIN",dout,din,TAccess.CA_WRITE);
      int rc = expLnk.execute(500, true);
      expLnk.close();
      switch (rc)
      {
        case 0:
          eqm.grpRegistered = true;
          TFecLog.log(exp + " joined group " + grp);
          return;
        case TErrorList.link_not_open:
        case TErrorList.connection_timeout:
          // try, try again
          return;
        default:
          eqm.grpRegistered = true;
          TFecLog.log(exp + " joining group failed : " + TErrorList.getErrorString(rc));
          return;
      }   
    }
    else
    {
      DbgLog.log("joinEnsGroup","No configured GENS !");
    }
  }
  private byte[] msgBytes = new byte[TTransport.UDP_BUFFER_SIZE];
  private void SendMessageToCaller(TClient tc, TSubscription s, int cc, byte[] data)
  {
    if (s == null || tc == null) return;
    if (isServiceRequest(s.contract.eqmName)) return;
    TClientEntry tce = new TClientEntry();
    TContractTable tct = new TContractTable();
    short totalsize = 2;
    short hdrsize = TPHdr.hdrSizeInBytes;
    short msgsize = TStrings.STATUS_SIZE;
    if (s.isLegacy)
    {
      hdrsize = TPHdr.hdrSizeInBytesLegacy;
      msgsize = TStrings.STATUS_SHORTSIZE;
    }
    Arrays.fill(msgBytes, (byte)0);
    byte[] payload = msgBytes; //new byte[1500];
    int len;
    tct.dtimestamp = System.currentTimeMillis() + gDataTimeStampOffset;
    tce.sts = new TClientStatus(tc.inetProtocol);
    if (s != null)
    {
      tct.contract = s.contract;
      tce.sts.starttime = ((long)s.starttime) * 1000;
      tce.sts.id = s.id;
    }
    tce.sts.statusCode = (short)cc;
    tce.sts.numblks = 1;
    tce.cln = tc;
    // tce contains the protocol level in the following call
    switch (cc)
    {
      case TErrorList.get_subscription_id:
       if (data == null) return;
       len = 2;
       break;
      case TErrorList.invalid_interval:
        len = 0;
        try
        {
          DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
          tct.sysstamp = dis.readInt();
        }
        catch (IOException e)
        {
          MsgLog.log("SendMessageToCaller", e.toString(),TErrorList.invalid_interval,e,0);
          return; // don't send back erroneous information (this should never happen!)
        }
        break;
      case TErrorList.invalid_datarequest:
        len = 0;
        try
        {
          DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
          tct.sysstamp = dis.readInt();
          tct.usrstamp = dis.readInt();
          dis.close();
        }
        catch (IOException e)
        {
          MsgLog.log("SendMessageToCaller", e.toString(),TErrorList.invalid_datarequest,e,0);
          return; // don't send back erroneous information (this should never happen!)
        }
        break;
      case TErrorList.property_is_mca:
        len = 0;
        try
        {
          DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
          tct.usrstamp = dis.readInt();
          tct.sysstamp = dis.readInt();
          dis.close();
          len = TStrings.DEVICE_NAME_SIZE + 8;
          System.arraycopy(TStrings.MCADEV_TAG.getBytes(), 0, data, 0, 8);         
        }
        catch (IOException e)
        {
          MsgLog.log("SendMessageToCaller", e.toString(),TErrorList.property_is_mca,e,0);
          return; // don't send back erroneous information (this should never happen!)
        }
        break;
      case TErrorList.has_bitfield_tag:
      case TErrorList.has_structure_tag:
      case TErrorList.call_redirection:
      case TErrorList.server_redirection:
        if (data == null) return;
        len = data.length;
        break;
      default:
        data = TErrorList.getErrorString(cc).getBytes();
        len = data.length;
        break;
    }
    TPHdr tpd = new TPHdr(msgsize+hdrsize, tce, tct);
    System.arraycopy(tpd.dBuffer.toByteArray(),0,payload,totalsize,hdrsize);
    totalsize += hdrsize;
    if (len > 0) System.arraycopy(data,0,payload,totalsize,len);
    totalsize += msgsize; //TErrorList.ERROR_SIZE;
    if (debugLevel > 0)
    {
      switch (cc)
      {
        case TErrorList.illegal_property:
        case TErrorList.illegal_equipment_name:
        case TErrorList.non_existent_elem:
        case TErrorList.call_redirection:
        case TErrorList.server_redirection:
        case TErrorList.access_denied:
          DbgLog.log("SendMessageToCaller","MSG : " + s.contract.eqmName + " " + s.contract.eqmProperty +
                             " -> " + TErrorList.getErrorString((short) cc));
          break;
        default:
          DbgLog.log("SendMessageToCaller","MSG : " + TErrorList.getErrorString((short) cc));
          break;
      }
    }
    payload[0] = (byte) (totalsize & 0xff);
    payload[1] = (byte) (totalsize >> 8);
    if (TTransport.isStream(tc.inetProtocol))
    { // different delivery strategy ...
      payload[2] = 0;
      payload[3] = 0;
    }
    sendToPeer(tc,payload,totalsize,null,null);
  }
  public class TAcceptorThread extends Thread
  {
    private int transportProtocol = TTransport.UDP; // default
    TAcceptorThread(int transport)
    {
      transportProtocol = transport;
      this.setName("Equipment Module " + TTransport.toString((short)transport) + " acceptor");
    }
    TAcceptorThread()
    {
      transportProtocol = TTransport.UDP;
      this.setName("Equipment Module UDP acceptor");
    }
    public synchronized void run()
    {
      MsgLog.log("TAcceptorThread","Acceptor Thread " + getName() + " started ...",0,null,1);
      while (!gSystemExitCondition)
      {
        try
        {
          if (!isInitialized) sleep(200);
          switch (transportProtocol)
          {
            case TTransport.UDP:
              systemAcceptDataGramRequest();
              gTotalUdpPackets++;
              break;
            case TTransport.NETSRV:
              systemAcceptNetServiceGramRequest();
              gTotalNetSrvPackets++;
              break;
            case TTransport.TCP:
              systemAcceptDataStreamRequest(transportProtocol);
              systemCleanupDataStreams()// do this as a matter of course (kills inactive streams)
              gTotalTcpPackets++;
              break;
            case TTransport.STREAM:
              systemAcceptDataStreamRequest(transportProtocol);
              systemCleanupDataStreams()// do this as a matter of course (kills inactive streams)
              gTotalStreamPackets++;
              break;
            case TTransport.PIPE:
              systemAcceptPipeStreamRequest();
              systemCleanupDataStreams()// do this as a matter of course (kills inactive streams)
              break;
            default:
              MsgLog.log("TAcceptorThread","transportProtocol "+TTransport.toString((short)transportProtocol)+" not supported",TErrorList.illegal_protocol,null,0);
              gSystemExitCondition = true;
          }
        }
        catch (InterruptedException e)
        {
          if (debugLevel > 2) DbgLog.log("TAcceptorThread","interrupted");
        }
        catch (Exception e)
        {
          e.printStackTrace();
          MsgLog.log("TAcceptorThread", "unhandled exception: "+e.toString(),TErrorList.code_failure,e,0);
        }
      }
      MsgLog.log("TAcceptorThread","Acceptor Thread terminating ...",0,null,0);
    }
  }
  public class TCycleThread extends Thread
  {
    public synchronized void run()
    {
      this.setName("Equipment Module Cycle");
      MsgLog.log("TCycleThread","Cycle Thread " + getName() + " started ...",0,null,1);
      while (!gSystemExitCondition)
      {
        try
        {
          sleep(systemSrvCycle());
        }
        catch (Exception e)
        {
          e.printStackTrace();
          DbgLog.log("TCycleThread","cycle thread execution error");
        }
      }
      TFecLog.log("System halted");
    }
  }
  public class TBackgroundThread extends Thread
  {
    private TEquipmentBackgroundTask tbkg;
    private boolean initialized = false;
    private TEquipmentModule eqm = null;
    TBackgroundThread(TEquipmentBackgroundTask tBkgTask)
    {
      tbkg = tBkgTask;
      eqm = tbkg.getEquipmentModule();
      if (eqm == null) initialized = true;
      this.setName("Equipment Module background " + tbkg.getClass().getSimpleName());
   }
    public synchronized void run()
    {
      long interim = 0, deadTimeInMsec = 0, bkgInterval;
      TFecLog.log("Background Thread "+getName()+" started ...");
      while (!gSystemExitCondition)
      {
        try
        {
          interim = System.currentTimeMillis();
          if (initialized && !tbkg.isIdleState()) tbkg.call();
          bkgInterval = tbkg.getBackgroundTaskInterval();
          deadTimeInMsec = bkgInterval - (System.currentTimeMillis() - interim);
          if (deadTimeInMsec < 0) deadTimeInMsec = 0;
          if (deadTimeInMsec > bkgInterval) deadTimeInMsec = bkgInterval;
          if (debugLevel > 3)
            DbgLog.log("TBackgroundThread","sleeping " + deadTimeInMsec + " msec in background task");
          sleep(deadTimeInMsec);
          if (!initialized && eqm != null) initialized = eqm.hasInitialized;
        }
        catch (Exception e)
        {
          e.printStackTrace();
          DbgLog.log("TBackgroundThread","thread execution error");
        }
      }
    }
  }
  private void systemCleanupDataStreams()
  {
    for (int i=0; i<bucketList.size(); i++)
    {
      if (((TContractBucket)bucketList.get(i)).active == false)
        bucketList.remove(i);
    }
  }
  private void initPipeSocket()
  {
    try
    {
      String ip = TInitializerFactory.getInstance().getInitializer().getMyIpAddr();
      InetAddress iaddr = InetAddress.getByName(ip);
      int port = initializer.getPipePort() + gPortOffset;
      TFecLog.log("open Debug server socket at port "+port);
      ServerSocketChannel sckChn = ServerSocketChannel.open();
      sckPipe = sckChn.socket();
      sckPipe.setReuseAddress(true);
      sckPipe.setReceiveBufferSize(sckRcvBufferSize);
      sckPipe.bind(new InetSocketAddress(iaddr,port));           
      String pipeName = TEquipmentManifest.getFilePath() +
        File.separatorChar + getFecName()+ ".ipc";
      BufferedWriter out;
      out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(pipeName)));
      out.write(""+sckPipe.getLocalPort());
      out.close();
      TFecLog.log("started pipe listener");
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("initPipeSocket",e.toString(),TErrorList.code_failure,e,1);
      TFecLog.log("could not start pipe listener : "+e.getMessage());
    }   
  }
  private void systemAcceptPipeStreamRequest()
  {
    if (!isInitialized) return;
    if (sckPipe == null) initPipeSocket();
    try
    {    
      if (sckPipe == null) return;
      Socket sck = sckPipe.accept();
      sck.setTcpNoDelay(true);
      sck.setTrafficClass(0x18);
      sck.setSoTimeout(10);
      bucketList.add(new TContractBucket(sck,TTransport.PIPE));
    }
    catch (IOException e)
    {
      if (debugLevel > 0) DbgLog.log("systemAcceptPipeStreamRequest","IO exception: "+e.toString());
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("systemAcceptPipeStreamRequest",e.toString(),TErrorList.code_failure,e,1);
    }   
  }
  private void systemAcceptDataStreamRequest(int transport)
  {
    if (!isInitialized) return;
    ServerSocket srvSck = transport == TTransport.TCP ? sckTcp : sckStream;
    try
    {
      if (sckTcp == null) return;
      Socket sck = srvSck.accept();
      sck.setTcpNoDelay(true);
      sck.setTrafficClass(0x18);
      // TODO: there's got to be a more efficient way than waking the stupid thread up every
      // 10 msec ! a .notify() from SendToPeer() doesn't work since it's not the owner of the
      // thread. BUT this can't block waiting for input when the scheduler needs to send
      // something out !!!
      sck.setSoTimeout(10);
      bucketList.add(new TContractBucket(sck,transport));
    }
    catch (IOException e)
    {
      if (debugLevel > 0) DbgLog.log("systemAcceptDataGramRequest","IO exception: "+e.toString());
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("systemAcceptDataStreamRequest",e.toString(),TErrorList.code_failure,e,1);
    }
  } 
  private void systemAcceptDataGramRequest()
  {
    if (!isInitialized) return;
    try
    {
      if (sckUdp == null) return; // socket not yet available 
      sckUdp.getSocket().receive(sckUdp.dpIn);
      gLinkTablesAccessed = true;
      TClient tc = LocateClientInList(sckUdp.dpIn.getAddress(), sckUdp.dpIn.getPort());
      tc.inetProtocol = TTransport.UDP;
      tc.IPport = (short)sckUdp.dpIn.getPort();
      // pass it on ...
      getRequest(tc, sckUdp.dpIn.getData());
      gLinkTablesAccessed = false;
    }
    catch (IOException e)
    {
      if (debugLevel > 0) DbgLog.log("systemAcceptDataGramRequest","IO exception: "+e.toString());
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("systemAcceptDataGramRequest",e.toString(),TErrorList.code_failure,e,0);
    }
  }
  private void systemAcceptNetServiceGramRequest()
  {
    if (!isInitialized) return;
    try
    {
      if (sckNetSrv == null) return; // socket not yet available 
      sckNetSrv.getSocket().receive(sckNetSrv.dpIn);
      gLinkTablesAccessed = true;
      TClient tc = LocateClientInList(sckNetSrv.dpIn.getAddress(), sckNetSrv.dpIn.getPort());
      tc.inetProtocol = TTransport.UDP;
      tc.IPport = (short)sckUdp.dpIn.getPort();
      // pass it on ...
      getRequest(tc, sckNetSrv.dpIn.getData());
      gLinkTablesAccessed = false;
    }
    catch (IOException e)
    {
      if (debugLevel > 0) DbgLog.log("systemAcceptNetServiceGramRequest","IO exception: "+e.toString());
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("systemAcceptNetServiceGramRequest",e.toString(),TErrorList.code_failure,e,0);
    }
  }
  private long srvCycleTime = 0;
  private OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
  //private RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean();
  private long getCycleTime()
  {
    ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    if (!bean.isThreadCpuTimeSupported()) return 0;
    return bean.getCurrentThreadCpuTime();
  }
  private long busyCountStart = getCycleTime();
  private long cycleTimeStart = System.currentTimeMillis();
  private int busyCountDown = 100;
  public static final int PRE_HSTHOME_UPDATE_WINDOW = 30000;
  private long systemSrvCycle()
  {
    srvCycleTime = System.currentTimeMillis();
    long msecToSleep = 10;
    if (!isInitialized) return STD_CYCLE_INTERVAL*200;
    if (gLinkTablesAccessed) return STD_CYCLE_INTERVAL;
    if (isInsideCycle) return STD_CYCLE_INTERVAL;
    isInsideCycle = true;
    try
    {
      if (debugLevel > 25) DbgLog.log("systemSrvCycle","SystemCycle called at " + new Date().toString());
      // basic server service engine (called at the polling rate)
      // call each module's update routine ...
      for (int i=0; i<numEqmTableEntries; i++) eqmTable[i].update();
      if (minDiskSpaceTbl.size() > 0) checkFreeBlocks();
      // process all external requests ...
      doScheduler(null,null); // check contract list ...
      if (registrationPending && srvCycleTime - registrationTime > 500)
      { // anything still need to be sent out ?
        int doneCounter=0;
        for (int i=0; i<numEqmTableEntries; i++)
        {
          if (eqmTable[i] == null)
          { // huh ?
            doneCounter++;
            MsgLog.log("systemSrvCycle", "eqm table entry "+i+" is empty!",TErrorList.non_existent_elem,null,0);
            continue;
          }
          if (!eqmTable[i].hasInitialized) continue;
          if (!eqmTable[i].isRegistered)
          {
            SendRegisteredExportToENS(eqmTable[i]);
            break; // just do one at a time
          }
          else if (eqmTable[i].registerAsMaster)
          {
            SendRegisteredExportToENS(eqmTable[i],true);
            break; // one at a time
          }
          else if (!eqmTable[i].grpRegistered)
          {
            joinEnsGroup(eqmTable[i]);
          }
          else
          { // nothing to do : increment the done counter
            doneCounter++;       
          }
        }
        if (doneCounter == numEqmTableEntries)
        { // really finished ...
          registrationPending = false;
        }
        registrationTime = srvCycleTime;
      }
      // update the history home manifest after 30 seconds ...
      if (!THistoryHomeManifest.hasUpdated &&
          srvCycleTime > getServerStartTime() + PRE_HSTHOME_UPDATE_WINDOW)
      {
        if (THistoryHomeManifest.update() != TErrorList.operation_busy)
          THistoryHomeManifest.hasUpdated = true;
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
      MsgLog.log("systemSrvCycle", "unhandled exception "+e.toString(),TErrorList.code_failure,e,0);     
    }
    if (gCycleDelay > 10) msecToSleep = gCycleDelay;
    if (busyCountDown-- == 0)
    {
      long ct = getCycleTime();
      long ts = System.currentTimeMillis();
      gAveBusyTime = (int)((ct - busyCountStart)/((ts-cycleTimeStart)*10000));
//      System.out.println("delta busy t : "+ct + " - "+ busyCountStart + " : "+ (ct - busyCountStart));
//      System.out.println("delta t : "+ts + " - "+ cycleTimeStart + " : "+ (ts-cycleTimeStart));
      busyCountStart = ct;
      busyCountDown = 100;
      cycleTimeStart = ts;
    }
    isInsideCycle = false;
    return msecToSleep;
  }
  class TUncaught implements Thread.UncaughtExceptionHandler
  {
    public void uncaughtException(Thread t, Throwable e)
    {
      e.printStackTrace();
      System.exit(1);
    }   
  }
  public int systemInit(String fecName, int portOffset)
  {
    return systemInit(fecName,portOffset,null);
  }
  public int systemInit(String fecName, int portOffset, String expName)
  {
    Thread.setDefaultUncaughtExceptionHandler(new TUncaught());
    getLinkFactory().SetRunningAsServer(true);
    TInitializer ti = instance.getInitializer();
    String thDisplay = ti.getTineHome();
    String fhDisplay = ti.getFecHome();
    try { myIp = InetAddress.getByName(ti.getMyIpAddr()); } catch (Exception e) {}
    String wd = new File(".").getAbsolutePath();
    if (thDisplay.compareTo(".") == 0) thDisplay = ". (" + wd + ")";
    if (fhDisplay.compareTo(".") == 0) fhDisplay = ". (" + wd + ")";
    TFecLog.log("VERSION   : " + TStockProperties.getStockSrvVersion());
    TFecLog.log("OS        : JAVA (on "+os.getName()+" "+os.getVersion()+")" );   
    TFecLog.log("TINE HOME : " + thDisplay);
    TFecLog.log("FEC HOME  : " + fhDisplay);

    if (fecName != null) setFecName(fecName);

    String lclName = null;
    // if export name not given then try to find a valid one ...
    int tblSearchIndex = expName == null ? 0 : numEqmTableEntries;
    boolean fecfound = false;
    do
    {
      for (int i = tblSearchIndex; i < numEqmTableEntries; i++)
      { // if there's already an entry then find it's eqm name
        if (eqmTable[i] == null) continue;
        lclName = eqmTable[i].getModuleName();
        expName = eqmTable[i].getExportName();
        String cntName = eqmTable[i].getContext();
        if (cntName != null && cntName.length() > 0) expName = "/"+cntName+"/"+expName;
        if (lclName != null) break;
        tblSearchIndex = i;
      }
      if (getFecInformationFromFile(expName,lclName) == 0) fecfound = true;
    } while (!fecfound && ++tblSearchIndex < numEqmTableEntries);
    if (!fecfound && (fecName == null || fecName.length() == 0))
    {
      MsgLog.log("systemInit","Needed to read fecid.csv and couldn't!",TErrorList.name_unknown,null,0);
    }
    if (portOffset > 0) gPortOffset = portOffset;
    validatePortOffset();
    // the UDP server port:
    int port = initializer.getSrvPort() + gPortOffset;
    TFecLog.log("open UDP server socket at port "+port);
    sckUdp = new TPacket(port,sckRcvBufferSize,sckSndBufferSize,sckTimeToLive);
    if (sckUdp.getPort() == 0)
    {
      TFecLog.log("Cannot start server (fec name "+gFecName+ ", port offset "+gPortOffset+ ") -> shutting down!");
      System.exit(1);     
    }
    if (gRespondToServiceRequests)
    {
      sckNetSrv = new TPacket(initializer.getNetCastPort());
      registerLocalServerModule();
      acceptThrd[3] = new TAcceptorThread(TTransport.NETSRV);
      acceptThrd[3].start();     
    }
    if (gFecName != null)
    { // everything looks okay now ...
      TLinkFactory tlf = getLinkFactory();
      String un = tlf.getUserName();
      tlf.setDoocsUserName(un);
      tlf.setUserName(getFecName());
    }
    else
    {
      MsgLog.log("systemInit","FEC Name unknown at start time !",TErrorList.name_unknown,null,0);
    }
    try
    { // the TCP server port
      String ip = TInitializerFactory.getInstance().getInitializer().getMyIpAddr();
      InetAddress iaddr = InetAddress.getByName(ip);
      port = initializer.getTCPPort() + gPortOffset;
      TFecLog.log("open TCP server socket at port "+port);
      ServerSocketChannel srvsChannel = ServerSocketChannel.open();
      sckTcp = srvsChannel.socket();
      sckTcp.setReuseAddress(true);
      sckTcp.setReceiveBufferSize(sckRcvBufferSize);
      sckTcp.bind(new InetSocketAddress(iaddr,port));
      // the STREAM server port
      port = initializer.getStreamPort() + gPortOffset;
      TFecLog.log("open STREAM server socket at port "+port);
      ServerSocketChannel strmChannel = ServerSocketChannel.open();
      sckStream = strmChannel.socket();
      sckStream.setReuseAddress(true);
      sckStream.setReceiveBufferSize(sckRcvBufferSize);
      sckStream.bind(new InetSocketAddress(iaddr,port));
      //if (!java.awt.GraphicsEnvironment.isHeadless())
      initPipeSocket();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      TFecLog.log("initialization problem: "+e.getMessage());
      TFecLog.log("Cannot start server (fec name "+gFecName+ ", port offset "+gPortOffset+ ") -> shutting down!");
      System.exit(1);
      return TErrorList.not_running;
    }
    String sas = System.getProperty("tine.");
    if (sas == null) System.getenv("TINE_STANDALONE");
    if (sas != null && sas.compareToIgnoreCase("TRUE") == 0)
    {
      gSystemRunningStandAlone = true;
      initializer.setENSAddress(null);
      TFecLog.log("FEC is running in stand-alone mode");
    }
    String env = System.getProperty("tine.networkaddress.resolution");
    if (env == null) System.getenv("TINE_NETWORKADDRESS_RESOLUTION");
    if (env != null && env.compareToIgnoreCase("TRUE") == 0)
    {
      setRespondToServiceRequests(true);
      MsgLog.log("TLink Factory","respond to service requests",0,null,0);
    }
  // is there a hook ?
    if (tEqmHook != null) tEqmHook.SystemInit();
    // send what we've got to the ENS ...
    for (int i = 0; i < numEqmTableEntries; i++)
    { // is there a devices file (needs to be done late) ?
      eqmTable[i].startup();
      if (!eqmTable[i].isRegistered) SendRegisteredExportToENS(eqmTable[i]);
      eqmTable[i].applyStoredPropertyValues();
      eqmTable[i].restoreStockDeviceProperties();
    }
    gSrvBaselineTime = gSrvStartupTime = System.currentTimeMillis();
    // the server starttime is systematically a 4-byte integer (UTC) across ALL platforms ...
    serverStatistics.setStarttime((int) (gSrvStartupTime/1000));
    // start global synchronization if required
    if (useGlobalSynchronization) TDataTime.systemStartGlobalSynchronization();
    if (useCycleTrigger) systemStartCycleTrigger();
    // signal the 'all clear' :
    isInitialized = true;
    return 0;
  }
  /**
   * Obtains the FEC port offset appropriate for the give FEC name.
   *
   * In cases where the pure registration API routines are used to
   * register FEC and server information (as opposed to a common
   * fecid.csv configuration file) it is often a more cumbersome
   * task to ensure a unique FEC port on the host machine.
   * This routine can be used to this end. It will search the
   * existing FEC manifest on the current host in order to find
   * the matching entry for the given FEC name.
   * If the entry is not found, then the 'next free port' is returned. In this way the FEC can always register with its own proper FEC port, without the need to hard code this number.
   *
   * If this routine is called after the server is running and
   * the FEC name is not found in the manifest the -name_unknown
   * is returned.
   *
   * @param fecName is the targeted FEC name for which a port offset is to be assigned.
   *
   * @return a positive port offset which is valid for the FEC name given
   * or a negative TINE error code on error (e.g. a null fecName parameter).
   *
   * \b example
   *
   * \include eg_getPortOffset.java
   */
  public static int getPortOffset(String fecName)
  {
    if (fecName == null || fecName.length() == 0) return -TErrorList.argument_list_error;
    LinkedList<TEquipmentManifest> lst = TEquipmentManifest.getFecManifestList();
    Iterator<TEquipmentManifest> it = lst.iterator();
    TEquipmentManifest fm;
    int fp = 0, p = -1, idx;
    double ts = 0, lts = -1;
    String tsstr;
    while (it.hasNext())
    {
      fm = it.next();
      if (fm.port > fp) fp = fm.port;
      if (fm.fec.compareToIgnoreCase(fecName) != 0) continue;
      if ((idx=fm.last_started.indexOf('(')) > 0)
        tsstr = fm.last_started.substring(0,idx);
      else
        tsstr = fm.last_started;
      ts = TDataTime.getDataTimeStamp(tsstr);
      if (ts > lts)
      { // find the most recently started entry with this FEC name
        lts = ts;
        p = fm.port;
      }
    }
    fp++;
    if (p < 0)
    {
      if (isInitialized) return -TErrorList.name_unknown;
      p = fp;
    }
    return p;
  }
  public int systemInit(int portOffset)
  {
    return systemInit(null, portOffset, null);
  }
  public int systemInit()
  {
    return systemInit(null, 0, null);
  }
  private TEquipmentModule srvEqm = null;
  private int registerLocalServerModule()
  {
    srvEqm = new TEquipmentModule(TSrvEntry.SRVEQM_NAME);
    srvEqm.isRegistered = true;
    return 0;
  }
  public boolean isServiceRequest(String eqm)
  {
    if (eqm == null) return false;
    return (eqm.compareTo(TSrvEntry.SRVEQM_NAME) == 0);
  }
  public void systemWait(long milliseconds)
  {
    if (milliseconds < 0)
    { // forever or until we quit
      while (!gSystemExitCondition)
      {
        try { Thread.sleep(1000); } catch (Exception e) {};
      }
    }
    else
    {
      try { Thread.sleep(milliseconds); } catch (Exception e) {};
    }
  }
  private boolean exitOnShutdown = true;
  public void shutdown() { shutdown(0); }
  public void shutdown(int status)
  {
    if (exitOnShutdown)
    { // not interested in keeping anything alive ...
      getLinkFactory().shutdown();
    }
    else
    { // close any active links ...
      TLink[] lnks = getLinkFactory().getActiveLinks();
      if (lnks != null) for (TLink lnk : lnks) lnk.close();
    }
    gSystemExitCondition = true;
    for (int i=0; i<eqmTable.length; i++)
    { // call any termination routines ...
      if (eqmTable[i] == null) continue;
      eqmTable[i].shutdown();
      eqmTable[i] = null;
    }
    try
    { // give the other threads a chance to close gracefully ...
      Thread.sleep(gSystemTick*2);
    }
    catch (InterruptedException e) {};
    numEqmTableEntries = 0;
    this.sckUdp.shutdown();
    this.sckUdp = null;
    if (this.sckNetSrv != null)
    {
      this.sckNetSrv.shutdown();
      this.sckNetSrv = null;
    }
    try {this.sckTcp.close();} catch (IOException e) { System.out.println("io exception on shutting down tcp server socket"); }
    this.sckTcp = null;
    try {this.sckStream.close();} catch (IOException e) { System.out.println("io exception on shutting down stream server socket"); }
    this.sckStream = null;
    if (exitOnShutdown)
    { // just quit !
      System.exit(status);
    }
    else
    { // reboot the factory ...
      factoryStartup();
    }
  }
  // csv file FECID handlers:
  class tgtHndlr implements csvHandler
  {
    String target;
    tgtHndlr(String name) { target = name; }
    public int process(String strValue,int index)
    {
      if (target == null ||  // target not given
          strValue.length() == 0) // empty string is as good as a match
      { // not given, halt iteration !
        TFecLog.log("accept first entry in fecid.csv as valid!");
        return -1;
      }
      if (strValue.compareTo(target) == 0) // got it !
      { // found it, halt iteration !
        TFecLog.log("found target " + target + " in fecid.csv!");
        return -1;
      }
      return 0;
    }
  }
  class FecRowHndlr implements RowHandler
  {
    private boolean latchExpName = false;
    private boolean latchEqmName = false;
    protected boolean namefound = false;
    private String target;
    public String ctx;
    public String sub;
    public String rsp;
    public String hdw;
    public String dsc;
    public String nam;
    public String loc;
    public String exp;
    public String eqm;
    public String hstHome;
    public int port;
    public int process(int index)
    {
      if (latchExpName)
      { // looking for a specific export name (and context name ?)
        String tctx = null;
        String texp = target;
        if (target.startsWith("/"))
        {
          String[] parts = target.split("/");
          if (parts.length < 3) return 0;
          tctx = parts[1];
          texp = parts[2];
        }
        if (exp == null || exp.length() == 0)
        {
          TFecLog.log("no exports column : accept " + nam + " as fecname");
          namefound = true; return -1;
        }
        if ((ctx != null && ctx.length() > 0) &&
            (tctx != null && tctx.length() > 0))
        { // contexts must match !
          if (ctx.compareToIgnoreCase(tctx) != 0) return 0;
        }
        if (exp.compareToIgnoreCase(texp) == 0)
        {
          namefound = true; return -1; // that's it!
        }
      }
      else if (latchEqmName)
      { // looking for a specific eqm name
        if (eqm == null || eqm.length() == 0)
        {
          TFecLog.log("no local name column : accept " + nam + " as fecname");
          namefound = true; return -1;
        }
        if (eqm.compareToIgnoreCase(target) == 0)
        {
          namefound = true; return -1; // that's it!
        }
      }
      else
      { // not latching anything -> stop on the first entry
        namefound = true; return -1;
      }
      return 0;
    }   
  }
  int getFecXmlDocument()
  {
    if (gFecXmlCfg != null) return 0; // been there, done that ...
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    File fecXml = null;
    try
    {
      fecXml = new File(initializer.getFecHome() + File.separator +"fec.xml");
      if (fecXml != null)
      {
        DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder;
        builder = f.newDocumentBuilder();
        Document doc = builder.parse(fecXml);
        TineXMLparser p = new TineXMLparser( doc );
        gFecXmlCfg = p.parse();
        setFecName(gFecXmlCfg.getName());
        setFecContext(gFecXmlCfg.getContext());
        setFecSubsystem(gFecXmlCfg.getSubsystem());
        setFecDescription(gFecXmlCfg.getDescription());
        setFecHardware(gFecXmlCfg.getHardware());
        setFecResponsible(gFecXmlCfg.getResponsible());
       
        LinkedList<EqmCfg> el = gFecXmlCfg.getEqmList();
        for( EqmCfg ec : el )
        {
          if (getFecContext() == null || getFecContext().length() == 0)
            setFecContext(ec.getContext());
          if (getFecDescription() == null || getFecDescription().length() == 0)
            setFecDescription(ec.getDescription());
          if (getFecHardware() == null || getFecHardware().length() == 0)
            setFecHardware(ec.getHardware());
          if (getFecSubsystem() == null || getFecSubsystem().length() == 0)
            setFecSubsystem(ec.getSubsystem());
          if (getFecResponsible() == null || getFecResponsible().length() == 0)
            setFecResponsible(ec.getResponsible());
          break;
        }           
        gPortOffset = (short)(gFecXmlCfg.getPortOffset().getValue());
        TFecLog.log("fec.xml read");
      }
    }
    catch (FileNotFoundException e)
    {
      gFecXmlCfg = null;
    }
    catch (Exception e)
    {
        e.printStackTrace();
        TFecLog.log("error reading fec.xml : " + e.getLocalizedMessage());
    }   
    return gFecXmlCfg == null ? TErrorList.no_such_file : 0;     
  }
  int getFecInformationFromFile(String targetExportName,String targetLocalName)
  {
    if (getFecXmlDocument() == 0) return 0;
    TFecLog.log("no fec.xml found, try fecid.csv ...");
    // register the csv database structure:
    FecRowHndlr fHndlr = new FecRowHndlr();
    if (targetExportName != null)
    {
      fHndlr.latchExpName = true;
      fHndlr.target = targetExportName;
      TFecLog.log("trying to latch export name " + targetExportName + " in fecid.csv");
    }
    else
    {
      fHndlr.latchEqmName = true;
      fHndlr.target = targetLocalName;
      TFecLog.log("trying to latch local name " + targetLocalName + " in fecid.csv");
    }
    csvColumn[] fecCols = new csvColumn[11];
    fecCols[0] = new csvColumn("FEC_NAME",null,new StringFieldHandler(fHndlr,"nam"));
    fecCols[1] = new csvColumn("PORT_OFFSET","0",new IntFieldHandler(fHndlr,"port"));
    fecCols[2] = new csvColumn("CONTEXT","",new StringFieldHandler(fHndlr,"ctx"));
    fecCols[3] = new csvColumn("EXPORT_NAME","",new StringFieldHandler(fHndlr,"exp"));
    fecCols[4] = new csvColumn("LOCAL_NAME","",new StringFieldHandler(fHndlr,"eqm"));
    fecCols[5] = new csvColumn("DESCRIPTION","",new StringFieldHandler(fHndlr,"dsc"));
    fecCols[6] = new csvColumn("LOCATION","",new StringFieldHandler(fHndlr,"loc"));
    fecCols[7] = new csvColumn("HARDWARE","none",new StringFieldHandler(fHndlr,"hdw"));
    fecCols[8] = new csvColumn("SUBSYSTEM","",new StringFieldHandler(fHndlr,"sub"));
    fecCols[9] = new csvColumn("RESPONSIBLE","",new StringFieldHandler(fHndlr,"rsp"));
    fecCols[10] = new csvColumn("HISTORY_HOME","",new StringFieldHandler(fHndlr,"hstHome"));
    // open it
    csv fecFile = new csv(initializer.getFecIdResource());
    // read it
    int rc = fecFile.readFile(fecCols,fHndlr);
    // close it
    fecFile.close()
    if (rc != TErrorList.no_such_file)
    {
      TFecLog.log("get fec information from fecid.csv : " + TErrorList.errorString[rc]);
    }
    if (!fHndlr.namefound)
    { // file was found, but no matching entry !
      TFecLog.log("get fec information from fecid.csv : could not find matching entry for " + fHndlr.target);
      rc = TErrorList.non_existent_elem;
    }
    if (rc == 0)
    {
      setFecName(fHndlr.nam);
      setFecContext(fHndlr.ctx);
      setFecDescription(fHndlr.dsc);
      setFecHardware(fHndlr.hdw);
      setFecLocation(fHndlr.loc);
      setFecSubsystem(fHndlr.sub);
      setFecResponsible(fHndlr.rsp);
     
      gPortOffset = (short)fHndlr.port;
    }
    return rc;
  }
  // csv ALIAS list handlers:
  class aliasListRowHndlr implements RowHandler
  { // the Row Handler will be called when all columns have been read in
    public String aName;
    public String aAlias;
    public int process(int index)
    {
      AliasTableEntry ate = new AliasTableEntry(aName,aAlias);
      gAliasList.put(aAlias,ate);
      return 0;
    }
  }
  int getAliasListFromFile()
  {
    FecCfg cfg = getFecXmlCfg();
    if (cfg != null)
    { // has parsed a fec.xml config file ...
      LinkedList<AliasCfg> al = cfg.getAliasList();
      for( AliasCfg ac : al )
      {
        gAliasList.put(ac.getName(), new AliasTableEntry(ac.getTarget(),ac.getName()));
      }   
      return 0;
    }
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    // register the csv database structure:
    csvColumn[] aliasCols = new csvColumn[2];
    aliasListRowHndlr aliasRows = new aliasListRowHndlr();
    aliasCols[0] = new csvColumn("NAME","",new StringFieldHandler(aliasRows,"aName"));
    aliasCols[1] = new csvColumn("ALIAS","0",new StringFieldHandler(aliasRows,"aAlias"));
    // open it
    csv aliasFile = new csv(initializer.getAliasResource(null));
    // read it
    aliasFile.readFile(aliasCols,aliasRows);
    // close it
    aliasFile.close()
    //TFecLog.log("get alias information from alias.csv : " + TErrorList.errorString[rc]);
    return 0;
  }
  // csv ALMWATCH handlers:
  class almwRowHndlr implements RowHandler
  { // the Row Handler will be called when all columns have been read in
    private TEquipmentModule eqm;
    public String dev;
    public String prp;
    public int siz;
    public int fmt;
    public int atyp;              /* array type */
    public int asys;              /* alarm system id */
    public int sev;               /* of hi and low (warn is sev - 2) */
    public int hisev;
    public int losev;
    public int hiwarnsev;
    public int lowarnsev;
    public int cnt;
    public int mask;
    public int normal;
    public float hi;               /* high threshold */
    public float lo;               /* low threshold */
    public float hiwarn;           /* high warn threshold */
    public float lowarn;           /* low warn threshold */
    public int code;
    public int codeHigh;
    public int codeLow;
    public String hitag = null;
    public String lotag = null;
    public String filter = null;
    public boolean normalIsAlarm = false;
    public int process(int index)
    {
      if (eqm == null) return TErrorList.non_existent_elem;
      TAlarmWatchThreshold awt;
      if (mask == 0)
      {
        awt = new TAlarmWatchThreshold(cnt,hi,hiwarn,lo,lowarn);
      }
      else
      {
        awt = new TAlarmWatchThreshold(cnt,mask,normal,normalIsAlarm);
      }
      TAlarmWatchEntry awe = new TAlarmWatchEntry(eqm,dev,prp,siz,fmt,atyp,asys,sev,code,codeHigh,codeLow,hitag,lotag,awt);
      if (filter != null && filter.length() > 0)
      { // try to instantiate a filter
        try
        {
          awe.setFilter(new TFilterLink(filter));
        }
        catch (Exception e)
        {
          TFecLog.log("almwRowHndlr","unable to exstablish alarm watch filter "+filter+" : "+e.getMessage());
        }
      }
      eqm.gAlarmWatchList.add(awe);
      return 0;
    }
  }
  class almwEqmHndlr implements csvHandler
  {
    private almwRowHndlr rHndlr;
    almwEqmHndlr(almwRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      for (int i=0; i<numEqmTableEntries; i++)
        if (eqmTable[i].getLocalName().compareTo(strValue) == 0)
        {
          rHndlr.eqm = eqmTable[i];
          return 0;
        }
      return 0; //TErrorList.non_existent_elem;
    }
  }
  class almwNormalValueHndlr implements csvHandler
  {
    private almwRowHndlr rHndlr;
    almwNormalValueHndlr(almwRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      String s;
      if (strValue.startsWith("!"))
      {
        rHndlr.normalIsAlarm = true;
        s = strValue.substring(1);
      }
      else
      {
        rHndlr.normalIsAlarm = false;
        s = strValue;       
      }
      rHndlr.normal = csv.getIntFromString(s);
      return 0; //TErrorList.non_existent_elem;
    }
  }
  int getAlarmWatchListFromFile(String eqmName)
  {
    FecCfg cfg = getFecXmlCfg();
    if (cfg != null)
    { // has parsed a fec.xml config file ...
      LinkedList<EqmCfg> el = cfg.getEqmList();
      for( EqmCfg ec : el )
      {
        if (ec.getName().compareTo(eqmName) == 0)
        {
          TEquipmentModule eqm = getEquipmentModule(eqmName);
          LinkedList<PropertyCfg> pl = ec.getPropertyList();
          AlarmCfg ac;
          TAlarmWatchThreshold awt;
          int mask;
          short fmt;
          for (PropertyCfg pc : pl)
          {
            fmt = (short)pc.getDTypeOut().getValue();
            if ((ac = pc.getAlarm()) == null) continue;           
            mask = ac.getValueMask().getValue();
            if (mask == 0)
            {
              awt = new TAlarmWatchThreshold(ac.getCountThreshold().getValue(),
                  ac.getValueHigh().getValue(),ac.getValueWarnHigh().getValue(),
                  ac.getValueLow().getValue(),ac.getValueWarnLow().getValue());
            }
            else
            {
              awt = new TAlarmWatchThreshold(ac.getCountThreshold().getValue(),
                  mask,ac.getValueNormal().getValue(),ac.isNormalAlarm());
            }
            TAlarmWatchEntry awe = new TAlarmWatchEntry(eqm,ac.getDeviceName(),
                pc.getName(),pc.getSizeOut().getValue(),fmt,
                pc.getDArrayTypeOut(),ac.getSystem().getValue(),
                ac.getSeverity().getValue(),ac.getCode().getValue(),
                ac.getCodeHigh().getValue(),ac.getCodeLow().getValue(),awt);       
            eqm.gAlarmWatchList.add(awe);
          }
          break;
        }
      }   
      return 0;
    }
    // no fec.xml : look for almwatch.csv ...
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    // register the csv database structure:
    csvColumn[] almwCols = new csvColumn[24];
    almwRowHndlr almwRows = new almwRowHndlr();
    almwCols[0] = new csvColumn("LOCAL_NAME","",new almwEqmHndlr(almwRows));
    almwCols[1] = new csvColumn("DEVICE_NAME","#0",new StringFieldHandler(almwRows,"dev"));
    almwCols[2] = new csvColumn("PROPERTY","",new StringFieldHandler(almwRows,"prp"));
    almwCols[3] = new csvColumn("SIZE","0",new IntFieldHandler(almwRows,"siz"));
    almwCols[4] = new csvColumn("FORMAT","null",new FormatFieldHandler(almwRows,"fmt"));
    almwCols[5] = new csvColumn("SEVERITY","0",new IntFieldHandler(almwRows,"sev"));
    almwCols[6] = new csvColumn("ALARM_SYSTEM","0",new IntFieldHandler(almwRows,"asys"));
    almwCols[7] = new csvColumn("COUNT_THRESHOLD","3",new IntFieldHandler(almwRows,"cnt"));
    almwCols[8] = new csvColumn("HIGH","1.0",new FloatFieldHandler(almwRows,"hi"));
    almwCols[9] = new csvColumn("LOW","0.0",new FloatFieldHandler(almwRows,"lo"));
    almwCols[10] = new csvColumn("HIGH_WARN","0.5",new FloatFieldHandler(almwRows,"hiwarn"));
    almwCols[11] = new csvColumn("LOW_WARN","0.1",new FloatFieldHandler(almwRows,"lowarn"));
    almwCols[12] = new csvColumn("MASK","0",new IntFieldHandler(almwRows,"mask"));
    almwCols[13] = new csvColumn("NORMAL","0",new almwNormalValueHndlr(almwRows));
    almwCols[14] = new csvColumn("SEVERITY_HIGH","0",new IntFieldHandler(almwRows,"hisev"));
    almwCols[15] = new csvColumn("SEVERITY_LOW","0",new IntFieldHandler(almwRows,"losev"));
    almwCols[16] = new csvColumn("SEVERITY_HIGHWARN","0",new IntFieldHandler(almwRows,"hiwarnsev"));
    almwCols[17] = new csvColumn("SEVERITY_LOWWARN","0",new IntFieldHandler(almwRows,"lowarnsev"));
    almwCols[18] = new csvColumn("ALARM_CODE","0",new IntFieldHandler(almwRows,"code"));
    almwCols[19] = new csvColumn("ALARM_CODE_HIGH","0",new IntFieldHandler(almwRows,"codeHigh"));
    almwCols[20] = new csvColumn("ALARM_CODE_LOW","0",new IntFieldHandler(almwRows,"codeLow"));
    almwCols[21] = new csvColumn("ALARM_TAG_HIGH","",new StringFieldHandler(almwRows,"hitag"));
    almwCols[22] = new csvColumn("ALARM_TAG_LOW","",new StringFieldHandler(almwRows,"lotag"));
    almwCols[23] = new csvColumn("FILTER","",new StringFieldHandler(almwRows,"filter"));
   
    // open it
    csv almwFile = new csv(initializer.getAlmWatchResource(eqmName));
    // read it
    int rc = almwFile.readFile(almwCols,almwRows);
    // close it
    almwFile.close();
    if (rc != TErrorList.no_such_file)
      TFecLog.log("get Alarm Watch information from almwatch.csv : " + TErrorList.errorString[rc]);
    return 0;
  }
  // csv HISTORY handlers:
  class hstRowHndlr implements RowHandler
  { // the Row Handler will be called when all columns have been read in
    private TEquipmentModule eqm;
    public String dev;
    public String prp;
    public int  siz;
    public int  fmt;
    public int  atyp;              /* array type */
    public int  pollingRate;               /* polling rate in sec */
    public int  archiveRate;               /* archive rate in sec */
    public int  depthShort;             /* for short term storage */
    public int  depthLong;               /* for long term storage */
    public int  heartbeat;            /* archive heartbeat in sec */
    public float pTolerance;                 /* percent tolerance */
    public float aTolerance;                /* absolute tolerance */
    public int  recordIndex;            /* local index identifier */
    public String redirString;
    public String filter = null;
    private boolean skip = false;
    LinkedList<THistoryRecord> reclst = null;
    public hstRowHndlr()
    {     
    }
    public hstRowHndlr(LinkedList<THistoryRecord> lst)
    {
      reclst = lst;
    }
    public int process(int index)
    {
      if (skip) return 0;
      if (eqm == null) return TErrorList.non_existent_elem;
      if (reclst == null) reclst = eqm.gLclHstList;
      THistorySpecification spc =
        new THistorySpecification(pollingRate,archiveRate,depthShort,depthLong,heartbeat,pTolerance,aTolerance,redirString);
      if (!THistoryRecord.isAllowableFormat((short)fmt))
      {
        TFecLog.log("history format " + TFormat.toString((short)fmt) + " read from database not archiveable");
        return 0;
      }
      THistoryRecord hst = new THistoryRecord(eqm,dev,prp,siz,fmt,atyp,recordIndex,spc);
      if (filter != null && filter.length() > 0)
      { // try to instantiate a filter
        try
        {
          hst.setFilter(new TFilterLink(filter));
        }
        catch (Exception e)
        {
          TFecLog.log("hstRowHndlr","cannot apply filter "+filter+" to history record : "+e.getMessage());
        }
      }
      if (eqm.isUseMSecHistoryTimestamps()) hst.setUseMinimalStorage(false);
      if (eqm.isUseMonthlyHistoryFiles()) hst.setUseMonthlyHistoryFiles(true);
      reclst.add(hst);
      if (spc.getDepthLong() <= 0 && spc.getDepthShortInSeconds() > 3600 * 6)
      { // no long term storage and more than 6 hours worth of short-term !
        hst.needsToFillSTS = true;
      }
      return 0;
    }
  }
  class hstEqmHndlr implements csvHandler
  {
    private hstRowHndlr rHndlr;
    hstEqmHndlr(hstRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      boolean pendingFound = false;
      for (int i=0; i<numEqmTableEntries; i++)
      {
        if (eqmTable[i].getLocalName().compareTo(strValue) == 0)
        {
          rHndlr.eqm = eqmTable[i];
          return 0;
        }
        if (pendingEqm == eqmTable[i]) pendingFound = true;
      }
      if (!pendingFound && pendingEqm != null)
      { // early access
        rHndlr.eqm = pendingEqm;
        return 0;
      }
      return TErrorList.non_existent_elem;
    }
  }
  class hstExpHndlr implements csvHandler
  {
    private String target;
    private hstRowHndlr rHndlr;
    hstExpHndlr(hstRowHndlr rowHndlr,String name) { rHndlr = rowHndlr; target = name; }
    public int process(String strValue,int index)
    {
      if (target == null || target.length() == 0 || strValue.compareTo(target) == 0)
      { // found it, halt iteration !
        rHndlr.skip = false;
      }
      else
      {
        rHndlr.skip = true;
      }
      return 0;
    }
  }
  class hstPollHndlr implements csvHandler
  {
    private hstRowHndlr rHndlr;
    hstPollHndlr(hstRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      try
      {
        rHndlr.pollingRate = Integer.parseInt(strValue);
        if (rHndlr.pollingRate < 20) rHndlr.pollingRate *= 1000; // was thinking in secs
      }
      catch (NumberFormatException e)
      {
        rHndlr.pollingRate = 1000;
      }
      return 0;
    }
  }
  class hstArchHndlr implements csvHandler
  {
    private hstRowHndlr rHndlr;
    hstArchHndlr(hstRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      try
      {
        rHndlr.archiveRate = Integer.parseInt(strValue);
        if (rHndlr.archiveRate < 20) rHndlr.archiveRate *= 1000; // was thinking in secs
      }
      catch (NumberFormatException e)
      {
        rHndlr.archiveRate = 10000;
      }
      return 0;
    }
  }
  class hstTolHndlr implements csvHandler
  {
    private hstRowHndlr rHndlr;
    hstTolHndlr(hstRowHndlr rowHndlr) { rHndlr = rowHndlr; }
    public int process(String strValue,int index)
    {
      int i=0;
      try
      {
        if ((i=strValue.indexOf('%')) == -1)
        { // absolute tolerance !
          rHndlr.aTolerance = Float.parseFloat(strValue);
          rHndlr.pTolerance = 0;
        }
        else
        {
          rHndlr.aTolerance = 0;
          rHndlr.pTolerance = Float.parseFloat(strValue.substring(0,i-1));
        }
      }
      catch (NumberFormatException e)
      {
        rHndlr.pTolerance = 0;
        rHndlr.aTolerance = 0;
      }
      return 0;
    }
  }
  int getHistoryListFromFile(String eqmName)
  {
    FecCfg cfg = getFecXmlCfg();
    if (cfg != null)
    { // has parsed a fec.xml config file ...
      LinkedList<EqmCfg> el = cfg.getEqmList();
      for( EqmCfg ec : el )
      {
        if (ec.getName().compareTo(eqmName) == 0)
        {
          TEquipmentModule eqm = getEquipmentModule(eqmName);
          LinkedList<PropertyCfg> pl = ec.getPropertyList();
          HistoryCfg hc;
          THistoryRecord hst;
          THistorySpecification spc;
          short fmt;
          for (PropertyCfg pc : pl)
          {
            fmt = (short)pc.getDTypeOut().getValue();
            if (!THistoryRecord.isAllowableFormat(fmt))
            {
              TFecLog.log("history format " + TFormat.toString(fmt) + " read from database not archiveable");
              continue;
            }
            hc = pc.getHistory();
            if (hc == null) continue;
            spc = new THistorySpecification(
               hc.getPollRate().getValue(),hc.getArchiveRate().getValue(),
               hc.getDepthShort().getValue(),hc.getDepthLong().getValue(),
               hc.getHeartbeat().getValue(),
               hc.getTolerance().getTolerancePercent(),
               hc.getTolerance().getToleranceAbsolute(),
               hc.getRedirection());
            hst = new THistoryRecord(eqm,hc.getDeviceName(),pc.getName(),
                pc.getSizeOut().getValue(),fmt,pc.getDArrayTypeOut(),
                hc.getIndex().getValue(),spc);
            if (eqm.isUseMinimalStorage()) hst.setUseMinimalStorage(true);
            if (eqm.isUseMonthlyHistoryFiles()) hst.setUseMonthlyHistoryFiles(true);
            eqm.gLclHstList.add(hst);
          }
          break;
        }
      }   
      return 0;
    }
    // no fec.xml : look for exports.csv ...
    TInitializer initializer = TInitializerFactory.getInstance().getInitializer();
    // register the csv database structure:
    csvColumn[] hstCols = new csvColumn[14];
    hstRowHndlr hstRows = new hstRowHndlr();
    hstCols[0] = new csvColumn("INDEX","1",new IntFieldHandler(hstRows,"recordIndex"));
    hstCols[1] = new csvColumn("LOCAL_NAME","",new hstEqmHndlr(hstRows));
    hstCols[2] = new csvColumn("PROPERTY","",new StringFieldHandler(hstRows,"prp"));
    hstCols[3] = new csvColumn("DEVICE","#0",new StringFieldHandler(hstRows,"dev"));
    hstCols[4] = new csvColumn("DATA_LENGTH","1",new IntFieldHandler(hstRows,"siz"));
    hstCols[5] = new csvColumn("FORMAT","float",new FormatFieldHandler(hstRows,"fmt"));
    hstCols[6] = new csvColumn("HEARTBEAT","900",new IntFieldHandler(hstRows,"heartbeat"));
    hstCols[7] = new csvColumn("POLLING_RATE","1000",new hstPollHndlr(hstRows));
    hstCols[8] = new csvColumn("ARCHIVE_RATE","1000",new hstArchHndlr(hstRows));
    hstCols[9] = new csvColumn("TOLERANCE","0%",new hstTolHndlr(hstRows));
    hstCols[10] = new csvColumn("SHORT_DEPTH","600",new IntFieldHandler(hstRows,"depthShort"));
    hstCols[11] = new csvColumn("LONG_DEPTH","1",new IntFieldHandler(hstRows,"depthLong"));
    hstCols[12] = new csvColumn("REMOTE_SERVER","",new StringFieldHandler(hstRows,"redirString"));
    hstCols[13] = new csvColumn("FILTER","",new StringFieldHandler(hstRows,"filter"));
    // open it
    csv hstFile = new csv(initializer.getHistoryResource(eqmName,null));
    // read it
    int rc = hstFile.readFile(hstCols,hstRows);
    // close it
    hstFile.close()
    if (rc != TErrorList.no_such_file)
      TFecLog.log("get local history information from history.csv : " + TErrorList.errorString[rc]);
    return 0;
  }
  public int getHistoryRecordIndexFromManifest(String eqm,String prp,String dev,int fmt,int siz)
  {
    LinkedList<THistoryRecord> lst = new LinkedList<THistoryRecord>()
    csvColumn[] hstCols = new csvColumn[13];
    hstRowHndlr hstRows = new hstRowHndlr(lst);
    hstCols[0] = new csvColumn("INDEX","1",new IntFieldHandler(hstRows,"recordIndex"));
    hstCols[1] = new csvColumn("LOCAL_NAME","",new hstEqmHndlr(hstRows));
    hstCols[2] = new csvColumn("PROPERTY","",new StringFieldHandler(hstRows,"prp"));
    hstCols[3] = new csvColumn("DEVICE","#0",new StringFieldHandler(hstRows,"dev"));
    hstCols[4] = new csvColumn("DATA_LENGTH","1",new IntFieldHandler(hstRows,"siz"));
    hstCols[5] = new csvColumn("FORMAT","float",new FormatFieldHandler(hstRows,"fmt"));
    hstCols[6] = new csvColumn("HEARTBEAT","900",new IntFieldHandler(hstRows,"heartbeat"));
    hstCols[7] = new csvColumn("POLLING_RATE","1000",new hstPollHndlr(hstRows));
    hstCols[8] = new csvColumn("ARCHIVE_RATE","1000",new hstArchHndlr(hstRows));
    hstCols[9] = new csvColumn("TOLERANCE","0%",new hstTolHndlr(hstRows));
    hstCols[10] = new csvColumn("SHORT_DEPTH","600",new IntFieldHandler(hstRows,"depthShort"));
    hstCols[11] = new csvColumn("LONG_DEPTH","1",new IntFieldHandler(hstRows,"depthLong"));
    hstCols[12] = new csvColumn("REMOTE_SERVER","",new StringFieldHandler(hstRows,"redirString"));
    // open it
    csv hstFile = new csv(initializer.getHistoryResource(eqm,"hstmf.csv"));
    // read it
    int rc = hstFile.readFile(hstCols,hstRows);
    // close it
    hstFile.close();
    if (rc == 0)
    {
      THistoryRecord hr;
      Iterator<THistoryRecord> it = lst.iterator();
      while (it.hasNext())
      {
        hr = it.next();
        if (hr.getEqm().getLocalName().compareTo(eqm) != 0) continue;
        if (hr.getPrp().compareToIgnoreCase(prp) != 0) continue;
        if (hr.getDev().compareToIgnoreCase(dev) != 0) continue;
        if (hr.getFmt() != (byte)fmt) continue;
        if (hr.getArraySize() != siz) continue;
        return hr.getRcdIdx();
      }
    }
    return -1;
  }
  public int getNextHistoryRecordIndex()
  {
    int idx = 0, n;
    Iterator<THistoryRecord> itr;
    THistoryRecord hr;
    for (int i = 0; i < numEqmTableEntries; i++)
    {
      if (eqmTable[i] == null) continue;
      if (eqmTable[i].getModuleName().length() == 0) continue;
      itr = eqmTable[i].gLclHstList.iterator();
      while (itr.hasNext())
      {
        hr = itr.next();
        if ((n=hr.getRcdIdx()) > idx) idx = n;
      }
    }
    idx++;
    if (idx < THistoryRecord.HRS_FREE_RECORD_INDEX_OFFSET)
      idx += THistoryRecord.HRS_FREE_RECORD_INDEX_OFFSET;
    return idx;
  }
  public int getNextHistoryRecordIndex(String eqm,String prp,String dev,int fmt,int siz)
  {
    int idx = getHistoryRecordIndexFromManifest(eqm,prp,dev,fmt,siz);
    if (idx >= 0) return idx;
    return getNextHistoryRecordIndex();
  }
  public THistoryRecord getHistoryFromRecordIndex(int idx)
  {
    TEquipmentModule[] em = getEquipmentModuleTable();
    THistoryRecord[] hr;
    for (int i=0; i<em.length; i++)
    {
      hr = (THistoryRecord[])em[i].gLclHstList.toArray(new THistoryRecord[0]);
      if (hr == null) continue;
      for (int r=0; r<hr.length; r++)
      {
        if (hr[r].getRcdIdx() == idx) return hr[r];
      }
    }
    return null;
  }
  public int updateHstHomeManifest()
  {
    int cc = 0;
   
    return cc;
  }
  public class TEqmFactoryPipeThread extends Thread
  {
    TContractBucket tb = null;
    TEqmFactoryPipeThread(TContractBucket tbucket)
    {
      tb = tbucket;
      tb.active = true;
    }
    public synchronized void run()
    {
      MsgLog.log("TEqmFactoryBucketThread","EQM Factory Pipe Thread started ...",0,null,1);
      try
      {
        InputStream in = tb.getInputStream();
        StringBuffer cmdsb = new StringBuffer(64);
        char c = 0;
        int ibyte = 0;
        while (tb.active)
        {
          if (gSystemExitCondition) break;
          try
          {           
            if ((ibyte = in.read()) < 0) break;
            tb.touch();
            c = (char)ibyte;
            cmdsb.append(c);
            if (c == '\n')
            {
              TCommandList.parseCommand(cmdsb.toString());
              cmdsb = new StringBuffer(64);
            }
          }
          catch (IOException e) { continue; };
        }
      }
      catch (Exception e)
      {
        e.printStackTrace();
        MsgLog.log("TEqmFactoryBucketThread", e.toString(),TErrorList.code_failure,e,0);
      }
      finally
      {
        tb.active = false// definitely !
        systemCleanupDataStreams();
      }
    }
   
  }
  // subclass this here in the server factory as well as in the client factory
  public class TEqmFactoryBucketThread extends Thread
  {
    private int transport = 0;
    private TContractBucket tb;
    boolean isWaiting = false;
    TEqmFactoryBucketThread(TContractBucket tbucket)
    {
      transport = tbucket.getTransport();
      tb = tbucket;
      tb.active = true;
      String s = null;
      Socket sck = tb.getSocket();
      if (sck != null) s = new String("tcp port " + sck.getLocalPort());
      else s = new String("tcp unbound socket ?");
      this.setName("Equipment Module Factory " + s);
    }
    public synchronized void run()
    {
      byte[] wa = new byte[TTransport.UDP_BUFFER_SIZE];
      byte[] msgsizb = new byte[2];
      int nread, nleft, n, waptr, sizeptr=0;
      MsgLog.log("TEqmFactoryBucketThread","EQM Factory Bucket Thread started ...",0,null,1);
      try
      {
        while (tb.active)
        {
          if (gSystemExitCondition) break;
          isWaiting = true;
          try
          { // the socket blocks (both is and os) for 10 msec
            nread = tb.getInputStream().read(wa,0,TTransport.UDP_BUFFER_SIZE);
          }
          catch (InterruptedIOException e)
          { // should never happen ...
            continue;
          }
          isWaiting = false;
          if (nread  == -1)
          {
            MsgLog.log("TEqmFactoryBucketThread", "no bytes read on socket: de-activate !",TErrorList.code_failure,null,1);
            tb.isDeactivating = true;
            break;
          }
          tb.touch();
          nleft = nread;
          waptr = 0;
          int ptr = tb.getBucketPointer();
          int siz = tb.getBucketSize();
          byte[] buf = tb.getBucketBuffer();
          while (nleft > 0)
          {                   
            if (ptr == 0)
            {
              int count = nleft < 2 ? nleft : 2 - sizeptr;
              System.arraycopy(wa, waptr, msgsizb, sizeptr, count);
              sizeptr += count;
              if (sizeptr >= 2
              {// reset
                sizeptr = 0;
              }
              else
              { // only a couple of bytes have dribbled in ...
                break;
              }
              //System.arraycopy(wa,waptr,msgsizb,0,2);
              siz = Swap.Short(new DataInputStream(new ByteArrayInputStream(msgsizb)).readShort());
              tb.setBucketSize(siz); // necessary ?
            }
            n = siz - ptr;
            if (n < 0 || waptr < 0 || ptr < 0)
            {
              MsgLog.log("TEqmFactoryBucketThread", "invalid copy parameters: payload ptr "+waptr+", bucket pointer "+ptr+", length "+n,TErrorList.tcp_socket_error,null,1);
              ptr = 0;
              nleft = 0;
              tb.setBucketPointer(ptr);
              continue;
            }
            if (nleft >= n// at least enough
            {
              if (ptr + n > buf.length)
              { // this shouldn't happen, but it seems to ?
                MsgLog.log("TEqmFactoryBucketThread", "copy "+n+" bytes at "+ptr+" would overflow buffer of "+buf.length+" bytes",TErrorList.tcp_socket_error,null,1);
                nleft = n =  buf.length - ptr;
              }
              System.arraycopy(wa,waptr,buf,ptr,n);
              gLinkTablesAccessed = true;
              TClient tc = LocateClientInList(tb.getBucketEndpoint(),tb.getBucketPort());
              tc.inetProtocol = (short)transport;
              tc.output = tb.getOutputStream();
              tc.sck = tb.getSocket();
              tc.IPport = (short)tc.sck.getPort();
              getRequest(tc, buf);
              gLinkTablesAccessed = false;
              waptr += n;                 // move the read buffer pointer along
              nleft -= n;                 // decrement what's remaining to be read
              ptr = 0;                 // reset the bucket pointer
            }
            else
            {
              System.arraycopy(wa,waptr,buf,ptr,nleft);
              ptr += nleft;
              nleft = 0;
            }
            tb.setBucketPointer(ptr);
            if (waptr >= TPacket.MAX_DATAGRAM_SIZE && nleft != 0)
            { // should not be possible, but it's happened !
              MsgLog.log("TBucketThread","payload pointer "+waptr+" past end; last set: "+n+" bytes",TErrorList.code_failure,null,0);
              ptr = 0;
              nleft = 0;
            }
          }                   
        }
        tb.getInputStream().close();
        tb.getOutputStream().close();
      }
      catch (IOException e)
      {
        if (debugLevel > 1) DbgLog.log("TEqmFactoryBucketThread","EQM Bucket IOException : " + e);
      }
      catch (Exception e)
      {
        e.printStackTrace();
        MsgLog.log("TEqmFactoryBucketThread", e.toString(),TErrorList.code_failure,e,0);
      }
      finally
      {
        tb.active = false// definitely !
      }
    }
  }
  public class TContractBucket extends TBucket
  {
    private int transport;
    public int getTransport() { return transport; }
    TContractBucket(Socket socket,int transport)
    {
      this.transport = transport;
      if (transport == TTransport.PIPE)
      {
        initBucket(socket,new TEqmFactoryPipeThread(this),TPacket.MAX_DATAGRAM_SIZE);       
      }
      else
      {
        initBucket(socket,new TEqmFactoryBucketThread(this),TPacket.MAX_DATAGRAM_SIZE);
      }
      activate();
    }
  }
  public static long getDataTimeStampOffset()
  {
    return gDataTimeStampOffset;
  }
  public static void setDataTimeStampOffset(long offset)
  {
    gDataTimeStampOffset = offset;
  }
  public int systemScheduleProperty(TEquipmentModule eqm,String prp)
  {
    String dev = null;
    int i, dn = -1;
    if ((i=prp.indexOf('<')) >= 0)
    {
      prp = prp.substring(0,i);
      dev = prp.substring(i+1);
      if ((i=dev.indexOf('>')) >= 0) dev = dev.substring(0, i);
    }
    if (eqm == null || eqm.hasProperty(prp) == false)
    {
      if (debugLevel > 0)
        DbgLog.log("systemScheduleProperty","attempt to schedule property " + prp + "refused (not a registered property)!");
      return TErrorList.illegal_property;
    }
    if (dev != null) dn = eqm.deviceList.getDeviceNumber(dev);
    int listsize;
    boolean needsHistoryCycle = false;
    boolean needsScheduler = false;
    TContractTable tct = null;
    synchronized (conTable)
    {
      listsize = conTable.size();
      if (debugLevel > 2) DbgLog.log("systemScheduleProperty","Property " + prp + " is being scheduled");
      for (i=0; i<listsize; i++)
      {
        tct = (TContractTable) conTable.get(i);
        if (tct.expired) continue;
        if (TAccess.isWrite(tct.contract.dataAccess)) continue;
        if (eqm.getLocalName().compareTo(tct.contract.eqmName) != 0) continue;
        if (dn >= 0 && 
            dn != eqm.deviceList.getDeviceNumber(tct.contract.eqmDeviceName)) continue;
        if (tct.nclients > 0 && tct.contract.eqmProperty.compareToIgnoreCase(prp) == 0)
        { /* guarantee immediate execution of any contract using this property */
          tct.lasttime = PRP_SCHEDULE_SIGNAL;
          needsScheduler = true;
          if (eqm.prpSigHdlrList.size() > 0)
            eqm.sendPropertySignal(prp, tct.contract, TPropertySignal.PS_SCHEDULED, 0);
        }
      }
    }
    if (needsScheduler) doScheduler(null,null);
    THistoryRecord lhr = null;
    listsize = eqm.gLclHstList.size();
    for (i=0; i<listsize; i++)
    {
      lhr = (THistoryRecord)eqm.gLclHstList.get(i);
      if (eqm.getLocalName().compareTo(lhr.getEqm().getLocalName()) != 0) continue;
      if (dn >= 0 && 
          dn != eqm.deviceList.getDeviceNumber(lhr.getDev())) continue;
      if (lhr.getPrp().compareToIgnoreCase(prp) == 0)
      {
        needsHistoryCycle = true;
        lhr.setScheduled(true);
      }
    }
    if (needsHistoryCycle) eqm.historyCycle(true);
    return 0;
  }
  private static LinkedList<TCycleTrigger> gCycTrgLst = new LinkedList<TCycleTrigger>();
  public static TCycleTrigger[] getCycleTriggerList()
  {
    return (TCycleTrigger[]) gCycTrgLst.toArray(new TCycleTrigger[0]);
  }
  /**
   * Registers a cycle trigger callback dispatch function
   *
   * If a CYCLER is running in a server's context, then the server will receive 'Cycle Number' events
   * scheduled by the designated CYCLER server.
   * The cycle number will make use of the 'System Data Stamp' to tag all data sets obtained from the
   * server.  A server can also register a trigger function dispatch routine (or routines) to be called when a 'Cycle
   * Number' event occurs.  The dispatch routines will be called prior to setting the 'System Stamp' to the new
   * Cycle Number, which will be set following the execution of all dispatch routines.  Optionally, the server can
   * provide a property (or list of properties) to be scheduled following the dispatch execution.  This will ensure
   * that such properties will be called immediately following dispatch execution AND contain the most recent Cycle Number
   * as the 'System Data Stamp'.
   *
   * @param trigger is a reference to the dispatch handler to be called when a new cycle number arrives.
   *
   * @return 0 upon success or TErrorList.already_assigned
   *
   * \b Example:
   * @include eg_cycleTrigger.java
   */
  public int registerCycleTrigger(TCycleTrigger trigger)
  {
    if (gCycTrgLst.contains(trigger)) return TErrorList.already_assigned;
    gCycTrgLst.add(trigger);
    return 0;
  }
  public int unregisterCycleTrigger(TCycleTrigger trigger)
  {
    if (!gCycTrgLst.contains(trigger)) return TErrorList.un_allocated;
    gCycTrgLst.remove(trigger);
    return 0;
  }
  private boolean useGlobalSynchronization = true;
  private boolean useCycleTrigger = true;
  public void setUseGlobalSynchronization(boolean value)
  {
    useGlobalSynchronization = value;
  }
  private static double[] gSyncTimeStamp = new double[1];
  public static double getSyncTimeStamp() { return gSyncTimeStamp[0]; }
  private boolean gCycleTriggerStarted = false;
  private boolean gUseDcsEvnSck = false;
  private InetAddress gCyclerAddr = null;
  private static int[] gCycleNumber = new int[1];
  public static int getCycleNumber() { return gCycleNumber[0]; }
  private boolean assertCycleTriggerExists()
  {
    if (gCyclerNumberKey == null) return false;
    if (gFecContext == null || gFecContext.length() == 0) return false;
    String[] parts = gCyclerNumberKey.split("/");
    if (parts.length != 4) return false; // MUST be of the form '/context/server/keyword'
    String cn = parts[1];
    String sn = parts[2];
    for (int i = 0; i<numEqmTableEntries; i++)
    {
      if (eqmTable[i].getLocalName() == null) continue; // should not be possible
      if (eqmTable[i].getContext() == null) continue; // should not be possible
      if (eqmTable[i].getExportName() == null) continue; // should not be possible
      if (eqmTable[i].getContext().compareToIgnoreCase(cn) != 0) continue;
      if (eqmTable[i].getExportName().compareToIgnoreCase(sn) == 0)
      {
        TFecLog.log("this server is the machine cycler for context " +
            gFecContext + " : not listening for machine cycles");
        return false;
      }
      TSrvEntry trgsrv = new TSrvEntry(sn,cn);
      if (trgsrv.fecAddr != null)
      {
        TFecLog.log("a machine cycler exists for context " + cn);
        if (trgsrv.isDoocsSrv()) gUseDcsEvnSck = true;
        gCyclerAddr = trgsrv.getFecAddr().fecHost;
        return true;
      }
    }
    return false;
  }
  private static int cycleTriggerDeadBand = 1000;
  public int getCycleTriggerDeadBand() { return cycleTriggerDeadBand; }
  public void setCycleTriggerDeadBand(int value)
  {
    if (value < 100) value = 100;
    cycleTriggerDeadBand = value;
  }
  private String gCyclerNumberKey = null;
  public void setCyclerNumberKey(String key)
  {
    gCyclerNumberKey = new String(key);
  }
  public String getCycleNumberKey()
  {
    return gCyclerNumberKey;
  }
  private int systemStartCycleTrigger()
  {
    if (gCycleTriggerStarted) return 0;
    gCyclerNumberKey = System.getProperty("tine.cycle.key");
    if (gCyclerNumberKey == null) System.getenv("TINE_CYCLE_KEY");
    if (gCyclerNumberKey == null)
    {
      gCyclerNumberKey = "/"+gFecContext+"/CYCLER/CycleNumber";
    }
    if (!assertCycleTriggerExists()) return TErrorList.non_existent;
    if (gUseDcsEvnSck)
    {
      TLinkFactory.startDcsEvnListening(gCyclerAddr.getHostAddress());
    }
    else
    { // normal case
      TDataType sdt = new TDataType(gCycleNumber);
      String[] parts = gCyclerNumberKey.split("/");
      String cn = parts[1];
      String sn = parts[2];
      String kn = parts[3];
      String srv = "/"+cn+"/"+sn;
      TLink tl = new TLink(srv,kn,sdt,null,TAccess.CA_READ);
      int id = tl.attach(TMode.CM_GLOBAL, new TCycleTriggerCallback(), cycleTriggerDeadBand);
      if (id < 0) return -id;
    }
    gCycleTriggerStarted = true;
    return 0;
  }
  public static int getBurstLimit()
  {
    return gBurstLimit;
  }
  public static void setBurstLimit(int burstLimit)
  {
    gBurstLimit = burstLimit;
  }
  public boolean isExitOnShutdown()
  {
    return exitOnShutdown;
  }
  public void setExitOnShutdown(boolean exitOnShutdown)
  {
    this.exitOnShutdown = exitOnShutdown;
  }
  public void setFecLinkErrorAlarm(TLink lnk,int mode)
  {
    if (eqmTable == null || eqmTable[0] == null || lnk == null) return;
    TEquipmentModule eqm = eqmTable[0]; // take the first one
    TDevice d = eqm.getDeviceList().getDevice(0);
    if (d == null) return;
    short flags = TAlarmDescriptor.NEW;
    if (mode == TMode.CM_SINGLE) flags = TAlarmDescriptor.TRANSIENT;
    d.setAlarm(TAlarm.encodeLinkErrorAlarm(lnk.linkId), lnk.getFullDeviceNameAndProperty().getBytes(), flags);
  }
  public void clearFecLinkErrorAlarm(TLink lnk)
  {
    if (eqmTable == null || eqmTable[0] == null) return;
    TEquipmentModule eqm = eqmTable[0]; // take the first one
    TDevice d = eqm.getDeviceList().getDevice(0);
    if (d == null) return;
    d.removeAlarm(TAlarm.encodeLinkErrorAlarm(lnk.linkId));
  }
}
TOP

Related Classes of de.desy.tine.server.equipment.TEquipmentModuleFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.