/*******************************************************************************
* 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.service;
import org.ws4d.java.DPWSFramework;
import org.ws4d.java.communication.DefaultResponseCallback;
import org.ws4d.java.communication.ProtocolData;
import org.ws4d.java.communication.TimeoutException;
import org.ws4d.java.dispatch.OutDispatcher;
import org.ws4d.java.eventing.ClientSubscription;
import org.ws4d.java.eventing.EventListener;
import org.ws4d.java.eventing.EventSink;
import org.ws4d.java.eventing.EventSource;
import org.ws4d.java.eventing.EventingException;
import org.ws4d.java.message.FaultMessage;
import org.ws4d.java.message.InvokeMessage;
import org.ws4d.java.message.Message;
import org.ws4d.java.message.SOAPHeader;
import org.ws4d.java.service.parameter.ParameterValue;
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.LockedSet;
import org.ws4d.java.types.QName;
import org.ws4d.java.types.URI;
import org.ws4d.java.types.URISet;
import org.ws4d.java.types.XAddressInfo;
import org.ws4d.java.util.IDGenerator;
import org.ws4d.java.util.Log;
import org.ws4d.java.wsdl.WSDLOperation;
/**
* Events are the source of server-side notifications to which interested
* clients may subscribe. Services exposing events create instances of this
* class and call its {@link #fire(ParameterValue, int)} method each time a
* notification is sent to subscribers.
* <p>
* The DPWS framework supports two types of <a
* href="http://www.w3.org/TR/wsdl">WSDL 1.1</a> and <a
* href="http://www.w3.org/Submission/WS-Eventing/">WS-Eventing</a> compliant
* events: <strong>notifications</strong> and <strong>solicit-response</strong>
* operations. Whilst the first ones represent one-way messages sent from the
* event source to its subscribers, the later additionally includes response
* messages sent back from the subscribers to the source. Those responses are
* delivered to the callback method
* {@link #solicitResponseReceived(ParameterValue, int, ServiceSubscription)}.
* Means to identify the particular subscriber responding as well as the
* original event to which the response belongs are also provided, see
* documentation for methods {@link #fire(ParameterValue, int)} and
* {@link #solicitResponseReceived(ParameterValue, int, ServiceSubscription)}.
* </p>
* <p>
* Clients willing to receive notifications from this event source can simply
* {@link #subscribe(EventListener, long, DataStructure) subscribe} to it. A
* subscription can be set up to expire after a certain amount of time (
* <em>duration</em>) or it may last "forever", i.e. until either the
* event source or the subscriber explicitly cancels it or it terminates due to
* shutdown or to lack of network reachability.
* </p>
* <strong>Note:</strong> According to <a href="http://www.w3.org/TR/wsdl">WSDL
* 1.1 Specification</a>, an operation's {@link #getName() name} is not required
* to be unique within the scope of its containing port type in order to support
* overloading. However, when overloading operations, the combination of each
* one's {@link #getName() name}, {@link #getInputName() input name} and
* {@link #getOutputName() output name} must be unique in order to avoid name
* clashes. </p>
*/
public class DefaultEventSource extends OperationCommons implements EventSource {
/** set of all service subscriptions subscribed to this operation */
private LockedSet subscriptions = new LockedSet(new HashSet(5));
private HashMap map_MsgId_2_Context = new LockedMap(new HashMap(5));
/**
* default constructor
*
* Son Han added to solve the serialization problem
* @date 2013/12/12
*
*/
public DefaultEventSource() {
super();
}
/**
* Creates a new event source instance with the given local
* <code>name</code> and <code>portType</code>.
*
* @param name the name of the event source; see {@link OperationCommons
* here} for a short description of uniqueness requirements
* regarding event source names
* @param portType the qualified port type of the event source
*/
public DefaultEventSource(String name, QName portType) {
super(name, portType);
}
/**
* @param operation
*/
public DefaultEventSource(WSDLOperation operation) {
super(operation);
}
/**
* Fires this event source. This will send notifications to each subscriber
* of this event source. The values of any parameters for the notification
* are taken from argument <code>paramValue</code>.
* <p>
* In case this event source represents a {@link #isSolicitResponse()
* solicit-response} operation (in the sense of <a
* href="http://www.w3.org/TR/wsdl">WSDL 1.1 Specification</a>), the value
* of argument <code>eventNumber</code> can be used by callers to correlate
* incoming responses with this particular event source. It will be passed
* as the second argument to
* {@link #solicitResponseReceived(ParameterValue, int, ServiceSubscription)}
* . It is recommended to increment the supplied value whenever calling this
* method, but this is not checked for, so other means of providing reliable
* correlation based on this value can also be used.
* </p>
*
* @param paramValue the parameters to be sent to all subscribers with this
* event notification
* @param eventNumber a number identifying this event notification and
* allowing correlation to possible
* {@link #solicitResponseReceived(ParameterValue, int, ServiceSubscription)
* responses} from subscribers
*/
public void fire(final ParameterValue paramValue, final int eventNumber) {
final ArrayList outdatedSubscriptions = new ArrayList();
subscriptions.sharedLock();
try {
long currentTime = System.currentTimeMillis();
for (Iterator it = subscriptions.iterator(); it.hasNext();) {
final ServiceSubscription subscription = (ServiceSubscription) it.next();
if (subscription.expirationTime < currentTime) {
/*
* subscription is out of date
*/
outdatedSubscriptions.add(subscription);
continue;
}
final OperationDescription op = this;
if (subscription.sink != null) {
/*
* CASE: Local Client
*/
DPWSFramework.getThreadPool().execute(new Runnable() {
public void run() {
// try {
// Thread.sleep(
// TIME_TO_SLEEP_BEFORE_CALL_LOCAL_SINK );
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
if (getType() == WSDLOperation.TYPE_SOLICIT_RESPONSE) {
ParameterValue rspParamValue;
rspParamValue = subscription.sink.receiveLocalEvent(subscription.clientSubscriptionId, new URI(getOutputAction()), paramValue);
if (rspParamValue != null) {
DefaultEventSource.this.solicitResponseReceived(rspParamValue, eventNumber, subscription);
} else {
Log.error("Local call of solicit response doesn't return response");
}
} else {
subscription.sink.receiveLocalEvent(subscription.clientSubscriptionId, new URI(getOutputAction()), paramValue);
}
}
});
} else {
/*
* CASE: Remote client
*/
DPWSFramework.getThreadPool().execute(new Runnable() {
public void run() {
InvokeMessage notification = new InvokeMessage(getOutputAction(), subscription.getCommunicationManagerID());
notification.setContent(paramValue);
SOAPHeader header = notification.getHeader();
notification.setProtocolInfo(subscription.getProtocolInfo());
/*
* Add client subscription id
*/
header.setEndpointReference(subscription.notifyTo.getEndpointReference());
// set to preferred xAddress of client / event sink
notification.setTargetXAddressInfo(subscription.notifyTo);
/*
* Send the message
*/
if (getType() == WSDLOperation.TYPE_SOLICIT_RESPONSE) {
/*
* CASE: Solicit response
*/
URI msgId = notification.getMessageId();
SolicitResponseContext msgContext = new SolicitResponseContext(msgId);
map_MsgId_2_Context.put(msgId, msgContext);
OutDispatcher.getInstance().send(notification, subscription.notifyTo, new DefaultEventSourceCallback(subscription.notifyTo, subscription, op));
synchronized (msgId) {
while (msgContext.waitingForNotfication) {
try {
msgId.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (msgContext.rspParamValue != null) {
solicitResponseReceived(msgContext.rspParamValue, eventNumber, subscription);
} else if (!msgContext.responseReceived) {
/*
* Remove subscription, if no response
* received
*/
Log.error("Event.fire(): No response received!");
subscriptions.remove(subscription);
}
} else {
/*
* CASE: Notification
*/
OutDispatcher.getInstance().send(notification, subscription.notifyTo, new DefaultEventSourceCallback(subscription.notifyTo, subscription, op));
}
}
});
}
}
} finally {
subscriptions.releaseSharedLock();
}
/*
* remove outdated subscriptions
*/
if (outdatedSubscriptions.size() > 0) {
subscriptions.exclusiveLock();
try {
for (Iterator it = outdatedSubscriptions.iterator(); it.hasNext();) {
subscriptions.remove(it.next());
}
} finally {
subscriptions.releaseExclusiveLock();
}
}
}
/**
* Returns the <code>transmission type</code> of this event source according
* to <a href="http://www.w3.org/TR/wsdl">WSDL 1.1 specification</a>. The
* value returned is one of {@link WSDLOperation#TYPE_NOTIFICATION} or
* {@link WSDLOperation#TYPE_SOLICIT_RESPONSE}.
*
* @return type the transmission type of this event source
*/
public final int getType() {
if (type == WSDLOperation.TYPE_UNKNOWN) {
if (getInput() == null && getFaultCount() == 0) {
type = WSDLOperation.TYPE_NOTIFICATION;
} else {
type = WSDLOperation.TYPE_SOLICIT_RESPONSE;
}
}
return type;
}
/**
* Returns <code>true</code>, if the transmission type of this event source
* is {@link WSDLOperation#TYPE_NOTIFICATION}. Returns <code>false</code> in
* any other case.
*
* @return checks whether this is a {@link WSDLOperation#TYPE_NOTIFICATION
* notification} event source
*/
public final boolean isNotification() {
return getType() == WSDLOperation.TYPE_NOTIFICATION;
}
/**
* Returns <code>true</code>, if the transmission type of this event source
* is {@link WSDLOperation#TYPE_SOLICIT_RESPONSE}. Returns
* <code>false</code> in any other case.
*
* @return checks whether this is a
* {@link WSDLOperation#TYPE_SOLICIT_RESPONSE solicit-response}
* event source
*/
public final boolean isSolicitResponse() {
return getType() == WSDLOperation.TYPE_SOLICIT_RESPONSE;
}
/*
* (non-Javadoc)
* @see org.ws4d.java.eventing.EventSource#subscribe(org.ws4d.java.eventing.
* EventListener, long)
*/
public ClientSubscription subscribe(EventListener client, long duration) throws EventingException, TimeoutException {
return subscribe(client, duration, null);
}
/*
* (non-Javadoc)
* @see org.ws4d.java.eventing.EventSource#subscribe(org.ws4d.java.eventing.
* EventListener, long, org.ws4d.java.structures.DataStructure)
*/
public ClientSubscription subscribe(EventListener client, long duration, DataStructure bindings) throws EventingException, TimeoutException {
Service service = getService();
EventSink sink = client.getEventSink(bindings);
if (service.isRemote()) {
sink.open();
}
String clientSubscriptionId = IDGenerator.URI_UUID_PREFIX + IDGenerator.getUUID();
return service.subscribe(sink, clientSubscriptionId, new URISet(new URI(getOutputAction())), duration);
}
/**
* Callback method for receiving responses to sent events if they are from
* {@link #getType() type} {@link #isSolicitResponse() solicit-response}. A
* call to this method is made by the DPWS framework each time a subscribed
* client responds to a fired event source (see
* {@link #fire(ParameterValue, int)}. The parameters sent by the client
* within its response are contained within argument <code>paramValue</code>
* . The value of <code>eventNumber</code> corresponds to the value passed
* to {@link #fire(ParameterValue, int)} when creating the notification to
* which this response belongs. Finally, argument <code>subscription</code>
* contains information allowing identification of the subscriber sending
* the response, as well as further details about its subscription (such as
* expiration time, filter, etc.).
*
* @param paramValue parameter value from the response received
* @param eventNumber number allowing for correlation between sent
* notifications (solicit requests) and their corresponding
* responses
* @param subscription provides information about the subscriber who sent
* the response
*/
public void solicitResponseReceived(ParameterValue paramValue, int eventNumber, ServiceSubscription subscription) {
Log.info("DefaultEventSource.receivedSolicitResponse: Overwrite this method to receive solicit responses.");
}
// ------------------ INTERNAL SUBSCRIPTION MANAGEMENT -------------------
void addSubscription(ServiceSubscription subscription) {
subscriptions.exclusiveLock();
try {
subscriptions.add(subscription);
} finally {
subscriptions.releaseExclusiveLock();
}
}
void removeSubscription(ServiceSubscription subscription) {
subscriptions.exclusiveLock();
try {
subscriptions.remove(subscription);
} finally {
subscriptions.releaseExclusiveLock();
}
}
// ADDED 2010-08-11 SSch Applications may need to know how many subscribers
// they have for an event
protected int getSubscriptionCount() {
subscriptions.sharedLock();
try {
return subscriptions.size();
} finally {
subscriptions.releaseSharedLock();
}
}
// =========================== INNER CLASSES ===========================
private class SolicitResponseContext {
final URI messageId;
ParameterValue rspParamValue = null;
boolean responseReceived;
volatile boolean waitingForNotfication = true;
SolicitResponseContext(URI messageId) {
this.messageId = messageId;
}
}
private class DefaultEventSourceCallback extends DefaultResponseCallback {
private final ServiceSubscription subscription;
private final OperationDescription op;
/**
*
*/
public DefaultEventSourceCallback(XAddressInfo targetXAddressInfo, ServiceSubscription subscription, OperationDescription op) {
super(targetXAddressInfo);
this.subscription = subscription;
this.op = op;
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.communication.DefaultResponseCallback#handle(org.ws4d
* .java.communication.message.Message,
* org.ws4d.java.message.invocation.InvokeMessage,
* org.ws4d.java.communication.ProtocolData)
*/
public void handle(Message request, InvokeMessage msg, ProtocolData protocolData) {
URI msgId = msg.getRelatesTo();
SolicitResponseContext msgContext = (SolicitResponseContext) map_MsgId_2_Context.remove(msgId);
if (msgContext != null) {
synchronized (msgContext.messageId) {
msgContext.responseReceived = true;
msgContext.rspParamValue = msg.getContent();
msgContext.waitingForNotfication = false;
msgContext.messageId.notify();
}
}
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.communication.DefaultResponseCallback#handle(org.ws4d
* .java.communication.message.Message,
* org.ws4d.java.message.FaultMessage,
* org.ws4d.java.communication.ProtocolData)
*/
public void handle(Message request, FaultMessage msg, ProtocolData protocolData) {
/*
* TODO here we should do what shall be done when a fault is
* received in response to a solicit message...
*/
handleTimeout(request);
}
/*
* (non-Javadoc)
* @see org.ws4d.java.communication.DefaultResponseCallback#
* handleMalformedResponseException(org.ws4d.java.message.Message,
* java.lang.Exception, org.ws4d.java.communication.ProtocolData)
*/
public void handleMalformedResponseException(Message request, Exception exception, ProtocolData protocolData) {
// just remove any sent solicit response context
handleTimeout(request);
}
/*
* (non-Javadoc)
* @see org.ws4d.java.communication.DefaultResponseCallback#
* handleTransmissionException(org.ws4d.java.message.Message,
* java.lang.Exception, org.ws4d.java.communication.ProtocolData)
*/
public void handleTransmissionException(Message request, Exception exception, ProtocolData protocolData) {
/*
* Remove subscription, if one error occurs
*/
subscriptions.exclusiveLock();
try {
subscriptions.remove(subscription);
Log.error("DefaultEventSource.fire(): Can't send notification!");
Log.printStackTrace(exception);
} finally {
subscriptions.releaseExclusiveLock();
}
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.communication.ResponseCallback#handleTimeout(org.ws4d
* .java.communication.message.Message)
*/
public void handleTimeout(Message request) {
URI msgId = request.getMessageId();
SolicitResponseContext msgContext = (SolicitResponseContext) map_MsgId_2_Context.remove(msgId);
if (msgContext != null) {
// TODO call back for timeout handling -> new class operation
// handler
synchronized (msgContext.messageId) {
msgContext.responseReceived = false;
msgContext.waitingForNotfication = false;
msgContext.messageId.notify();
}
}
}
/*
* (non-Javadoc)
* @see org.ws4d.java.communication.ResponseCallback#getOperation()
*/
public OperationDescription getOperation() {
return op;
}
}
}