/******************************************************************************* * Copyright (c) 2009 MATERNA Information & Communications. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html. For further * project-related information visit http://www.ws4d.org. The most recent * version of the JMEDS framework can be obtained from * http://sourceforge.net/projects/ws4d-javame. ******************************************************************************/ package org.ws4d.java.client; import java.io.IOException; import org.ws4d.java.DPWSFramework; import org.ws4d.java.communication.CommunicationBinding; import org.ws4d.java.communication.CommunicationManager; import org.ws4d.java.communication.CommunicationManagerRegistry; import org.ws4d.java.communication.DefaultIncomingMessageListener; import org.ws4d.java.communication.DiscoveryBinding; import org.ws4d.java.communication.ProtocolData; import org.ws4d.java.communication.monitor.MonitorStreamFactory; import org.ws4d.java.communication.monitor.MonitoringContext; import org.ws4d.java.constants.DPWSMessageConstants; import org.ws4d.java.dispatch.DeviceServiceRegistry; import org.ws4d.java.dispatch.HelloData; import org.ws4d.java.dispatch.ServiceReferenceEventRegistry; import org.ws4d.java.eventing.ClientSubscription; import org.ws4d.java.eventing.EventListener; import org.ws4d.java.eventing.EventSink; import org.ws4d.java.eventing.EventingFactory; import org.ws4d.java.message.MessageDiscarder; import org.ws4d.java.message.discovery.HelloMessage; import org.ws4d.java.service.Device; import org.ws4d.java.service.Service; import org.ws4d.java.service.parameter.ParameterValue; import org.ws4d.java.service.reference.DeviceListener; import org.ws4d.java.service.reference.DeviceReference; import org.ws4d.java.service.reference.ServiceListener; import org.ws4d.java.service.reference.ServiceReference; import org.ws4d.java.structures.ArrayList; import org.ws4d.java.structures.DataStructure; import org.ws4d.java.structures.HashMap; import org.ws4d.java.structures.Iterator; import org.ws4d.java.structures.MessageIdBuffer; import org.ws4d.java.types.EndpointReference; import org.ws4d.java.types.QNameSet; import org.ws4d.java.types.ScopeSet; import org.ws4d.java.types.URI; import org.ws4d.java.util.Log; import org.ws4d.java.util.WS4DIllegalStateException; /** * Default DPWS Client implementation. This class provides easy access to * several points of interaction within the DPWS framework, such as searching * for devices/services, tracking a device's or service's state changes and * receiving events from subscribed services. * <p> * The basic idea behind this class is: it extends several callback interfaces * and provides empty implementations for all of their methods, so that an * implementing client can easily overwrite those in which it is really * interested in. * </p> * <p> * A simple use case of this class could be a client searching for a particular * device. This can be accomplished by a call to * {@link #searchDevice(SearchParameter)} providing the desired search criteria * within the expected <code>SearchParameter</code> argument. The framework will * then start looking asynchronously for devices matching those criteria and * will invoke {@link #deviceFound(DeviceReference, SearchParameter)} each time * a corresponding device is discovered.<br /> * Searching for services can be done in a similar manner, this time using the * method {@link #searchService(SearchParameter)} to initiate the search and * receiving results by means of * {@link #serviceFound(ServiceReference, SearchParameter)}. * </p> * <p> * When a client starts a {@link #searchDevice(SearchParameter) device search}, * it is automatically registered as {@link DeviceListener} to any device * matching the search criteria. This is especially useful for getting * notifications about state changes of the device, such as a * {@link #deviceBye(DeviceReference) device shut-down}, an * {@link #deviceChanged(DeviceReference) update of a device's metadata}, etc. * </p> * <p> * Listening to service state changes differs from the aforementioned approach. * In order to start receiving service update notifications, a client must * {@link #registerServiceListening() register} itself for that purpose. It will * then be notified about any state change regarding <em>every</em> service the * DPWS framework knows about. This also includes any services not explicitly * {@link #searchService(SearchParameter) searched for} by this client. * </p> * <p> * A simple client implementation interested in devices providing the * <code>ex:Printer</code> port type (where <code>ex</code> is a XML namespace * prefix referring to the <code>http://www.example.org/printing</code> * namespace) could look like: * * <pre> * // create a new client * Client client = new DefaultClient() { * * // overwrite deviceFound method in order to receive callbacks * public void deviceFound(DeviceReference devRef, SearchParameter search) { * // start interacting with matching device * ... * } * * }; * // describe device port type to look for * QName printerType = new QName("Printer", "http://www.example.org/printlng"); * QNameSet types = new QNameSet(printerType); * * // create a search parameter object and store desired type(s) into it * SearchParameter criteria = new SearchParameter(); * criteria.setDeviceTypes(types); * * // start the asynchronous search * client.searchDevice(criteria); * </pre> * * </p> */ public class DefaultClient implements DeviceListener, ServiceListener, SearchCallback, EventListener, HelloListener { private final static int[] INCOMING_MESSAGE_TYPES = { DPWSMessageConstants.HELLO_MESSAGE, DPWSMessageConstants.BYE_MESSAGE }; HashMap helloReceivers = null; /** * Default constructor. Ensures the DPWS framework is running (see * {@link DPWSFramework#isRunning()}. Throws a * <code>java.lang.RuntimeException</code> if this is not the case. * * @throws RuntimeException if the DPWS framework is not running; i.e. it * was either not started by means of * {@link DPWSFramework#start(String[])} or has already been * stopped via {@link DPWSFramework#stop()} before calling this * constructor */ public DefaultClient() { super(); if (!DPWSFramework.isRunning()) { throw new RuntimeException("Client Constructor: DPWSFramework isn't running!"); } } public DataStructure getAllDiscoveryBindings() { DataStructure bindings = new ArrayList(); for (Iterator it = CommunicationManagerRegistry.getLoadedManagers(); it.hasNext();) { CommunicationManager manager = (CommunicationManager) it.next(); try { bindings.addAll(manager.getDiscoveryBindings()); } catch (IOException e) { Log.printStackTrace(e); } } return bindings; } /* * (non-Javadoc) * @see * org.ws4d.java.eventing.EventListener#getEventSink(org.ws4d.java.structures * .DataStructure) */ public EventSink getEventSink(DataStructure bindings) { EventingFactory eFac = null; try { eFac = DPWSFramework.getEventingFactory(); } catch (IOException e) { throw new RuntimeException("Cannot return event sink. Eventing support not found."); } return eFac.createEventSink(this, bindings); } /** * Generates an event sink which can be used when registering for event * notifications from a service. The supplied configuration id is supposed * to refer to an EventSink property which contains at least one binding * property to create a {@link CommunicationBinding} instance. This * {@link CommunicationBinding} instance defines a local transport address, * to which incoming notifications will be delivered. * * @param configurationId Configuration id of the properties of the event * sink to generate * @return a new event sink */ public EventSink generateEventSink(int configurationId) { EventingFactory eFac = null; try { eFac = DPWSFramework.getEventingFactory(); } catch (IOException e) { throw new RuntimeException("Cannot return event sink. Eventing support not found."); } return eFac.createEventSink(this, configurationId); } // --------------------- DEVICE STATE ---------------------- /* * (non-Javadoc) * @see * org.ws4d.java.service.reference.DeviceListener#deviceBye(org.ws4d.java * .service.reference.DeviceReference) */ public void deviceBye(DeviceReference deviceRef) { Log.info("Client: Overwrite deviceBye() to receive device status changes"); } /* * (non-Javadoc) * @see * org.ws4d.java.service.reference.DeviceListener#deviceCompletelyDiscovered * (org.ws4d.java.service.reference.DeviceReference) */ public void deviceCompletelyDiscovered(DeviceReference deviceRef) { Log.info("Client: Overwrite deviceCompletelyDiscovered() to receive device status changes"); } /* * (non-Javadoc) * @see * org.ws4d.java.service.reference.DeviceListener#deviceChanged(org.ws4d * .java.service.reference.DeviceReference) */ public void deviceChanged(DeviceReference deviceRef) { Log.info("Client: Overwrite deviceChanged() to receive device status changes"); } /* * (non-Javadoc) * @see * org.ws4d.java.service.reference.DeviceListener#deviceRunning(org.ws4d * .java.service.reference.DeviceReference) */ public void deviceRunning(DeviceReference deviceRef) { Log.info("Client: Overwrite deviceRunning() to receive device status changes"); } /* * (non-Javadoc) * @see * org.ws4d.java.service.reference.DeviceListener#deviceBuiltUp(org.ws4d * .java.service.reference.DeviceReference, org.ws4d.java.service.Device) */ public void deviceBuiltUp(DeviceReference deviceRef, Device device) { Log.info("Client: Overwrite deviceBuiltUp() to receive device status changes"); } /* * (non-Javadoc) * @see * org.ws4d.java.service.reference.DeviceListener#deviceNotResponding(org * .ws4d.java.service.reference.DeviceReference) */ public void deviceCommunicationErrorOrReset(DeviceReference deviceRef) { Log.info("Client: Overwrite deviceCommunicationErrorOrReset() to receive device status changes"); } // --------------------- SERVICE CHANGE LISTENING ------------------------ /* * (non-Javadoc) * @see * org.ws4d.java.service.reference.ServiceListener#serviceChanged(org.ws4d * .java.service.reference.ServiceReference, org.ws4d.java.service.Service) */ public void serviceChanged(ServiceReference serviceRef, Service service) { Log.info("Client: Overwrite serviceChanged() to receive service status changes"); } /* * (non-Javadoc) * @see * org.ws4d.java.service.reference.ServiceListener#serviceCreated(org.ws4d * .java.service.reference.ServiceReference, org.ws4d.java.service.Service) */ public void serviceCreated(ServiceReference serviceRef, Service service) { Log.info("Client: Overwrite serviceCreated() to receive service status changes"); } /* * (non-Javadoc) * @see * org.ws4d.java.service.reference.ServiceListener#onServiceDisposed(org * .ws4d.java.service.reference.ServiceReference) */ public void serviceDisposed(ServiceReference serviceRef) { Log.info("Client: Overwrite serviceDisposed() to receive service status changes"); } /** * Registers client for service reference changes. Client gets information * about service changes. * * @see ServiceListener */ public void registerServiceListening() { ServiceReferenceEventRegistry.getInstance().registerServiceListening(this); } /** * Unregisters service listening for this service client. This method should * be called, if holder of reference is no longer interested in this * reference. * * @see ServiceListener */ public void unregisterServiceListening() { ServiceReferenceEventRegistry.getInstance().unregisterServiceListening(this); } // ---------------------- DISCOVERY ------------------------ /** * Gets device reference of device with specified endpoint reference. This * <code>Client</code> instance will be used as callback for device changes * of the corresponding device. * * @param deviceEpr endpoint reference of device to get device reference for * @return device reference * @see SearchManager#getDeviceReference(EndpointReference, DeviceListener) */ public DeviceReference getDeviceReference(EndpointReference deviceEpr, DiscoveryBinding binding) { /* * we won't send resolve messages, let it be done by user with * devRef.getDevice() */ return SearchManager.getDeviceReference(deviceEpr, this, binding); } public DeviceReference getDeviceReference(EndpointReference deviceEpr) { /* * we won't send resolve messages, let it be done by user with * devRef.getDevice() */ return SearchManager.getDeviceReference(deviceEpr, this, null); } /** * Gets device reference of device which sent the specified hello data. This * <code>Client</code> instance will be used as callback for device changes * of the corresponding device. * * @param helloData Hello data received from * {@link #helloReceived(HelloData)} callback method. * @return device reference * @see SearchManager#getDeviceReference(HelloData, DeviceListener) */ public DeviceReference getDeviceReference(HelloData helloData) { /* * we won't send resolve messages, let it be done by user with * devRef.getDevice() */ return SearchManager.getDeviceReference(helloData, this); } /** * Gets service reference of service with specified endpoint reference. * * @param serviceEpr endpoint reference of service to get service reference * for * @param comManId ID of the communication manager to use when interacting * with supplied endpoint reference * @return service reference * @see SearchManager#getServiceReference(EndpointReference) */ public ServiceReference getServiceReference(EndpointReference serviceEpr, String comManId) { /* * we won't send GetMetadata messages, let it be done by user with * servRef.getService() */ return SearchManager.getServiceReference(serviceEpr, comManId); } /** * Shorthand method for searching devices. Expect search results * asynchronously within this client instance's * {@link #deviceFound(DeviceReference, SearchParameter)} method. * * @param search search criteria * @see SearchManager#searchDevice(SearchParameter, SearchCallback, * DeviceListener) */ public void searchDevice(SearchParameter search) { SearchManager.searchDevice(search, this, this); } /** * Searches for services. Uses search parameter to specify the search. * Obtained results will be delivered asynchronously to this client * instance's {@link #serviceFound(ServiceReference, SearchParameter)} * method. * * @param search search parameter to specify the search for device and * service * @see SearchManager#searchService(SearchParameter, SearchCallback) */ public void searchService(SearchParameter search) { SearchManager.searchDevice(search, this, this); } /** * Registers for incoming HelloMessages for all possible domains. * <p> * This method will check every {@link CommunicationManager} registered * inside the framework and registers all discovery domains found with * {@link #registerHelloListening(CommunicationBinding)}. * </p> * <p> * The client will be used as receiver for the incoming Hello messages. * </p> */ public synchronized void registerHelloListening() { try { Iterator mans = CommunicationManagerRegistry.getLoadedManagers(); while (mans.hasNext()) { CommunicationManager manager = (CommunicationManager) mans.next(); DataStructure bindings = manager.getDiscoveryBindings(); if (bindings != null) { Iterator binds = bindings.iterator(); while (binds.hasNext()) { DiscoveryBinding binding = (DiscoveryBinding) binds.next(); registerHelloListening(binding); } } } } catch (IOException e) { Log.error("Cannot register for incoming wsd:Hello messages. " + e.getMessage()); } } /** * Registers for incoming HelloMessages for all possible domains, with * specified types and scopes ({@link SearchParameter}). * <p> * {@link #helloReceived(HelloData)} is called to deliver the hello data. * </p> * <p> * This method will check every {@link CommunicationManager} registered * inside the framework and registers all discovery domains found with * {@link #registerHelloListening(CommunicationBinding)}. * </p> * <p> * The client will be used as receiver for the incoming Hello messages. * </p> * * @param search containing the types and scopes. */ public synchronized void registerHelloListening(SearchParameter search) { try { Iterator mans = CommunicationManagerRegistry.getLoadedManagers(); while (mans.hasNext()) { CommunicationManager manager = (CommunicationManager) mans.next(); DataStructure bindings = manager.getDiscoveryBindings(); if (bindings != null) { Iterator binds = bindings.iterator(); while (binds.hasNext()) { DiscoveryBinding binding = (DiscoveryBinding) binds.next(); registerHelloListening(search, binding); } } } } catch (IOException e) { Log.error("Cannot register for incoming wsd:Hello messages. " + e.getMessage()); } } /** * Registers for incoming Hello messages. * <p> * {@link #helloReceived(HelloData)} is called to deliver the hello data. * </p> * * * <p> * The client will be used as receiver for the incoming Hello messages. * </p> * * @param binding the binding for the listener. */ public synchronized void registerHelloListening(DiscoveryBinding binding) { SearchParameter any = new SearchParameter(); registerHelloListening(any, binding); } /** * Registers for incoming Hello messages, which matches to the specified * types and scopes ({@link SearchParameter}). * <p> * {@link #helloReceived(HelloData)} is called to deliver the hello data. * </p> * <p> * The client will be used as receiver for the incoming Hello messages. * </p> * * @param search containing the types and scopes. * @param binding the binding for the listener. */ public void registerHelloListening(SearchParameter search, DiscoveryBinding binding) { registerHelloListening(search, binding, this); } /** * Registers for incoming HelloMessages, which matches to the specified * types and scopes ({@link SearchParameter}). * <p> * {@link #helloReceived(HelloData)} is called to deliver the hello data. * </p> * * @param search containing the types and scopes. * @param binding the binding for the listener. * @param helloListener the listener to receive the hello data from matching * hello messages. */ public synchronized void registerHelloListening(final SearchParameter search, DiscoveryBinding binding, HelloListener helloListener) { if (helloReceivers == null) { helloReceivers = new HashMap(3); } HelloRegisterKey key = new HelloRegisterKey(search, binding); HelloReceiver helloReceiver = new HelloReceiver(helloListener == null ? this : helloListener, search); if (helloReceivers.containsKey(key)) { return; } helloReceivers.put(key, helloReceiver); try { CommunicationManager manager = CommunicationManagerRegistry.getManager(binding.getCommunicationManagerId()); manager.registerDiscovery(INCOMING_MESSAGE_TYPES, binding, helloReceiver); } catch (WS4DIllegalStateException e) { throw new RuntimeException(e.getMessage()); } catch (IOException e) { throw new RuntimeException(e.getMessage()); } DeviceServiceRegistry.incAppSequenceUser(); } /** * Unregisters the listeners for incoming hello messages according to the * given {@link DiscoveryBinding}. * * @param binding the binding for the listener. */ public synchronized void unregisterHelloListening(DiscoveryBinding binding) { SearchParameter any = new SearchParameter(); unregisterHelloListening(any, binding); } /** * Unregisters the listeners for incoming hello messages according to the * given {@link SearchParameter} and ALL {@link DiscoveryBinding} found. * * @param search containing the types and scopes. */ public synchronized void unregisterHelloListening(SearchParameter search) { try { Iterator mans = CommunicationManagerRegistry.getLoadedManagers(); while (mans.hasNext()) { CommunicationManager manager = (CommunicationManager) mans.next(); DataStructure bindings = manager.getDiscoveryBindings(); if (bindings != null) { Iterator binds = bindings.iterator(); while (binds.hasNext()) { DiscoveryBinding binding = (DiscoveryBinding) binds.next(); unregisterHelloListening(search, binding); } } } } catch (IOException e) { Log.error("Cannot unregister for incoming wsd:Hello messages. " + e.getMessage()); } } /** * Unregisters the listeners for incoming hello messages according to the * given {@link SearchParameter} and {@link DiscoveryBinding}. * * @param search containing the types and scopes. * @param binding the binding for the listener. */ public synchronized void unregisterHelloListening(SearchParameter search, DiscoveryBinding binding) { if (helloReceivers != null && helloReceivers.size() > 0) { HelloRegisterKey key = new HelloRegisterKey(search, binding); HelloReceiver helloReceiver = (HelloReceiver) helloReceivers.remove(key); if (helloReceiver == null) return; try { CommunicationManager manager = CommunicationManagerRegistry.getManager(binding.getCommunicationManagerId()); manager.unregisterDiscovery(INCOMING_MESSAGE_TYPES, binding, helloReceiver); } catch (WS4DIllegalStateException e) { throw new RuntimeException(e.getMessage()); } catch (IOException e) { throw new RuntimeException(e.getMessage()); } DeviceServiceRegistry.decAppSequenceUser(); } } /** * @param helloData */ public void helloReceived(HelloData helloData) { Log.info("Client: Overwrite helloReceived() to receive and handle the UUIDs of new HelloMessages"); } // --------------------- SEARCH CALLBACKS ------------------ /* * (non-Javadoc) * @see * org.ws4d.java.client.SearchCallback#onDeviceFound(org.ws4d.java.service * .reference.DeviceReference, org.ws4d.java.client.SearchParameter) */ public void deviceFound(DeviceReference devRef, SearchParameter search) { Log.info("Client: Overwrite deviceFound() to receive device discovery responses"); } /* * (non-Javadoc) * @see * org.ws4d.java.client.SearchCallback#onServiceFound(org.ws4d.java.service * .reference.ServiceReference, org.ws4d.java.client.SearchParameter) */ public void serviceFound(ServiceReference servRef, SearchParameter search) { Log.info("Client: Overwrite serviceFound() to receive service discovery responses"); } // --------------------- EVENT CALLBACKS --------------------- /* * (non-Javadoc) * @see * org.ws4d.java.client.EventListener#receiveEvent(org.ws4d.java.eventing * .ClientSubscription, org.ws4d.java.types.uri.URI, * org.ws4d.java.service.ParameterValue) */ public ParameterValue eventReceived(ClientSubscription subscription, URI actionURI, ParameterValue parameterValue) { Log.info("Client: Overwrite eventReceived() to receive and handle events"); return null; } /* * (non-Javadoc) * @see * org.ws4d.java.client.EventListener#receiveSubscriptionEnd(org.ws4d.java * .eventing.ClientSubscription, org.ws4d.java.types.uri.URI) */ public void subscriptionEndReceived(ClientSubscription subscription, URI reason) { Log.info("Client: Overwrite subscriptionEndReceived() to receive and handle end of subscriptions"); } /* * (non-Javadoc) * @see * org.ws4d.java.client.EventListener#receiveSubscriptionTimeout(org.ws4d * .java.eventing.ClientSubscription) */ public void subscriptionTimeoutReceived(ClientSubscription subscription) { Log.info("Client: Overwrite subscriptionTimeoutReceived() to receive and handle subscription timeouts"); } /** * This class helps to generate hash codes for a pair of * {@link SearchParameter} and {@link DiscoveryBinding}. */ private final class HelloRegisterKey { private final SearchParameter search; private final DiscoveryBinding binding; public HelloRegisterKey(SearchParameter search, DiscoveryBinding binding) { this.search = search; this.binding = binding; } /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ public int hashCode() { final int prime = 31; int result = 1; result = prime * result + getOuterType().hashCode(); result = prime * result + ((binding == null) ? 0 : binding.hashCode()); result = prime * result + ((search == null) ? 0 : search.hashCode()); return result; } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; HelloRegisterKey other = (HelloRegisterKey) obj; if (!getOuterType().equals(other.getOuterType())) return false; if (binding == null) { if (other.binding != null) return false; } else if (!binding.equals(other.binding)) return false; if (search == null) { if (other.search != null) return false; } else if (!search.equals(other.search)) return false; return true; } private DefaultClient getOuterType() { return DefaultClient.this; } } private final class HelloReceiver extends DefaultIncomingMessageListener { private final MessageIdBuffer messageIdBuffer = new MessageIdBuffer(); private final SearchParameter search; private final HelloListener helloListener; public HelloReceiver(HelloListener helloListener, SearchParameter search) { this.helloListener = helloListener; this.search = search; } public void handle(HelloMessage hello, ProtocolData protocolData) { QNameSet inTypes = hello.getDiscoveryData().getTypes(); QNameSet searchTypes = search.getDeviceTypes(); ScopeSet inScopes = hello.getScopes(); ScopeSet searchScopes = search.getScopes(); boolean typesMatch = false; boolean scopesMatch = false; /* * contains types? or no types searched? */ if ((searchTypes != null && inTypes != null && inTypes.containsAll(searchTypes)) || searchTypes == null) { typesMatch = true; } int reason = MessageDiscarder.NOT_DISCARDED; /* * contains scopes? or no scopes searched? */ if ((searchScopes != null && inScopes != null && inScopes.containsAll(searchScopes)) || searchScopes == null) { scopesMatch = true; } if (!typesMatch && !scopesMatch) { if (Log.isDebug()) Log.debug("Discarding Hello message! Message does not match the search criteria!", Log.DEBUG_LAYER_APPLICATION); reason = MessageDiscarder.NOT_RELEVANT_MESSAGE; } if (messageIdBuffer.containsOrEnqueue(hello.getMessageId())) { if (Log.isDebug()) Log.debug("Discarding Hello message! Already saw this message ID!", Log.DEBUG_LAYER_APPLICATION); reason = MessageDiscarder.DUPLICATE_MESSAGE; } if (reason > MessageDiscarder.NOT_DISCARDED) { MonitorStreamFactory msf = DPWSFramework.getMonitorStreamFactory(); if (msf != null) { MonitoringContext context = msf.getMonitoringContextIn(protocolData); if (context != null) { msf.discard(protocolData, context, reason); } else { Log.warn("Cannot get correct monitoring context for message generation."); } } /* * Message discarded */ return; } helloListener.helloReceived(new HelloData(hello, protocolData)); } } }