/*******************************************************************************
* 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 org.ws4d.java.DPWSFramework;
import org.ws4d.java.communication.CommunicationManager;
import org.ws4d.java.communication.CommunicationManagerRegistry;
import org.ws4d.java.communication.DefaultResponseCallback;
import org.ws4d.java.communication.Discovery;
import org.ws4d.java.communication.DiscoveryBinding;
import org.ws4d.java.communication.ProtocolData;
import org.ws4d.java.communication.ProtocolDomain;
import org.ws4d.java.communication.TimeoutException;
import org.ws4d.java.dispatch.DeviceServiceRegistry;
import org.ws4d.java.dispatch.DuplicateServiceReferenceException;
import org.ws4d.java.dispatch.HelloData;
import org.ws4d.java.dispatch.OutDispatcher;
import org.ws4d.java.message.Message;
import org.ws4d.java.message.discovery.ProbeMatch;
import org.ws4d.java.message.discovery.ProbeMatchesMessage;
import org.ws4d.java.message.discovery.ProbeMessage;
import org.ws4d.java.service.Device;
import org.ws4d.java.service.reference.DeviceListener;
import org.ws4d.java.service.reference.DeviceReference;
import org.ws4d.java.service.reference.ServiceReference;
import org.ws4d.java.structures.DataStructure;
import org.ws4d.java.structures.HashSet;
import org.ws4d.java.structures.Iterator;
import org.ws4d.java.types.EndpointReference;
import org.ws4d.java.types.ProbeScopeSet;
import org.ws4d.java.types.QNameSet;
import org.ws4d.java.types.URI;
import org.ws4d.java.types.XAddressInfo;
import org.ws4d.java.types.XAddressInfoSet;
import org.ws4d.java.util.Log;
/**
* This class provides tools for searching local and remote devices and services
* given a set of search criteria (see {@link SearchParameter}) and obtaining
* references to devices/services with known endpoint addresses.
* <p>
* A typical usage of the search functionality states that the caller provides
* an implementation of the {@link SearchCallback} interface which will receive
* asynchronous notifications about matching services/devices found during the
* search. Given this <code>SearchCallback</code> implementation and a
* <code>SearchParameter</code> instance describing what kind of
* devices/services to look for, the actual search can be started as depicted in
* the following sample code:
* </p>
*
* <pre>
* SearchCallback callback = ...; // provide a receiver for search matches
* SearchParameter search = ...; // specify what to search for
* SearchManager.searchDevice(parameter, callback, null);
* </pre>
* <p>
* This example starts a search for a device, as the name of the called method
* {@link #searchDevice(SearchParameter, SearchCallback, null)} suggests. If a device
* fulfilling the given search parameter criteria is found, this will be
* indicated asynchronously by a call to
* {@link SearchCallback#deviceFound(DeviceReference, SearchParameter)}.
* Similarly, if a search for services was issued (by means of
* {@link #searchService(SearchParameter, SearchCallback)}), then matches would
* result in a call to
* {@link SearchCallback#serviceFound(ServiceReference, SearchParameter)}.
* </p>
* <p>
* The second purpose of this class is to enable the obtaining of a reference to
* a (local or remote) device/service when knowing its endpoint address (i.e.
* one of its endpoint references). This process differs somehow from the
* aforementioned search as it doesn't involve probing the network to assert the
* existence of the specified device or service (as its endpoint reference is
* already known). Thus, it is possible that calling
* {@link DeviceReference#getDevice()} or {@link ServiceReference#getService()}
* on the returned reference object results in a {@link TimeoutException} being
* thrown because the specified device/service is for some reason currently not
* reachable (e.g. it is not running at the moment or there is no network path
* connecting it with the local machine). In contrast, using the search
* abilities will provide notifications only about devices/services which are
* currently running and reachable.
* </p>
*/
public final class SearchManager {
/**
* Gets device reference of device specified by an endpoint reference and an
* address. If <code>listener</code> is not <code>null</code>, it will be used as
* callback for device changes of the corresponding device.
* <p>
* This method will NOT try to discover (resolve/probe) the device. If the
* address is unreachable or wrong this method will return <code>null</code>.
* </p>
*
* <p>
* A DeviceReference that was created by this method has not DiscoveryBinding and
* will therefore not receive hello or bye messages form its referenced device.
* </p>
*
* @param epr endpoint reference of device for which to get device reference
* @param address the address of the device
* @param listener optional; will be informed on changes of device' state
* @param comManId ID of the communication manager to use when interacting
* with supplied endpoint reference
* @return device reference for the specified device
*/
public static DeviceReference getDeviceReference(EndpointReference epr, URI address, DeviceListener listener, String comManId) {
XAddressInfo xAdrInfo = new XAddressInfo(address, comManId);
DeviceReference dRef = DeviceServiceRegistry.getDeviceReference(epr, new XAddressInfoSet(xAdrInfo), true);
if (listener != null) {
dRef.addListener(listener);
}
if (Log.isDebug()) {
Log.debug("Device reference created from " + address + " over " + comManId);
}
return dRef;
}
/**
* Gets device reference of device specified by an endpoint reference and an
* address. If <code>listener</code> is not <code>null</code>, it will be used as
* callback for device changes of the corresponding device.
* <p>
* This method will NOT try to discover (resolve/probe) the device. If the
* address is unreachable or wrong this method will return <code>null</code>
* .
* </p>
*
* @param epr endpoint reference of device for which to get device reference
* @param address the address of the device
* @param listener optional; will be informed on changes of device' state
* @param binding a DiscoveryBinding that specifies how to receive hello and bye messages for the DeviceReference
* @return device reference for the specified device
*/
public static DeviceReference getDeviceReference(EndpointReference epr, URI address, DeviceListener listener, DiscoveryBinding binding) {
String comManId = binding.getCommunicationManagerId();
XAddressInfo xAdrInfo = new XAddressInfo(address, comManId);
DeviceReference dRef = DeviceServiceRegistry.getDeviceReference(epr, new XAddressInfoSet(xAdrInfo), true);
if (listener != null) {
dRef.addListener(listener);
}
DeviceServiceRegistry.register(binding);
if (Log.isDebug()) {
Log.debug("Device reference created from " + address + " over " + comManId);
}
return dRef;
}
/**
* Gets device reference of device with specified endpoint reference. If
* <code>listener</code> is not <code>null</code>, it will be used as
* callback for device changes of the corresponding device.
*
* @param epr endpoint reference of device for which to get device reference
* @param listener optional; will be informed on changes of device' state
* @param binding a DiscoveryBinding that specifies how to receive hello and bye messages for the DeviceReference
* @return device reference
*/
public static DeviceReference getDeviceReference(EndpointReference epr, DeviceListener listener, DiscoveryBinding binding) {
DeviceReference devRef = DeviceServiceRegistry.getDeviceReference(epr);
if (listener != null) {
devRef.addListener(listener);
}
DeviceServiceRegistry.register(binding);
return devRef;
}
/**
* Gets device reference of device with specified endpoint reference. If
* <code>listener</code> is not <code>null</code>, it will be used as
* callback for device changes of the corresponding device.
*
* @param helloData hello data of device for which to get device reference
* @param listener optional; will be informed about changes of the device's
* state
* @return device reference
*/
public static DeviceReference getDeviceReference(HelloData helloData, DeviceListener listener) {
DeviceReference devRef = DeviceServiceRegistry.getDeviceReference(helloData);
if (listener != null) {
devRef.addListener(listener);
}
for (Iterator it = CommunicationManagerRegistry.getLoadedManagers(); it.hasNext();) {
CommunicationManager manager = (CommunicationManager) it.next();
DeviceServiceRegistry.register(manager.getDiscoveryBindingForProtocolData(helloData.getProtocolData()));
}
return devRef;
}
/**
* Gets service reference of service with specified endpoint reference.
* <p>
* The returned @link {@link ServiceReference} instance can be used to
* obtain the actual service by calling
* {@link ServiceReference#getService()}.
* </p>
*
* @param epr 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
*/
public static ServiceReference getServiceReference(EndpointReference epr, String comManId) {
return DeviceServiceRegistry.getServiceReference(epr, comManId, true);
}
/**
* Gets service reference of service with specified endpoint reference.
* <p>
* The returned @link {@link ServiceReference} instance can be used to
* obtain the actual service by calling
* {@link ServiceReference#getService()}.
* </p>
*
* @param epr 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
* @throws DuplicateServiceReferenceException in case a service reference
* with the same endpoint reference is already present
*/
public static ServiceReference createServiceReference(EndpointReference epr, QNameSet portTypes, String comManId) throws DuplicateServiceReferenceException {
return DeviceServiceRegistry.createServiceReference(epr, portTypes, comManId, null);
}
/**
* Searches for services. Uses search parameter to specify the search
* criteria. When matching services are found, notifications are sent to the
* given <code>callback</code> by means of the method
* {@link SearchCallback#serviceFound(ServiceReference, SearchParameter)}.
*
* @param search search parameter to specify the criteria that matching
* services must fulfill
* @param callback recipient of notifications about found matching services
*/
public static void searchService(SearchParameter search, SearchCallback callback) {
searchDevice(search, callback, null);
}
/**
* Initiates a search for devices. A device is considered to match this
* search if its properties correspond to the values provided within
* argument <code>search</code>.
* <p>
* When a matching device is found, it is passed to the method
* {@link SearchCallback#deviceFound(DeviceReference, SearchParameter)} of
* the specified <code>callback</code> argument. Should
* <code>listener</code> not be <code>null</code>, it will be registered for
* tracking device changes on each matching device.
* </p>
*
* @param search the search criteria for matching devices
* @param callback where search results are to be delivered to; must not be
* <code>null</code>
* @param listener if not <code>null</code>, the listener is used for
* asynchronous callbacks each time the state of a device
* matching the search criteria changes (i.e. when it goes
* online, etc.)
*/
public static void searchDevice(SearchParameter search, SearchCallback callback, DeviceListener listener) {
if (callback == null) {
throw new NullPointerException("callback is null");
}
if (search == null) {
search = new SearchParameter();
}
if ((search.getSearchMode() & SearchParameter.MODE_LOCAL) != 0) {
// look for local devices which would match the search criteria
searchLocalReferences(search, callback, listener);
}
if ((search.getSearchMode() & SearchParameter.MODE_REMOTE) != 0) {
/*
* FIXME handle searches over DeviceServiceRegistry, as potentially
* there could already exist some matching (cached) devices!
*/
ProbeMessage probe = new ProbeMessage(CommunicationManager.ID_NULL);
QNameSet deviceTypes = search.getDeviceTypes();
if (deviceTypes != null) {
probe.setTypes(deviceTypes);
}
ProbeScopeSet scopes = search.getScopes();
if (scopes != null) {
probe.setScopes(scopes);
}
SearchMap map = search.getSearchMap();
DataStructure domains;
if (map != null) {
domains = new HashSet();
for (Iterator it = map.getPaths().iterator(); it.hasNext();) {
SearchPath path = (SearchPath) it.next();
ProtocolDomain domain = Discovery.getDomain(path.getTechnologyIdentifier(), path.getDomainIdentifier());
if (domain != null) {
domains.add(domain);
} else {
Log.warn("No protocol domain found for search path " + path);
}
}
} else {
// fall back to default output domains
domains = Discovery.getDefaultOutputDomains();
}
OutDispatcher.getInstance().send(probe, null, domains, new SearchManagerCallback(null, search, callback, listener));
}
}
/**
* Returns a data structure containing all the local devices within the
* current DPWS framework.
*
* @return all local devices
*/
public static DataStructure getLocalDevices() {
return DeviceServiceRegistry.getLocalDeviceReferences(null, null);
}
private static void searchLocalReferences(final SearchParameter search, final SearchCallback callback, final DeviceListener listener) {
QNameSet serviceTypes = search.getServiceTypes();
if (serviceTypes != null && serviceTypes.size() > 0) {
DataStructure matchingLocalServices = DeviceServiceRegistry.getLocalServiceReferences(search.getServiceTypes(), search.getDeviceTypes(), search.getScopes());
for (Iterator it = matchingLocalServices.iterator(); it.hasNext();) {
final ServiceReference servRef = (ServiceReference) it.next();
try {
final DeviceReference devRef = servRef.getService().getParentDeviceReference();
/*
* Call client code in a new thread, as it might call device
* remotely
*/
DPWSFramework.getThreadPool().execute(new Runnable() {
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
if (devRef != null) {
devRef.addListener(listener);
}
callback.serviceFound(servRef, search);
}
});
} catch (TimeoutException e) {
// this should not happen
Log.printStackTrace(e);
}
}
} else {
DataStructure matchingLocalDevices = DeviceServiceRegistry.getLocalDeviceReferences(search.getDeviceTypes(), search.getScopes());
for (Iterator it = matchingLocalDevices.iterator(); it.hasNext();) {
final DeviceReference devRef = (DeviceReference) it.next();
devRef.addListener(listener);
/*
* Call client code in a new thread, as it might call device
* remotely
*/
DPWSFramework.getThreadPool().execute(new Runnable() {
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
callback.deviceFound(devRef, search);
}
});
}
}
}
private SearchManager() {
super();
}
private static class SearchManagerCallback extends DefaultResponseCallback {
private final SearchParameter search;
private final SearchCallback callback;
private final DeviceListener listener;
private volatile boolean noneFound = true;
/**
* @param search
* @param callback
* @param listener
*/
SearchManagerCallback(XAddressInfo targetXAddressInfo, SearchParameter search, SearchCallback callback, DeviceListener listener) {
super(targetXAddressInfo);
this.search = search;
this.callback = callback;
this.listener = listener;
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.communication.ResponseCallback#handle(org.ws4d.java
* .communication.message.Message,
* org.ws4d.java.message.discovery.ProbeMatchesMessage,
* org.ws4d.java.communication.ProtocolData)
*/
public void handle(Message request, ProbeMatchesMessage response, ProtocolData protocolData) {
noneFound = false;
for (Iterator it = CommunicationManagerRegistry.getLoadedManagers(); it.hasNext();) {
CommunicationManager manager = (CommunicationManager) it.next();
DeviceServiceRegistry.register(manager.getDiscoveryBindingForProtocolData(protocolData));
}
// DeviceServiceRegistry.register(DiscoveryBinding.getDiscoveryBinding(protocolData));
// Only searches can reach this callback
final QNameSet serviceTypes = search.getServiceTypes();
if (serviceTypes != null && serviceTypes.size() > 0) {
// CASE: search service reference before return to client
for (Iterator it = response.getProbeMatches().iterator(); it.hasNext();) {
ProbeMatch match = (ProbeMatch) it.next();
final DeviceReference devRef = DeviceServiceRegistry.getUpdatedDeviceReference(match, response, protocolData);
/*
* Calls client code in a new thread, as it might call
* device remotely
*/
DPWSFramework.getThreadPool().execute(new Runnable() {
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
informOnServiceFound(devRef);
}
});
}
} else {
// CASE: device discovered, return
DataStructure matches = response.getProbeMatches();
if (matches != null) {
for (Iterator it = matches.iterator(); it.hasNext();) {
ProbeMatch match = (ProbeMatch) it.next();
final DeviceReference devRef = DeviceServiceRegistry.getUpdatedDeviceReference(match, response, protocolData);
if (listener != null) {
devRef.addListener(listener);
}
DPWSFramework.getThreadPool().execute(new Runnable() {
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
callback.deviceFound(devRef, search);
}
});
}
}
}
}
public void handleTimeout(Message request) {
if (noneFound) {
if (Log.isDebug()) {
Log.debug("Search timeout for query: " + search, Log.DEBUG_LAYER_FRAMEWORK);
} else {
Log.info("Search timeout.");
}
}
}
/**
* Inform the client about the service founds.
*
* @param devRef device reference to get device from
*/
private void informOnServiceFound(DeviceReference devRef) {
// Builds up device, do it in a different thread. We won't block.
try {
Device device = devRef.getDevice();
for (Iterator it_servRef = device.getServiceReferences(); it_servRef.hasNext();) {
ServiceReference servRef = (ServiceReference) it_servRef.next();
if (search.getServiceTypes().isContainedBy(servRef.getPortTypes())) {
// we register a listener only if the service type
// matches
if (listener != null) {
devRef.addListener(listener);
}
callback.serviceFound(servRef, search);
}
}
} catch (TimeoutException e) {
Log.printStackTrace(e);
}
}
}
}