/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.felix.wireadmin; import java.util.Dictionary; import java.util.Enumeration; import java.util.Vector; import java.util.Date; import org.osgi.framework.Filter; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.service.wireadmin.Consumer; import org.osgi.service.wireadmin.Producer; import org.osgi.service.wireadmin.Wire; import org.osgi.service.wireadmin.WireConstants; import org.osgi.service.wireadmin.WireAdminEvent; /** * A connection between a Producer service and a Consumer service. * * <p>A <tt>Wire</tt> object connects a Producer service * to a Consumer service. * Both the Producer and Consumer services are identified * by their unique <tt>service.pid</tt> values. * The Producer and Consumer services may communicate with * each other via <tt>Wire</tt> objects that connect them. * The Producer service may send updated values to the * Consumer service by calling the {@link #update} method. * The Consumer service may request an updated value from the * Producer service by calling the {@link #poll} method. * * <p>A Producer service and a Consumer service may be * connected through multiple <tt>Wire</tt> objects. * * <p>Security Considerations. <tt>Wire</tt> objects are available to * Producer and Consumer services connected to a given * <tt>Wire</tt> object and to bundles which can access the <tt>WireAdmin</tt> service. * A bundle must have <tt>ServicePermission[GET,WireAdmin]</tt> to get the <tt>WireAdmin</tt> service to * access all <tt>Wire</tt> objects. * A bundle registering a Producer service or a Consumer service * must have the appropriate <tt>ServicePermission[REGISTER,Consumer|Producer]</tt> to register the service and * will be passed <tt>Wire</tt> objects when the service object's * <tt>consumersConnected</tt> or <tt>producersConnected</tt> method is called. * * <p>Scope. Each Wire object can have a scope set with the <tt>setScope</tt> method. This * method should be called by a Consumer service when it assumes a Producer service that is * composite (supports multiple information items). The names in the scope must be * verified by the <tt>Wire</tt> object before it is used in communication. The semantics of the * names depend on the Producer service and must not be interpreted by the Wire Admin service. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> * */ public class WireImpl implements Wire, java.io.Serializable { static final long serialVersionUID = -3637966367104019136L; // Persistent attributes // p. 327 "The Wire admin object contains a Persistend Identity (PID) for // a consumer service and a PID for a producer service" These properties // are also contained in the dictionary, but they are stored to optimize // access. private String m_producerPID; private String m_consumerPID; private Dictionary m_properties; // Transient attributes transient private boolean m_isValid = true; transient private boolean m_isConnected = false; transient private Filter m_filter = null; transient private ServiceReference m_producerServiceRef; transient private Producer m_producer; transient private boolean m_producerIsComposite = false; transient private String [] m_producerScope = null; transient private ServiceReference m_consumerServiceRef; transient private Consumer m_consumer; transient private boolean m_consumerIsComposite = false; transient private String [] m_consumerScope = null; transient private BundleContext m_bundleContext; transient private EventManager m_eventManager; transient private Object m_lastValue; transient private String[] m_scope; transient private long m_lastUpdate; transient private boolean m_isFirstUpdate; transient FilterDictionary m_dictionary; /** * Constructor with package visibility * * @param producerPID * @param consumerPID * @param properties */ WireImpl(String producerPID, String consumerPID, Dictionary properties) { m_producerPID = producerPID; m_consumerPID = consumerPID; m_properties = properties; // set the scope which is the intersection of strings in the WIREADMIN_PRODUCER_SCOPE properties and the strings in the WIREADMIN_CONSUMER_SCOPE m_scope = null; } /** * Method called after construction and after deserialization * * @param ctxt * @param eventManager */ void initialize(BundleContext ctxt, EventManager eventManager) { m_isValid = true; m_isConnected = false; m_bundleContext = ctxt; m_eventManager = eventManager; m_lastValue = null; m_lastUpdate = 0; m_isFirstUpdate = true; /* if(m_date == null) { m_date = new Date(); } */ m_dictionary = new FilterDictionary(); } /** * Return the state of this <tt>Wire</tt> object. * * <p>A connected <tt>Wire</tt> must always be disconnected before * becoming invalid. * * @return <tt>false</tt> if this <tt>Wire</tt> object is invalid because it * has been deleted via {@link WireAdmin#deleteWire}; * <tt>true</tt> otherwise. */ public boolean isValid() { return m_isValid; } /** * Return the connection state of this <tt>Wire</tt> object. * * <p>A <tt>Wire</tt> is connected after the Wire Admin service receives * notification that the Producer service and * the Consumer service for this <tt>Wire</tt> object are both registered. * This method will return <tt>true</tt> prior to notifying the Producer * and Consumer services via calls * to their respective <tt>consumersConnected</tt> and <tt>producersConnected</tt> * methods. * <p>A <tt>WireAdminEvent</tt> of type {@link WireAdminEvent#WIRE_CONNECTED} * must be broadcast by the Wire Admin service when * the <tt>Wire</tt> becomes connected. * * <p>A <tt>Wire</tt> object * is disconnected when either the Consumer or Producer * service is unregistered or the <tt>Wire</tt> object is deleted. * <p>A <tt>WireAdminEvent</tt> of type {@link WireAdminEvent#WIRE_DISCONNECTED} * must be broadcast by the Wire Admin service when * the <tt>Wire</tt> becomes disconnected. * * @return <tt>true</tt> if both the Producer and Consumer * for this <tt>Wire</tt> object are connected to the <tt>Wire</tt> object; * <tt>false</tt> otherwise. */ public boolean isConnected() { return m_isConnected; } /** * Return the list of data types understood by the * Consumer service connected to this <tt>Wire</tt> object. Note that * subclasses of the classes in this list are acceptable data types as well. * * <p>The list is the value of the {@link WireConstants#WIREADMIN_CONSUMER_FLAVORS} * service property of the * Consumer service object connected to this object. If no such * property was registered or the type of the property value is not * <tt>Class[]</tt>, this method must return <tt>null</tt>. * * @return An array containing the list of classes understood by the * Consumer service or <tt>null</tt> if * the <tt>Wire</tt> is not connected, * or the consumer did not register a {@link WireConstants#WIREADMIN_CONSUMER_FLAVORS} property * or the value of the property is not of type <tt>Class[]</tt>. */ public Class[] getFlavors() { if(isConnected()) { try { return (Class [])m_consumerServiceRef.getProperty(WireConstants.WIREADMIN_CONSUMER_FLAVORS); } catch(ClassCastException ex) { return null; } } else { return null; } } /** * Update the value. * * <p>This methods is called by the Producer service to * notify the Consumer service connected to this <tt>Wire</tt> object * of an updated value. * <p>If the properties of this <tt>Wire</tt> object contain a * {@link WireConstants#WIREADMIN_FILTER} property, * then filtering is performed. * If the Producer service connected to this <tt>Wire</tt> * object was registered with the service * property {@link WireConstants#WIREADMIN_PRODUCER_FILTERS}, the * Producer service will perform the filtering according to the rules specified * for the filter. Otherwise, this <tt>Wire</tt> object * will perform the filtering of the value. * <p>If no filtering is done, or the filter indicates the updated value should * be delivered to the Consumer service, then * this <tt>Wire</tt> object must call * the {@link Consumer#updated} method with the updated value. * If this <tt>Wire</tt> object is not connected, then the Consumer * service must not be called and the value is ignored.<p> * If the value is an <tt>Envelope</tt> object, and the scope name is not permitted, then the * <tt>Wire</tt> object must ignore this call and not transfer the object to the Consumer * service. * * <p>A <tt>WireAdminEvent</tt> of type {@link WireAdminEvent#WIRE_TRACE} * must be broadcast by the Wire Admin service after * the Consumer service has been successfully called. * * @param value The updated value. The value should be an instance of * one of the types returned by {@link #getFlavors}. * @see WireConstants#WIREADMIN_FILTER */ public void update(Object value) { // TODO Implement Access Control (p. 338) if (isConnected()) { if(m_producerIsComposite == true) { // TODO Implement update for composite producers WireAdminImpl.traceln("WireImpl.update: update for composite producers not yet implemented"); return; } else // not a composite (Note: p. 341 "Filtering for composite producer services is not supported") { //long time = m_date.getTime(); long time = new Date().getTime(); // We ignore filtering the first time... if(m_isFirstUpdate == false && m_filter != null) { // If the Producer service was registered with the WIREADMIN_PRODUCER_FILTERS // service property indicating that the Producer service will perform the data // filtering then the Wire object will not perform data filtering. Otherwise, // the Wire object must perform basic filtering. try { m_dictionary.reset(value,time); if(!m_filter.match(m_dictionary)) { WireAdminImpl.traceln("### Update rejected ("+m_properties.get(WireConstants.WIREADMIN_PID)+") filter evaluated to false:"+m_filter); WireAdminImpl.traceln(" WIREVALUE_CURRENT.class"+m_dictionary.get(WireConstants.WIREVALUE_CURRENT).getClass().getName()); WireAdminImpl.traceln(" WIREVALUE_CURRENT="+m_dictionary.get(WireConstants.WIREVALUE_CURRENT)); WireAdminImpl.traceln(" WIREVALUE_PREVIOUS="+m_dictionary.get(WireConstants.WIREVALUE_PREVIOUS)); WireAdminImpl.traceln(" WIREVALUE_DELTA_ABSOLUTE="+m_dictionary.get(WireConstants.WIREVALUE_DELTA_ABSOLUTE)); WireAdminImpl.traceln(" WIREVALUE_DELTA_RELATIVE="+m_dictionary.get(WireConstants.WIREVALUE_DELTA_RELATIVE)); WireAdminImpl.traceln(" WIREVALUE_ELAPSED="+m_dictionary.get(WireConstants.WIREVALUE_ELAPSED)); return; } } catch(Exception ex) { // Could happen... WireAdminImpl.trace(ex); } } try { m_consumer.updated(this, value); if(m_isFirstUpdate == true) { m_isFirstUpdate = false; } m_lastUpdate = time; m_lastValue = value; // Fire event m_eventManager.fireEvent(WireAdminEvent.WIRE_TRACE,this); } catch(Exception ex) { m_eventManager.fireEvent(WireAdminEvent.CONSUMER_EXCEPTION,this,ex); } } } } /** * Poll for an updated value. * * <p>This methods is normally called by the Consumer service to * request an updated value from the Producer service * connected to this <tt>Wire</tt> object. * This <tt>Wire</tt> object will call * the {@link Producer#polled} method to obtain an updated value. * If this <tt>Wire</tt> object is not connected, then the Producer * service must not be called.<p> * * If this <tt>Wire</tt> object has a scope, then this method * must return an array of <tt>Envelope</tt> objects. The objects returned must * match the scope of this object. The <tt>Wire</tt> object must remove * all <tt>Envelope</tt> objects with a scope name that is not in the <tt>Wire</tt> object's scope. * Thus, the list of objects returned * must only contain <tt>Envelope</tt> objects with a permitted scope name. If the * array becomes empty, <tt>null</tt> must be returned. * * <p>A <tt>WireAdminEvent</tt> of type {@link WireAdminEvent#WIRE_TRACE} * must be broadcast by the Wire Admin service after * the Producer service has been successfully called. * * @return A value whose type should be one of the types * returned by {@link #getFlavors}, <tt>Envelope[]</tt>, or <tt>null</tt> if * the <tt>Wire</tt> object is not connected, * the Producer service threw an exception, or * the Producer service returned a value which is not an instance of * one of the types returned by {@link #getFlavors}. */ public Object poll() { // p.330 "Update filtering must not apply to polling" if (isConnected()) { try { Object value = m_producer.polled(this); Class []flavors = getFlavors(); boolean valueOk = false; // Test if the value is ok with respect to the flavors understood by // the consumer for(int i=0; i<flavors.length; i++) { Class currentClass = flavors[i]; if(currentClass.isInstance(value)) { valueOk = true; } } if(valueOk) { m_eventManager.fireEvent(WireAdminEvent.WIRE_TRACE,this); m_lastValue = value; return m_lastValue; } else { WireAdminImpl.traceln("WireImpl.poll: value returned by producer is not undestood by consumer"); return null; } } catch(Exception ex) { m_eventManager.fireEvent(WireAdminEvent.PRODUCER_EXCEPTION,this,ex); return null; } } else { // p. 333 "If the poll() method on the wire object is called and the // producer is unregistered it must return a null value" return null; } } /** * Return the last value sent through this <tt>Wire</tt> object. * * <p>The returned value is the most recent, valid value passed to the * {@link #update} method or returned by the {@link #poll} method * of this object. If filtering is performed by this <tt>Wire</tt> object, * this methods returns the last value provided by the Producer service. This * value may be an <tt>Envelope[]</tt> when the Producer service * uses scoping. If the return value is an Envelope object (or array), it * must be verified that the Consumer service has the proper WirePermission to see it. * * @return The last value passed though this <tt>Wire</tt> object * or <tt>null</tt> if no valid values have been passed or the Consumer service has no permission. */ public Object getLastValue() { return m_lastValue; } /** * Return the wire properties for this <tt>Wire</tt> object. * * @return The properties for this <tt>Wire</tt> object. * The returned <tt>Dictionary</tt> must be read only. */ public Dictionary getProperties() { return m_properties; } /** * Return the calculated scope of this <tt>Wire</tt> object. * * The purpose of the <tt>Wire</tt> object's scope is to allow a Producer * and/or Consumer service to produce/consume different types * over a single <tt>Wire</tt> object (this was deemed necessary for efficiency * reasons). Both the Consumer service and the * Producer service must set an array of scope names (their scope) with * the service registration property <tt>WIREADMIN_PRODUCER_SCOPE</tt>, or <tt>WIREADMIN_CONSUMER_SCOPE</tt> when they can * produce multiple types. If a Producer service can produce different types, it should set this property * to the array of scope names it can produce, the Consumer service * must set the array of scope names it can consume. The scope of a <tt>Wire</tt> * object is defined as the intersection of permitted scope names of the * Producer service and Consumer service. * <p>If neither the Consumer, or the Producer service registers scope names with its * service registration, then the <tt>Wire</tt> object's scope must be <tt>null</tt>. * <p>The <tt>Wire</tt> object's scope must not change when a Producer or Consumer services * modifies its scope. * <p>A scope name is permitted for a Producer service when the registering bundle has * <tt>WirePermission[PRODUCE]</tt>, and for a Consumer service when * the registering bundle has <tt>WirePermission[CONSUME]</tt>.<p> * If either Consumer service or Producer service has not set a <tt>WIREADMIN_*_SCOPE</tt> property, then * the returned value must be <tt>null</tt>.<p> * If the scope is set, the <tt>Wire</tt> object must enforce the scope names when <tt>Envelope</tt> objects are * used as a parameter to update or returned from the <tt>poll</tt> method. The <tt>Wire</tt> object must then * remove all <tt>Envelope</tt> objects with a scope name that is not permitted. * * @return A list of permitted scope names or null if the Produce or Consumer service has set no scope names. */ public String[] getScope() { return m_scope; } /** * Return true if the given name is in this <tt>Wire</tt> object's scope. * * @param name The scope name * @return true if the name is listed in the permitted scope names */ public boolean hasScope(String name) { if (m_scope != null) { for (int i = 0; i < m_scope.length; i++) { if (name.equals(m_scope[i])) { return true; } } } return false; } /** * @see java.lang.Object#toString() */ public String toString() { return super.toString() + ":[" + getProperties().get(WireConstants.WIREADMIN_PID) + "," + getProperties().get(WireConstants.WIREADMIN_PRODUCER_PID) + "," + getProperties().get(WireConstants.WIREADMIN_CONSUMER_PID) + "]"; } /** * Bind the consumer. * * @param consumer A <tt>ServiceReference</tt> corresponding to the consumer service */ void bindConsumer(ServiceReference consumerRef) { if(m_consumerServiceRef != null) { WireAdminImpl.traceln("WireImpl: consumer already bound!"); return; } // Pid must be present if(consumerRef.getProperty(Constants.SERVICE_PID) == null) { WireAdminImpl.traceln("WireImpl: Consumer service has no PID "+consumerRef); return; } // Flavors must be present if(consumerRef.getProperty(WireConstants.WIREADMIN_CONSUMER_FLAVORS) == null) { WireAdminImpl.traceln("WireImpl: Consumer service has no WIREADMIN_CONSUMER_FLAVORS "+consumerRef); return; } // Is this a composite? if(consumerRef.getProperty(WireConstants.WIREADMIN_CONSUMER_COMPOSITE) != null) { m_consumerIsComposite = true; m_consumerScope = (String []) consumerRef.getProperty(WireConstants.WIREADMIN_CONSUMER_SCOPE); } m_consumerServiceRef = consumerRef; m_consumer = (Consumer) m_bundleContext.getService(consumerRef); if(m_producer != null) { if(m_producerIsComposite) { if(matchComposites() == false) { WireAdminImpl.traceln("WireImpl.bindConsumer : warning composite identities do not match"); } } m_isConnected = true; m_eventManager.fireEvent(WireAdminEvent.WIRE_CONNECTED,this); } } /** * Unbind the consumer * */ void unbindConsumer() { // This test is made because knopflerfish doesn't support ungetService(null); if(m_consumerServiceRef != null) { m_bundleContext.ungetService(m_consumerServiceRef); if(m_isConnected) { m_isConnected = false; m_eventManager.fireEvent(WireAdminEvent.WIRE_DISCONNECTED,this); } m_consumer = null; m_consumerServiceRef = null; } } /** * Bind the producer * * @param producer A <tt>ServiceReference</tt> corresponding to the producer service */ void bindProducer(ServiceReference producerRef) { if(m_producerServiceRef != null) { WireAdminImpl.traceln("WireImpl: producer already bound!"); return; } // Pid must be present if(producerRef.getProperty(Constants.SERVICE_PID) == null) { WireAdminImpl.traceln("WireImpl.bindProducer: Producer service has no PID "+producerRef); return; } // Flavors must be present if(producerRef.getProperty(WireConstants.WIREADMIN_PRODUCER_FLAVORS) == null) { WireAdminImpl.traceln("WireImpl: Consumer service has no WIREADMIN_PRODUCER_FLAVORS "+producerRef); return; } // Is this a composite? if(producerRef.getProperty(WireConstants.WIREADMIN_PRODUCER_COMPOSITE) != null) { m_producerIsComposite = true; m_producerScope = (String []) producerRef.getProperty(WireConstants.WIREADMIN_PRODUCER_SCOPE); } m_producerServiceRef = producerRef; m_producer = (Producer) m_bundleContext.getService(producerRef); // p. 329 " If this property (wireadmin.producer.filters) is not set, // the Wire object must filter according to the description in CompositeObjects" if(producerRef.getProperty(WireConstants.WIREADMIN_PRODUCER_FILTERS) == null) { String filter = (String) m_properties.get(WireConstants.WIREADMIN_FILTER); if(filter != null) { try { m_filter = m_bundleContext.createFilter(filter); } catch(InvalidSyntaxException ex) { WireAdminImpl.traceln("WireImpl.bindProducer: Ignoring filter with invalid syntax "+filter); } } } if(m_consumer != null) { if(m_consumerIsComposite) { if(matchComposites() == false) { WireAdminImpl.traceln("WireImpl.bindProducer : warning composite identities do not match"); } } m_isConnected = true; // fire the corresponding event m_eventManager.fireEvent(WireAdminEvent.WIRE_CONNECTED,this); } } /** * Unbind the producer * */ void unbindProducer() { if(m_producerServiceRef != null) { m_bundleContext.ungetService(m_producerServiceRef); if(m_isConnected) { m_isConnected = false; // fire the corresponding event m_eventManager.fireEvent(WireAdminEvent.WIRE_DISCONNECTED,this); } m_producer = null; m_producerServiceRef = null; } } /** * Check if composites match. Match occurs when "at least one equal composite identity is * listed on both the Producer and Consmer composite identity service property" (p. 336) * * @return <tt>true</tt> if they match, <tt>false</tt>otherwise */ private boolean matchComposites() { String [] producerIdentities = (String []) m_producerServiceRef.getProperty(WireConstants.WIREADMIN_PRODUCER_COMPOSITE); String [] consumerIdentities = (String []) m_producerServiceRef.getProperty(WireConstants.WIREADMIN_PRODUCER_COMPOSITE); boolean match = false; // confirm matching for(int i = 0; i< producerIdentities.length; i++) { String currentProducerIdentity = producerIdentities [i]; for(int j = 0; j < consumerIdentities.length; j++) { String currentConsumerIdentity = consumerIdentities [j]; if (currentProducerIdentity.equals(currentConsumerIdentity)) { match = true; break; } } } // setup wire scope if (m_consumerScope != null && m_producerScope != null) { // p. 337 "It is allowed to register with a wildcard, indicating that all scopenames // are supported. In that case, the WIREADMIN_SCOPE_ALL ({"*"}) should be registered as the // scope of the serivce. The WireObject's scope is then fully defined by the // other service connected to the wire object if(m_consumerScope.length == 1 && m_consumerScope[0].equals("*")) { m_scope = m_producerScope; } else if(m_producerScope.length == 1 && m_producerScope[0].equals("*")) { m_scope = m_consumerScope; } else { /* // TODO Implement wire scope creation based on producer, consumer and wire permissions (p. 338) ServiceReference ref = m_bundleContext.getServiceReference(PermissionAdmin.class.getName()); if(ref != null) { PermissionAdmin permAdmin = (PermissionAdmin) m_bundleContext.getService(ref); PermissionInfo [] producerPermissions = permAdmin.getPermissions(m_producerServiceRef.getBundle().getLocation()); PermissionInfo [] consumerPermissions = permAdmin.getPermissions(m_consumerServiceRef.getBundle().getLocation()); } */ } } else { // p. 337 "Not registering this property (WIREADMIN_CONSUMER_SCOPE or WIREADMIN_PRODUCER_SCOPE) // by the Consumer or the Producer service indicates to the WireAdmin service that any // Wire object connected to that service must return null for the Wire.getScope() method" m_scope = null; } return match; } /** * Called to invalidate the wire * */ void invalidate() { if(m_isValid) { unbindProducer(); unbindConsumer(); m_isValid=false; } } /** * Update the properties * * @param properties new properties */ void updateProperties(Dictionary properties) { m_properties = properties; } /** * Return the service reference corresponding to the producer * * @return a <tt>ServiceReference</tt> corresponding to the producer */ ServiceReference getProducerServiceRef() { return m_producerServiceRef; } /** * Return the producer service object * * @return An <tt>Object</tt> corresponding to the producer */ Producer getProducer() { return m_producer; } /** * return the producer PID * * @return */ String getProducerPID() { return m_producerPID; } /** * Return the service reference corresponding to the consumer * * @return a <tt>ServiceReference</tt> corresponding to the consumer */ ServiceReference getConsumerServiceRef() { return m_consumerServiceRef; } /** * Returns the consumer service object * * @return An <tt>Object</tt> corresponding to the consumer */ Consumer getConsumer() { return m_consumer; } /** * return the consumer PID * * @return */ String getConsumerPID() { return m_consumerPID; } /** * This inner class implements a dictionary that is used to filter out values * during calls to update. This design choice was favored to avoid constructing * a new dictionary for every call to update and to avoid doing unnecessary * calculations. */ class FilterDictionary extends Dictionary { private Object m_value; private long m_time; /** * Must be called prior to evaluating the filter * * @param value * @param time */ void reset(Object value, long time) { m_value = value; m_time = time; } /** * */ public Object get(Object key) { if(key.equals(WireConstants.WIREVALUE_CURRENT)) { return m_value; } else if(key.equals(WireConstants.WIREVALUE_PREVIOUS)) { return m_lastValue; } else if(m_value instanceof Number && key.equals(WireConstants.WIREVALUE_DELTA_ABSOLUTE)) { return null; } else if(m_value instanceof Number && key.equals(WireConstants.WIREVALUE_DELTA_RELATIVE)) { return null; } else if(key.equals(WireConstants.WIREVALUE_ELAPSED)) { if(m_lastUpdate == 0) { return new Long(0); } else { long delay = m_time - m_lastUpdate; //System.out.println("### delay = "+(delay)); return new Long(delay); } } else { WireAdminImpl.traceln("### key not found:"+key); return null; } } /** * Never empty */ public boolean isEmpty() { return false; } /** * Remove not supported */ public Object remove(Object obj) { return null; } /** * Size is static */ public int size() { return 5; } /** * Put not supported */ public Object put(Object key, Object value) { return null; } /** * */ public Enumeration keys() { Vector keys=new Vector(); keys.addElement(WireConstants.WIREVALUE_ELAPSED); keys.addElement(WireConstants.WIREVALUE_CURRENT); keys.addElement(WireConstants.WIREVALUE_PREVIOUS); keys.addElement(WireConstants.WIREVALUE_DELTA_ABSOLUTE); keys.addElement(WireConstants.WIREVALUE_DELTA_RELATIVE); return keys.elements(); } /** * Not supported */ public Enumeration elements() { return null; } } }