/*******************************************************************************
* 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.eventing;
import java.io.IOException;
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.ProtocolData;
import org.ws4d.java.communication.TimeoutException;
import org.ws4d.java.communication.protocol.soap.generator.handlers.ClientSubscriptionElementHandler;
import org.ws4d.java.configuration.EventingProperties;
import org.ws4d.java.constants.DPWSMessageConstants;
import org.ws4d.java.constants.SOAPConstants;
import org.ws4d.java.constants.WSAConstants;
import org.ws4d.java.constants.WSEConstants;
import org.ws4d.java.io.xml.ElementHandlerRegistry;
import org.ws4d.java.message.FaultMessage;
import org.ws4d.java.message.InvokeMessage;
import org.ws4d.java.message.SOAPException;
import org.ws4d.java.message.eventing.SubscriptionEndMessage;
import org.ws4d.java.schema.PredefinedSchemaTypes;
import org.ws4d.java.service.DefaultClientSubscription;
import org.ws4d.java.service.OperationDescription;
import org.ws4d.java.service.Service;
import org.ws4d.java.service.parameter.ParameterValue;
import org.ws4d.java.service.parameter.QNameValue;
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.HashSet;
import org.ws4d.java.structures.Iterator;
import org.ws4d.java.structures.LockedMap;
import org.ws4d.java.structures.ReadOnlyIterator;
import org.ws4d.java.types.LocalizedString;
import org.ws4d.java.types.QName;
import org.ws4d.java.types.URI;
import org.ws4d.java.util.Log;
import org.ws4d.java.util.StringUtil;
import org.ws4d.java.util.WS4DIllegalStateException;
/**
* Class represents an endpoint to receive notifications.
*/
public class DefaultEventSink implements EventSink {
private static final int[] EVENT_SINK_MESSAGE_TYPES = { DPWSMessageConstants.INVOKE_MESSAGE, DPWSMessageConstants.SUBSCRIPTION_END_MESSAGE };
private final EventSinkMessageListener incomingListener = new EventSinkMessageListener();
private final DataStructure bindings = new ArrayList();
private final EventListener eventListener;
private boolean registered = false;
private HashMap map_CSubId_2_CSub = new LockedMap(new HashMap(5));
static {
/*
* Register element handler for eventListener side eventing.
*/
ElementHandlerRegistry.getRegistry().registerElementHandler(WSEConstants.WSE_QN_IDENTIFIER, new ClientSubscriptionElementHandler());
}
/**
* Constructor.
*
* @param eventListener Client with which this event sink should be
* associated. Received events will be transmitted to the
* eventListener.
*/
private DefaultEventSink(EventListener eventListener) {
this.eventListener = eventListener;
}
/**
* Constructor.
*
* @param eventListener Client with which this event sink should be
* associated. Received events will be transmitted to the
* eventListener.
* @param bindings a data structure of {@link CommunicationBinding}
* instances over which to expose this event sink
*/
public DefaultEventSink(EventListener eventListener, DataStructure bindings) {
this(eventListener);
if (bindings != null) {
// needed only for a remote event sink
this.bindings.addAll(bindings);
}
}
/**
* @param eventListener
* @param configurationId
*/
public DefaultEventSink(EventListener eventListener, int configurationId) {
this(eventListener);
this.bindings.addAll(EventingProperties.getInstance().getBindings(new Integer(configurationId)));
}
/*
* (non-Javadoc)
* @see org.ws4d.java.communication.Bindable#hasBindings()
*/
public boolean hasBindings() {
return (bindings.size() > 0);
}
/*
* (non-Javadoc)
* @see org.ws4d.java.communication.Bindable#getBindings()
*/
public Iterator getBindings() {
return new ReadOnlyIterator(bindings);
}
/*
* (non-Javadoc)
* @see org.ws4d.java.communication.Bindable#supportsBindingChanges()
*/
public boolean supportsBindingChanges() {
return !registered;
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.communication.Bindable#addBinding(org.ws4d.java.communication
* .CommunicationBinding)
*/
public void addBinding(CommunicationBinding binding) throws WS4DIllegalStateException {
if (registered) {
throw new WS4DIllegalStateException("Event Sink is already running, unable to add binding");
}
bindings.add(binding);
}
/*
* (non-Javadoc)
* @seeorg.ws4d.java.communication.Bindable#removeBinding(org.ws4d.java.
* communication.CommunicationBinding)
*/
public boolean removeBinding(CommunicationBinding binding) throws WS4DIllegalStateException {
if (registered) {
throw new WS4DIllegalStateException("Event Sink is already running, unable to remove binding");
}
return bindings.remove(binding);
}
/*
* (non-Javadoc)
* @see org.ws4d.java.communication.Bindable#clearBindings()
*/
public void clearBindings() throws WS4DIllegalStateException {
if (registered) {
throw new WS4DIllegalStateException("Event Sink is already running, unable to clear bindings");
}
bindings.clear();
}
/**
* Opens event receiving for this event endpoint.
*/
public void open() throws EventingException {
if (registered == true) {
if (Log.isDebug()) {
Log.debug("EventSink already opened", Log.DEBUG_LAYER_FRAMEWORK);
}
return;
}
if (!hasBindings()) {
String descriptor = StringUtil.simpleClassName(getClass());
if (Log.isDebug()) {
Log.debug("No bindings found, autobinding event sink " + descriptor, Log.DEBUG_LAYER_FRAMEWORK);
}
DataStructure autoBindings = new HashSet();
for (Iterator it = CommunicationManagerRegistry.getLoadedManagers(); it.hasNext();) {
CommunicationManager manager = (CommunicationManager) it.next();
try {
manager.getAutobindings(descriptor, autoBindings);
} catch (IOException e) {
Log.error("Unable to obtain autobindings from communication manager " + manager.getCommunicationManagerId());
Log.printStackTrace(e);
}
}
for (Iterator it = autoBindings.iterator(); it.hasNext();) {
CommunicationBinding binding = (CommunicationBinding) it.next();
addBinding(binding);
}
}
for (Iterator it = bindings.iterator(); it.hasNext();) {
CommunicationBinding binding = (CommunicationBinding) it.next();
try {
CommunicationManager manager = CommunicationManagerRegistry.getManager(binding.getCommunicationManagerId());
//TODO: Group user
manager.registerService(EVENT_SINK_MESSAGE_TYPES, binding, incomingListener, null);
} catch (IOException e) {
// FIXME no need to signal per invocation exception
EventingException ex = new EventingException("Unable to bind Event Sink to " + binding.getTransportAddress() + ": " + e);
Log.printStackTrace(ex);
throw ex;
}
}
registered = true;
}
/**
* Closes event receiving for this event endpoint.
*/
public void close() {
// unbind all communication bindings
for (Iterator it = bindings.iterator(); it.hasNext();) {
CommunicationBinding binding = (CommunicationBinding) it.next();
try {
CommunicationManager manager = CommunicationManagerRegistry.getManager(binding.getCommunicationManagerId());
manager.unregisterService(EVENT_SINK_MESSAGE_TYPES, binding, incomingListener);
} catch (IOException e) {
Log.error("unable to unbind from " + binding.getTransportAddress());
e.printStackTrace();
}
}
map_CSubId_2_CSub.clear();
registered = false;
}
public EventListener getEventListener() {
return eventListener;
}
public boolean isOpen() {
return registered;
}
/*
* (non-Javadoc)
* @see org.ws4d.java.eventing.EventSink#getSubscription(java.lang.String)
*/
public ClientSubscription getSubscription(String clientSubId) {
return (ClientSubscription) map_CSubId_2_CSub.get(clientSubId);
}
/**
* @param clientSubId
* @return the removed client subscription
*/
public ClientSubscription removeSubscription(String clientSubId) {
return (ClientSubscription) map_CSubId_2_CSub.remove(clientSubId);
}
/*
* (non-Javadoc)
* @see org.ws4d.java.eventing.EventSink#addSubscription(java.lang.String,
* org.ws4d.java.eventing.ClientSubscription)
*/
public void addSubscription(String clientSubId, ClientSubscription subscription) {
map_CSubId_2_CSub.put(clientSubId, subscription);
}
/**
* @param clientSubscriptionId
* @param actionUri
* @param outputParameter
* @return a possible result to a solicit-response event
*/
public ParameterValue receiveLocalEvent(String clientSubscriptionId, URI actionUri, ParameterValue outputParameter) {
ClientSubscription subscription;
subscription = (ClientSubscription) map_CSubId_2_CSub.get(clientSubscriptionId);
return eventListener.eventReceived(subscription, actionUri, outputParameter);
}
private final class EventSinkMessageListener extends DefaultIncomingMessageListener {
private EventSinkMessageListener() {
super();
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.communication.DefaultIncomingMessageListener#handle
* (org.ws4d.java.message.invocation.InvokeMessage,
* org.ws4d.java.communication.ProtocolData)
*/
public InvokeMessage handle(final InvokeMessage msg, ProtocolData protocolData) throws SOAPException {
if (!isOpen()) {
// send Fault wsa:EndpointUnavailable
throw new SOAPException(FaultMessage.createEndpointUnavailableFault(msg));
}
String clientSubscriptionId = null;
if (msg.getHeader().getWseIdentifier() == null) {
Log.error("A header representing the eventListener supbscription ID (as part of the [reference parameters]) is missing.");
} else {
clientSubscriptionId = msg.getHeader().getWseIdentifier();
}
if (clientSubscriptionId == null) {
// throw wsa:InvalidAddresingHeader exception
FaultMessage fault = new FaultMessage(WSAConstants.WSA_ACTION_ADDRESSING_FAULT, protocolData.getCommunicationManagerId());
fault.setResponseTo(msg);
fault.setCode(SOAPConstants.SOAP_FAULT_SENDER);
// fill in subcode, reason and detail
fault.setSubcode(WSAConstants.WSA_FAULT_INVALID_ADDRESSING_HEADER);
LocalizedString reason = new LocalizedString("A header representing the eventListener supbscription ID (as part of the [reference parameters]) is missing", null);
fault.addReason(reason);
ParameterValue detail = ParameterValue.createElementValue(PredefinedSchemaTypes.WSA_PROBLEM_HEADER_QNAME);
if (detail.getValueType() == ParameterValue.TYPE_QNAME) {
QNameValue value = (QNameValue) detail;
value.set(new QName(WSEConstants.WSE_ELEM_IDENTIFIER, WSEConstants.WSE_NAMESPACE_NAME));
}
fault.setDetail(detail);
throw new SOAPException(fault);
}
final ClientSubscription subscription;
subscription = (ClientSubscription) map_CSubId_2_CSub.get(clientSubscriptionId);
if (subscription == null) {
// throw wsa:InvalidAddresingHeader exception
FaultMessage fault = new FaultMessage(WSAConstants.WSA_ACTION_ADDRESSING_FAULT, protocolData.getCommunicationManagerId());
fault.setResponseTo(msg);
fault.setCode(SOAPConstants.SOAP_FAULT_SENDER);
// fill in subcode, reason and detail
fault.setSubcode(WSAConstants.WSA_FAULT_INVALID_ADDRESSING_HEADER);
LocalizedString reason = new LocalizedString("Unknown eventListener supbscription ID found: " + clientSubscriptionId, null);
fault.addReason(reason);
ParameterValue detail = ParameterValue.createElementValue(PredefinedSchemaTypes.WSA_PROBLEM_HEADER_QNAME);
if (detail.getValueType() == ParameterValue.TYPE_QNAME) {
QNameValue value = (QNameValue) detail;
value.set(new QName(WSEConstants.WSE_ELEM_IDENTIFIER, WSEConstants.WSE_NAMESPACE_NAME));
}
fault.setDetail(detail);
throw new SOAPException(fault);
}
ParameterValue paramValue = eventListener.eventReceived(subscription, msg.getAction(), msg.getContent());
if (paramValue != null) {
/*
* Send solicit response message type response.
*/
String outputActionName = msg.getAction().toString();
Service service;
try {
service = subscription.getServiceReference().getService();
EventSource event = service.getEventSource(outputActionName);
String inputActionName = event.getInputAction();
InvokeMessage rspMsg = new InvokeMessage(inputActionName, false, protocolData.getCommunicationManagerId());
rspMsg.setResponseTo(msg);
// set DPWSVersion from the Request to the Response
rspMsg.setProtocolInfo(msg.getProtocolInfo());
rspMsg.setContent(paramValue);
return rspMsg;
} catch (TimeoutException e) {
Log.error("EventSink.handleMessage(Invoke): can't get service (timeout).");
e.printStackTrace();
throw new SOAPException(FaultMessage.createEndpointUnavailableFault(msg));
}
} else {
// send HTTP response (202)
return null;
}
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.communication.DefaultIncomingMessageListener#handle
* (org.ws4d.java.message.eventing.SubscriptionEndMessage,
* org.ws4d.java.communication.ProtocolData)
*/
public void handle(SubscriptionEndMessage msg, ProtocolData protocolData) {
if (!isOpen()) {
return;
}
String msgSubId = msg.getSubscriptionManager().getReferenceParameters().getWseIdentifier();
if (msgSubId == null) {
Log.error("DefaultEventSink.handleMessage(SubscriptionEnd): received subscription end message without service subscription id");
return;
}
DefaultClientSubscription clientSub = null;
DataStructure clientSubs = map_CSubId_2_CSub.values();
for (Iterator it = clientSubs.iterator(); it.hasNext();) {
/*
* We have to check each eventListener subscription in map
*/
clientSub = (DefaultClientSubscription) it.next();
if (clientSub != null) {
String serviceSubId = clientSub.getServiceSubscriptionId();
if (msgSubId.equals(serviceSubId)) {
URI reason = msg.getStatus();
eventListener.subscriptionEndReceived(clientSub, reason);
}
}
}
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.communication.DefaultIncomingMessageListener#getOperation
* (java.lang.String)
*/
public OperationDescription getOperation(String action) {
OperationDescription operation = null;
DefaultClientSubscription clientSub = null;
DataStructure clientSubs = map_CSubId_2_CSub.values();
for (Iterator it = clientSubs.iterator(); it.hasNext();) {
/*
* We have to check each eventListener subscription in map
*/
clientSub = (DefaultClientSubscription) it.next();
if (clientSub != null) {
// String serviceSubId =
// clientSub.getServiceSubscriptionId();
ServiceReference servRef = clientSub.getServiceReference();
Service service;
try {
service = servRef.getService();
operation = service.getEventSource(action);
if (operation != null) {
break;
}
} catch (TimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return operation;
}
}
}