/* * Copyright (c) 2008 Stiftung Deutsches Elektronen-Synchrotron, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY. * * THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS. * WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE * IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR * CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. * NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. * DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, * OR MODIFICATIONS. * THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION, * USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS * PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY * AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM */ package org.csstudio.platform.internal.simpledal; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.csstudio.dal.Timestamp; import org.csstudio.platform.internal.simpledal.converters.ConverterUtil; import org.csstudio.platform.model.pvs.IProcessVariableAddress; import org.csstudio.platform.model.pvs.IProcessVariableAdressProvider; import org.csstudio.platform.model.pvs.ValueType; import org.csstudio.platform.simpledal.ConnectionState; import org.csstudio.platform.simpledal.IConnector; import org.csstudio.platform.simpledal.IProcessVariableValueListener; import org.csstudio.platform.simpledal.IProcessVariableWriteListener; import org.csstudio.platform.simpledal.SettableState; import org.eclipse.core.runtime.Platform; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Base class for connectors. A connector encapsulates all program logic that is * needed to use a certain application layer that accesses process variables. * * A connector can be used for one-time-action, e.g. getting or setting a * process variable�s value as well for permanent-action which means to register * listeners for updates of process variables values. * * For convenience the {@link IProcessVariableValueListener}s are only weakly * referenced. The connector tracks for {@link IProcessVariableValueListener}s * that have been garbage collected and removes those references from its * internal list. This way {@link IProcessVariableValueListener}s don�t have to * be removed from the connector explicitly. * * @author Sven Wende, Xihui Chen * */ @SuppressWarnings("unchecked") public abstract class AbstractConnector implements IConnector, IProcessVariableAdressProvider {// TODO jhatje , IProcessVariable { private static final Logger LOG = LoggerFactory.getLogger(AbstractConnector.class); public static final int BLOCKING_TIMEOUT = 5000; /** * True, if the connector has been initialized. */ private boolean initialized = false; /** * The latest received value. */ private Object _latestValue; /** * The latest received connection state. */ private ConnectionState _latestConnectionState = ConnectionState.INITIAL; /** * The process variable pointer for the channel this connector is connected * to. */ private IProcessVariableAddress _processVariableAddress; /** * A list of value listeners to which control system events are forwarded. */ protected List<ListenerReference> _weakListenerReferences; /** * The latest error. */ private String _latestError; /** * The value type. */ private ValueType _valueType; /** * Time in milliseconds until which this connector is not disposed. */ private long _keepAliveUntil = 0; /** * Constructor. * * @param pvAddress * the address of the process variable this connector is for * @param valueType * the type of values expected */ public AbstractConnector(IProcessVariableAddress pvAddress, ValueType valueType) { assert pvAddress != null; assert valueType != null; _processVariableAddress = pvAddress; _valueType = valueType; _weakListenerReferences = Collections.synchronizedList(new ArrayList<ListenerReference>()); } /** * {@inheritDoc} */ @Override public synchronized void init() { if (!initialized) { try { doInit(); } catch (Exception e) { LOG.error(e.toString()); } initialized = true; } } /** * {@inheritDoc} */ @Override public final int getListenerCount() { return _weakListenerReferences.size(); } /** * {@inheritDoc} */ @Override public final ConnectionState getLatestConnectionState() { return _latestConnectionState; } /** * {@inheritDoc} */ @Override public final Object getLatestValue() { return _latestValue; } /** * {@inheritDoc} */ @Override public final String getLatestError() { return _latestError; } /** * Forwards the specified connection state to all registered listeners. * * @param connectionState * the connection state */ protected final void forwardConnectionState(ConnectionState connectionState) { doForwardConnectionStateChange(connectionState); } /** * Forwards the specified value to all registered listeners. * * @param value * the value */ protected final void forwardValue(Object value) { doForwardValue(value, new Timestamp()); } /** * Forwards the specified value to all registered listeners. * * @param value * the value */ protected final void forwardError(String error) { doForwardError(error); _latestError = error; } /** * Adds a value listener. When a characteristic is provided, the specified * listener is only informed of changes in that characteristic. * * @param listener * a value listener (has to be !=null) * * @param characteristicId * the id of a characteristic (can be null) */ public final void addProcessVariableValueListener(String characteristicId, IProcessVariableValueListener listener) { assert listener != null; _weakListenerReferences.add(new ListenerReference(characteristicId, listener)); sendInitialValuesForNewListener(characteristicId, listener); } protected void sendInitialValuesForNewListener(String characteristicId, IProcessVariableValueListener listener) { // send initial connection state if (_latestConnectionState != null) { listener.connectionStateChanged(_latestConnectionState); } // send initial value if (_latestValue != null && characteristicId == null) { listener.valueChanged(_latestValue, null); } // send latest error if (_latestError != null) { listener.errorOccured(_latestError); } // send characteristics if (characteristicId != null) { getCharacteristicAsynchronously(characteristicId, getValueType(), listener); } } /** * Removes the specified value listener. This is optional - a connector does * reference its listeners weak by design. This way connectors that are no * longer referenced outside of the connector will get garbage collected * anyway. * * @param listener * the value listener to be removed from the connector */ public final boolean removeProcessVariableValueListener(IProcessVariableValueListener listener) { ListenerReference[] listeners = getWeakReferenceListeners(); ListenerReference toRemove = null; for (ListenerReference ref : listeners) { IProcessVariableValueListener lr = ref.getListener(); if (lr != null && listener == lr) { toRemove = ref; _weakListenerReferences.remove(toRemove); return true; } } return false; } /** * Determines, whether this connector can get disposed. This is, when no * value listeners for the connector live in the JVM anymore. * * @return true, if this connector can be disposed, false otherwise */ @Override public final boolean isDisposable() { // perform a cleanup first // cleanupWeakReferences(); // this connector can be disposed it there are not weak references left return _weakListenerReferences.isEmpty() && !isBlocked(); } /** * {@inheritDoc} */ @Override public final IProcessVariableAddress getProcessVariableAddress() { return _processVariableAddress; } /** * {@inheritDoc} */ @Override public final ValueType getValueType() { return _valueType; } /** * Determines whether it is possible to write values to the underlying * process variable. * * @return a state that defines whether write access is possible in a * yes/no/unknown manner */ public final SettableState isSettable() { SettableState state = null; try { state = doIsSettable(); } catch (Exception e) { LOG.error(e.toString()); } return state != null ? state : SettableState.UNKNOWN; } /** * Disposes the connector. */ public final void dispose() { try { doDispose(); } catch (Exception e) { LOG.error(e.toString()); } } /** *{@inheritDoc} */ @Override public void forceDispose() { _weakListenerReferences.clear(); dispose(); } /** * Queries the current value using a synchronous call that will block the * current thread. * * @param <E> * return type * * @return the current value or null * @throws Exception */ public final <E> E getValueSynchronously() throws Exception { Object value = doGetValueSynchronously(); E result = value != null ? (E) ConverterUtil.convert(value, getValueType()) : null; return result; } /** * Queries the current value using an asynchronous call. The result will be * reported by calling the valueChanged() method on the specified listener. * * @param <E> * return type */ public final void getValueAsynchronously(final IProcessVariableValueListener listener) { try { doGetValueAsynchronously(listener); } catch (Exception e) { LOG.error(e.toString()); } } /** * Queries the specified characteristic using a synchronous call.The result * will be reported by calling the valueChanged() method on the specified * listener. * * @param <E> * return type * @param characteristicId * the characteristic id * @param valueType * the type of the expected value * * @return the current value or null * @throws Exception */ public final <E> E getCharacteristicSynchronously(String characteristicId, final ValueType valueType) throws Exception { Object value = doGetCharacteristicSynchronously(characteristicId, valueType); E result = value != null ? (E) value : null; return result; } /** * Queries the specified characteristic using an asynchronous call that will * block the current thread. * * @param <E> * return type * @param characteristicId * the characteristic id * @param valueType * the type of the expected value * * @return the current value or null */ public final void getCharacteristicAsynchronously(final String characteristicId, final ValueType valueType, final IProcessVariableValueListener listener) { try { doGetCharacteristicAsynchronously(characteristicId, valueType, listener); } catch (Exception e) { LOG.error(e.toString()); } } /** * Sets the specified value using an asynchronous call. * * @param value * the value to be set * @param listener * an optional call-back listener */ public final void setValueAsynchronously(Object value, final IProcessVariableWriteListener listener) { try { doSetValueAsynchronously(value, listener); } catch (Exception e) { LOG.error(e.toString()); } } /** * * @param value * @return */ public final boolean setValueSynchronously(Object value) throws Exception { boolean result = false; result = doSetValueSynchronously(value); return result; } /** * Template method. Subclasses should determine whether write access to the * underlying process variable is possible. * * @return a state that defines whether write access is possible in a * yes/no/unknown manner * @throws Exception * an arbitrary exception */ protected abstract SettableState doIsSettable() throws Exception; /** * Template method. Subclasses should initialize the physical connection * here. * * @throws Exception * an arbitrary exception * */ protected abstract void doInit() throws Exception; /** * Template method. Subclasses should shut down all physical connections * here. * * @throws Exception * an arbitrary exception * */ protected abstract void doDispose() throws Exception; /** * Template method. Subclasses should implement asynchronous access logic * for characteristics here. * * @param characteristicId * the characteristic id * @param valueType * the expected value type * @param listener * the listener has to be informed * * @throws Exception * an arbitrary exception */ protected abstract void doGetCharacteristicAsynchronously(String characteristicId, ValueType valueType, IProcessVariableValueListener listener) throws Exception; /** * Template method. Subclasses should implement synchronous access logic for * characteristics here. * * @param characteristicId * the characteristic id * @param valueType * the expected value type * * @return the characteristic value or null * * @throws Exception * an arbitrary exception */ protected abstract Object doGetCharacteristicSynchronously(String characteristicId, ValueType valueType) throws Exception; /** * Template method. Subclasses should implement synchronous access logic for * the value here. * * @return the current value or null * * @throws Exception * an arbitrary exception */ protected abstract Object doGetValueSynchronously() throws Exception; /** * Template method. Subclasses should implement asynchronous access logic * for the value here. * * @param listener * the listener that has to be informed in a call-back style * * @throws Exception * an arbitrary exception */ protected abstract void doGetValueAsynchronously(final IProcessVariableValueListener listener) throws Exception; /** * Template method. Subclasses should implement synchronous logic for * setting a value here. * * @param value * the value to be set * * @return true on success, false otherwise * @throws Exception * an arbitrary exception */ protected abstract boolean doSetValueSynchronously(Object value) throws Exception; /** * Template method. Subclasses should implement asynchronous logic for * setting a value here. * * @param value * the value to be set * @param listener * an optional call-back listener * * @throws Exception * an arbitrary exception */ protected abstract void doSetValueAsynchronously(Object value, final IProcessVariableWriteListener listener) throws Exception; /** * Forward the specified connection event to the value listeners. * * @param event * the DAL connection event */ protected final void doForwardConnectionStateChange(final ConnectionState connectionState) { if (connectionState != null) { // remember the latest state _latestConnectionState = connectionState; execute(new IInternalRunnable() { @Override public void doRun(IProcessVariableValueListener valueListener, String characteristicId) { valueListener.connectionStateChanged(connectionState); } @Override public void doRun(IProcessVariableValueListener valueListener) { valueListener.connectionStateChanged(connectionState); } }); } } /** * Forward the current value with its time stamp. * * @param value * the value * @param timestamp * the time stamp of the latest event */ protected final void doForwardValue(final Object value, final Timestamp timestamp) { if (value != null) { execute(new IInternalRunnable() { @Override public void doRun(IProcessVariableValueListener valueListener, String characteristicId) { // nothing to do - this is for "normal" value listeners only } @Override public void doRun(IProcessVariableValueListener valueListener) { try { valueListener.valueChanged(ConverterUtil.convert(value, _valueType), timestamp); // memorize the latest value _latestValue = value; } catch (NumberFormatException nfe) { // Do nothing! Is a invalid value format! LOG.warn("Invalid value format. (" + value + ") is not set to " + getName() + "."); } } }); } } /** * Forwards the specified characteristic value to all listeners registered * for that characteristic. * * @param characteristicValue * the characteristic value * @param timestamp * the timestamp * @param characteristicId * the characteristic id */ protected final void doForwardCharacteristic(final Object characteristicValue, final Timestamp timestamp, final String characteristicId) { if (characteristicValue != null && characteristicId != null) { // forward the value execute(new IInternalRunnable() { @Override public void doRun(IProcessVariableValueListener valueListener, String cId) { // forward the value only, if the current listener is // registered for the same characteristic id if (cId != null && cId.equals(characteristicId)) { valueListener.valueChanged(characteristicValue, timestamp); } } @Override public void doRun(IProcessVariableValueListener valueListener) { // do not forward the value because these listeners are not // registered for a characteristic } }); } } /** * Forward the current value. * * @param event * the DAL connection event */ protected final void doForwardError(final String error) { execute(new IInternalRunnable() { @Override public void doRun(IProcessVariableValueListener valueListener, String characteristicId) { valueListener.errorOccured(error); } @Override public void doRun(IProcessVariableValueListener valueListener) { valueListener.errorOccured(error); } }); } /** * Updates all listeners that are connected to a characteristic. * * Characteristics are requested asynchronously. * * FIXME: This is a only workaround. DAL should deliver propertyChange() * Events for characteristics whenever a DalProperty switches to "connected" * state. The same already works for valueChanged() updates. */ protected void updateCharacteristicListeners() { ListenerReference[] listeners = getWeakReferenceListeners(); for (ListenerReference ref : listeners) { IProcessVariableValueListener listener = ref.getListener(); if (listener != null && ref.getCharacteristicId() != null) { getCharacteristicAsynchronously(ref.getCharacteristicId(), getValueType(), listener); } } } /** * Logs a debug message that is prefixed with common connector information * (e.g. the name of the process variable). * * @param message * the message */ protected void printDebugInfo(String message) { StringBuffer sb = new StringBuffer(); sb.append(getProcessVariableAddress().toString()); sb.append(": "); sb.append(message); LOG.debug(sb.toString()); } /** * Executes the specified runnable for all existing value listeners. * * Only for valid listeners that still live in the JVM the hook method * {@link IInternalRunnable#doRun(IProcessVariableValueListener)} is called. * * @param runnable * the runnable */ private void execute(IInternalRunnable runnable) { ListenerReference[] listeners = getWeakReferenceListeners(); for (ListenerReference wr : listeners) { IProcessVariableValueListener listener = wr.getListener(); if (listener != null) { // split the calls for listeners that are registered for a // characteristic and those which are registered for a // "normal" value if (wr.getCharacteristicId() != null) { runnable.doRun(listener, wr.getCharacteristicId()); } else { runnable.doRun(listener); } } } } /** * Removes weak references for value listeners that have been garbage * collected. */ void cleanupWeakReferences() { ListenerReference[] listeners = getWeakReferenceListeners(); List<ListenerReference> deletionCandidates = new ArrayList<ListenerReference>(); for (ListenerReference ref : listeners) { if (ref.getListener() == null) { deletionCandidates.add(ref); } } synchronized (_weakListenerReferences) { for (ListenerReference wr : deletionCandidates) { _weakListenerReferences.remove(wr); } } } /** * {@inheritDoc} */ @Override public String toString() { return _processVariableAddress.getProperty(); } /** * {@inheritDoc} */ @Override public List<IProcessVariableAddress> getProcessVariableAdresses() { return Collections.singletonList(_processVariableAddress); } /** * {@inheritDoc} */ @Override public IProcessVariableAddress getPVAdress() { return _processVariableAddress; } /** * {@inheritDoc} */ public String getName() { return _processVariableAddress.toString(); } // /** // * {@inheritDoc} // */ // public String getTypeId() { // return IProcessVariable.TYPE_ID; // } public void block() { _keepAliveUntil = System.currentTimeMillis() + BLOCKING_TIMEOUT; } /** * {@inheritDoc} */ public Object getAdapter(Class adapterType) { return Platform.getAdapterManager().getAdapter(this, adapterType); } private boolean isBlocked() { long diff = _keepAliveUntil - System.currentTimeMillis(); return diff > 0; } private ListenerReference[] getWeakReferenceListeners() { ListenerReference[] ret = null; synchronized (_weakListenerReferences) { ret = _weakListenerReferences.toArray(new ListenerReference[_weakListenerReferences.size()]); } return ret; } /** * Runnable which is used to forward events to process variable value * listeners. * * @author Sven Wende * */ interface IInternalRunnable { /** * Hook which is called only for valid value listeners, which still live * in the JVM and have not already been garbage collected AND that is * registered for a specific characteristic id. * * @param valueListener * a value listener instance (it is ensured, that this is not * null) * @param characteristicId * the characteristic id (it is ensured, that this is not * null) */ void doRun(IProcessVariableValueListener valueListener, String characteristicId); /** * Hook which is called only for valid value listeners, which still live * in the JVM and have not already been garbage collected. * * @param valueListener * a value listener instance (we ensure, that this is not * null) */ void doRun(IProcessVariableValueListener valueListener); } /** * Keeps a weak reference to a listener. Because of its weakness this * reference does not prevent the listener from getting garbage collected * when its not references elsewhere. * * A listener could have been registered for a specific characteristic id, * which is a special case, as several characteristics can be delivered via * the same connection and can be reported as a reaction to certain system * events. * * @author Sven Wende */ static class ListenerReference { private WeakReference<IProcessVariableValueListener> _listener; private String _characteristicId = null; public ListenerReference(String characteristic, IProcessVariableValueListener<?> listener) { _characteristicId = characteristic; _listener = new WeakReference<IProcessVariableValueListener>(listener); } public boolean isCharacteristic() { return _characteristicId != null; } public IProcessVariableValueListener<?> getListener() { return _listener.get(); } public String getCharacteristicId() { return _characteristicId; } } }