/******************************************************************************* * 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.util.concurrent.atomic.AtomicLong; import org.omg.CORBA.LongHolder; import alma.ACS.CBDescIn; import alma.ACS.Callback; import alma.ACS.MonitorOperations; import alma.ACS.jbaci.BACIDispatchAction; import alma.ACS.jbaci.BACIFramework; import alma.ACS.jbaci.BACIPriority; import alma.ACS.jbaci.BACITimer; import alma.ACS.jbaci.CompletionUtil; import alma.ACS.jbaci.PrioritizedRunnable; import alma.ACS.jbaci.BACIDispatchAction.DispatchRequest; import alma.ACSErr.Completion; import alma.ACSErr.CompletionHolder; import alma.acs.util.UTCUtility; /** * Implementation of common monitor. * @author <a href="mailto:matej.sekoranjaATcosylab.com">Matej Sekoranja</a> * @version $id$ */ public class CommonMonitorImpl implements MonitorOperations, BACITimer.TimerRunnable, PrioritizedRunnable, BACIDispatchAction.DispatchFailedListener { /** * Monitor timer task. */ protected Object monitorTimerTask; /** * Monitorized property. */ protected CommonPropertyImpl property; /** * Dispatch action. */ protected BACIDispatchAction dispatchAction; /** * Time trigger (monitoring interval in ms). */ protected long timeTrigger; /** * Start time (java time). */ protected long startTime; /** * Suspend/resume status. */ protected boolean isSuspended; /** * Destruction status. */ protected boolean isDestroyed; /** * Start time control status. */ protected boolean userControlledStartTime; /** * Key time to process, 0 means none. */ protected AtomicLong queuedKeyTime = new AtomicLong(0); /** * Default constructor. */ protected CommonMonitorImpl() { } /** * Constructor with immediate monitor notification (synchronized monitors supported). * @param property property to be monitored, non-<code>null</code>. * @param callback callback, non-<code>null</code>. * @param descIn callback in-descriptor. */ public CommonMonitorImpl(CommonPropertyImpl property, Callback callback, CBDescIn descIn) { this(property, callback, descIn, 0); } /** * Constructor. * @param property property to be monitored, non-<code>null</code>. * @param callback callback, non-<code>null</code>. * @param descIn callback in-descriptor. * @param startTime startTime (OMG time), values less or equal to current time mean immediately, * value 0 means that start time should be controlled automatically (synchronized monitors). */ public CommonMonitorImpl(CommonPropertyImpl property, Callback callback, CBDescIn descIn, long startTime) { if (property == null) throw new NullPointerException("property == null"); if (callback == null) throw new NullPointerException("callback == null"); this.property = property; this.startTime = startTime; // create dispatch action dispatchAction = new BACIDispatchAction(callback, descIn, property, getPriority()); // TODO // make override policy configurable per instance, perhaps using a finite queue... // Currently the ACS mastercomp module relies on having the following line commented out, // since otherwise a few state change notifications are discarded. // this happens when changing into an activity state, which shortly afterwards changes // to the next state, thus producing two change events separated by a few ms only. // dispatchAction.setOverridePolicy(true); dispatchAction.addDispatchFailedListener(this); // initializate and start initialize(); } /** * Initialize monitor. */ protected void initialize() { // initialize monitor variables isSuspended = false; isDestroyed = false; // start time control if (startTime == 0) userControlledStartTime = false; else { // user took over start time userControlledStartTime = true; startTime = UTCUtility.utcOmgToJava(startTime); } // set time trigger and reschedule setTimeTrigger(property.default_timer_trigger() / 10000); } /** * @see alma.ACS.jbaci.BACIDispatchAction.DispatchFailedListener#dispatchFailed(alma.ACS.jbaci.BACIDispatchAction, alma.ACS.jbaci.BACIDispatchAction.DispatchRequest) */ public void dispatchFailed( BACIDispatchAction action, DispatchRequest failedRequest) { // destroy itself destroy(); } /** * Schedule monitor using fixed rate interval <code>timeTrigger</code> * starting from <code>startTime</code> paramterer. */ public void schedule() { // cancel first... if (monitorTimerTask != null) // canceling is threadsafe (and can be done multiple times) BACITimer.cancel(monitorTimerTask); // do not reschedule, if suspended if (isSuspended || timeTrigger <= 0) return; // schedule monitorTimerTask = BACIFramework.INSTANCE.getTimer().executePeriodically(timeTrigger, this, startTime); } /** * Set time trigger. * If <code>userControlledStartTime == false<code> also aligns (fixes) start time * required for synchronized monitors. * @param timeTtrigger time trigger (monitor interval in ms), * if <= 0 monitoring is disabled but not resumed since * there still might be on-change trigger enabled. * <code>timeTrigger</code> is limited with lower bound * <code>property.min_timer_trigger()</code>. */ protected void setTimeTrigger(long timeInterval) { // noop if (timeTrigger == timeInterval) return; if (timeInterval <= 0) timeTrigger = 0; else if (timeInterval < (property.min_timer_trigger() / 10000)) timeTrigger = property.min_timer_trigger() / 10000; else timeTrigger = timeInterval; // realign start time alignStartTime(); // reschedule schedule(); } /** * If <code>userControlledStartTime == false<code> aligns (fixes) start time * required for synchronized monitors. */ protected void alignStartTime() { /* * synchronization of second intervals: 0.5, 1, 5, 10, 60, 300 * 0.5s is fired every half or round second * 1s is fired every round second * 5s is fired every 0, 5, 10, 15, 20, ... 55 second of a minute * 10s is fired every 0, 10, 20, 30, 40, 50 second of a minute * 60s is fired every round minute * 300s is fired every 0, 5, 10, 15, 20, ... 55 minute of an hour */ if (!userControlledStartTime && timeTrigger > 0) startTime = (System.currentTimeMillis()/timeTrigger + 1) * timeTrigger; } /** * @see alma.ACS.jbaci.PrioritizedRunnable#getPriority() */ public BACIPriority getPriority() { return BACIPriority.NORMAL; } /** * Timer implementation - it stores time when triggered * and delegates value retrival to <code>BACIExecutor</code>. * @see alma.ACS.jbaci.BACITimer.TimerRunnable#timeout(long) */ public void timeout(long timeToRun) { // if none is queued, initiate executor otherwise override old value if (queuedKeyTime.getAndSet(timeToRun) == 0) { property.getParentComponent().execute(CommonMonitorImpl.this); } } /** * @see java.lang.Runnable#run() */ public void run() { long keyTime = queuedKeyTime.getAndSet(0); retrieveValueAndDispatch(keyTime, false); } /** * Retrieve property value via cached <code>mnemonicValue</code> * and add response to <code>BACIDispatchAction<code>. */ protected void retrieveValueAndDispatch(long keyTime, boolean done) { // create new holder (done expeditiously) CompletionHolder completionHolder = CompletionUtil.createCompletionHolder(); // retrieve value Object value = property.mnemonicValue(keyTime, completionHolder); // TODO make a copy of completion Completion completion = CompletionUtil.cloneCompletion(completionHolder.value); // fix time... completion.timeStamp = UTCUtility.utcJavaToOmg(keyTime); // TODO // set monitor type and on-time code, if non-error type //completion.code = ACSErr.ACSErrMonitorOnTimer; // ... and dispatch if (!done) dispatchAction.dispatchWorkingRequest(completion, value); else dispatchAction.dispatchDoneRequest(completion, value); // TODO remove //System.out.println("Dispatched monitor (" + done + "): " + new java.util.Date(keyTime)); } /*********************** [ Monitor ] ***********************/ /** * @see alma.ACS.MonitorOperations#start_time() */ public long start_time() { return UTCUtility.utcJavaToOmg(startTime); } /** * @see alma.ACS.MonitorOperations#set_timer_trigger(long) */ public void set_timer_trigger(long timeInterval) { setTimeTrigger(timeInterval / 10000); } /** * @see alma.ACS.MonitorOperations#get_timer_trigger(org.omg.CORBA.LongHolder) */ public void get_timer_trigger(LongHolder timeIntervalHolder) { timeIntervalHolder.value = timeTrigger * 10000; } /** * @see alma.ACS.SubscriptionOperations#suspend() */ public synchronized void suspend() { // noop if (isSuspended) return; isSuspended = true; // cancel... if (monitorTimerTask != null) { // canceling is thradsafe (and can be done multiple times) BACITimer.cancel(monitorTimerTask); monitorTimerTask = null; } } /** * @see alma.ACS.SubscriptionOperations#resume() */ public synchronized void resume() { // noop if (!isSuspended) return; // fix startTime to next schedule time... if (!userControlledStartTime) alignStartTime(); else if (timeTrigger > 0) { // consider user start time... long now = System.currentTimeMillis(); startTime = startTime + ((now-startTime)/timeTrigger + 1) * timeTrigger; } // reschedule schedule(); isSuspended = false; } /** * @see alma.ACS.SubscriptionOperations#destroy() */ public synchronized void destroy() { // noop if (isDestroyed) return; isDestroyed = true; // remove listener not to be notified ever again if (dispatchAction != null) dispatchAction.removeDispatchFailedListener(this); if (!isSuspended) suspend(); // dispatch done request retrieveValueAndDispatch(System.currentTimeMillis(), true); // remove from property monitor list property.unregisterMonitor(this); } }