001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.wsn;
018    
019    import java.util.GregorianCalendar;
020    
021    import javax.jws.WebMethod;
022    import javax.jws.WebParam;
023    import javax.jws.WebResult;
024    import javax.jws.WebService;
025    import javax.xml.bind.JAXBElement;
026    import javax.xml.datatype.DatatypeConfigurationException;
027    import javax.xml.datatype.DatatypeConstants;
028    import javax.xml.datatype.DatatypeFactory;
029    import javax.xml.datatype.Duration;
030    import javax.xml.datatype.XMLGregorianCalendar;
031    import javax.xml.namespace.QName;
032    
033    import org.apache.servicemix.wsn.jaxws.InvalidFilterFault;
034    import org.apache.servicemix.wsn.jaxws.InvalidMessageContentExpressionFault;
035    import org.apache.servicemix.wsn.jaxws.InvalidProducerPropertiesExpressionFault;
036    import org.apache.servicemix.wsn.jaxws.InvalidTopicExpressionFault;
037    import org.apache.servicemix.wsn.jaxws.PausableSubscriptionManager;
038    import org.apache.servicemix.wsn.jaxws.PauseFailedFault;
039    import org.apache.servicemix.wsn.jaxws.ResourceUnknownFault;
040    import org.apache.servicemix.wsn.jaxws.ResumeFailedFault;
041    import org.apache.servicemix.wsn.jaxws.SubscribeCreationFailedFault;
042    import org.apache.servicemix.wsn.jaxws.TopicExpressionDialectUnknownFault;
043    import org.apache.servicemix.wsn.jaxws.TopicNotSupportedFault;
044    import org.apache.servicemix.wsn.jaxws.UnableToDestroySubscriptionFault;
045    import org.apache.servicemix.wsn.jaxws.UnacceptableInitialTerminationTimeFault;
046    import org.apache.servicemix.wsn.jaxws.UnacceptableTerminationTimeFault;
047    import org.oasis_open.docs.wsn.b_2.InvalidFilterFaultType;
048    import org.oasis_open.docs.wsn.b_2.InvalidMessageContentExpressionFaultType;
049    import org.oasis_open.docs.wsn.b_2.InvalidProducerPropertiesExpressionFaultType;
050    import org.oasis_open.docs.wsn.b_2.InvalidTopicExpressionFaultType;
051    import org.oasis_open.docs.wsn.b_2.PauseSubscription;
052    import org.oasis_open.docs.wsn.b_2.PauseSubscriptionResponse;
053    import org.oasis_open.docs.wsn.b_2.QueryExpressionType;
054    import org.oasis_open.docs.wsn.b_2.Renew;
055    import org.oasis_open.docs.wsn.b_2.RenewResponse;
056    import org.oasis_open.docs.wsn.b_2.ResumeSubscription;
057    import org.oasis_open.docs.wsn.b_2.ResumeSubscriptionResponse;
058    import org.oasis_open.docs.wsn.b_2.Subscribe;
059    import org.oasis_open.docs.wsn.b_2.SubscribeCreationFailedFaultType;
060    import org.oasis_open.docs.wsn.b_2.TopicExpressionType;
061    import org.oasis_open.docs.wsn.b_2.UnableToDestroySubscriptionFaultType;
062    import org.oasis_open.docs.wsn.b_2.UnacceptableInitialTerminationTimeFaultType;
063    import org.oasis_open.docs.wsn.b_2.UnacceptableTerminationTimeFaultType;
064    import org.oasis_open.docs.wsn.b_2.Unsubscribe;
065    import org.oasis_open.docs.wsn.b_2.UnsubscribeResponse;
066    import org.oasis_open.docs.wsn.b_2.UseRaw;
067    import org.w3._2005._08.addressing.EndpointReferenceType;
068    
069    @WebService(endpointInterface = "org.apache.servicemix.wsn.jaxws.PausableSubscriptionManager")
070    public abstract class AbstractSubscription extends AbstractEndpoint implements PausableSubscriptionManager {
071    
072        public static final String WSN_URI = "http://docs.oasis-open.org/wsn/b-2";
073    
074        public static final String XPATH1_URI = "http://www.w3.org/TR/1999/REC-xpath-19991116";
075    
076        public static final QName QNAME_TOPIC_EXPRESSION = new QName(WSN_URI, "TopicExpression");
077    
078        public static final QName QNAME_PRODUCER_PROPERTIES = new QName(WSN_URI, "ProducerProperties");
079    
080        public static final QName QNAME_MESSAGE_CONTENT = new QName(WSN_URI, "MessageContent");
081    
082        public static final QName QNAME_USE_RAW = new QName(WSN_URI, "UseRaw");
083    
084        protected DatatypeFactory datatypeFactory;
085    
086        protected XMLGregorianCalendar terminationTime;
087    
088        protected boolean useRaw;
089    
090        protected TopicExpressionType topic;
091    
092        protected QueryExpressionType contentFilter;
093    
094        protected EndpointReferenceType consumerReference;
095    
096        protected AbstractNotificationBroker broker;
097    
098        public AbstractSubscription(String name) {
099            super(name);
100            try {
101                this.datatypeFactory = DatatypeFactory.newInstance();
102            } catch (DatatypeConfigurationException e) {
103                throw new RuntimeException("Unable to initialize subscription", e);
104            }
105        }
106    
107        /**
108         * 
109         * @param renewRequest
110         * @return returns org.oasis_open.docs.wsn.b_1.RenewResponse
111         * @throws UnacceptableTerminationTimeFault
112         * @throws ResourceUnknownFault
113         */
114        @WebMethod(operationName = "Renew")
115        @WebResult(name = "RenewResponse", 
116                   targetNamespace = "http://docs.oasis-open.org/wsn/b-2", 
117                   partName = "RenewResponse")
118        public RenewResponse renew(
119                @WebParam(name = "Renew", 
120                          targetNamespace = "http://docs.oasis-open.org/wsn/b-2", 
121                          partName = "RenewRequest")
122                Renew renewRequest) throws ResourceUnknownFault, UnacceptableTerminationTimeFault {
123    
124            XMLGregorianCalendar time = validateTerminationTime(renewRequest.getTerminationTime());
125            renew(time);
126            RenewResponse response = new RenewResponse();
127            response.setTerminationTime(time);
128            response.setCurrentTime(getCurrentTime());
129            return response;
130        }
131    
132        /**
133         * 
134         * @param unsubscribeRequest
135         * @return returns org.oasis_open.docs.wsn.b_1.UnsubscribeResponse
136         * @throws UnableToDestroySubscriptionFault
137         * @throws ResourceUnknownFault
138         */
139        @WebMethod(operationName = "Unsubscribe")
140        @WebResult(name = "UnsubscribeResponse", 
141                   targetNamespace = "http://docs.oasis-open.org/wsn/b-2", 
142                   partName = "UnsubscribeResponse")
143        public UnsubscribeResponse unsubscribe(
144                @WebParam(name = "Unsubscribe", 
145                          targetNamespace = "http://docs.oasis-open.org/wsn/b-2", 
146                          partName = "UnsubscribeRequest")
147                Unsubscribe unsubscribeRequest) throws ResourceUnknownFault, UnableToDestroySubscriptionFault {
148    
149            broker.unsubscribe(getAddress());
150            return new UnsubscribeResponse();
151        }
152    
153        /**
154         * 
155         * @param pauseSubscriptionRequest
156         * @return returns org.oasis_open.docs.wsn.b_1.PauseSubscriptionResponse
157         * @throws PauseFailedFault
158         * @throws ResourceUnknownFault
159         */
160        @WebMethod(operationName = "PauseSubscription")
161        @WebResult(name = "PauseSubscriptionResponse", 
162                   targetNamespace = "http://docs.oasis-open.org/wsn/b-2", 
163                   partName = "PauseSubscriptionResponse")
164        public PauseSubscriptionResponse pauseSubscription(
165                @WebParam(name = "PauseSubscription", 
166                          targetNamespace = "http://docs.oasis-open.org/wsn/b-2", 
167                          partName = "PauseSubscriptionRequest")
168                PauseSubscription pauseSubscriptionRequest) throws PauseFailedFault, ResourceUnknownFault {
169    
170            pause();
171            return new PauseSubscriptionResponse();
172        }
173    
174        /**
175         * 
176         * @param resumeSubscriptionRequest
177         * @return returns org.oasis_open.docs.wsn.b_1.ResumeSubscriptionResponse
178         * @throws ResumeFailedFault
179         * @throws ResourceUnknownFault
180         */
181        @WebMethod(operationName = "ResumeSubscription")
182        @WebResult(name = "ResumeSubscriptionResponse", 
183                   targetNamespace = "http://docs.oasis-open.org/wsn/b-2", 
184                   partName = "ResumeSubscriptionResponse")
185        public ResumeSubscriptionResponse resumeSubscription(
186                @WebParam(name = "ResumeSubscription", 
187                          targetNamespace = "http://docs.oasis-open.org/wsn/b-2", 
188                          partName = "ResumeSubscriptionRequest")
189                ResumeSubscription resumeSubscriptionRequest) throws ResourceUnknownFault, ResumeFailedFault {
190    
191            resume();
192            return new ResumeSubscriptionResponse();
193        }
194    
195        protected XMLGregorianCalendar validateInitialTerminationTime(String value) 
196            throws UnacceptableInitialTerminationTimeFault {
197            XMLGregorianCalendar tt = parseTerminationTime(value);
198            if (tt == null) {
199                UnacceptableInitialTerminationTimeFaultType fault = new UnacceptableInitialTerminationTimeFaultType();
200                throw new UnacceptableInitialTerminationTimeFault("Unable to parse initial termination time: '" + value
201                        + "'", fault);
202            }
203            XMLGregorianCalendar ct = getCurrentTime();
204            int c = tt.compare(ct);
205            if (c == DatatypeConstants.LESSER || c == DatatypeConstants.EQUAL) {
206                UnacceptableInitialTerminationTimeFaultType fault = new UnacceptableInitialTerminationTimeFaultType();
207                fault.setMinimumTime(ct);
208                throw new UnacceptableInitialTerminationTimeFault("Invalid initial termination time", fault);
209            }
210            return tt;
211        }
212    
213        protected XMLGregorianCalendar validateTerminationTime(String value) throws UnacceptableTerminationTimeFault {
214            XMLGregorianCalendar tt = parseTerminationTime(value);
215            if (tt == null) {
216                UnacceptableTerminationTimeFaultType fault = new UnacceptableTerminationTimeFaultType();
217                throw new UnacceptableTerminationTimeFault("Unable to parse termination time: '" + value + "'", fault);
218            }
219            XMLGregorianCalendar ct = getCurrentTime();
220            int c = tt.compare(ct);
221            if (c == DatatypeConstants.LESSER || c == DatatypeConstants.EQUAL) {
222                UnacceptableTerminationTimeFaultType fault = new UnacceptableTerminationTimeFaultType();
223                fault.setMinimumTime(ct);
224                throw new UnacceptableTerminationTimeFault("Invalid termination time", fault);
225            }
226            return tt;
227        }
228    
229        protected XMLGregorianCalendar parseTerminationTime(String value) {
230            try {
231                Duration d = datatypeFactory.newDuration(value);
232                XMLGregorianCalendar c = getCurrentTime();
233                c.add(d);
234                return c;
235            } catch (Exception e) {
236                // Ignore
237            }
238            try {
239                Duration d = datatypeFactory.newDurationDayTime(value);
240                XMLGregorianCalendar c = getCurrentTime();
241                c.add(d);
242                return c;
243            } catch (Exception e) {
244                // Ignore
245            }
246            try {
247                Duration d = datatypeFactory.newDurationYearMonth(value);
248                XMLGregorianCalendar c = getCurrentTime();
249                c.add(d);
250                return c;
251            } catch (Exception e) {
252                // Ignore
253            }
254            try {
255                return datatypeFactory.newXMLGregorianCalendar(value);
256            } catch (Exception e) {
257                // Ignore
258            }
259            return null;
260        }
261    
262        protected XMLGregorianCalendar getCurrentTime() {
263            return datatypeFactory.newXMLGregorianCalendar(new GregorianCalendar());
264        }
265    
266        public XMLGregorianCalendar getTerminationTime() {
267            return terminationTime;
268        }
269    
270        public void setTerminationTime(XMLGregorianCalendar terminationTime) {
271            this.terminationTime = terminationTime;
272        }
273    
274        public void create(Subscribe subscribeRequest) throws InvalidFilterFault, InvalidMessageContentExpressionFault,
275                InvalidProducerPropertiesExpressionFault, InvalidTopicExpressionFault, SubscribeCreationFailedFault,
276                TopicExpressionDialectUnknownFault, TopicNotSupportedFault, UnacceptableInitialTerminationTimeFault {
277            validateSubscription(subscribeRequest);
278            start();
279        }
280    
281        protected abstract void start() throws SubscribeCreationFailedFault;
282    
283        protected abstract void pause() throws PauseFailedFault;
284    
285        protected abstract void resume() throws ResumeFailedFault;
286    
287        protected abstract void renew(XMLGregorianCalendar time) throws UnacceptableTerminationTimeFault;
288    
289        protected void unsubscribe() throws UnableToDestroySubscriptionFault {
290            try {
291                unregister();
292            } catch (EndpointRegistrationException e) {
293                UnableToDestroySubscriptionFaultType fault = new UnableToDestroySubscriptionFaultType();
294                throw new UnableToDestroySubscriptionFault("Error unregistering endpoint", fault, e);
295            }
296        }
297    
298        protected String createAddress() {
299            return "http://servicemix.org/wsnotification/Subscription/" + getName();
300        }
301    
302        protected void validateSubscription(Subscribe subscribeRequest) throws InvalidFilterFault,
303                InvalidMessageContentExpressionFault, InvalidProducerPropertiesExpressionFault,
304                InvalidTopicExpressionFault, SubscribeCreationFailedFault, TopicExpressionDialectUnknownFault,
305                TopicNotSupportedFault, UnacceptableInitialTerminationTimeFault {
306            // Check consumer reference
307            consumerReference = subscribeRequest.getConsumerReference();
308            // Check terminationTime
309            if (subscribeRequest.getInitialTerminationTime() != null
310                    && !subscribeRequest.getInitialTerminationTime().isNil()
311                    && subscribeRequest.getInitialTerminationTime().getValue() != null) {
312                String strTerminationTime = subscribeRequest.getInitialTerminationTime().getValue();
313                terminationTime = validateInitialTerminationTime(strTerminationTime.trim());
314            }
315            // Check filter
316            if (subscribeRequest.getFilter() != null) {
317                for (Object f : subscribeRequest.getFilter().getAny()) {
318                    JAXBElement e = null;
319                    if (f instanceof JAXBElement) {
320                        e = (JAXBElement) f;
321                        f = e.getValue();
322                    }
323                    if (f instanceof TopicExpressionType) {
324                        if (!e.getName().equals(QNAME_TOPIC_EXPRESSION)) {
325                            InvalidTopicExpressionFaultType fault = new InvalidTopicExpressionFaultType();
326                            throw new InvalidTopicExpressionFault("Unrecognized TopicExpression: " + e, fault);
327                        }
328                        topic = (TopicExpressionType) f;
329                    } else if (f instanceof QueryExpressionType) {
330                        if (e != null && e.getName().equals(QNAME_PRODUCER_PROPERTIES)) {
331                            InvalidProducerPropertiesExpressionFaultType fault = 
332                                new InvalidProducerPropertiesExpressionFaultType();
333                            throw new InvalidProducerPropertiesExpressionFault("ProducerProperties are not supported",
334                                    fault);
335                        } else if (e != null && e.getName().equals(QNAME_MESSAGE_CONTENT)) {
336                            if (contentFilter != null) {
337                                InvalidMessageContentExpressionFaultType fault = 
338                                    new InvalidMessageContentExpressionFaultType();
339                                throw new InvalidMessageContentExpressionFault(
340                                        "Only one MessageContent filter can be specified", fault);
341                            }
342                            contentFilter = (QueryExpressionType) f;
343                            // Defaults to XPath 1.0
344                            if (contentFilter.getDialect() == null) {
345                                contentFilter.setDialect(XPATH1_URI);
346                            }
347                        } else {
348                            InvalidFilterFaultType fault = new InvalidFilterFaultType();
349                            throw new InvalidFilterFault("Unrecognized filter: " + (e != null ? e.getName() : f), fault);
350                        }
351                    } else {
352                        InvalidFilterFaultType fault = new InvalidFilterFaultType();
353                        throw new InvalidFilterFault("Unrecognized filter: " + (e != null ? e.getName() : f), fault);
354                    }
355                }
356            }
357            // Check policy
358            if (subscribeRequest.getSubscriptionPolicy() != null) {
359                for (Object p : subscribeRequest.getSubscriptionPolicy().getAny()) {
360                    JAXBElement e = null;
361                    if (p instanceof JAXBElement) {
362                        e = (JAXBElement) p;
363                        p = e.getValue();
364                    }
365                    if (p instanceof UseRaw) {
366                        useRaw = true;
367                    } else {
368                        InvalidFilterFaultType fault = new InvalidFilterFaultType();
369                        throw new InvalidFilterFault("Unrecognized policy: " + p, fault);
370                    }
371                }
372            }
373            // Check all parameters
374            if (consumerReference == null) {
375                SubscribeCreationFailedFaultType fault = new SubscribeCreationFailedFaultType();
376                throw new SubscribeCreationFailedFault("Invalid ConsumerReference: null", fault);
377            }
378            // TODO check we can resolve endpoint
379            if (topic == null) {
380                InvalidFilterFaultType fault = new InvalidFilterFaultType();
381                throw new InvalidFilterFault("Must specify a topic to subscribe on", fault);
382            }
383            if (contentFilter != null && !contentFilter.getDialect().equals(XPATH1_URI)) {
384                InvalidMessageContentExpressionFaultType fault = new InvalidMessageContentExpressionFaultType();
385                throw new InvalidMessageContentExpressionFault("Unsupported MessageContent dialect: '"
386                        + contentFilter.getDialect() + "'", fault);
387            }
388            if (terminationTime != null) {
389                UnacceptableInitialTerminationTimeFaultType fault = new UnacceptableInitialTerminationTimeFaultType();
390                throw new UnacceptableInitialTerminationTimeFault("InitialTerminationTime is not supported", fault);
391            }
392        }
393    
394        public AbstractNotificationBroker getBroker() {
395            return broker;
396        }
397    
398        public void setBroker(AbstractNotificationBroker broker) {
399            this.broker = broker;
400        }
401    }