/** * */ package org.csstudio.dal.proxy; import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.csstudio.dal.CharacteristicInfo; import org.csstudio.dal.DataExchangeException; import org.csstudio.dal.DynamicValueCondition; import org.csstudio.dal.DynamicValueState; import org.csstudio.dal.RemoteException; import org.csstudio.dal.Request; import org.csstudio.dal.Response; import org.csstudio.dal.ResponseListener; import org.csstudio.dal.SimpleProperty; import org.csstudio.dal.context.ConnectionState; import org.csstudio.dal.impl.PropertyUtilities; import org.csstudio.dal.impl.RequestImpl; import org.csstudio.dal.impl.ResponseImpl; import org.csstudio.dal.simple.impl.DynamicValueConditionConverterUtil; /** * Abstract implementation of PropertyProxy, as help for plug implementation. * * @author ikriznar * */ public abstract class AbstractPropertyProxyImpl<T,P extends AbstractPlug,M extends MonitorProxy> extends AbstractProxyImpl<P> implements PropertyProxy<T,P> { private DynamicValueCondition condition= new DynamicValueCondition(DynamicValueState.NORMAL); private Response<T> lastValueResponse; private Map<String, Object> characteristics; private List<M> monitors; private boolean liveDataNotSet = true; private boolean metaDataNotSet = true; public AbstractPropertyProxyImpl(String name, P plug) { super(name,plug); } /** * Internal storage for monitors. * @return characteristics cache */ protected List<M> getMonitors() { if (monitors == null) { synchronized (this) { if (monitors==null) monitors = newMonitorsList(); } } return monitors; } protected boolean isMonitorListCreated() { return monitors!=null; } protected boolean isCharacteristicsCacheCreated() { return characteristics!=null; } /** * Creates new instance of list that is used for storing monitors. * Plug implementation may override to provide own list implementation. * @return new instance of list that is used for storing monitors */ private List<M> newMonitorsList() { return new ArrayList<M>(2); } /** * Destroy all monitors. */ protected void destroyMonitors(){ if (monitors!=null) { MonitorProxy[] array; synchronized (monitors) { array = new MonitorProxy[monitors.size()]; monitors.toArray(array); } // destroy all for (int i = 0; i < array.length; i++) array[i].destroy(); } } /** * Add monitor. * @param monitor monitor to be added. */ public void addMonitor(M monitor){ synchronized (getMonitors()) { if (!monitors.contains(monitor)) monitors.add(monitor); } } @SuppressWarnings("unchecked") @Override public void addProxyListener(ProxyListener<?> l) { super.addProxyListener(l); @SuppressWarnings("rawtypes") ProxyEvent e = new ProxyEvent(this, getCondition(), getConnectionState(), null); try { l.dynamicValueConditionChange(e); } catch (Exception ex) { Logger.getLogger(this.getClass()).error("Failed to forward listener.", ex); } } /** * Remove monitor. * @param monitor monitor to be removed. */ public void removeMonitor(M monitor){ if (monitors==null) return; synchronized (monitors){ monitors.remove(monitor); } } /** * Internal cache for proxy characteristics. * @return characteristics cache */ protected Map<String, Object> getCharacteristics() { if (characteristics == null) { synchronized (this) { if (characteristics==null) characteristics = newCharacteristicsCache(); } } return characteristics; } /** * Returns new characteristics cache map. Called only once when cache is created. * Plug implementation may override this to implement own creation and initialization. * @return new characteristics cache map */ protected Map<String, Object> newCharacteristicsCache() { return new HashMap<String, Object>(16); } @Override protected void handleConnectionState(ConnectionState s) { /* we update condition and fire update before connection update, * may not be perfect, but at least assures synchronization betwean * two of them in proxy and property implementation. */ setCondition(connectionStateMachine.deriveUpdatedCondition(getCondition())); super.handleConnectionState(s); } /** * Fires new condition event. */ protected void fireCondition() { if (this.proxyListeners == null) return; @SuppressWarnings("unchecked") ProxyListener<T>[] l= (ProxyListener<T>[])this.proxyListeners.toArray(); ProxyEvent<PropertyProxy<T,?>> pe= new ProxyEvent<PropertyProxy<T,?>>((PropertyProxy<T,P>)this,this.condition,this.connectionStateMachine.getConnectionState(),null); for (int i = 0; i < l.length; i++) { try { l[i].dynamicValueConditionChange(pe); } catch (Exception e) { Logger.getLogger(this.getClass()).warn("Event handler error, continuing.", e); } } if (metaDataNotSet && condition.containsAnyOfStates(DynamicValueState.HAS_METADATA)) { metaDataNotSet=false; if (connectionStateMachine.requestOperationalState(getCondition().getStates())) fireConnectionState(ConnectionState.OPERATIONAL,null); } } /* * (non-Javadoc) * @see org.csstudio.dal.proxy.PropertyProxy#getCondition() */ @Override public DynamicValueCondition getCondition() { return this.condition; } /** * Intended for only within plug. * @param s new condition state. */ public synchronized void setCondition(DynamicValueCondition s) { if (s == null || this.condition == s || this.condition.areStatesEqual(s)) return; this.condition=s; fireCondition(); } public void updateConditionWith(String message, DynamicValueState... states) { if (states == null) return; DynamicValueCondition c= new DynamicValueCondition(DynamicValueState.deriveSetWithStates(getCondition().getStates(), states),null, message); setCondition(c); } @Override public Response<T> getLatestValueResponse() { return lastValueResponse; } public void updateValueReponse(Response<T> r) { lastValueResponse=r; if (liveDataNotSet && lastValueResponse!=null) { liveDataNotSet=false; getCondition().getStates().remove(DynamicValueState.NO_VALUE); updateConditionWith(null, DynamicValueState.HAS_LIVE_DATA); if (connectionStateMachine.requestOperationalState(getCondition().getStates())) fireConnectionState(ConnectionState.OPERATIONAL,null); } } /** * Returns some value for those characteristics, which does not require remote connection * but can provide value from proxy within. * @param name the characteristic name * @return the proxy local characteristic value */ public Object getLocalProxyCharacteristic(String name) { if (name == null) return null; if (name.equals(CharacteristicInfo.C_SEVERITY.getName())) return condition; if (name.equals(CharacteristicInfo.C_STATUS.getName())) return DynamicValueConditionConverterUtil.extractStatusInfo(condition); if (name.equals(CharacteristicInfo.C_TIMESTAMP.getName())) return DynamicValueConditionConverterUtil.extractTimestampInfo(condition); return null; } /** * Default implementation trying to help getting characteristic value. */ public Object getCharacteristic(String characteristicName) throws DataExchangeException { if (characteristicName==null) return null; // get system characteristic Object value= getLocalProxyCharacteristic(characteristicName); value = processCharacteristicBeforeCache(value, characteristicName); // get characteristic from cache if (value==null && characteristics!=null) { synchronized (characteristics) { value= characteristics.get(characteristicName); } } value = processCharacteristicAfterCache(value, characteristicName); if (value==null && (this instanceof DirectoryProxy)) value= PropertyUtilities.verifyCharacteristic((DirectoryProxy<?>)this, characteristicName, value); return value; } /** * Plug implementation should implement here processing characteristic value after has not been found in * characteristic cache. * * Plus should here implement remote requesting of characteristic. Simulator should simply * provide some value, if it is not already in cache. * * @param value the value establishes so far * @param characteristicName the name of requested characteristic * @return new value or just provided value */ protected abstract Object processCharacteristicAfterCache(Object value, String characteristicName); /** * Plug implementation should implement here processing characteristic value before it is taken from * characteristic cache. * * If plug has nothing to do, then should simply return provided value. * * @param value the value establishes so far * @param characteristicName the name of requested characteristic * @return new value or just provided value */ protected abstract Object processCharacteristicBeforeCache(Object value, String characteristicName); @Override public void destroy() { destroyMonitors(); super.destroy(); } /** * Fires new characteristics changed event */ protected void fireCharacteristicsChanged(PropertyChangeEvent ev){ if (proxyListeners == null) return; ProxyListener<?>[] l = (ProxyListener[])proxyListeners.toArray(); for (int i = 0; i < l.length; i++) { try { l[i].characteristicsChange(ev); } catch (Exception e) { Logger.getLogger(this.getClass()).warn("Simulator error.", e); } } } /* * Dummy methods because of DirectiryProxy, not needed or used in PropertyProxy. */ public String[] getCommandNames() throws DataExchangeException { return null; } public Class<? extends SimpleProperty<?>> getPropertyType( String propertyName) throws RemoteException { return null; } public String[] getPropertyNames() throws RemoteException { return null; } @SuppressWarnings("unchecked") public Request<?> getCharacteristics(final String[] characteristics, ResponseListener<?> callback) throws DataExchangeException { RequestImpl<Object> r = new RequestImpl<Object>(this, (ResponseListener<Object>) callback); handleCharacteristicsReponses(characteristics, (ResponseListener<Object>) callback, r); return r; } /** * Handles firing responses with characteristics. * Plug implementation may override this method to handles this operation asynchronously. * Default implementation calls synchronous method. * @param characteristics * @param callback * @param request */ protected void handleCharacteristicsReponses(final String[] characteristics, final ResponseListener<Object> callback, final RequestImpl<Object> request){ handleCharacteristicsReponsesSync(characteristics, callback, request); } /** * Handles getting and firing characteristics responses in synchronous way. * @param characteristics * @param callback * @param request */ protected void handleCharacteristicsReponsesSync(final String[] characteristics, final ResponseListener<Object> callback, final RequestImpl<Object> request) { for (int i = 0; i < characteristics.length; i++) { Object value; try { value= getCharacteristic(characteristics[i]); request.addResponse(new ResponseImpl<Object>(this, request, value, characteristics[i], value != null, null, getCondition(), null, i+1 == characteristics.length)); } catch (DataExchangeException e) { request.addResponse(new ResponseImpl<Object>(this, request, null, characteristics[i], false, e, getCondition(), null, i+1 == characteristics.length)); } } } /** * Sets new characteristics value and fires property change event to proxy listeners * with new characteristic value. Note that adding value directly to getCharacteristics() * object does not fire property change event. * * @param chName characteristic name * @param newValue new value to be stores in characteristic cache * @return true if characteristics value in property has changed by this operation */ public boolean updateCharacteristic(String chName, Object newValue){ if (chName==null || (newValue==null && characteristics==null)) return false; Object old = getCharacteristics().put(chName, newValue); if (newValue!=null) { if (newValue.equals(old)) return false; } else if (old==null) return false; fireCharacteristicsChanged(new PropertyChangeEvent(this,chName,old,newValue)); return true; } }