/* * Copyright (c) 2006 Stiftung Deutsches Elektronen-Synchroton, * 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.dal.impl; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.apache.log4j.Logger; import org.csstudio.dal.DataExchangeException; import org.csstudio.dal.DynamicValueProperty; import org.csstudio.dal.RemoteException; import org.csstudio.dal.Request; import org.csstudio.dal.Response; import org.csstudio.dal.ResponseEvent; import org.csstudio.dal.ResponseListener; import org.csstudio.dal.SimpleProperty; import org.csstudio.dal.commands.AsynchronousCommand; import org.csstudio.dal.commands.Command; import org.csstudio.dal.context.ConnectionEvent; import org.csstudio.dal.context.ConnectionState; import org.csstudio.dal.context.DeviceFamily; import org.csstudio.dal.context.Identifier; import org.csstudio.dal.context.IdentifierUtilities; import org.csstudio.dal.context.LifecycleReporterSupport; import org.csstudio.dal.context.LinkBlocker; import org.csstudio.dal.context.LinkListener; import org.csstudio.dal.context.Linkable; import org.csstudio.dal.context.PropertyContext; import org.csstudio.dal.device.AbstractDevice; import org.csstudio.dal.group.GroupDataAccess; import org.csstudio.dal.group.PropertyGroupConstrain; import org.csstudio.dal.proxy.CommandProxy; import org.csstudio.dal.proxy.ConnectionStateMachine; import org.csstudio.dal.proxy.DeviceProxy; import org.csstudio.dal.proxy.DirectoryProxy; import org.csstudio.dal.proxy.PropertyProxy; import org.csstudio.dal.proxy.Proxy; import org.csstudio.dal.proxy.ProxyEvent; import org.csstudio.dal.proxy.ProxyListener; import org.csstudio.dal.spi.Plugs; import com.cosylab.util.ListenerList; /** * Glue implementation of AbstractDevice around DeviceProxy and * DirectoryProxy. * * @author Igor Kriznar (igor.kriznarATcosylab.com) */ public class AbstractDeviceImpl extends LifecycleReporterSupport implements AbstractDevice { class ProxyInterceptor implements ProxyListener<Object> { /* (non-Javadoc) * @see org.csstudio.dal.proxy.ProxyListener#connectionStateChange(org.csstudio.dal.proxy.Proxy, org.csstudio.dal.context.ConnectionState) */ @Override public void connectionStateChange(ProxyEvent<Proxy<?>> e) { if (e.getProxy() != deviceProxy) { return; } setConnectionState(e.getConnectionState()); setConnectionState(e.getConnectionState()); } /* (non-Javadoc) * @see org.csstudio.dal.proxy.ProxyListener#dynamicValueConditionChange(org.csstudio.dal.proxy.PropertyProxy, java.util.EnumSet) */ @Override public void dynamicValueConditionChange(ProxyEvent<PropertyProxy<Object,?>> e) { // not needed for device } @Override public void characteristicsChange(PropertyChangeEvent e) { firePropertyChangeEvent(e); } } private class PropertyInterceptor implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { firePropertyChangeEvent(evt); } } protected ResponseListener defaultResponseLitener = new ResponseListener() { @Override public void responseReceived(ResponseEvent event) { fireResponseReceived(event); } @Override public void responseError(ResponseEvent event) { fireResponseReceived(event); } }; protected ListenerList propertyListeners = new ListenerList(PropertyChangeListener.class); protected ListenerList responseListeners = new ListenerList(ResponseListener.class); protected Identifier identifier; protected DeviceProxy deviceProxy; protected DirectoryProxy directoryProxy; protected String name; protected ConnectionStateMachine connectionStateMachine = new ConnectionStateMachine(); protected ProxyInterceptor proxyInterceptor = new ProxyInterceptor(); protected PropertyChangeListener propertyInterceptor = new PropertyInterceptor(); protected String[] propertyNames; protected ArrayList<LinkListener<? extends Linkable>> linkListeners = new ArrayList<LinkListener<? extends Linkable>>(); protected Request lastRequest = null; protected Response lastResponse = null; private int suspended = 0; private boolean isdebug = false; protected DeviceFamily<?> deviceFamily; /** * Do not access this field directly. * * @see #getProperties() */ protected Map<String, DynamicValueProperty<?>> properties; protected String[] commandNames; protected Map<String, Command> commands; /** * */ public AbstractDeviceImpl(String name, DeviceFamily<?> deviceFamily) { super(); this.name = name; this.deviceFamily=deviceFamily; } /** * Initializes the device. Device and directory proxy must both be defined or both <code>null</code> * * @param devp Device proxy * @param dirp Directory proxy * * @throws IllegalStateException is thrown device has already been initialized * @throws IllegalArgumentException is thrown if both proxies aren't non-null or null. */ public void initialize(DeviceProxy devp, DirectoryProxy dirp) throws IllegalStateException { if ((devp != null && dirp == null) || (devp == null && dirp != null)) { throw new IllegalArgumentException( "Both proxies must be null or non-null."); } if (devp == null && dirp == null) { setConnectionState(ConnectionState.DISCONNECTING); if (deviceProxy != null) { deviceProxy.removeProxyListener(proxyInterceptor); } deviceProxy = null; directoryProxy = null; setConnectionState(ConnectionState.DISCONNECTED); return; } if (this.deviceProxy != null || this.directoryProxy != null) { throw new IllegalStateException("Device '" + name + "' is already initialized."); } deviceProxy = devp; directoryProxy = dirp; deviceProxy.addProxyListener(proxyInterceptor); // additional check if there was race condition within the listner. Anyway reduntant events will be ignored if (deviceProxy.getConnectionState() == ConnectionState.CONNECTED || deviceProxy.getConnectionState() == ConnectionState.CONNECTION_FAILED || deviceProxy.getConnectionState() == ConnectionState.CONNECTION_LOST) { setConnectionState(deviceProxy.getConnectionState()); } } /** * This method implements lazy initialization of properties field. * Do not access properties field directly but use this method. * * @return ensures lazy creation of properties array */ protected Map<String, DynamicValueProperty<?>> getProperties() { if (properties == null) { properties = new HashMap<String, DynamicValueProperty<?>>(getPropertyNames().length); String[] names = getPropertyNames(); for (int i = 0; i < names.length; i++) { try { properties.put(names[i], createProperty(names[i])); } catch (Exception e) { Logger.getLogger(AbstractDeviceImpl.class).error("Unhandled exception.", e); } } } return properties; } /* (non-Javadoc) * @see org.csstudio.dal.device.AbstractDevice#getUniqueName() */ @Override public String getUniqueName() { return name; } /* (non-Javadoc) * @see org.csstudio.dal.commands.CommandContext#getCommand(java.lang.String) */ @Override public Command getCommand(String name) throws RemoteException { if (commands == null) { commands = new HashMap<String, Command>(getCommandNames().length); } Command c = commands.get(name); if (c == null) { CommandProxy cp = deviceProxy.getCommand(name); if (cp== null) throw new RemoteException(this,"No such command "+name); if (cp.isAsynchronous()) { c = new AsynchronousCommandImpl(cp, this, defaultResponseLitener); commands.put(name, c); } else { c = new CommandImpl(cp, this); commands.put(name, c); } } return c; } @Override public AsynchronousCommand getCommandAsync(String name) throws RemoteException { Command c= getCommand(name); if (c instanceof AsynchronousCommand) { return (AsynchronousCommand)c; } return null; } /* (non-Javadoc) * @see org.csstudio.dal.commands.CommandContext#getCommandNames() */ @Override public String[] getCommandNames() throws RemoteException { if (commandNames == null) { commandNames = directoryProxy.getCommandNames(); } String[] n = new String[commandNames.length]; System.arraycopy(commandNames, 0, n, 0, commandNames.length); return n; } /* (non-Javadoc) * @see org.csstudio.dal.commands.CommandContext#getCommands() */ @Override public Command[] getCommands() throws RemoteException { String[] commNames = getCommandNames(); Command[] comms = new Command[commNames.length]; for (int i = 0; i < commNames.length; i++) { comms[i] = getCommand(commNames[i]); } return comms; } /* (non-Javadoc) * @see org.csstudio.dal.context.PropertyContext#containsProperty(java.lang.Object) */ @Override public boolean containsProperty(Object property) { return getProperties().containsValue(property); } /* (non-Javadoc) * @see org.csstudio.dal.context.PropertyContext#containsProperty(java.lang.String) */ @Override public boolean containsProperty(String name) { return getProperties().containsKey(name); } /* (non-Javadoc) * @see org.csstudio.dal.context.PropertyContext#getProperty(java.lang.String) */ @Override public synchronized DynamicValueProperty getProperty(String name) { if (!containsProperty(name)) { throw new IllegalArgumentException("Property '" + name + "' not found in '" + getUniqueName() + "'."); } DynamicValueProperty<?> p = getProperties().get(name); if (p == null) { try { p = createProperty(name); properties.put(name, p); } catch (Exception e) { Logger.getLogger(AbstractDeviceImpl.class).error("Unhandled exception.", e); } } return p; } /* (non-Javadoc) * @see org.csstudio.dal.context.PropertyContext#getPropertyNames() */ @Override public String[] getPropertyNames() { if (propertyNames == null) { try { propertyNames = directoryProxy.getPropertyNames(); } catch (RemoteException e) { handleException(e); propertyNames = new String[0]; } } String[] n = new String[propertyNames.length]; System.arraycopy(propertyNames, 0, n, 0, propertyNames.length); return n; } /** * Override this method to handle exception, whcih are not handled by DAL. * @param e an exception to handle. */ protected void handleException(Exception e) { Logger.getLogger(AbstractDeviceImpl.class).error("Unhandled exception.", e); } /* (non-Javadoc) * @see org.csstudio.dal.context.PropertyContext#toPropertyArray() */ @Override public DynamicValueProperty<?>[] toPropertyArray() { String[] n = getPropertyNames(); DynamicValueProperty<?>[] p = new DynamicValueProperty[n.length]; for (int i = 0; i < n.length; i++) { p[i] = getProperty(n[i]); } return p; } /* (non-Javadoc) * @see org.csstudio.dal.context.Identifiable#getIdentifier() */ @Override public Identifier getIdentifier() { if (identifier == null) { identifier = IdentifierUtilities.createIdentifier(this); } return identifier; } /* (non-Javadoc) * @see org.csstudio.dal.context.Identifiable#isDebug() */ @Override public boolean isDebug() { return isdebug; } /* (non-Javadoc) * @see org.csstudio.dal.AsynchronousContext#addResponseListener(org.csstudio.dal.ResponseListener) */ @Override public void addResponseListener(ResponseListener<?> l) { responseListeners.add(l); } /* (non-Javadoc) * @see org.csstudio.dal.AsynchronousContext#getLatestRequest() */ @Override public Request<?> getLatestRequest() { return lastRequest; } /* (non-Javadoc) * @see org.csstudio.dal.AsynchronousContext#getLatestResponse() */ @Override public Response<?> getLatestResponse() { return lastResponse; } /* (non-Javadoc) * @see org.csstudio.dal.AsynchronousContext#getLatestSuccess() */ @Override public boolean getLatestSuccess() { return lastResponse == null ? true : lastResponse.success(); } /* (non-Javadoc) * @see org.csstudio.dal.AsynchronousContext#getResponseListeners() */ @Override public ResponseListener<?>[] getResponseListeners() { return (ResponseListener<?>[])responseListeners.toArray(new ResponseListener[responseListeners .size()]); } /* (non-Javadoc) * @see org.csstudio.dal.AsynchronousContext#removeResponseListener(org.csstudio.dal.ResponseListener) */ @Override public void removeResponseListener(ResponseListener<?> l) { responseListeners.remove(l); } /* (non-Javadoc) * @see org.csstudio.dal.context.Linkable#addLinkListener(org.csstudio.dal.context.LinkListener) */ @Override public void addLinkListener(LinkListener<? extends Linkable> l) { linkListeners.add(l); } /* (non-Javadoc) * @see org.csstudio.dal.context.Linkable#isConnected() */ @Override public boolean isConnected() { return connectionStateMachine.isConnected(); } public boolean isConnecting(){ return connectionStateMachine.isConnecting(); } @Override public boolean isOperational() { return connectionStateMachine.isOperational(); } /* (non-Javadoc) * @see org.csstudio.dal.context.Linkable#isDestroyed() */ @Override public boolean isDestroyed() { return connectionStateMachine.isDestroyed(); } /* (non-Javadoc) * @see org.csstudio.dal.context.Linkable#isConnectionAlive() */ @Override public boolean isConnectionAlive() { return connectionStateMachine.isConnectionAlive(); } /* (non-Javadoc) * @see org.csstudio.dal.context.Linkable#isConnectionFailed() */ @Override public boolean isConnectionFailed() { return connectionStateMachine.isConnectionFailed(); } /* (non-Javadoc) * @see org.csstudio.dal.context.Linkable#isSuspended() */ @Override public boolean isSuspended() { return suspended > 0; } /* (non-Javadoc) * @see org.csstudio.dal.context.Linkable#refresh() */ @Override public void refresh() throws RemoteException { deviceProxy.refresh(); directoryProxy.refresh(); } /* (non-Javadoc) * @see org.csstudio.dal.context.Linkable#removeLinkListener(org.csstudio.dal.context.LinkListener) */ @Override public void removeLinkListener(LinkListener<? extends Linkable> l) { linkListeners.remove(l); } /* (non-Javadoc) * @see org.csstudio.dal.context.Linkable#resume() */ @Override public void resume() throws RemoteException { if (suspended > 0) { suspended--; } } /* (non-Javadoc) * @see org.csstudio.dal.context.Linkable#suspend() */ @Override public void suspend() throws RemoteException { suspended++; } /* (non-Javadoc) * @see org.csstudio.dal.group.GroupDataAccessProvider#getGroupDataAccess(java.lang.Class, java.lang.Class, org.csstudio.dal.group.PropertyGroupConstrain) */ @Override public <T, P extends DynamicValueProperty<T>> GroupDataAccess<T, P> getGroupDataAccess( Class<T> dataType, Class<P> propertyType, PropertyGroupConstrain constrain) { // NOT TO BE DONE // Auto-generated method stub return null; } /* (non-Javadoc) * @see org.csstudio.dal.group.GroupDataAccessProvider#getGroupDataAccess(java.lang.Class, java.lang.Class) */ @Override public <T, P extends DynamicValueProperty<?>> GroupDataAccess<T, P> getGroupDataAccess( Class<T> dataType, Class<P> propertyType) { // NOT TO BE DONE // auto-generated method stub return null; } /* (non-Javadoc) * @see org.csstudio.dal.CharacteristicContext#addPropertyChangeListener(java.beans.PropertyChangeListener) */ @Override public void addPropertyChangeListener(PropertyChangeListener l) { propertyListeners.add(l); } protected void firePropertyChangeEvent(PropertyChangeEvent e) { for (int i = 0; i < propertyListeners.size(); i++) { ((PropertyChangeListener)propertyListeners.get(i)).propertyChange(e); } } /* (non-Javadoc) * @see org.csstudio.dal.CharacteristicContext#getCharacteristic(java.lang.String) */ @Override public Object getCharacteristic(String name) throws DataExchangeException { // NOT TO BE DONE // Auto-generated method stub return null; } /* (non-Javadoc) * @see org.csstudio.dal.CharacteristicContext#getCharacteristicNames() */ @Override public String[] getCharacteristicNames() throws DataExchangeException { // NOT TO BE DONE // Auto-generated method stub return null; } /* (non-Javadoc) * @see org.csstudio.dal.CharacteristicContext#getCharacteristics(java.lang.String[]) */ @Override public Map<String, Object> getCharacteristics(String[] names) throws DataExchangeException { // NOT TO BE DONE // Auto-generated method stub return null; } /* (non-Javadoc) * @see org.csstudio.dal.CharacteristicContext#getPropertyChangeListeners() */ @Override public PropertyChangeListener[] getPropertyChangeListeners() { return (PropertyChangeListener[])propertyListeners.toArray(new PropertyChangeListener[propertyListeners .size()]); } /* (non-Javadoc) * @see org.csstudio.dal.CharacteristicContext#removePropertyChangeListener(java.beans.PropertyChangeListener) */ @Override public void removePropertyChangeListener(PropertyChangeListener l) { propertyListeners.remove(l); } /** * Sets connection state and fires event. * * @param s new state */ protected void setConnectionState(ConnectionState s) { boolean change= false; try { change= connectionStateMachine.requestNextConnectionState(s); } catch (IllegalStateException e) { Logger.getLogger(this.getClass()).error("Internal error.", e); throw e; } if (!change) { return; } ConnectionEvent<AbstractDevice> e = new ConnectionEvent<AbstractDevice>(this, connectionStateMachine.getConnectionState()); LinkListener<AbstractDevice>[] listenersArry = linkListeners.toArray(new LinkListener[linkListeners .size()]); switch (connectionStateMachine.getConnectionState()) { case CONNECTED: { for (LinkListener<AbstractDevice> l : listenersArry) { l.connected(e); } break; } case DISCONNECTED: { for (LinkListener<AbstractDevice> l : listenersArry) { l.disconnected(e); } break; } case CONNECTION_LOST: { for (LinkListener<AbstractDevice> l : listenersArry) { l.connectionLost(e); } break; } case DESTROYED: { for (LinkListener<AbstractDevice> l : listenersArry) { l.destroyed(e); } break; } default: break; } } protected DynamicValueProperty<?> createProperty(String name) throws RemoteException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException { PropertyProxy pp = deviceProxy.getPropertyProxy(name); DirectoryProxy dp = deviceProxy.getDirectoryProxy(name); Class<? extends SimpleProperty<?>> type = directoryProxy.getPropertyType(name); // Creates property implementation Class<?> impClass = PropertyUtilities.getImplementationClass(type); DynamicValuePropertyImpl<?> property = (DynamicValuePropertyImpl<?>)impClass.getConstructor(String.class, PropertyContext.class).newInstance(name, this); property.initialize(pp, dp); property.addPropertyChangeListener(propertyInterceptor); if (property.getConnectionState() != ConnectionState.CONNECTED) { Logger.getLogger(AbstractDeviceImpl.class).debug("Property '" + name +"' is not connected. Waiting for connection to be established..."); } LinkBlocker.blockUntillConnected(property,Plugs.getConnectionTimeout(null, 30000) * 2, true); return property; } protected void fireResponseReceived(ResponseEvent event) { lastResponse = event.getResponse(); lastRequest = event.getRequest(); Iterator<ResponseListener<?>> ite = responseListeners.iterator(); while (ite.hasNext()) { ite.next().responseReceived(event); } } public DeviceProxy getProxy() { return deviceProxy; } public Proxy[] releaseProxy(boolean destroy) { setConnectionState(ConnectionState.DISCONNECTING); if (properties!=null) { Collection<DynamicValueProperty<?>> props = properties.values(); for (Iterator<DynamicValueProperty<?>> iterator = props.iterator(); iterator.hasNext();) { DynamicValueProperty<?> dynamicValueProperty = (DynamicValueProperty<?>) iterator .next(); ((DataAccessImpl<?>) dynamicValueProperty).releaseProxy(destroy); } } Proxy[] temp = new Proxy[]{deviceProxy,directoryProxy}; deviceProxy = null; directoryProxy = null; setConnectionState(ConnectionState.DISCONNECTED); if (destroy) { setConnectionState(ConnectionState.DESTROYED); linkListeners.clear(); propertyListeners.clear(); responseListeners.clear(); } return temp; } public DirectoryProxy getDirectoryProxy() { return directoryProxy; } @Override public ConnectionState getConnectionState() { return connectionStateMachine.getConnectionState(); } /** * Returns plug type string, which is distinguishing for plug which * creates proxies for particular communication layer.<p>For * example plug that connects to EPICS device my return string "EPICS".</p> * * @return plug distinguishing type name */ @Override public String getPlugType() { // TODO: missing implementation return null; } @Override public DeviceFamily<?> getParentContext() { return deviceFamily; } } /* __oOo__ */