/*
* 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.dal;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import org.csstudio.dal.CharacteristicInfo;
import org.csstudio.dal.DataExchangeException;
import org.csstudio.dal.DynamicValueCondition;
import org.csstudio.dal.DynamicValueEvent;
import org.csstudio.dal.DynamicValueListener;
import org.csstudio.dal.DynamicValueProperty;
import org.csstudio.dal.ResponseEvent;
import org.csstudio.dal.ResponseListener;
import org.csstudio.dal.Timestamp;
import org.csstudio.dal.context.ConnectionEvent;
import org.csstudio.dal.context.LinkListener;
import org.csstudio.dal.simple.RemoteInfo;
import org.csstudio.dal.spi.PropertyFactory;
import org.csstudio.platform.internal.simpledal.AbstractConnector;
import org.csstudio.platform.internal.simpledal.converters.ConverterUtil;
import org.csstudio.platform.model.pvs.DALPropertyFactoriesProvider;
import org.csstudio.platform.model.pvs.IProcessVariableAddress;
import org.csstudio.platform.model.pvs.ValueType;
import org.csstudio.platform.simpledal.ConnectionState;
import org.csstudio.platform.simpledal.IProcessVariableValueListener;
import org.csstudio.platform.simpledal.IProcessVariableWriteListener;
import org.csstudio.platform.simpledal.SettableState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* DAL Connectors are connected to the control system via the DAL API.
*
* All events received from DAL are forwarded to
* {@link IProcessVariableValueListener}s which abstract from DAL.
*
* 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 disposed explicitly.
*
* @author Sven Wende
*
*/
@SuppressWarnings("unchecked")
public final class DalConnector extends AbstractConnector implements DynamicValueListener, LinkListener, ResponseListener,
PropertyChangeListener {
private static final Logger LOG = LoggerFactory.getLogger(DalConnector.class);
private static final int CONNECTION_TIMEOUT = 3000;
/**
* The DAL property, this connector is connected to.
*/
private DynamicValueProperty _dalProperty;
/**
* Constructor.
*/
public DalConnector(IProcessVariableAddress pvAddress, ValueType valueType) {
super(pvAddress, valueType);
}
/**
* {@inheritDoc}
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
// a property change event indicates a change in a characteristic value
Object value = evt.getNewValue();
String characteristicId = evt.getPropertyName();
doForwardCharacteristic(value, new Timestamp(), characteristicId);
}
/**
* {@inheritDoc}
*/
@Override
public void conditionChange(DynamicValueEvent event) {
// translate a condition change to certain characteristics listeners
// might be registered for
processConditionChange(event.getCondition(), event.getTimestamp());
}
@Override
protected void sendInitialValuesForNewListener(String characteristicId, IProcessVariableValueListener listener) {
super.sendInitialValuesForNewListener(characteristicId, listener);
if (_dalProperty != null) {
processConditionChange(_dalProperty.getCondition(), _dalProperty.getLatestValueUpdateTimestamp());
}
}
private void processConditionChange(DynamicValueCondition condition, Timestamp timestamp) {
if (condition != null) {
// ... characteristic "timestamp"
doForwardCharacteristic(condition.getTimestamp(), timestamp, CharacteristicInfo.C_TIMESTAMP.getName());
// ... characteristic "status"
doForwardCharacteristic(EpicsUtil.extratStatus(condition), timestamp, CharacteristicInfo.C_STATUS.getName());
// ... characteristic "severity"
doForwardCharacteristic(EpicsUtil.toEPICSFlavorSeverity(condition), timestamp, CharacteristicInfo.C_SEVERITY.getName());
}
}
/**
* {@inheritDoc}
*/
@Override
public void errorResponse(DynamicValueEvent event) {
// FIXME: forward condition changes
}
/**
* {@inheritDoc}
*/
@Override
public void timelagStarts(DynamicValueEvent event) {
// FIXME: forward condition changes
}
/**
* {@inheritDoc}
*/
@Override
public void timelagStops(DynamicValueEvent event) {
// FIXME: forward condition changes
}
/**
* {@inheritDoc}
*/
@Override
public void timeoutStarts(DynamicValueEvent event) {
// FIXME: forward condition changes
}
/**
* {@inheritDoc}
*/
@Override
public void timeoutStops(DynamicValueEvent event) {
// FIXME: forward condition changes
}
/**
* {@inheritDoc}
*/
@Override
public void valueChanged(final DynamicValueEvent event) {
doHandleValueUpdate(event);
}
/**
* {@inheritDoc}
*/
@Override
public void valueUpdated(final DynamicValueEvent event) {
doHandleValueUpdate(event);
}
/**
* {@inheritDoc}
*/
@Override
public void connected(final ConnectionEvent e) {
// ... forward the new connection state
doForwardConnectionStateChange(ConnectionState.translate(e.getState()));
// ... forward initial values
updateCharacteristicListeners();
}
/**
* {@inheritDoc}
*/
@Override
public void operational(final ConnectionEvent e) {
// ... forward the new connection state
doForwardConnectionStateChange(ConnectionState.translate(e.getState()));
// ... forward initial values
updateCharacteristicListeners();
}
/**
* {@inheritDoc}
*/
@Override
public void connectionFailed(ConnectionEvent e) {
doForwardConnectionStateChange(ConnectionState.translate(e.getState()));
}
/**
* {@inheritDoc}
*/
@Override
public void connectionLost(ConnectionEvent e) {
forwardConnectionEvent(e);
}
/**
* {@inheritDoc}
*/
@Override
public void destroyed(ConnectionEvent e) {
forwardConnectionEvent(e);
}
/**
* {@inheritDoc}
*/
@Override
public void disconnected(ConnectionEvent e) {
forwardConnectionEvent(e);
}
/**
* {@inheritDoc}
*/
@Override
public void resumed(ConnectionEvent e) {
forwardConnectionEvent(e);
}
/**
* {@inheritDoc}
*/
@Override
public void suspended(ConnectionEvent e) {
forwardConnectionEvent(e);
}
/**
* {@inheritDoc}
*/
@Override
public void responseError(ResponseEvent event) {
Exception e = event.getResponse().getError();
doForwardError(e != null ? e.getMessage() : "Unknown error!");
}
/**
* {@inheritDoc}
*/
@Override
public void responseReceived(ResponseEvent event) {
// Igor: if necessary update last value. We expect one event only
// originating
// from initial asynchronous get
doForwardValue(event.getResponse().getValue(), event.getResponse().getTimestamp());
}
private void forwardConnectionEvent(ConnectionEvent e) {
doForwardConnectionStateChange(ConnectionState.translate(e.getState()));
}
/**
* Waits until DAL property is connected or timeout has elapsed
*
* @param timeout
* the timeout to wait
*
* @return <code>true</code> if property was connected
*/
public boolean waitTillConnected(long timeout) {
return EpicsUtil.waitTillConnected(_dalProperty, timeout);
}
/**
* {@inheritDoc}
*/
@Override
protected void doGetValueAsynchronously(final IProcessVariableValueListener listener) {
if (waitTillConnected(CONNECTION_TIMEOUT)) {
block();
ResponseListener responseListener = new ResponseListener() {
@Override
public void responseError(ResponseEvent event) {
// forward the error
Exception error = event.getResponse().getError();
String errorMsg = error != null ? error.getMessage() : "Unknown Error!";
listener.errorOccured(errorMsg);
printDebugInfo("AGET-ERROR : " + error + " (" + event.getResponse().toString() + ")");
}
@Override
public void responseReceived(ResponseEvent event) {
Object value = event.getResponse().getValue();
Timestamp timestamp = event.getResponse().getTimestamp();
listener.valueChanged(ConverterUtil.convert(value, getValueType()), timestamp);
printDebugInfo("AGET-RETURN: " + getValueType() + " " + value);
}
};
printDebugInfo("GET ASYNC");
try {
_dalProperty.getAsynchronous(responseListener);
} catch (Exception e) {
listener.errorOccured(e.getLocalizedMessage());
}
} else {
listener.errorOccured("Internal error. No connection available.");
}
}
/**
* {@inheritDoc}
*/
@Override
protected Object doGetValueSynchronously() throws Exception {
Object result = null;
// ... try to read the value
if (waitTillConnected(CONNECTION_TIMEOUT)) {
printDebugInfo("GET SYNC");
result = _dalProperty.getValue();
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
protected void doSetValueAsynchronously(Object value, final IProcessVariableWriteListener listener) throws Exception {
if (waitTillConnected(3000)) {
if (_dalProperty.isSettable()) {
Object convertedValue;
try {
convertedValue = ConverterUtil.convert(value, getValueType());
_dalProperty.setAsynchronous(convertedValue, new ResponseListener() {
@Override
public void responseReceived(ResponseEvent event) {
if (listener != null) {
listener.success();
}
LOG.debug(event.getResponse().toString());
}
@Override
public void responseError(ResponseEvent event) {
if (listener != null) {
listener.error(event.getResponse().getError());
}
LOG.error(event.getResponse().getError().toString());
}
});
} catch (NumberFormatException nfe) {
// Do nothing! Is a invalid value format!
LOG.warn("Invalid value format. (" + value + ") is not set to " + getName());
return;
}
} else {
throw new Exception("Property " + _dalProperty.getUniqueName() + " is not settable");
}
} else {
throw new Exception("Property not available");
}
}
/**
* {@inheritDoc}
*/
@Override
protected boolean doSetValueSynchronously(Object value) {
boolean success = false;
if (waitTillConnected(CONNECTION_TIMEOUT)) {
if (_dalProperty.isSettable()) {
try {
_dalProperty.setValue(ConverterUtil.convert(value, getValueType()));
success = true;
} catch (NumberFormatException nfe) {
LOG.warn("Invalid value format. (" + value + ") is not set to" + getName());
} catch (DataExchangeException e) {
LOG.error(e.toString());
}
} else {
printDebugInfo("Property not settable");
}
} else {
printDebugInfo("Property not available");
}
return success;
}
/**
* {@inheritDoc}
*/
@Override
protected void doInit() {
// get or create a real DAL property
DynamicValueProperty property = null;
try {
RemoteInfo ri = getProcessVariableAddress().toDalRemoteInfo();
PropertyFactory factory = DALPropertyFactoriesProvider.getInstance().getPropertyFactory(
getProcessVariableAddress().getControlSystem());
switch (getValueType()) {
case OBJECT:
property = factory.getProperty(ri);
break;
case STRING:
/*
* swende: 2010-03-06: this is a dirty quickfix which is related
* to problems with SDS displays that specifiy
* "pv[severity], String" as pv address / please remove if it
* does not work as expected or when all current SDS files at
* DESY have been propertly changed
*/
String characteristic = getProcessVariableAddress().getCharacteristic();
//If connection is made as pv[severity] or just pv, than ignore everything
//and go to default. In all other cases (e.g. pv[graphMin}, string), create
//a default property.
if (characteristic != null && !CharacteristicInfo.C_SEVERITY.getName().equals(characteristic)) {
property = factory.getProperty(ri);
break;
}
default:
property = factory.getProperty(ri, getValueType().getDalType(), null);
break;
}
if (property != null) {
setDalProperty(property);
}
} catch (Throwable e) {
forwardError(e.getLocalizedMessage());
}
}
/**
* {@inheritDoc}
*/
@Override
protected void doDispose() {
printDebugInfo("DISPOSE");
DynamicValueProperty property = _dalProperty;
setDalProperty(null);
if (property != null && !property.isDestroyed()) {
// remove link listener
property.removeLinkListener(this);
// remove value listeners
property.removeDynamicValueListener(this);
// remove response listeners
property.removeResponseListener(this);
// try to dispose the DAL property
PropertyFactory factory = DALPropertyFactoriesProvider.getInstance().getPropertyFactory(
getProcessVariableAddress().getControlSystem());
// if the property is not used anymore by other connectors,
// destroy it
if (property.getDynamicValueListeners().length <= 1 && property.getResponseListeners().length <= 0) {
printDebugInfo("DESTROY");
factory.getPropertyFamily().destroy(property);
// <**** Workarround (FIXME: Remove, when DAL is fixed) ***
// DAL caches a reference to a former ResponseListener
// via its latestResponse and latestRequest fields on
// DynamicValuePropertyImpl.class
// ********************************************************
/*
* try { Object e = property.getLatestResponse();
*
* property.getAsynchronous(null);
*
* while (e == property.getLatestResponse()) { Thread.sleep(1);
* } } catch (DataExchangeException e) { e.printStackTrace(); }
* catch (InterruptedException e) { e.printStackTrace(); }
*/
// **** Workarround (Remove, when DAL is fixed)************>
assert !factory.getPropertyFamily().contains(property) : "!getPropertyFactory().getPropertyFamily().contains(property)";
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected SettableState doIsSettable() {
SettableState result = SettableState.UNKNOWN;
try {
// DAL encapsulates the detection of the current user internally
// (probably via global system properties)
if (waitTillConnected(CONNECTION_TIMEOUT)) {
result = _dalProperty.isSettable() ? SettableState.SETTABLE : SettableState.NOT_SETTABLE;
}
} catch (Exception e) {
LOG.error("We could not check the settable-state of [" + getProcessVariableAddress().toString() + "]",
e);
result = SettableState.UNKNOWN;
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
protected void doGetCharacteristicAsynchronously(final String characteristicId, final ValueType valueType,
final IProcessVariableValueListener listener) {
try {
if (waitTillConnected(CONNECTION_TIMEOUT)) {
ResponseListener responseListener = new ResponseListener() {
@Override
public void responseError(ResponseEvent event) {
// forward the error
Exception error = event.getResponse().getError();
String errorMsg = error != null ? error.getMessage() : "Unknown Error!";
listener.errorOccured(errorMsg);
printDebugInfo("AGET-ERROR [" + characteristicId + "] : " + error + " (" + event.getResponse().toString() + ")");
}
@Override
public void responseReceived(ResponseEvent event) {
Object value = event.getResponse().getValue();
Timestamp timestamp = event.getResponse().getTimestamp();
listener.valueChanged(value, timestamp);
// listener.valueChanged(ConverterUtil.convert(value,
// valueType), timestamp);
printDebugInfo("AGET-RETURN: " + valueType + " " + value);
}
};
printDebugInfo("GET ASYNC [" + characteristicId + "]");
_dalProperty.getCharacteristicAsynchronously(characteristicId, responseListener);
} else {
listener.errorOccured("Internal error. No connection available.");
}
} catch (Exception e) {
listener.errorOccured(e.getLocalizedMessage());
}
}
/**
* {@inheritDoc}
*/
@Override
protected Object doGetCharacteristicSynchronously(String characteristicId, ValueType valueType) throws Exception {
Object result = null;
// ... try to read the value
if (waitTillConnected(CONNECTION_TIMEOUT)) {
if (characteristicId.equals(CharacteristicInfo.C_SEVERITY.getName())) {
result = EpicsUtil.toEPICSFlavorSeverity(_dalProperty.getCondition());
} else if (characteristicId.equals(CharacteristicInfo.C_STATUS.getName())) {
result = EpicsUtil.extratStatus(_dalProperty.getCondition());
} else if (characteristicId.equals(CharacteristicInfo.C_TIMESTAMP.getName())) {
result = _dalProperty.getCondition().getTimestamp();
} else {
Object tmp = _dalProperty.getCharacteristic(characteristicId);
result = valueType != null ? ConverterUtil.convert(tmp, valueType) : tmp;
}
}
return result;
}
/**
* Returns the DAL property that is internally used.
*
* @return the internally used DAL property
*/
protected DynamicValueProperty getDalProperty() {
return _dalProperty;
}
/**
* Sets the DAL property, this connector is connected to.
*
* @param dalProperty
* the DAL property
*/
private void setDalProperty(DynamicValueProperty dalProperty) {
if (_dalProperty != null) {
_dalProperty.removeDynamicValueListener(this);
_dalProperty.removePropertyChangeListener(this);
_dalProperty.removeLinkListener(this);
}
_dalProperty = dalProperty;
if (_dalProperty != null) {
_dalProperty.addDynamicValueListener(this);
_dalProperty.addPropertyChangeListener(this);
// we add a LinkListener to get informed of connection state changes
_dalProperty.addLinkListener(this);
// send initial connection state
forwardConnectionState(ConnectionState.translate(_dalProperty.getConnectionState()));
}
}
/**
* A change of the "normal" value has been reported and needs to be
* forwarded.
*
* @param event
* the event that reports the value update
*/
private void doHandleValueUpdate(DynamicValueEvent event) {
// ... forward the value
doForwardValue(event.getValue(), event.getTimestamp());
// ... forward an additional "timestamp" characteristic
doForwardCharacteristic(event.getTimestamp(), event.getTimestamp(), CharacteristicInfo.C_TIMESTAMP.getName());
}
}