/******************************************************************************* * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2002 * Copyright by ESO (in the framework of the ALMA collaboration) * and Cosylab 2002, All rights reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package alma.ACS.impl; import java.lang.reflect.Array; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import java.util.logging.Logger; import org.omg.CORBA.BAD_PARAM; import org.omg.CORBA.NO_RESOURCES; import org.omg.PortableServer.Servant; import alma.ACS.CBDescIn; import alma.ACS.CBvoid; import alma.ACS.Callback; import alma.ACS.Monitor; import alma.ACS.MonitorHelper; import alma.ACS.MonitorOperations; import alma.ACS.NoSuchCharacteristic; import alma.ACS.OffShootOperations; import alma.ACS.TimeSeqHolder; import alma.ACS.jbaci.BACIAction; import alma.ACS.jbaci.BACIPriority; import alma.ACS.jbaci.CallbackDispatcher; import alma.ACS.jbaci.CompletionUtil; import alma.ACS.jbaci.DataAccess; import alma.ACS.jbaci.MemoryDataAccess; import alma.ACS.jbaci.PropertyInitializationFailed; import alma.ACSErr.Completion; import alma.ACSErr.CompletionHolder; import alma.ACSErrTypeCommon.wrappers.AcsJCouldntPerformActionEx; import alma.ACSErrTypeCommon.wrappers.AcsJUnknownEx; import alma.acs.exceptions.AcsJException; /** * Implementation of common property, i.e. type of <code>java.lang.Object</code>. * @author <a href="mailto:matej.sekoranjaATcosylab.com">Matej Sekoranja</a> * @version $id$ */ public abstract class CommonPropertyImpl extends TypelessPropertyImpl implements CallbackDispatcher { /** * Logger variable */ protected Logger m_logger; /** * Default timer trigger (in 100ns units). */ protected long defaultTimerTrigger; /** * Min timer trigger (in 100ns units). */ protected long minTimerTrigger; /** * Default value. */ protected Object defaultValue; /** * Read-only data access. */ protected DataAccess dataAccess; /** * Property <code>Class</code> type. */ protected Class propertyType; /** * History size, if 0 history is disabled. */ protected int historySize; /** * Pointer in history arrays, points to first empty element. */ protected int historyPosition; /** * <code>true</code>, if values in history arrays exceed end of arrays (circular arrays). */ protected boolean historyTurnaround; /** * Array of history values. */ protected Object historyValue; /** * Array of history times (OMG standard time). */ protected long[] historyTime; /** * List of all property monitors (needed on property destruction). */ protected Map monitors; /************ [ Mnemonic value retrival mechanism ] ************/ /** * Mnemonic read time key of (last) pending read. */ protected long mnemonicReadPending = 0; /** * Mnemonic read lock (dummy object). */ protected Object mnemonicValueRetrival = new Object(); /** * Mnemonic variables lock. */ protected ReadWriteLock mnemonicDataLock = new ReentrantReadWriteLock(); /** * Time "key" (Java) if when last mnemonic retrival. */ protected long mnemonicTime; /** * Value of latest mnemonic value retrival. */ protected Object mnemonicValue; /** * Completion of latest mnemonic value retrival. */ protected Completion mnemonicCompletion; /***************************************************************/ /** * Constructor with memory data access. * @param propertyType property <code>Class</code> type, non-<code>null</code>. * @param name property name, non-<code>null</code>. * @param parentComponent parent component, non-<code>null</code>. * @throws PropertyInitializationFailed exception is thrown on failure */ public CommonPropertyImpl( Class propertyType, String name, CharacteristicComponentImpl parentComponent) throws PropertyInitializationFailed { this(propertyType, name, parentComponent, new MemoryDataAccess()); m_logger = parentComponent.getComponentContainerServices().getLogger(); } /** * Constructor. * @param propertyType property <code>Class</code> type, non-<code>null</code>. * @param name property name, non-<code>null</code>. * @param parentComponent parent component, non-<code>null</code>. * @param dataAccess read-write data access to be use, non-<code>null</code>. * @throws PropertyInitializationFailed exception is thrown on failure */ public CommonPropertyImpl( Class propertyType, String name, CharacteristicComponentImpl parentComponent, DataAccess dataAccess) throws PropertyInitializationFailed { super(name, parentComponent); this.propertyType = propertyType; this.dataAccess = dataAccess; readCharacteristics(); m_logger = parentComponent.getComponentContainerServices().getLogger(); // TODO to be configurable historySize = 32; historyPosition = 0; historyTurnaround = false; historyTime = new long[historySize]; historyValue = Array.newInstance(propertyType, historySize); // initialize data access, if required if (dataAccess.initializeValue()) { try { CompletionHolder completionHolder = CompletionUtil.createCompletionHolder(); dataAccess.set(defaultValue, completionHolder); } catch (Throwable th) { m_logger.log(Level.WARNING, "jBaci::CommonPropertyImpl::CommonPropertyImpl - Cannot create Completion Holder"); throw new NO_RESOURCES(th.getMessage()); } } // create monitor data structure monitors = new HashMap(); // create history monitor registerNonCorbaMonitor(new HistoryMonitorImpl(this)); } /** * Read property characteristics. * @throws PropertyInitializationFailed exception is thrown on failure */ public void readCharacteristics() throws PropertyInitializationFailed { super.readCharacteristics(); try { // defaultTimerTrigger = characteristicModelImpl.getLong("default_timer_trig"); // minTimerTrigger = characteristicModelImpl.getLong("min_timer_trig"); defaultTimerTrigger = (long)(characteristicModelImpl.getDouble("default_timer_trig")*10000000L); minTimerTrigger = (long)(characteristicModelImpl.getDouble("min_timer_trig")*10000000L); defaultValue = readPropertyTypeCharacteristic("default_value"); } catch (Throwable t) { throw new PropertyInitializationFailed("Failed to read all property characteristics.", t); } } /** * Get property data access layer. * @return property data access. */ public DataAccess getDataAccess() { return dataAccess; } /** * @see alma.ACS.PropertyImpl#destroy() */ public void destroy() { super.destroy(); // destroy all monitors if (monitors.size() != 0) { MonitorOperations[] monitorArray = null; synchronized (monitors) { monitorArray = new MonitorOperations[monitors.size()]; monitors.keySet().toArray(monitorArray); } for (int i = 0; i < monitorArray.length; i++) { try { monitorArray[i].destroy(); } catch (Throwable th) { // TODO log m_logger.log(Level.WARNING, "jBaci::CommonPropertyImpl::destroy - cannot destroy monitorArray[]."); throw new NO_RESOURCES(th.getMessage()); } } } } /*********************** [ P<type> ] ***********************/ /** * @see alma.ACS.P<type>Operations#default_timer_trigger() */ public long default_timer_trigger() { return defaultTimerTrigger; } /** * @see alma.ACS.P<type>Operations#min_timer_trigger() */ public long min_timer_trigger() { return minTimerTrigger; } /*********************** [ P<type> helpers ] ***********************/ /** * Read property type characteristic. * @throws NoSuchCharacteristic is thrown if characterstic does not exist. */ public abstract Object readPropertyTypeCharacteristic(String name) throws NoSuchCharacteristic; /** * Add value to the history. * @param value value to be added to the history. * @param timestamp value timestamp (OMG) to be added to the history. */ protected void addValueToHistory(Object value, long timestamp) { // if history is disabled if (historySize == 0) return; synchronized (historyValue) { // add historyTime[historyPosition] = timestamp; if (propertyType.isPrimitive()) { if(propertyType.isAssignableFrom(double.class)) Array.setDouble(historyValue, historyPosition, ((Double)value).doubleValue()); else if (propertyType.isAssignableFrom(int.class)) Array.setInt(historyValue, historyPosition, ((Integer)value).intValue()); else if (propertyType.isAssignableFrom(long.class)) Array.setLong(historyValue, historyPosition, ((Long)value).longValue()); else if (propertyType.isAssignableFrom(short.class)) Array.setShort(historyValue, historyPosition, ((Short)value).shortValue()); else if (propertyType.isAssignableFrom(boolean.class)) Array.setBoolean(historyValue, historyPosition, ((Boolean)value).booleanValue()); else if (propertyType.isAssignableFrom(byte.class)) Array.setByte(historyValue, historyPosition, ((Byte)value).byteValue()); else if (propertyType.isAssignableFrom(float.class)) Array.setFloat(historyValue, historyPosition, ((Float)value).floatValue()); else if (propertyType.isAssignableFrom(char.class)) Array.setChar(historyValue, historyPosition, ((Character)value).charValue()); else { m_logger.log(Level.WARNING, "jBaci::CommonPropertyImpl::addValueToHistory - Unhandled primitive."); throw new NO_RESOURCES("Unhandled primitive"); } } else Array.set(historyValue, historyPosition, value); // manage control variables historyPosition = ++historyPosition % historySize; if (!historyTurnaround && historyPosition == 0) historyTurnaround = true; } } protected Object getHistory(int lastValues, TimeSeqHolder timeSeqHolder) { // check bad parameter if (lastValues < 0) throw new BAD_PARAM("lastValues < 0"); synchronized (historyValue) { int length, first; if (historyTurnaround) { length = historySize; first = historyPosition; } else { length = historyPosition; first = 0; } // last n values (and not first n values) if (lastValues > length) lastValues = length; first = (first + length - lastValues) % historySize; // get required number of values if (lastValues < length) length = lastValues; timeSeqHolder.value = new long[length]; Object values = Array.newInstance(propertyType, length); // no history case if (length == 0) return values; // copy if (first + length < historySize) { System.arraycopy(historyTime, first, timeSeqHolder.value, 0, length); System.arraycopy(historyValue, first, values, 0, length); } else { int split = historySize - first; System.arraycopy(historyTime, first, timeSeqHolder.value, 0, split); System.arraycopy(historyValue, first, values, 0, split); System.arraycopy(historyTime, 0, timeSeqHolder.value, split, length - split); System.arraycopy(historyValue, 0, values, split, length - split); } return values; } } /** * @see alma.ACS.P<type>Operations#get_sync(alma.ACSErr.CompletionHolder) */ protected Object getSync(CompletionHolder completionHolder) throws AcsJException { try { Object retVal = dataAccess.get(completionHolder); // generate no-error completion, if not generated if (completionHolder.value == null) completionHolder.value = CompletionUtil.generateNoErrorCompletion(); return retVal; } catch (AcsJException acsex) { throw new AcsJCouldntPerformActionEx("Failed to retrieve value.", acsex); } } /** * BACI action to invoke <code>getSync</code> asynchroniously. */ protected class GetAsyncAction extends BACIAction { /** * @see alma.ACS.jbaci.BACIAction */ public GetAsyncAction(Callback callback, CBDescIn descIn) { super(getParentComponent(), callback, descIn, CommonPropertyImpl.this); } /** * @see alma.ACS.jbaci.BACIAction */ public GetAsyncAction( Callback callback, CBDescIn descIn, BACIPriority priority) { super(getParentComponent(), callback, descIn, CommonPropertyImpl.this, priority); } /** * @see alma.ACS.jbaci.BACIAction#execute() */ public Object execute() throws AcsJException { try { CompletionHolder completionHolder = CompletionUtil.createCompletionHolder(); Object retVal = getSync(completionHolder); // set completion this.completion = completionHolder.value; return retVal; } catch (AcsJException acsex) { // send default value in case of failure this.returnValue = defaultValue; throw new AcsJCouldntPerformActionEx("Failed to retrieve value asynchroniously.", acsex); } } } /** * @see alma.ACS.P<type>Operations#get_async(alma.ACS.CB<type>, alma.ACS.CBDescIn) */ protected void getAsync(Callback callback, CBDescIn desc) { new GetAsyncAction(callback, desc).submit(); } /** * Register monitor on this property (and optionally CORBA activate). * Registration is needed for monitor destruction on property destruction. * @param monitorImpl monitor implementation (e.g. class implementing <code>MonitorOperations</code> interface). * @param monitorServant monitor CORBA servant (e.g. Monitor<type>POATie class). If <code>null</code> monitor will * be treated as non-CORBA monitor and no CORBA activation will be done. * (Not sure how to correctly pass a null, now that the method uses generics. See {@link #registerNonCorbaMonitor(MonitorOperations)} * for an alternative call.) * @return CORBA activated monitor reference, <code>null</code> if <code>monitorServant == null</code>. */ public <T extends Servant & OffShootOperations> Monitor registerMonitor(MonitorOperations monitorImpl, T monitorServant) { // allow activation if already in monitor list... Monitor monitor = null; if (monitorServant != null) { try { // TODO pesistent, user ID activation monitor = MonitorHelper.narrow( parentComponent.getComponentContainerServices().activateOffShoot(monitorServant) ); } catch (Throwable th) { m_logger.log(Level.WARNING, "jBaci::CommonPropertyImpl::registerMonitor - Cannot activate Off Shoot with the monitorServant."); throw new NO_RESOURCES(th.getMessage()); } } // add to list synchronized (monitors) { if (!monitors.containsKey(monitorImpl)) monitors.put(monitorImpl, monitorServant); } return monitor; } /** * Register monitor on this property, without corba activation. * Registration is needed for monitor destruction on property destruction. * <p> * Note that this method was broken out from {@link #registerMonitor(MonitorOperations, Servant)} * to avoid passing null as the second argument of that method, which was in conflict with the generics * definition, which in turn was added there to match the one of ContainerServices.activateOffshoot. * * @param monitorImpl monitor implementation (e.g. class implementing <code>MonitorOperations</code> interface). * @return CORBA activated monitor reference, <code>null</code> if <code>monitorServant == null</code>. */ public Monitor registerNonCorbaMonitor(MonitorOperations monitorImpl) { // allow activation if already in monitor list... Monitor monitor = null; // add to list synchronized (monitors) { if (!monitors.containsKey(monitorImpl)) monitors.put(monitorImpl, null); } return monitor; } /** * Unregister monitor on this property (and optionally CORBA deactivate). * Should be called by <code>MonitorOperations.destroy()</code> method. */ public void unregisterMonitor(MonitorOperations monitorImpl) { Servant monitorServant = null; // remove from list synchronized (monitors) { monitorServant = (Servant)monitors.remove(monitorImpl); } // deativate CORBA monitor servant if (monitorServant != null) { try { parentComponent.getComponentContainerServices().deactivateOffShoot(monitorServant); } catch (Throwable th) { m_logger.log(Level.WARNING, "jBaci::CommonPropertyImpl::unregisterMonitor - Cannot deactivate Off Shoot with monitorServant"); throw new NO_RESOURCES(th.getMessage()); } } } /*********************** [ RW<type> helpers ] ***********************/ /** * @see alma.ACSErr.Completion alma.ACS.RW<type>Operations#set_sync(<type>) */ protected Completion setSync(Object value) throws AcsJException { try { CompletionHolder completionHolder = CompletionUtil.createCompletionHolder(); dataAccess.set(value, completionHolder); // generate no-error completion, if not generated if (completionHolder.value == null) completionHolder.value = CompletionUtil.generateNoErrorCompletion(); return completionHolder.value; } catch (AcsJException acsex) { throw new AcsJCouldntPerformActionEx("Failed to set value.", acsex); } } /** * BACI action to invoke <code>setSync</code> asynchronously. */ protected class SetAsyncAction extends BACIAction { /** * Value to be set. */ Object value; /** * @see alma.ACS.jbaci.BACIAction */ public SetAsyncAction(Object value, Callback callback, CBDescIn descIn) { super(getParentComponent(), callback, descIn, null); this.value = value; } /** * @see alma.ACS.jbaci.BACIAction */ public SetAsyncAction( Object value, Callback callback, CBDescIn descIn, BACIPriority priority) { super(getParentComponent(), callback, descIn, null, priority); this.value = value; } /** * @see alma.ACS.jbaci.BACIAction#execute() */ public Object execute() throws AcsJException { try { completion = setSync(value); } catch (AcsJException acsex) { throw new AcsJCouldntPerformActionEx("Failed to set value asynchroniously.", acsex); } // no return value return null; } } /** * @see alma.ACS.RW<type>Operations#get_async(<type>, alma.ACS.CBvoid, alma.ACS.CBDescIn) */ protected void setAsync(Object value, CBvoid callback, CBDescIn desc) { new SetAsyncAction(value, callback, desc).submit(); } /** * @see void alma.ACS.RW<type>Operations#set_nonblocking(<type>) */ protected void setNonblocking(Object value) { try { setSync(value); } catch (Throwable th) { // TODO log m_logger.log(Level.WARNING, "jBaci::CommonPropertyImpl::setNonblocking - Cannot setSync the value."); throw new NO_RESOURCES(th.getMessage()); } } /** * Mnemonic value retrival. * If <code>keyTime == mnemonicTime</code> cached mnemonic value is returned. * @param keyTime time (java) of mnemonic request. * @param completionHolder completion holder that will be given completion. * NOTE: completion is passsed by reference, so do not change its value, * copy its value before and do it on a local copy * @return current property value. * @see getSync */ // TODO implement test with many threads calling this method... public Object mnemonicValue(long keyTime, CompletionHolder completionHolder) { // // cache lookup // for (;;) { // read mnemonic data lock try { mnemonicDataLock.readLock().lock(); } catch (Throwable th) { completionHolder.value = mnemonicCompletion; return mnemonicValue; /* return old */ } try { // if same time or newer exist return cached value if (keyTime <= mnemonicTime) { completionHolder.value = mnemonicCompletion; return mnemonicValue; } } finally { mnemonicDataLock.readLock().unlock(); } // read value wait, if reading is already pending synchronized (mnemonicValueRetrival) { // lock if not newer read if (keyTime <= mnemonicReadPending) { try { mnemonicValueRetrival.wait(); } catch (InterruptedException ie) {} // re-read again continue; } else { mnemonicReadPending = keyTime; break; } } } // // value retrival // Object retValue = null; try { retValue = getSync(completionHolder); } catch (AcsJException acsex) { retValue = defaultValue; completionHolder.value = CompletionUtil.generateCompletion(acsex); } catch (Throwable th) { retValue = defaultValue; completionHolder.value = CompletionUtil.generateCompletion( new AcsJUnknownEx("Failed to retrieve value.", th)); } // // value memorization // // write mnemonic data lock try { mnemonicDataLock.writeLock().lock(); } catch (Throwable th) { return mnemonicValue; /* return old */ } try { if (keyTime > mnemonicTime) { mnemonicTime = keyTime; mnemonicValue = retValue; mnemonicCompletion = completionHolder.value; } return mnemonicValue; } finally { mnemonicDataLock.writeLock().unlock(); // read value wait release synchronized (mnemonicValueRetrival) { mnemonicValueRetrival.notifyAll(); } } } }