/**
*
*/
package org.csstudio.dal.simple;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import org.csstudio.dal.DynamicValueListener;
import org.csstudio.dal.DynamicValueMonitor;
import org.csstudio.dal.DynamicValueProperty;
import org.csstudio.dal.ExpertMonitor;
import org.csstudio.dal.RemoteException;
import org.csstudio.dal.Request;
import org.csstudio.dal.ResponseListener;
import org.csstudio.dal.Timestamp;
import org.csstudio.dal.context.AbstractApplicationContext;
import org.csstudio.dal.context.ConnectionException;
import org.csstudio.dal.context.LifecycleEvent;
import org.csstudio.dal.context.LifecycleListener;
import org.csstudio.dal.context.LinkBlocker;
import org.csstudio.dal.impl.DefaultApplicationContext;
import org.csstudio.dal.impl.RequestImpl;
import org.csstudio.dal.impl.ResponseImpl;
import org.csstudio.dal.spi.DefaultPropertyFactoryBroker;
import org.csstudio.dal.spi.LinkPolicy;
import org.csstudio.dal.spi.Plugs;
import org.csstudio.dal.spi.PropertyFactoryService;
import com.cosylab.util.CommonException;
/**
* Simple DAL main object for access to DAL in request style.
* DAL offers here functionality trough addressed synchronous and
* asynchronous interface.
*
* @author ikriznar
*
*/
public class SimpleDALBroker {
private static SimpleDALBroker broker;
private final AbstractApplicationContext ctx;
private final HashMap<String, PropertyHolder> properties;
/**
* Time duration which property is alive after has been last used.
* After this time it could be deleted.
*/
private final long timeToLive = 60000;
private DefaultPropertyFactoryBroker factory;
private static final long CLEANUP_INTERVAL = 60000;
private final Timer cleanupTimer;
private class PropertiesCleanupTask extends TimerTask {
@Override
public void run() {
final ArrayList<String> toRemove = new ArrayList<String>();
synchronized (properties) {
PropertyHolder holder;
for (final String key : properties.keySet()) {
holder = properties.get(key);
if (holder.expires > 0 && holder.expires < System.currentTimeMillis() && !holder.property.hasDynamicValueListeners())
toRemove.add(key);
}
for (final String key : toRemove) {
holder = properties.remove(key);
getFactory().destroy(holder.property);
}
}
}
}
public class PropertyHolder {
public PropertyHolder(final DynamicValueProperty<?> p) {
this.property=p;
}
DynamicValueProperty<?> property;
/**
* Tells when broker garbage collector could check if the property is no longer used.
* If 0, then it is in active use.
* If 1 or more than it can be checked if already expired and is candidate for deletion.
*/
public long expires;
}
public class UninitializedPropertyHolder extends PropertyHolder {
boolean propertyInitialized = false;
RemoteException re;
InstantiationException ie;
public UninitializedPropertyHolder() {
super(null);
expires = -1;
}
}
private static String createKey(final ConnectionParameters cparam, final String defaultPlugType) {
final StringBuilder sb = new StringBuilder();
if (cparam.getRemoteInfo().getPlugType()==null)
sb.append(defaultPlugType);
else
sb.append(cparam.getRemoteInfo().getPlugType());
sb.append(',');
sb.append(cparam.getRemoteInfo().getRemoteName());
sb.append(',');
final DataFlavor df = cparam.getConnectionType();
if (df == null)
sb.append("null");
else
sb.append(df.toString());
return sb.toString();
}
/**
* Creates singleton instance of SimpleDALBroker.
* This is same as calling {@link SimpleDALBroker#newInstance(AbstractApplicationContext)} with <code>null</code> as parameter.
* @return singleton instance of SimpleDALBroker
*/
public static SimpleDALBroker getInstance() {
return newInstance(null, null);
}
/**
* Creates new instance of SimpleDALBroker or singelton instance of parameter is <code>null</code>.
* @param ctx application context or null
* @return if ctx is provided new instance, otherwise singelton
*/
public static SimpleDALBroker newInstance(final AbstractApplicationContext ctx) {
final Object o= ctx.getApplicationProperty(Plugs.PROPERTY_FACTORY_SERVICE_IMPLEMENTATION);
if (o instanceof PropertyFactoryService)
return newInstance(ctx, (PropertyFactoryService)o);
return newInstance(ctx, null);
}
/**
* Creates new instance of SimpleDALBroker or singelton instance of parameter is <code>null</code>.
* @param ctx application context or null
* @param service implementation of PropertyFactoryService which will be used to instantiate factories.
* @return if ctx is provided new instance, otherwise singelton
*/
public static SimpleDALBroker newInstance(final AbstractApplicationContext ctx, final PropertyFactoryService service) {
if (ctx==null) {
if (broker == null)
broker = new SimpleDALBroker(new DefaultApplicationContext("SimpleDALContext"));
return broker;
}
final SimpleDALBroker sdb= new SimpleDALBroker(ctx);
if (service!=null)
sdb.getFactory().setPropertyFactoryService(service);
return sdb;
}
private SimpleDALBroker(final AbstractApplicationContext ctx) {
this.ctx=ctx;
properties= new HashMap<String, PropertyHolder>();
cleanupTimer = new Timer("Cleanup Timer");
cleanupTimer.scheduleAtFixedRate(new PropertiesCleanupTask(), CLEANUP_INTERVAL, CLEANUP_INTERVAL);
ctx.addLifecycleListener(new LifecycleListener() {
@Override
public void destroyed(final LifecycleEvent event) {
// TODO implement?
}
@Override
public void destroying(final LifecycleEvent event) {
// TODO implement?
cleanupTimer.cancel();
}
@Override
public void initialized(final LifecycleEvent event) {
// not important
}
@Override
public void initializing(final LifecycleEvent event) {
// not important
}
});
}
private PropertyHolder getPropertyHolder(final ConnectionParameters cparam) throws InstantiationException, CommonException {
return getPropertyHolder(cparam, System.currentTimeMillis()+timeToLive);
}
private PropertyHolder getPropertyHolder(final ConnectionParameters cparam, final long expires) throws InstantiationException, CommonException {
PropertyHolder ph;
UninitializedPropertyHolder uph = null;
final String key = createKey(cparam,getFactory().getDefaultPlugType());
synchronized (properties) {
ph= properties.get(key);
if (ph == null) {
uph = new UninitializedPropertyHolder();
properties.put(key, uph);
}
}
if (ph==null) {
DynamicValueProperty<?> property;
try {
if (cparam.getConnectionType() != null) {
Class<? extends DynamicValueProperty<?>> dalType = cparam.getConnectionType().getDALType();
property = getFactory().
getProperty(cparam.getRemoteInfo(), dalType, null);
}
else {
property = getFactory().
getProperty(cparam.getRemoteInfo());
}
} catch (final RemoteException e) {
uph.re = e;
synchronized (properties) {
properties.remove(key);
}
synchronized (uph) {
uph.propertyInitialized = true;
uph.notifyAll();
}
throw e;
} catch (final InstantiationException e) {
uph.ie = e;
synchronized (properties) {
properties.remove(key);
}
synchronized (uph) {
uph.propertyInitialized = true;
uph.notifyAll();
}
throw e;
}
ph= new PropertyHolder(property);
synchronized (properties) {
properties.put(key, ph);
}
synchronized (uph) {
uph.propertyInitialized = true;
uph.notifyAll();
}
}
else if (ph instanceof UninitializedPropertyHolder) {
uph = (UninitializedPropertyHolder) ph;
synchronized (uph) {
try {
while (!uph.propertyInitialized)
uph.wait();
} catch (final InterruptedException e) {
throw new CommonException(this, "Thread has been interrupted.", e);
}
}
synchronized (properties) {
ph= properties.get(key);
if (ph == null) {
if (uph.re != null)
throw uph.re;
if (uph.ie != null)
throw uph.ie;
throw new CommonException(this, "Internal error.");
}
if (ph instanceof UninitializedPropertyHolder)
throw new CommonException(this, "Internal error.");
}
}
ph.expires = expires;
return ph;
}
/**
* Utility method for JUnit testing.
* @return the size of properties map
*/
public int getPropertiesMapSize() {
int size = -1;
synchronized (properties) {
size = properties.size();
}
return size;
}
private DefaultPropertyFactoryBroker getFactory() {
if (factory == null)
factory = DefaultPropertyFactoryBroker.getInstance(ctx, LinkPolicy.ASYNC_LINK_POLICY);
return factory;
}
/**
* Returns remote value.
* Value is read and returned synchronously.
* The remote connection to remote object is closed not sooner
* then one minute and no later than two minutes after
* last time the connection was used.
*
* @param cparam connection parameter to remote value
* @return remote value
* @throws InstantiationException
* @throws CommonException
*/
public Object getValue(final ConnectionParameters cparam) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(cparam);
blockUntillConnected(ph.property);
if (cparam.getRemoteInfo().getCharacteristic()!=null)
return ph.property.getCharacteristic(cparam.getRemoteInfo().getCharacteristic());
return ph.property.getValue();
}
/**
* Returns remote value with synchronous call.
* The remote connection to remote object is closed not sooner
* then one minute and no later than two minutes after
* last time the connection was used.
*
* @param rinfo connection information to remote value
* @param type Java data type for expected remote value
* @return returned value already cast to requested data type, can be <code>null</code>
* @throws InstantiationException if error
* @throws CommonException if error
*/
public <T> T getValue(final RemoteInfo rinfo, final Class<T> type) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(new ConnectionParameters(rinfo, type));
blockUntillConnected(ph.property);
if (rinfo.getCharacteristic()!=null) {
final Object o= ph.property.getCharacteristic(rinfo.getCharacteristic());
if (o!=null)
return type.cast(o);
return null;
}
Object o=null;
if (type == AnyData.class)
o= ph.property.getData();
else
o= ph.property.getValue();
if (o!=null)
return type.cast(o);
return null;
}
/**
* Return remote value with synchronous call.
* The remote connection to remote object is closed not sooner
* then one minute and no later than two minutes after
* last time the connection was used.
*
* @param rinfo connection information to remote value
* @return returned value, can be <code>null</code>
* @throws InstantiationException if error
* @throws CommonException if error
*/
public Object getValue(final RemoteInfo rinfo) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(new ConnectionParameters(rinfo));
blockUntillConnected(ph.property);
if (rinfo.getCharacteristic()!=null)
return ph.property.getCharacteristic(rinfo.getCharacteristic());
return ph.property.getValue();
}
/**
* Return remote value with synchronous call.
* The remote connection to remote object is closed not sooner
* then one minute and no later than two minutes after
* last time the connection was used.
*
* @param property name of remote property
* @return returned value, can be <code>null</code>
* @throws InstantiationException if error
* @throws CommonException if error
*/
public Object getValue(final String property) throws InstantiationException, CommonException {
return getValue(RemoteInfo.fromString(property, RemoteInfo.DAL_TYPE_PREFIX + getFactory().getDefaultPlugType()));
}
/**
* Asynchronously requests remote value.
* The remote connection to remote object is closed not sooner
* then one minute and no later than two minutes after
* last time the connection was used.
*
* @param cparam complete connection parameters to remote value
* @param callback callback which will be notified when remote value is returned
* @return request object, which identifies and controls response returned to callback.
* @throws InstantiationException if error
* @throws CommonException if error
*/
public <T> Request<T> getValueAsync(final ConnectionParameters cparam, final ResponseListener<T> callback) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(cparam);
blockUntillConnected(ph.property);
if (cparam.getRemoteInfo().getCharacteristic()!=null)
return (Request<T>)ph.property.getCharacteristicAsynchronously(cparam.getRemoteInfo().getCharacteristic(), callback);
if (cparam.getDataType().getJavaType() == AnyData.class) {
final RequestImpl<T> r= new RequestImpl<T>(ph.property, callback);
r.addResponse(new ResponseImpl<T>(ph.property, r, (T)ph.property.getData(), "", true, null, null, new Timestamp(), true));
return r;
}
return ((DynamicValueProperty<T>)ph.property).getAsynchronous(callback);
}
/**
* Sends new value to remote object.
* The remote connection to remote object is closed not sooner
* then one minute and no later than two minutes after
* last time the connection was used.
*
* @param rinfo connection information about remote entity
* @param value new value to be set
* @throws CommonException if fails
* @throws InstantiationException if fails
*/
public void setValue(final RemoteInfo rinfo, final Object value) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(new ConnectionParameters(rinfo, value.getClass()));
blockUntillConnected(ph.property);
ph.property.setValueAsObject(value);
}
public <T> Request<T> setValueAsync(final ConnectionParameters cparam, final Object value, final ResponseListener<T> callback) throws Exception {
final PropertyHolder ph = getPropertyHolder(cparam);
blockUntillConnected(ph.property);
return ((DynamicValueProperty<T>) ph.property).setAsynchronous((T)value, callback);
}
public void registerListener(final ConnectionParameters cparam, final ChannelListener listener) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(cparam, 0);
if (cparam.getRemoteInfo().getCharacteristic()!=null)
return;
ph.property.addListener(listener);
}
public void deregisterListener(final ConnectionParameters cparam, final ChannelListener listener) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(cparam, 1);
if (cparam.getRemoteInfo().getCharacteristic()!=null)
return;
ph.property.removeListener(listener);
}
public void registerListener(final ConnectionParameters cparam, final DynamicValueListener listener) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(cparam, 0);
if (cparam.getRemoteInfo().getCharacteristic()!=null)
return;
ph.property.addDynamicValueListener(listener);
}
public void deregisterListener(final ConnectionParameters cparam, final DynamicValueListener listener) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(cparam, 1);
if (cparam.getRemoteInfo().getCharacteristic()!=null)
return;
ph.property.removeDynamicValueListener(listener);
}
public void registerListener(final ConnectionParameters cparam, final PropertyChangeListener listener) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(cparam, 0);
ph.property.addPropertyChangeListener(listener);
}
public void deregisterListener(final ConnectionParameters cparam, final PropertyChangeListener listener) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(cparam, 1);
ph.property.removePropertyChangeListener(listener);
}
/**
* Registers listener to special ExpertMonitor, which can be created by plug specific parameters.
*
* @param cparam connection parameters for remote property
* @param listener listener which should receive value and status updated
* @param paremeters plug specific parameters intended for ExpertMonitor
* @throws InstantiationException if fails
* @throws CommonException if fails
*/
public void registerListener(final ConnectionParameters cparam, final DynamicValueListener listener, final Map<String,Object> parameters) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(cparam, 0);
if (cparam.getRemoteInfo().getCharacteristic()!=null)
return;
if (parameters == null || parameters.size()==0)
ph.property.addDynamicValueListener(listener);
else
ph.property.createNewExpertMonitor(listener, parameters);
}
/**
* Deregisters listener to special ExpertMonitor, which was created by plug specific parameters.
*
* @param cparam connection parameters for remote property
* @param listener listener which should receive value and status updated
* @param paremeters plug specific parameters which were used to create ExpertMonitor
* @throws InstantiationException if fails
* @throws CommonException if fails
*/
public void deregisterListener(final ConnectionParameters cparam, final DynamicValueListener listener, final Map<String,Object> parameters) throws InstantiationException, CommonException {
final PropertyHolder ph= getPropertyHolder(cparam, 0);
if (cparam.getRemoteInfo().getCharacteristic()!=null)
return;
if (parameters == null || parameters.size()==0)
ph.property.removeDynamicValueListener(listener);
else {
final DynamicValueMonitor[] mon= ph.property.getMonitors();
for (final DynamicValueMonitor m : mon) {
if (m instanceof ExpertMonitor && parameters.equals(((ExpertMonitor)m).getParameters())) {
m.destroy();
return;
}
}
}
}
private void blockUntillConnected(final DynamicValueProperty<?> property) throws ConnectionException {
LinkBlocker.blockUntillConnected(property, Plugs.getConnectionTimeout(ctx.getConfiguration(), 30000) * 2, true);
}
/**
* Return default plug type, which is used for all remote names, which does not
* explicitly declare plug or connection type.
*
* <p>
* By default (if not set) plug type equals to Simulator.
* </p>
*
* @return default plug type
*/
public String getDefaultPlugType() {
return getFactory().getDefaultPlugType();
}
/**
* Sets default plug type, which is used for all remote names, which does not
* explicitly declare plug or connection type.
*
* <p>
* So far supported values are: EPICS, TINE, Simulator.
* By default (if not set) plug type equals to Simulator.
* </p>
*
* @param defautl plug type.
*/
public void setDefaultPlugType(final String plugType) {
getFactory().setDefaultPlugType(plugType);
}
public void releaseAll() {
getFactory().releaseAll();
synchronized (properties) {
properties.clear();
}
}
}