/*******************************************************************************
* 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.CommunicationManager;
import org.ws4d.java.communication.CommunicationManagerRegistry;
import org.ws4d.java.communication.CommunicationUtil;
import org.ws4d.java.communication.ProtocolData;
import org.ws4d.java.communication.TimeoutException;
import org.ws4d.java.configuration.FrameworkProperties;
import org.ws4d.java.constants.ConstantsHelper;
import org.ws4d.java.constants.SOAPConstants;
import org.ws4d.java.constants.WSAConstants;
import org.ws4d.java.constants.WSEConstants;
import org.ws4d.java.dispatch.OutDispatcher;
import org.ws4d.java.eventing.ClientSubscription;
import org.ws4d.java.eventing.EventSink;
import org.ws4d.java.eventing.EventingException;
import org.ws4d.java.eventing.SubscriptionManager;
import org.ws4d.java.message.FaultMessage;
import org.ws4d.java.message.Message;
import org.ws4d.java.message.SOAPException;
import org.ws4d.java.message.eventing.GetStatusMessage;
import org.ws4d.java.message.eventing.GetStatusResponseMessage;
import org.ws4d.java.message.eventing.RenewMessage;
import org.ws4d.java.message.eventing.RenewResponseMessage;
import org.ws4d.java.message.eventing.SubscribeMessage;
import org.ws4d.java.message.eventing.SubscribeResponseMessage;
import org.ws4d.java.message.eventing.SubscriptionEndMessage;
import org.ws4d.java.message.eventing.UnsubscribeMessage;
import org.ws4d.java.message.eventing.UnsubscribeResponseMessage;
import org.ws4d.java.schema.SchemaUtil;
import org.ws4d.java.structures.HashMap.Entry;
import org.ws4d.java.structures.Iterator;
import org.ws4d.java.structures.LockedMap;
import org.ws4d.java.types.Delivery;
import org.ws4d.java.types.EndpointReference;
import org.ws4d.java.types.EprInfo;
import org.ws4d.java.types.Filter;
import org.ws4d.java.types.LocalizedString;
import org.ws4d.java.types.QName;
import org.ws4d.java.types.ReferenceParametersMData;
import org.ws4d.java.types.URI;
import org.ws4d.java.types.URISet;
import org.ws4d.java.util.IDGenerator;
import org.ws4d.java.util.TimedEntry;
import org.ws4d.java.util.WatchDog;
/**
*
*/
public class DefaultSubscriptionManager implements SubscriptionManager {
private static final String FAULT_REASON_DELIVERY_MODE = "The requested delivery mode is not supported.";
private static final String FAULT_REASON_FILTERING_DIALECT = "The requested filter dialect is not supported.";
private static final String FAULT_REASON_FILTER_ACTION_NOT_SUPPORTED = "No notifications match the supplied filter.";
private static final String FAULT_REASON_INVALID_MESSAGE = "The message is not valid and cannot be processed.";
private static final String FAULT_REASON_UNABLE_TO_RENEW__NO_SUBSCRIPTION = "No such subscription";
private static final String EVENT_SOURCE_SHUTTING_DOWN = "Event source shutting down.";
private static final long REMOVAL_POLL_INTERVAL = SchemaUtil.MILLIS_PER_MINUTE;
/*
* key = wse:Identifier (as uuid: URN), value = service subscription entry
* instance
*/
private final LockedMap subscriptions = new LockedMap();
/** this subscription manager is associated to this service */
private final LocalService service;
public DefaultSubscriptionManager(LocalService service) {
super();
this.service = service;
TimedEntry entry = new TimedEntry() {
protected void timedOut() {
cleanUpSubscriptions();
WatchDog.getInstance().register(this, REMOVAL_POLL_INTERVAL);
}
};
WatchDog.getInstance().register(entry, REMOVAL_POLL_INTERVAL);
}
/**
* @param msg
* @param subcode
* @param reason
* @return
*/
static SOAPException createFault(Message msg, QName subcode, LocalizedString reason) {
return createFault(msg, SOAPConstants.SOAP_FAULT_SENDER, subcode, reason);
}
/**
* @param msg
* @param subcode
* @param reason
* @return SOAPException
*/
static SOAPException createFault(Message msg, QName code, QName subcode, LocalizedString reason) {
FaultMessage fault = new FaultMessage(WSAConstants.WSA_ACTION_ADDRESSING_FAULT, CommunicationManager.ID_NULL);
fault.setResponseTo(msg);
fault.setCode(code);
fault.setSubcode(subcode);
fault.addReason(reason);
fault.setResponseTo(msg);
return new SOAPException(fault);
}
static SOAPException createDeliveryModeUnavailableFault(Message msg) {
return createFault(msg, WSEConstants.WSE_FAULT_DELIVERY_MODE_REQUESTED_UNVAILABLE, new LocalizedString(FAULT_REASON_DELIVERY_MODE, LocalizedString.LANGUAGE_EN));
}
static SOAPException createInvalidMessageFault(Message msg) {
return createFault(msg, WSEConstants.WSE_FAULT_INVALID_MESSAGE, new LocalizedString(FAULT_REASON_INVALID_MESSAGE, LocalizedString.LANGUAGE_EN));
}
static SOAPException createUnableToRenew(Message msg) {
return createFault(msg, SOAPConstants.SOAP_FAULT_RECEIVER, WSEConstants.WSE_FAULT_UNABLE_TO_RENEW, new LocalizedString(FAULT_REASON_UNABLE_TO_RENEW__NO_SUBSCRIPTION, LocalizedString.LANGUAGE_EN));
}
static EndpointReference createSubscriptionManager(URI address, String wseIdentifier) {
ReferenceParametersMData parameters = new ReferenceParametersMData();
parameters.setWseIdentifier(wseIdentifier);
return new EndpointReference(address, parameters);
}
private void cleanUpSubscriptions() {
subscriptions.exclusiveLock();
try {
long now = System.currentTimeMillis();
for (Iterator it = subscriptions.entrySet().iterator(); it.hasNext();) {
Entry ent = (Entry) it.next();
ServiceSubscription subscription = (ServiceSubscription) ent.getValue();
if (subscription.expirationTime <= now) {
// OutDispatcher.getInstance().send(subscriptionEnd(subscription));
it.remove();
removeSubscriptionFromEventSources(subscription);
}
}
} finally {
subscriptions.releaseExclusiveLock();
}
}
/**
* Removes the subscription from each subscribed evented operation.
*
* @param subscription subscription to from operations.
*/
private void removeSubscriptionFromEventSources(ServiceSubscription subscription) {
for (Iterator it = subscription.filterActions.iterator(); it.hasNext();) {
String action = ((URI) it.next()).toString();
DefaultEventSource ev = (DefaultEventSource) service.getEventSource(action);
if (ev != null) {
ev.removeSubscription(subscription);
}
}
}
/**
* Adds service subscription to each matching operation with matching action
* uri.
*
* @param subscription service subscription to add.
* @return true if at least one action matches an evented operation.
*/
private boolean addSubscriptionToEventSource(ServiceSubscription subscription) {
boolean hasMatchingAction = false;
URISet actions = subscription.filterActions;
for (Iterator it = actions.iterator(); it.hasNext();) {
/*
* Add the subscription to each evented operation
*/
String action = ((URI) it.next()).toString();
DefaultEventSource ev = (DefaultEventSource) service.getEventSource(action);
if (ev != null) {
ev.addSubscription(subscription);
hasMatchingAction = true;
}
}
return hasMatchingAction;
}
/**
* TODO
*
* @param subscription
* @return
*/
private SubscriptionEndMessage subscriptionEnd(ServiceSubscription subscription, URI status, String reason) {
SubscriptionEndMessage subscriptionEndMessage = new SubscriptionEndMessage(subscription.getCommunicationManagerID());
// set to preferred xAddress of client / event sink
subscriptionEndMessage.setTargetXAddressInfo(subscription.endTo);
subscriptionEndMessage.getHeader().setEndpointReference(subscription.endTo.getEndpointReference());
subscriptionEndMessage.setStatus(status);
subscriptionEndMessage.setReason(new LocalizedString(reason, LocalizedString.DEFAULT_LANG));
subscriptionEndMessage.setSubscriptionManager(subscription.getSubscriptionManager());
return subscriptionEndMessage;
}
// ------------------PUBLIC SUBSCRIPTION MANAGEMENT -------------------
/*
* (non-Javadoc)
* @see org.ws4d.java.message.eventing.SubscriptionManager#subscribe
* (org.ws4d.java.message.eventing.SubscribeMessage,
* org.ws4d.java.communication.ProtocolData)
*/
public SubscribeResponseMessage subscribe(SubscribeMessage msg, ProtocolData protocolData) throws SOAPException {
Delivery delivery = msg.getDelivery();
if (delivery == null) {
// Fault wse:DeliveryModeRequestedUnavailable
throw createDeliveryModeUnavailableFault(msg);
}
URI mode = delivery.getMode();
if (mode == null || !WSEConstants.WSE_DELIVERY_MODE_PUSH.equals(mode.toString())) {
// Fault wse:DeliveryModeRequestedUnavailable
throw createDeliveryModeUnavailableFault(msg);
}
ServiceSubscription subscription = new ServiceSubscription(protocolData.getProtocolInfo());
subscription.notifyTo = new EprInfo(delivery.getNotifyTo(), protocolData.getCommunicationManagerId());
subscription.notifyTo.mergeProtocolInfo(protocolData.getProtocolInfo());
if (msg.getEndTo() != null) {
subscription.endTo = new EprInfo(msg.getEndTo(), protocolData.getCommunicationManagerId());
subscription.endTo.mergeProtocolInfo(protocolData.getProtocolInfo());
} else {
subscription.endTo = null;
}
subscription.communicationManagerId = protocolData.getCommunicationManagerId();
CommunicationManager comMan = DPWSFramework.getCommunicationManager(protocolData.getCommunicationManagerId());
CommunicationUtil comUtil = comMan.getCommunicationUtil();
ConstantsHelper helper = comUtil.getHelper(msg.getProtocolInfo().getVersion());
Filter filter = msg.getFilter();
if (filter != null) {
URI dialect = filter.getDialect();
if (dialect == null) {
// Fault wse:FilteringRequestedUnavailable
throw createFault(msg, SOAPConstants.SOAP_FAULT_SENDER, WSEConstants.WSE_FAULT_FILTERING_REQUESTED_UNAVAILABLE, new LocalizedString(FAULT_REASON_FILTERING_DIALECT, LocalizedString.DEFAULT_LANG));
}
if (helper.getDPWSFilterEventingAction().equals(dialect.toString())) {
subscription.filterActions = filter.getActions();
boolean hasMatchingAction = addSubscriptionToEventSource(subscription);
if (!hasMatchingAction) {
/*
* Fault dpws:FilterActionNotSupported
*/
FaultMessage fault = new FaultMessage(helper.getDPWSActionFault(), protocolData.getCommunicationManagerId());
fault.setResponseTo(msg);
fault.setCode(SOAPConstants.SOAP_FAULT_SENDER);
fault.setSubcode(helper.getDPWSFaultFilterActionNotSupported());
fault.addReason(new LocalizedString(FAULT_REASON_FILTER_ACTION_NOT_SUPPORTED, LocalizedString.DEFAULT_LANG));
throw new SOAPException(fault);
}
}
// XXX ok to add subscription without filter?
subscription.setExpiration(msg.getExpires(), msg);
/*
* create subscribe response message
*/
SubscribeResponseMessage response = new SubscribeResponseMessage(protocolData.getCommunicationManagerId());
response.setResponseTo(msg);
// set DPWSVersion from the Request to the Response
response.setProtocolInfo(msg.getProtocolInfo());
URI to = msg.getTo();
String wseIdentifier = IDGenerator.URI_UUID_PREFIX + IDGenerator.getUUID();
EndpointReference subscriptionManager;
if (FrameworkProperties.REFERENCE_PARAM_MODE) {
subscriptionManager = createSubscriptionManager(to, wseIdentifier);
} else {
to.setFragmentEncoded(wseIdentifier);
subscriptionManager = new EndpointReference(to);
}
subscription.setSubscriptionManager(subscriptionManager);
response.setSubscriptionManager(subscriptionManager);
response.setExpires(SchemaUtil.createDuration(subscription.expirationTime - System.currentTimeMillis()));
subscriptions.exclusiveLock();
try {
subscriptions.put(wseIdentifier, subscription);
} finally {
subscriptions.releaseExclusiveLock();
}
return response;
} else { // Fault wse:FilteringRequestedUnavailable
throw createFault(msg, SOAPConstants.SOAP_FAULT_SENDER, WSEConstants.WSE_FAULT_FILTERING_REQUESTED_UNAVAILABLE, new LocalizedString(FAULT_REASON_FILTERING_DIALECT, LocalizedString.DEFAULT_LANG));
}
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.service.SubscriptionManagerInterface#subscribe(org.ws4d
* .java.eventing.EventSink, java.lang.String,
* org.ws4d.java.types.uri.URISet, long)
*/
public ClientSubscription subscribe(EventSink sink, String clientSubscriptionId, URISet eventActionURIs, long duration) throws EventingException {
ServiceSubscription entry = new ServiceSubscription(null);
entry.filterActions = eventActionURIs;
entry.sink = sink;
entry.clientSubscriptionId = clientSubscriptionId;
entry.setExpiration(duration);
entry.communicationManagerId = CommunicationManagerRegistry.getDefault();
boolean hasMatchingAction = addSubscriptionToEventSource(entry);
if (!hasMatchingAction) {
/*
* Fault dpws:FilterActionNotSupported
*/
CommunicationManager comMan = DPWSFramework.getCommunicationManager(entry.getCommunicationManagerID());
CommunicationUtil comUtil = comMan.getCommunicationUtil();
Iterator it = comMan.getSupportedVersions().iterator();
QName subcode = (it.hasNext()) ? comUtil.getHelper(((Integer)it.next()).intValue()).getDPWSFaultFilterActionNotSupported() : new QName(null);
throw new EventingException(subcode, FAULT_REASON_FILTER_ACTION_NOT_SUPPORTED);
}
String wseIdentifier = IDGenerator.URI_UUID_PREFIX + IDGenerator.getUUID();
subscriptions.exclusiveLock();
try {
subscriptions.put(wseIdentifier, entry);
} finally {
subscriptions.releaseExclusiveLock();
}
/*
* Create client subscription
*/
Iterator serviceEprs = service.getEprInfos();
EprInfo eprInfo = (EprInfo) serviceEprs.next();
URI serviceUri = eprInfo.getEndpointReference().getAddress();
return new DefaultClientSubscription(sink, clientSubscriptionId, createSubscriptionManager(serviceUri, wseIdentifier), entry.getCommunicationManagerID(), duration, service.getServiceReference());
}
/*
* (non-Javadoc)
* @see org.ws4d.java.message.eventing.SubscriptionManager#unsubscribe
* (org.ws4d.java.message.eventing.UnsubscribeMessage,
* org.ws4d.java.communication.ProtocolData)
*/
public UnsubscribeResponseMessage unsubscribe(UnsubscribeMessage msg, ProtocolData protocolData) throws SOAPException {
String wseIdentifier = msg.getHeader().getWseIdentifier();
if (wseIdentifier == null) {
// Fault wse:InvalidMessage
throw createInvalidMessageFault(msg);
}
ServiceSubscription subscription = null;
subscriptions.exclusiveLock();
try {
subscription = (ServiceSubscription) subscriptions.remove(wseIdentifier);
} finally {
subscriptions.releaseExclusiveLock();
}
if (subscription == null) {
// Fault wse:InvalidMessage
throw createInvalidMessageFault(msg);
}
removeSubscriptionFromEventSources(subscription);
UnsubscribeResponseMessage response = new UnsubscribeResponseMessage(protocolData.getCommunicationManagerId());
response.setResponseTo(msg);
// set DPWSVersion from the Request to the Response
response.setProtocolInfo(msg.getProtocolInfo());
return response;
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.service.SubscriptionManagerInterface#unsubscribe(org.ws4d
* .java.eventing.ClientSubscription)
*/
public void unsubscribe(ClientSubscription subscription) throws EventingException {
String wseIdentifier = subscription.getServiceSubscriptionId();
ServiceSubscription serviceSubscription = null;
subscriptions.exclusiveLock();
try {
serviceSubscription = (ServiceSubscription) subscriptions.remove(wseIdentifier);
} finally {
subscriptions.releaseExclusiveLock();
}
if (serviceSubscription == null) {
/*
* Fault wse:InvalidMessage
*/
throw new EventingException(WSEConstants.WSE_FAULT_INVALID_MESSAGE, FAULT_REASON_INVALID_MESSAGE);
}
removeSubscriptionFromEventSources(serviceSubscription);
}
/*
* (non-Javadoc)
* @see org.ws4d.java.message.eventing.SubscriptionManager#renew
* (org.ws4d.java.message.eventing.RenewMessage,
* org.ws4d.java.communication.ProtocolData)
*/
public RenewResponseMessage renew(RenewMessage msg, ProtocolData protocolData) throws SOAPException {
String wseIdentifier = msg.getHeader().getWseIdentifier();
if (wseIdentifier == null) {
// Fault wse:InvalidMessage
throw createInvalidMessageFault(msg);
}
RenewResponseMessage response = new RenewResponseMessage(protocolData.getCommunicationManagerId());
response.setResponseTo(msg);
// set DPWSVersion from the Request to the Response
response.setProtocolInfo(msg.getProtocolInfo());
subscriptions.exclusiveLock();
try {
ServiceSubscription serviceSubscription = (ServiceSubscription) subscriptions.get(wseIdentifier);
if (serviceSubscription == null) {
// Fault wse:InvalidMessage
throw createUnableToRenew(msg);
}
long currentTime = System.currentTimeMillis();
if (serviceSubscription.expirationTime <= currentTime) {
// Fault wse:InvalidMessage
throw createUnableToRenew(msg);
}
serviceSubscription.setExpiration(msg.getExpires(), msg);
// this MUST be done while we still hold the lock!
response.setExpires(SchemaUtil.createDuration(serviceSubscription.expirationTime - currentTime));
} finally {
subscriptions.releaseExclusiveLock();
}
return response;
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.service.SubscriptionManagerInterface#renew(org.ws4d.java
* .eventing.ClientSubscription, long)
*/
public long renew(ClientSubscription subscription, long duration) throws EventingException {
String wseIdentifier = subscription.getServiceSubscriptionId();
ServiceSubscription serviceSubscription = null;
subscriptions.exclusiveLock();
try {
serviceSubscription = (ServiceSubscription) subscriptions.get(wseIdentifier);
if (serviceSubscription == null) {
// Fault wse:InvalidMessage
throw new EventingException(WSEConstants.WSE_FAULT_INVALID_MESSAGE, FAULT_REASON_INVALID_MESSAGE);
}
long currentTime = System.currentTimeMillis();
if (serviceSubscription.expirationTime <= currentTime) {
// Fault wse:InvalidMessage
throw new EventingException(WSEConstants.WSE_FAULT_INVALID_MESSAGE, FAULT_REASON_INVALID_MESSAGE);
}
serviceSubscription.setExpiration(duration);
return duration;
} finally {
subscriptions.releaseExclusiveLock();
}
}
/*
* (non-Javadoc)
* @see org.ws4d.java.message.eventing.SubscriptionManager#getStatus
* (org.ws4d.java.message.eventing.GetStatusMessage,
* org.ws4d.java.communication.ProtocolData)
*/
public GetStatusResponseMessage getStatus(GetStatusMessage msg, ProtocolData protocolData) throws SOAPException {
String wseIdentifier = msg.getHeader().getWseIdentifier();
if (wseIdentifier == null) {
// Fault wse:InvalidMessage
throw createInvalidMessageFault(msg);
}
GetStatusResponseMessage response = new GetStatusResponseMessage(protocolData.getCommunicationManagerId());
response.setResponseTo(msg);
// set DPWSVersion from the Request to the Response
response.setProtocolInfo(msg.getProtocolInfo());
subscriptions.sharedLock();
try {
ServiceSubscription subscription = (ServiceSubscription) subscriptions.get(wseIdentifier);
if (subscription == null) {
// Fault wse:InvalidMessage
throw createInvalidMessageFault(msg);
}
long currentTime = System.currentTimeMillis();
if (subscription.expirationTime <= currentTime) {
// Fault wse:InvalidMessage
throw createInvalidMessageFault(msg);
}
// this MUST be done while we hold the lock!
response.setExpires(SchemaUtil.createDuration(subscription.expirationTime - currentTime));
} finally {
subscriptions.releaseSharedLock();
}
return response;
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.eventing.SubscriptionManager#getStatus(org.ws4d.java.eventing
* .ClientSubscription)
*/
public long getStatus(ClientSubscription subscription) throws EventingException, TimeoutException {
String wseIdentifier = subscription.getServiceSubscriptionId();
ServiceSubscription serviceSubscription = null;
subscriptions.exclusiveLock();
try {
serviceSubscription = (ServiceSubscription) subscriptions.get(wseIdentifier);
if (serviceSubscription == null) {
// Fault wse:InvalidMessage
throw new EventingException(WSEConstants.WSE_FAULT_INVALID_MESSAGE, FAULT_REASON_INVALID_MESSAGE);
}
long currentTime = System.currentTimeMillis();
if (serviceSubscription.expirationTime <= currentTime) {
// Fault wse:InvalidMessage
throw new EventingException(WSEConstants.WSE_FAULT_INVALID_MESSAGE, FAULT_REASON_INVALID_MESSAGE);
}
return serviceSubscription.expirationTime - currentTime;
} finally {
subscriptions.releaseExclusiveLock();
}
}
/*
* (non-Javadoc)
* @see org.ws4d.java.eventing.SubscriptionManager#sendSubscriptionEnd()
*/
public void sendSubscriptionEnd() {
subscriptions.exclusiveLock();
try {
for (Iterator it = subscriptions.values().iterator(); it.hasNext();) {
ServiceSubscription subscription = (ServiceSubscription) it.next();
it.remove();
removeSubscriptionFromEventSources(subscription);
if (subscription.sink == null) {
// remote subscription
SubscriptionEndMessage subscriptionEnd = subscriptionEnd(subscription, SubscriptionEndMessage.SOURCE_SHUTTING_DOWN_STATUS, EVENT_SOURCE_SHUTTING_DOWN);
OutDispatcher.getInstance().send(subscriptionEnd, subscription.endTo);
} else {
ClientSubscription clientSubscription = subscription.sink.getSubscription(subscription.clientSubscriptionId);
if (clientSubscription != null) {
subscription.sink.getEventListener().subscriptionEndReceived(clientSubscription, SubscriptionEndMessage.SOURCE_SHUTTING_DOWN_STATUS);
}
}
}
} finally {
subscriptions.releaseExclusiveLock();
}
}
public boolean isRemote() {
return false;
}
}