View Javadoc

1   /*
2    *  Copyright (c) 2008 Rodrigo Ruiz
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.apache.ws.addressing.handler;
17  
18  import java.net.MalformedURLException;
19  import java.net.URL;
20  import java.util.ArrayList;
21  import java.util.Iterator;
22  import java.util.List;
23  
24  import javax.xml.namespace.QName;
25  import javax.xml.rpc.Call;
26  import javax.xml.rpc.JAXRPCException;
27  import javax.xml.rpc.Service;
28  import javax.xml.rpc.ServiceException;
29  import javax.xml.rpc.ServiceFactory;
30  import javax.xml.rpc.handler.MessageContext;
31  import javax.xml.rpc.handler.soap.SOAPMessageContext;
32  import javax.xml.soap.Node;
33  import javax.xml.soap.SOAPBody;
34  import javax.xml.soap.SOAPConnection;
35  import javax.xml.soap.SOAPConnectionFactory;
36  import javax.xml.soap.SOAPElement;
37  import javax.xml.soap.SOAPException;
38  import javax.xml.soap.SOAPMessage;
39  import javax.xml.transform.TransformerFactory;
40  
41  import org.apache.axis.message.addressing.Action;
42  import org.apache.axis.message.addressing.AddressingHeaders;
43  import org.apache.axis.message.addressing.AddressingVersion;
44  import org.apache.axis.message.addressing.AttributedURI;
45  import org.apache.axis.message.addressing.Constants;
46  import org.apache.axis.message.addressing.EndpointReference;
47  import org.apache.axis.message.addressing.MessageID;
48  import org.apache.axis.message.addressing.To;
49  import org.apache.axis.message.addressing.util.AddressingUtils;
50  import org.apache.axis.types.URI;
51  import org.apache.axis.types.URI.MalformedURIException;
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  
55  /**
56   * A server-side JAX-RPC {@link javax.xml.rpc.handler.Handler} that extracts
57   * WS-Addressing headers from incoming SOAP requests and inserts them into
58   * outgoing SOAP responses.
59   *
60   * @author Davanum Srinivas
61   * @author Ian P. Springer
62   * @version $Revision: 14 $
63   */
64  public class ServerSideAddressingHandler extends AbstractAddressingHandler {
65  
66    /**
67     * Class logger.
68     */
69    private static final Log LOG = LogFactory.getLog(ServerSideAddressingHandler.class);
70  
71    /**
72     * Thread local that gives each thread its own TransformerFactory (since it is
73     * not thread-safe).
74     */
75    public static final FactoryThreadLocal TRANSFORMER_FACTORY = new FactoryThreadLocal();
76  
77    /**
78     * {@inheritDoc}
79     */
80    @Override
81    public boolean handleRequest(MessageContext msgContext) {
82      SOAPMessageContext soapMsgContext = (SOAPMessageContext) msgContext;
83      try {
84        SOAPMessage msg = soapMsgContext.getMessage();
85        if (msg == null) {
86          return CONTINUE_HANDLER_CHAIN_PROCESSING;
87        }
88        AddressingHeaders headers = new AddressingHeaders(msg.getSOAPPart().getEnvelope(),
89          getActor(), true, isRemoveHeadersEnabled(), false, getReferencePropertyQNames());
90  
91        if (headers.getTo() == null) {
92          // not a WS-A request; let it pass thru w/out doing anything
93          return CONTINUE_HANDLER_CHAIN_PROCESSING;
94        }
95        if (headers.getAction() == null) {
96          // should we throw a SOAPFaultException here?
97          LOG.debug("WSA request to endpoint " + headers.getTo()
98            + " is missing the required wsa:Action header.");
99        }
100       msgContext.setProperty(Constants.ENV_ADDRESSING_REQUEST_HEADERS, headers);
101 
102       // set the target service based on To header if it hasn't already been
103       // determined. NOTE: May want to add an option to override later.
104       setTargetService(soapMsgContext, headers);
105     } catch (Exception e) {
106       if (LOG.isDebugEnabled()) {
107         e.printStackTrace();
108       }
109       throw new JAXRPCException("Unexpected error in handleRequest()", e);
110     }
111     return CONTINUE_HANDLER_CHAIN_PROCESSING;
112   }
113 
114   /**
115    * {@inheritDoc}
116    */
117   @Override
118   public boolean handleResponse(MessageContext ctx) {
119     SOAPMessageContext soapMsgContext = (SOAPMessageContext) ctx;
120     try {
121       SOAPMessage msg = soapMsgContext.getMessage();
122       if (msg == null) {
123         return CONTINUE_HANDLER_CHAIN_PROCESSING;
124       }
125 
126       AddressingVersion version = AddressingUtils.getAddressingVersion(ctx);
127 
128       AddressingHeaders reqHeaders = (AddressingHeaders) ctx
129         .getProperty(Constants.ENV_ADDRESSING_REQUEST_HEADERS);
130 
131       if (reqHeaders == null) {
132         // not a WS-A response; let it pass thru w/out doing anything
133         return CONTINUE_HANDLER_CHAIN_PROCESSING;
134       }
135       AddressingHeaders resHeaders = AddressingUtils.getResponseHeaders(ctx);
136       resHeaders.setSetMustUnderstand(isMustUnderstandEnabled(ctx));
137       processFromHeader(reqHeaders, resHeaders);
138       processActionHeader(reqHeaders, resHeaders);
139       processToHeader(version, reqHeaders, resHeaders);
140       processRelatesToHeader(version, reqHeaders, resHeaders);
141       processMessageIdHeader(resHeaders);
142       resHeaders.toEnvelope(msg.getSOAPPart().getEnvelope(), getActor());
143       processReplyToHeader(reqHeaders, soapMsgContext);
144     } catch (Exception e) {
145       if (LOG.isDebugEnabled()) {
146         e.printStackTrace();
147       }
148       throw new JAXRPCException("Unexpected error in handleResponse()", e);
149     }
150     return CONTINUE_HANDLER_CHAIN_PROCESSING;
151   }
152 
153   /**
154    * {@inheritDoc}
155    */
156   @Override
157   public boolean handleFault(MessageContext ctx) {
158     SOAPMessageContext soapMsgContext = (SOAPMessageContext) ctx;
159     AddressingVersion version = AddressingUtils.getAddressingVersion(ctx);
160 
161     try {
162       AddressingHeaders reqHeaders = (AddressingHeaders) ctx
163         .getProperty(Constants.ENV_ADDRESSING_REQUEST_HEADERS);
164       if (reqHeaders == null) {
165         // not a WSA fault; let it pass thru w/out doing anything
166         return CONTINUE_HANDLER_CHAIN_PROCESSING;
167       }
168       AddressingHeaders resHeaders = AddressingUtils.getResponseHeaders(ctx);
169       processFromHeader(reqHeaders, resHeaders);
170       resHeaders.setAction(new Action(version.getFaultActionURI()));
171       addRelatesToHeader(version, reqHeaders, resHeaders);
172       addMessageIdHeader(resHeaders);
173       addAddressingHeadersToSOAPEnvelope(soapMsgContext, resHeaders);
174       processFaultToHeader(reqHeaders, soapMsgContext);
175     } catch (Exception e) {
176       if (LOG.isDebugEnabled()) {
177         e.printStackTrace();
178       }
179       throw new JAXRPCException("Unexpected error in handleFault()", e);
180     }
181     return CONTINUE_HANDLER_CHAIN_PROCESSING;
182   }
183 
184   /**
185    * Forward the SOAP message contained in the specified SOAP message context on
186    * to the specified end-point reference.
187    *
188    * @param ctx Message context to get the message from
189    * @param ref End-point reference to forward to
190    */
191   protected void forwardMessage(SOAPMessageContext ctx, EndpointReference ref) {
192     try {
193       SOAPConnection soapConn = SOAPConnectionFactory.newInstance().createConnection();
194       soapConn.call(ctx.getMessage(), new URL(ref.getAddress().toString()));
195       soapConn.close();
196       SOAPBody responseBody = ctx.getMessage().getSOAPPart().getEnvelope().getBody();
197       removeAllChildElements(responseBody);
198     } catch (SOAPException e) {
199       throw new JAXRPCException("Failed to forward SOAP message.", e);
200     } catch (MalformedURLException e) {
201       throw new JAXRPCException("Failed to forward SOAP message.", e);
202     }
203   }
204 
205   /**
206    * Override this method if you need something other than the default Service.
207    * <p>
208    * The service returned by this method is used in creating the new Call
209    * object. Something like: <br>
210    *
211    * <pre>
212    * Service service = getService(msgContext);
213    * Call call = service.createCall()
214    * call.setTargetEndpointAddress(toEndPointReference.getAddress().toString());
215    * </pre>
216    *
217    * @param ctx Message context
218    * @return Service instance
219    */
220   protected Service getService(MessageContext ctx) throws ServiceException {
221     return ServiceFactory.newInstance().createService(new QName(""));
222   }
223 
224   /**
225    * Override this method to prepare the new call, for instance to add
226    * properties from the old MessageContext that may be needed by other
227    * handlers.
228    *
229    * @param call Call object about to be invoked
230    * @param oldContext MessageContext of the original request/response.
231    */
232   protected void configureCall(Call call, SOAPMessageContext oldContext) {
233     // intentionally empty
234   }
235 
236   /**
237    * Can be overridden by subclasses to customize how the wsa:to header is
238    * interpreted.
239    *
240    * @param headers Headers to extract the information from
241    * @return The target service name
242    * @throws Exception If an error occurs
243    */
244   protected String getTargetServiceName(AddressingHeaders headers) throws Exception {
245     To toURI = headers.getTo();
246     if (toURI == null) {
247       return null;
248     }
249     String to = toURI.getPath();
250     if (to == null) {
251       return null;
252     }
253     // set the target service
254     return (to.substring(to.lastIndexOf('/') + 1));
255   }
256 
257   /**
258    * Platform-specific subclasses can optionally implement this method.
259    *
260    * @param ctx Context to get information from
261    * @param headers Headers to use
262    * @throws Exception If an error occurs
263    */
264   protected void setTargetService(SOAPMessageContext ctx, AddressingHeaders headers)
265     throws Exception {
266     // intentionally empty
267   }
268 
269   /**
270    * Processes the request From header and sets the appropriate values at the
271    * corresponding response headers.
272    *
273    * @param reqHeaders Request header container
274    * @param resHeaders Response header container
275    */
276   private void processFromHeader(AddressingHeaders reqHeaders,
277     AddressingHeaders resHeaders) {
278     EndpointReference fromEPR = resHeaders.getFrom();
279     if (fromEPR == null) {
280       To toURI = reqHeaders.getTo();
281       if (toURI != null) {
282         fromEPR = new EndpointReference(toURI);
283         fromEPR.setProperties(reqHeaders.getReferenceProperties());
284         resHeaders.setFrom(fromEPR);
285       }
286     }
287   }
288 
289   /**
290    * Processes the request Action header and sets the appropriate values at the
291    * corresponding response headers.
292    *
293    * @param reqHeaders Request header container
294    * @param resHeaders Response header container
295    */
296   private void processActionHeader(AddressingHeaders reqHeaders,
297     AddressingHeaders resHeaders) throws URI.MalformedURIException {
298     Action action = resHeaders.getAction();
299     if (action == null) {
300       // not set - try request headers
301       action = reqHeaders.getAction();
302       if (action != null) {
303         resHeaders
304           .setAction(new Action(new URI(action.toString() + "Response")));
305       }
306     }
307   }
308 
309   /**
310    * Processes the request To header and sets the appropriate values at the
311    * corresponding response headers.
312    *
313    * @param version    WS-Addressing version to use
314    * @param reqHeaders Request header container
315    * @param resHeaders Response header container
316    */
317   private void processToHeader(AddressingVersion version, AddressingHeaders reqHeaders,
318     AddressingHeaders resHeaders) throws MalformedURIException {
319     if (resHeaders.getFrom() == null && reqHeaders.getFrom() != null) {
320       resHeaders.setTo(reqHeaders.getFrom().getAddress());
321     } else {
322       resHeaders.setTo(new To(version.getAnonymousRoleURI()));
323     }
324   }
325 
326   /**
327    * Processes the request RelatesTo header and sets the appropriate values at
328    * the corresponding response headers.
329    *
330    * @param version    WS-Addressing version to use
331    * @param reqHeaders Request header container
332    * @param resHeaders Response header container
333    */
334   private void processRelatesToHeader(AddressingVersion version,
335     AddressingHeaders reqHeaders, AddressingHeaders resHeaders)
336     throws MalformedURIException {
337     MessageID msgID = reqHeaders.getMessageID();
338     if (msgID != null) {
339       resHeaders.addRelatesTo(msgID.toString(), version.getResponseRelationshipType());
340     }
341   }
342 
343   /**
344    * Sets the appropriate values at the MessageID response header.
345    *
346    * @param resHeaders Response header container
347    */
348   private void processMessageIdHeader(AddressingHeaders resHeaders)
349     throws URI.MalformedURIException {
350     resHeaders.setMessageID(createMessageID());
351   }
352 
353   /**
354    * Processes the request ReplyTo header and sets the appropriate values at the
355    * message context.
356    *
357    * @param reqHeaders Request header container
358    * @param ctx        Message context information
359    */
360   private void processReplyToHeader(AddressingHeaders reqHeaders,
361     SOAPMessageContext ctx) throws Exception {
362     EndpointReference replyTo = reqHeaders.getReplyTo();
363     if (replyTo != null) {
364       AttributedURI address = replyTo.getAddress();
365       if (address != null) {
366         String uri = address.toString();
367         if (uri != null && !uri.equals(AddressingUtils.getAnonymousRoleURI(ctx))) {
368           forwardMessage(ctx, replyTo);
369         }
370       }
371     }
372   }
373 
374   /**
375    * Adds the response headers to the current response SOAP envelope.
376    *
377    * @param soapMsgContext Context information (for retrieving the SOAP
378    *        envelope)
379    * @param resHeaders Response header container
380    * @throws Exception If an error occurs
381    */
382   private void addAddressingHeadersToSOAPEnvelope(SOAPMessageContext soapMsgContext,
383     AddressingHeaders resHeaders) throws Exception {
384     SOAPMessage msg = soapMsgContext.getMessage();
385     if (msg == null) {
386       throw new JAXRPCException("Unable to get response message from message context.");
387     }
388     resHeaders.toEnvelope(msg.getSOAPPart().getEnvelope());
389   }
390 
391   /**
392    * Processes the request FaultTo header and sets the appropriate values at the
393    * message context.
394    *
395    * @param reqHeaders Request header container
396    * @param soapMsgContext Message context
397    */
398   private void processFaultToHeader(AddressingHeaders reqHeaders,
399     SOAPMessageContext soapMsgContext) throws Exception {
400     EndpointReference faultTo = reqHeaders.getFaultTo();
401     if (faultTo != null) {
402       AttributedURI address = faultTo.getAddress();
403       if (address != null && address.toString() != null) {
404         forwardMessage(soapMsgContext, faultTo);
405       }
406     }
407   }
408 
409   /**
410    * Processes the request RelatesTo header and sets the appropriate values at
411    * the corresponding response headers.
412    *
413    * @param version    WS-Addressing version to use
414    * @param reqHeaders Request header container
415    * @param resHeaders Response header container
416    */
417   private void addRelatesToHeader(AddressingVersion version,
418     AddressingHeaders reqHeaders, AddressingHeaders resHeaders)
419     throws MalformedURIException {
420     MessageID msgID = reqHeaders.getMessageID();
421     if (msgID != null) {
422       resHeaders.addRelatesTo(msgID.toString(), version.getResponseRelationshipType());
423     }
424   }
425 
426   /**
427    * Adds the MessageID header.
428    *
429    * @param resHeaders Response header container
430    * @throws URI.MalformedURIException If an error occurs
431    */
432   private void addMessageIdHeader(AddressingHeaders resHeaders)
433     throws URI.MalformedURIException {
434     MessageID msgID = new MessageID(new URI("uuid:" + generateUUId()));
435     resHeaders.setMessageID(msgID);
436   }
437 
438   /**
439    * Removes all child elements from the specified
440    * {@link javax.xml.soap.SOAPElement}.
441    *
442    * @param soapElem Element to strip
443    */
444   protected static void removeAllChildElements(SOAPElement soapElem) {
445     Iterator<?> iter = soapElem.getChildElements();
446     // NOTE: Convert iterator to list to avoid ConcurrentModificationExceptions
447     // caused by modifying items in an iterator during iteration
448     List<?> children = toList(iter);
449     for (int i = 0; i < children.size(); i++) {
450       Node child = (Node) children.get(i);
451       if (child.getParentElement() != null) {
452         child.detachNode();
453         child.recycleNode();
454       }
455     }
456   }
457 
458   /**
459    * Iterates over the specified iterator and puts all its elements into a list.
460    *
461    * @param <T> Iterator type
462    * @param iter Iterator
463    * @return List
464    */
465   private static <T> List<T> toList(Iterator<T> iter) {
466     List<T> list = new ArrayList<T>();
467     if (iter != null) {
468       while (iter.hasNext()) {
469         list.add(iter.next());
470       }
471     }
472     return list;
473   }
474 
475   /**
476    * ThreadLocalExtension type.
477    *
478    * @author Rodrigo Ruiz
479    */
480   private static final class FactoryThreadLocal extends ThreadLocal<TransformerFactory> {
481     /**
482      * {@inheritDoc}
483      */
484     @Override
485     protected synchronized TransformerFactory initialValue() {
486       return TransformerFactory.newInstance();
487     }
488   }
489 }