Version 30

    This is just a (possible outdated) draft design for WS-Eventing implementation that was done as my (Jan Martiska) diploma thesis. The implementation was merged into CXF and should be part of the 3.0.0 release.

     

    Link to specification: http://www.w3.org/TR/ws-eventing

     

    Event Source example:

    - EventingFeature adds required interceptors for SOAP fault handling logic and enables access to SOAP headers

    - AbstractEventSource implements WS-Eventing event source behavior

    - Event source accepts only "subscribe" messages

     

    @WebService(endpointInterface = "org.apache.cxf.ws.eventing.service.EventSourceEndpoint")
    public class ExampleEventSource extends AbstractEventSource {
    
    
    @Override
        protected SubscriptionManagerInterfaceForEventSources getSubscriptionManagerBackend() {
            // here will be the code where to obtain subscription manager backing bean
        }
    
    }
    

    EventSourceEndpoint interface (implemented by AbstractEventSource):

    @WebService(targetNamespace = "http://www.w3.org/2011/03/ws-evt")
    @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
    @Addressing(enabled=true, required=true)
    @Features(features = {"org.apache.cxf.ws.eventing.EventingFeature"})
    public interface EventSourceInterface {
    
    
        @Action(
                input = EventingConstants.ACTION_SUBSCRIBE,
                output = EventingConstants.ACTION_SUBSCRIBE_RESPONSE,
                  fault = @FaultAction(
                        className = SoapFault.class, value = EventingConstants.ACTION_FAULT
                )
        )
        public @WebResult(name = "SubscribeResponse")
        SubscribeResponse subscribeOp(
                @WebParam(name = "Subscribe", targetNamespace = "http://www.w3.org/2011/03/ws-evt", partName = "body")
                Subscribe body) throws CannotProcessFilter;
    
    }
    

    Subscription Manager example:

    - Event source accepts Renew, GetStatus and Unsubscribe messages

    - EventingFeature adds required interceptors for SOAP fault handling logic and enables access to SOAP headers

    - AbstractSubscriptionMAnager implements WS-Eventing subscription manager behavior

    @WebService(endpointInterface = "org.apache.cxf.ws.eventing.service.SubscriptionManagerInterface")
    public class MySubscriptionManager extends AbstractSubscriptionManager {
    
        @Override
        protected SubscriptionManagerBackendInterfaceForManagers getSubscriptionManagerBackend() {
            // here will be the code where to obtain subscription manager backing bean
        }
    
    }
    

     

    @WebService(targetNamespace = "http://www.w3.org/2011/03/ws-evt")
    @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
    @Addressing(enabled=true, required=true)
    @Features(features = {"org.apache.cxf.ws.eventing.EventingFeature"})
    public interface SubscriptionManagerInterface {
    
        @Action(
                input = EventingConstants.ACTION_RENEW,
                output = EventingConstants.ACTION_RENEW_RESPONSE,
                  fault = @FaultAction(
                        className = SoapFault.class, value = EventingConstants.ACTION_FAULT
                )
        )
        public @WebResult(name = "RenewResponse")
        RenewResponse renewOp(
               @WebParam(name = "Renew", targetNamespace = "http://www.w3.org/2011/03/ws-evt", partName = "body")
               Renew body
        );
    
        @Action(
                input = EventingConstants.ACTION_GET_STATUS,
                output = EventingConstants.ACTION_GET_STATUS_RESPONSE,
                  fault = @FaultAction(
                        className = SoapFault.class, value = EventingConstants.ACTION_FAULT
                )
        )
        public @WebResult(name = "GetStatusResponse") GetStatusResponse getStatusOp(
                @WebParam(name = "GetStatus", targetNamespace = "http://www.w3.org/2011/03/ws-evt", partName = "body")
                GetStatus body
        );
    
        @Action(
                input = EventingConstants.ACTION_UNSUBSCRIBE,
                output = EventingConstants.ACTION_UNSUBSCRIBE_RESPONSE,
                fault = @FaultAction(
                        className = SoapFault.class, value = EventingConstants.ACTION_FAULT
                )
        )
        public @WebResult(name = "UnsubscribeResponse") UnsubscribeResponse unsubscribeOp(
                @WebParam(name = "Unsubscribe", targetNamespace = "http://www.w3.org/2011/03/ws-evt", partName = "body")
                Unsubscribe body
        );
    
    }
    

    SubscriptionManagerBackend:

    This is a class that holds the logic for managing subscriptions, eg. includes a subscription database. Both EventSources and SubscriptionManagers need to have access to an instance of this (typically one instance will be shared between an EventSource and a SubscriptionManager webservices). However, they use a different interface for communicating with the Backend.

     

    The logic how the Backend will be provided for EventSources and SubscriptionManagers in case these two services are deployed in different JVMs, is left to the programmer. One can use for instance EJB, WS or any other Java framework capable of remote method invocation.

     

     

    //this is the default implementation of SubscriptionManagers' backend business logic. Typically an instance of this class will be deployed for every Subscription Manager endpoint, most likely on the same machine.
    public class SubscriptionManagerImpl implements SubscriptionManager {
    
        private SubscriptionDatabase database;
    
        public SubscriptionManagerImpl() {
            database = new SubscriptionDatabaseImpl();
        }
    (........)
    }
    

     

    // a class implementing this will encapsulate the logic of a SubscriptionManager - eg. include a database and logic for granting expiration dates
    public interface SubscriptionManager
            extends SubscriptionManagerInterfaceForManagers, SubscriptionManagerInterfaceForEventSources {
    }
    
    
    
    
    // this is the interface which an Event Source needs to have access to, eg. by remote invocation in case of distributed system
    public interface SubscriptionManagerInterfaceForEventSources {
    
        SubscriptionTicket subscribe(DeliveryType delivery, EndpointReferenceType endTo, ExpirationType expires, FilterType filter);
    
    }
    
    // this is the interface which a SubscriptionManager service needs to have access to, as the implementing class will encapsulate SubscriptionManagers' logic
    public interface SubscriptionManagerInterfaceForManagers extends SubscriptionManagerInterfaceForEventSources {
    
        public SubscriptionDatabase getDatabase();
    
    }
    

    Event sources only need to the subscribe method, which will be invoked after the event source receives a "Subscribe" message from a client. This request is sent to the SubscriptionManager logic, who decides if the request is valid, and grants a SubscriptionTicket, which is saved to the SubscriptionDatabase AND returned back to the calling Event Source, whose responsibility then is to transform the ticket into a JAX-WS response for the client.

     

     

     

    Client example:

    Client creates a JAX-WS client from the EventSourceInterface (see above) and sends requests as needed.

    For example, usage with Spring context:

     

    <jaxws:client id="eventingClient"
                      serviceClass="org.apache.cxf.ws.eventing.service.EventSourceInterface"
                      address="http://www.example.com/newsApp/EventSource">
    

     

    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
    EventSourceInterface client = (EventSourceInterface)ctx.getBean("eventingClient");
    Subscribe sub = new Subscribe();   // create a new Subscribe message (Subscribe is a JAXB generated class)
    sub.setFilter(new FilterType());
    sub.getFilter().getContent().add("/my-xpath[@filter]");  // set an XPath filter which notifications we want to receive
    sub.setEndTo(new EndpointReferenceType());
    sub.getEndTo().setAddress(new AttributedURIType());
    sub.getEndTo().getAddress().setValue("http://www.example.org/news-eventsink/EventSink") // set where notifications should be sent to
    SubscribeResponse response = client.subscribeOp(sub); // invoke the service
    

     

     

    Event sink (subscriber) example:

    Event Sink needs to implement two methods: notification (when a notification is delivered) and subscriptionEnd (when the event source decides to void the subscription).

     

    @WebService
    public class EventSinkClass implements org.apache.cxf.ws.eventing.subscription.client.EventSinkInterface {
    
        @Override
        public void notification(Object o) {
               // handle notification
        }
    
        @Override
        public void subscriptionEnd(SubscriptionEnd subscriptionEnd) {
               // handle the fact that our subscription has ended 
        }
    
    }
    

    SOAP fault handling:

    (See chapter 6 of the specification)

    There will be a separate exception class for every fault listed in chapter 6, all are subclasses of WSEventingException.:

    public abstract class WSEventingException extends Fault {
    
        private static final Logger LOG = LogUtils.getLogger(WSEventingException.class);
    
    
        public WSEventingException(String reason, Element detail, QName faultCode) {
            super(reason, LOG);
            if (detail != null) {
                setDetail(detail);
            }
            if (faultCode != null) {
                setFaultCode(faultCode);
            }
        }
    
    }
    

    Every subclass defines its own reason, detail and faultCode according to specification.

    Throwing the fault is done simply by throwing the appropriate exception from one of these methods {renew, subscribe, unsubscribe, getstatus}

     

    Subscription ticket

    public class SubscriptionTicket {
    
        public SubscriptionTicket() {
        }
    
        private EndpointReferenceType endTo;   // information needed to know where to send a 'SubscriptionEnd' message if needed
        private DeliveryType delivery;   // information needed to know where to send notifications
        private Date expires;   // date of expiration
        private FilterType filter;   // the requested filter, through which outgoing messages will go
        private UUID uuid;   // the unique subscription's ID
    
    //   getters/setters omitted
    }
    

    all of the included properties are JAXB-generated classes from WS-Eventing XSD.

     

    Event emission

    There will be a dispatcher with this interface:

    public void dispatch(URI eventSourceNS, Element event) {
          (...)
    }
    

     

    The dispatcher will forward the event to a SubscriptionManagerBackend instance, which will take care of sending notifications to subscribed clients.

     

     

    Filtering

    (see chapter 4.1 of specification)

    Planned filter dialects are XPath 1.0 and 2.0. Outgoing messages will be filtered through this method:

    public static boolean doesConformToFilter(Element elm, List<String> filter) {
    
    if(filter.size() == 0)
                return true;
            try {
                XPathFactory xPathFactory = XPathFactory.newInstance();
                XPath xPath = xPathFactory.newXPath();
                for (String xpathString : filter) {
                    XPathExpression xPathExpression = xPath.compile(xpathString);
                    boolean ok = (Boolean) xPathExpression.evaluate(elm, XPathConstants.BOOLEAN);
                    if(!ok)
                        return false;
                }
                return true;
            } catch (XPathExpressionException e) {
                LOG.severe(e.toString());
                throw new EmptyFilter();       // chapter 6.7 of spec
            }
        }
    

     

    Subscription tickets persistence

    TBD. There will probably be some utility allowing to persist tickets to a JDBC datasource and read from it upon service restart.

     

    Making decisions about granted Expiration

    If a client makes a request containing the requested subscription duration, he has to specify either a duration (unmarshalled into javax.xml.datatype.Duration) or a timestamp (unmarshalled into javax.xml.datatype.XMLGregorianCalendar) until when he would like the subscription to last. The service can then decide what duration/timestamp it will grant to the subscriber. This can be done by overriding these methods in AbstractEventSource:

     

    protected XMLGregorianCalendar grantExpirationFor(XMLGregorianCalendar requested) { // the client requested a timestamp.
    //By default, let's grant the same timestamp as was requested
            return requested;
        }
    
       protected Duration grantExpirationFor(Duration requested) {   // the client requested a duration.
    //By default, let's grant the same duration as was requested
            return requested;
        }
    
       protected javax.xml.datatype.Duration grantExpiration() { // the client did not specify any request for expiration time.
    //According to the specification, we must then give him a duration. By default, let's set the duration as "forever"
            try {
                return DatatypeFactory.newInstance().newDuration("PT0S");
            } catch(DatatypeConfigurationException ex) {
                throw new Error(ex);
            }
        }
    

     

    Questions to be resolved:

    • as a developer, it is not very pleasant to use the JAXB generated classes directly to generate XML-based messages to pass as arguments into JAXWS methods. Should there be any abstraction API for convenient creation of SubscribeResponse, RenewResponse, GetStatus, (.....) messages? This would be useful for all service development, client development and event sink development.
      • RESOLVED: we will use JAXB classes directly