/*
* 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.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.csstudio.platform.model.pvs.IProcessVariableAddress;
import org.csstudio.platform.model.pvs.ValueType;
import org.csstudio.platform.simpledal.ConnectionException;
import org.csstudio.platform.simpledal.IConnector;
import org.csstudio.platform.simpledal.IProcessVariableConnectionService;
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;
/**
* Standard implementation of {@link IProcessVariableConnectionService}.
*
* @author Sven Wende
*/
public class ProcessVariableConnectionService implements IProcessVariableConnectionService {
private static final Logger LOG = LoggerFactory.getLogger(ProcessVariableConnectionService.class);
private Map<ConnectorIdentification, AbstractConnector> _connectors;
private IConnectorFactory _connectorFactory;
/**
* A cleanup thread which disposes unnecessary connections.
*/
private Thread _cleanupThread;
/**
* Constructor.
*/
public ProcessVariableConnectionService(IConnectorFactory connectorFactory) {
assert connectorFactory != null;
_connectors = new HashMap<ConnectorIdentification, AbstractConnector>();
_connectorFactory = connectorFactory;
_cleanupThread = new CleanupThread();
_cleanupThread.start();
}
/**
* {@inheritDoc}
*/
@Override
public List<IConnector> getConnectors() {
List<IConnector> result = new ArrayList<IConnector>();
result.addAll(_connectors.values());
return result;
}
@Override
public int getNumberOfActiveConnectors() {
int result = 0;
synchronized (_connectors) {
for (IConnector c : _connectors.values()) {
result += c != null ? 1 : 0;
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public <E> E readValueSynchronously(IProcessVariableAddress processVariableAddress, ValueType valueType) throws ConnectionException {
AbstractConnector connector = getConnector(processVariableAddress, valueType);
E value = null;
try {
if (processVariableAddress.isCharacteristic()) {
value = connector.getCharacteristicSynchronously(processVariableAddress.getCharacteristic(), valueType);
} else {
value = connector.getValueSynchronously();
}
} catch (Exception e) {
LOG.debug(e.toString());
throw new ConnectionException(e);
}
return value;
}
/**
* {@inheritDoc}
*/
@Override
public void readValueAsynchronously(IProcessVariableAddress processVariableAddress, ValueType valueType,
IProcessVariableValueListener listener) {
AbstractConnector connector = getConnector(processVariableAddress, valueType);
if (processVariableAddress.isCharacteristic()) {
connector.getCharacteristicAsynchronously(processVariableAddress.getCharacteristic(), valueType, listener);
} else {
connector.getValueAsynchronously(listener);
}
}
@Override
public boolean writeValueSynchronously(IProcessVariableAddress processVariableAddress, Object value, ValueType valueType)
throws ConnectionException {
AbstractConnector connector = getConnector(processVariableAddress, valueType);
boolean result = false;
try {
result = connector.setValueSynchronously(value);
} catch (Exception e) {
LOG.debug(e.toString());
throw new ConnectionException(e);
}
return result;
}
@Override
public void writeValueAsynchronously(IProcessVariableAddress processVariableAddress, Object value, ValueType valueType,
IProcessVariableWriteListener listener) {
AbstractConnector connector = getConnector(processVariableAddress, valueType);
connector.setValueAsynchronously(value, listener);
}
/**
* {@inheritDoc}
*/
@Override
public void register(IProcessVariableValueListener listener, IProcessVariableAddress pv, ValueType valueType) {
AbstractConnector connector = getConnector(pv, valueType);
connector.addProcessVariableValueListener(pv.getCharacteristic(), listener);
}
/**
* {@inheritDoc}
*/
@Override
public void unregister(IProcessVariableValueListener listener) {
// we remove the listener from all connectors
synchronized (_connectors) {
for (AbstractConnector c : _connectors.values()) {
if (c.removeProcessVariableValueListener(listener)) {
LOG.debug("UNREGISTER '" + c.getName());
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public SettableState checkWriteAccessSynchronously(IProcessVariableAddress pv) {
AbstractConnector connector = getConnector(pv, pv.getValueTypeHint() != null ? pv.getValueTypeHint() : ValueType.DOUBLE);
return connector.isSettable();
}
/**
* Returns a connector for the specified process variable and type. If a
* connector exists already it is returned, otherwise a new connector will
* be created.
*
* @param pv
* the process variable
* @param valueType
* the value type
* @return the connector (it is ensured, that this is not null)
*/
private AbstractConnector getConnector(IProcessVariableAddress pv, ValueType valueType) {
AbstractConnector connector = null;
// there is one connector for each pv-type-combination
ConnectorIdentification key = new ConnectorIdentification(pv, valueType);
// get a connector
synchronized (_connectors) {
connector = (AbstractConnector) _connectors.get(key);
// ... reuse existing connector a create a new one
if (connector == null) {
connector = _connectorFactory.createConnector(pv, valueType);
_connectors.put(key, connector);
}
connector.block();
}
assert connector != null;
connector.init();
return connector;
}
/**
* Injects a connector factory.
*
* @param connectorFactory
* the connector factory
*/
public void setConnectorFactory(IConnectorFactory connectorFactory) {
_connectorFactory = connectorFactory;
}
/**
* Cleanup thread, which removes connectors that are not needed anymore.
*
* @author swende
*
*/
final class CleanupThread extends Thread {
private long _sleepTime;
/**
* Flag that indicates if the thread should continue its execution.
*/
private boolean _running;
/**
* Standard constructor.
*/
CleanupThread() {
super("ProcessVariableConnectionService#CleanupThread");
// Have to be a daemon to be automatically stopped on a system
// shutdown.
this.setDaemon(true);
_running = true;
_sleepTime = 10000;
}
/**
* {@inheritDoc}.
*/
@Override
public void run() {
while (_running) {
try {
sleep(_sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
doCleanup();
yield();
}
}
/**
* Stops the execution of this BundelingThread.
*/
public void stopExecution() {
_running = false;
}
/**
* Performs the cleanup.
*/
private void doCleanup() {
List<AbstractConnector> deletedConnectors = new ArrayList<AbstractConnector>();
synchronized (_connectors) {
List<ConnectorIdentification> deleteCandidates = new ArrayList<ConnectorIdentification>();
Iterator<ConnectorIdentification> it = _connectors.keySet().iterator();
while (it.hasNext()) {
ConnectorIdentification key = it.next();
AbstractConnector connector = _connectors.get(key);
// perform cleanup
connector.cleanupWeakReferences();
// dispose if possible
if (connector.isDisposable()) {
deleteCandidates.add(key);
}
}
for (ConnectorIdentification key : deleteCandidates) {
AbstractConnector connector = _connectors.remove(key);
deletedConnectors.add(connector);
}
}
// important: dispose the connectors outside the synchronized block
for (AbstractConnector connector : deletedConnectors) {
connector.dispose();
}
LOG.debug("Cleanup-Thread: " + deletedConnectors.size() + " connectors disposed!");
}
}
}