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