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 }