View Javadoc

1   /*
2    * The Apache Software License, Version 1.1
3    *
4    *
5    * Copyright (c) 2001 The Apache Software Foundation.  All rights
6    * reserved.
7    *
8    * Redistribution and use in source and binary forms, with or without
9    * modification, are permitted provided that the following conditions
10   * are met:
11   *
12   * 1. Redistributions of source code must retain the above copyright
13   *    notice, this list of conditions and the following disclaimer.
14   *
15   * 2. Redistributions in binary form must reproduce the above copyright
16   *    notice, this list of conditions and the following disclaimer in
17   *    the documentation and/or other materials provided with the
18   *    distribution.
19   *
20   * 3. The end-user documentation included with the redistribution,
21   *    if any, must include the following acknowledgment:
22   *       "This product includes software developed by the
23   *        Apache Software Foundation (http://www.apache.org/)."
24   *    Alternately, this acknowledgment may appear in the software itself,
25   *    if and wherever such third-party acknowledgments normally appear.
26   *
27   * 4. The names "Axis" and "Apache Software Foundation" must
28   *    not be used to endorse or promote products derived from this
29   *    software without prior written permission. For written
30   *    permission, please contact apache@apache.org.
31   *
32   * 5. Products derived from this software may not be called "Apache",
33   *    nor may "Apache" appear in their name, without prior written
34   *    permission of the Apache Software Foundation.
35   *
36   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47   * SUCH DAMAGE.
48   * ====================================================================
49   *
50   * This software consists of voluntary contributions made by many
51   * individuals on behalf of the Apache Software Foundation.  For more
52   * information on the Apache Software Foundation, please see
53   * <http://www.apache.org/>.
54   */
55  
56  package org.codehaus.ivory.provider;
57  
58  import java.lang.reflect.InvocationHandler;
59  import java.lang.reflect.InvocationTargetException;
60  import java.lang.reflect.Method;
61  import java.lang.reflect.Proxy;
62  
63  import javax.xml.rpc.server.ServiceLifecycle;
64  
65  import org.apache.avalon.framework.service.ServiceManager;
66  import org.apache.axis.AxisFault;
67  import org.apache.axis.MessageContext;
68  import org.apache.axis.handlers.soap.SOAPService;
69  import org.apache.axis.providers.java.RPCProvider;
70  
71  /***
72   * Provider class which allows you to specify an Avalon <b>ROLE</b> for
73   * servicing Axis SOAP requests.
74   *
75   * <p>
76   *  The specified <b>ROLE</b> corresponds to a particular implementation
77   *  which is retrieved by a given Avalon <code>ComponentManager</code>.
78   *  For more information about Avalon, see the Avalon.
79   *  <a href="http://jakarta.apache.org/avalon">website</a>.
80   * </p>
81   *
82   * <p>
83   *  To use this class, you need to add your Avalon <code>ComponentManager</code>
84   *  instance to the <code>MessageContext</code> that is Axis uses to process
85   *  messages with.
86   * </p>
87   *
88   * <p>
89   *  To do this you could for example subclass the AxisServlet and override the
90   *  <code>createMessageContext()</code> method adding the ComponentManager, eg:
91   *
92   *  <pre>
93   *   protected MessageContext createMessageContext(...)
94   *   {
95   *      MessageContext context = super.createMessageContext();
96   *      context.setProperty(AvalonProvider.COMPONENT_MANAGER, m_manager);
97   *      return context;
98   *   }
99   *  </pre>
100  *
101  *  and appropriately add the AvalonProvider to the list of handlers in your
102  *  server-config.wsdd (suggestions on how to improve this are welcomed)
103  * </p>
104  *
105  * <p>
106  *  This provider will use that <code>ComponentManager</code> reference to
107  *  retrieve objects.
108  * </p>
109  *
110  * <p>
111  *  In your deployment descriptor use the following syntax:
112  *
113  * <pre>
114  *  &lt;service name="myservice" provider="java:Avalon"&gt;
115  *    &lt;parameter name="role" value="my.avalon.role.name"/&gt;
116  *    &lt;parameter name="className" value="my.avalon.roles.interface.name"/&gt;
117  *    &lt;parameter name="allowedMethods" value="allowed.methods"/&gt;
118  *  &lt;/service&gt;
119  * </pre>
120  *
121  * </p>
122  *
123  * @author <a href="mailto:crafterm@apache.org">Marcus Crafter</a>
124  * @revision CVS $Id: AvalonProvider.java,v 1.2 2004/08/02 14:14:08 dandiep Exp $
125  */
126 public class AvalonProvider
127     extends RPCProvider
128 {
129     /***
130      * Constant used to retrieve the ServiceManager reference
131      * from the MessageContext object.
132      */
133     public static final String SERVICE_MANAGER = "service-manager";
134 
135     /***
136      * Constant which represents the name of the ROLE this
137      * provider should <i>lookup</i> to service a request with. This is
138      * specified in the &lt;parameter name="" value=""/&gt; part of the
139      * deployment xml.
140      */
141     public static final String ROLE = "role";
142 
143     /***
144      * Returns the service object.
145      * 
146      * @param msgContext the message context
147      * @param role the Avalon ROLE to lookup to find the service object implementation
148      * @return an object that implements the service
149      * @exception Exception if an error occurs
150      */
151     protected Object makeNewServiceObject(
152         MessageContext msgContext,
153         String role)
154         throws Exception
155     {
156         ServiceManager manager =
157             (ServiceManager) msgContext
158                 .getAxisEngine()
159                 .getApplicationSession()
160                 .get( SERVICE_MANAGER );
161 
162         if (manager == null)
163             throw new AxisFault("Could not access Avalon ServiceManager");
164 
165         return decorate(manager.lookup(role), manager);
166     }
167 
168     /***
169      * Helper method for decorating a <code>Component</code> with a Handler
170      * proxy (see below).
171      *
172      * @param object a <code>Component</code> instance
173      * @param manager a <code>ComponentManager</code> instance
174      * @return the <code>Proxy</code> wrapped <code>Component</code> instance
175      * @exception Exception if an error occurs
176      */
177     private Object decorate(final Object object, final ServiceManager manager)
178         throws Exception
179     {
180         // obtain a list of all interfaces this object implements
181         Class[] interfaces = object.getClass().getInterfaces();
182 
183         // add ServiceLifecycle to it
184         Class[] adjusted = new Class[interfaces.length + 1];
185         System.arraycopy(interfaces, 0, adjusted, 0, interfaces.length);
186         adjusted[interfaces.length] = ServiceLifecycle.class;
187 
188         // create a proxy implementing those interfaces
189         Object proxy =
190             Proxy.newProxyInstance(
191                 this.getClass().getClassLoader(),
192                 adjusted,
193                 new Handler(object, manager));
194 
195         // return the proxy
196         return proxy;
197     }
198 
199     /***
200      * Get the service class description
201      * 
202      * @param role the Avalon ROLE name
203      * @param service a <code>SOAPService</code> instance
204      * @param msgContext the message context
205      * @return service class description
206      * @exception AxisFault if an error occurs
207      */
208     protected Class getServiceClass(
209         String role,
210         SOAPService service,
211         MessageContext msgContext)
212         throws AxisFault
213     {
214         // Assuming ExcaliburComponentManager semantics the ROLE name is
215         // actually the class name, potentially with a variant following
216         // the class name with a '/' separator
217 
218         ClassLoader cl = Thread.currentThread().getContextClassLoader();
219         String className = role;
220         
221         int i;
222         
223         if ((i = role.indexOf('/')) != -1)
224         {
225             className = role.substring(0, i);
226         }
227         
228         try
229         {
230             return cl.loadClass( className );
231         }
232         catch (ClassNotFoundException e)
233         {
234             throw new AxisFault( "Couldn't find class " + className, e );
235         }
236     }
237 
238     /***
239      * <code>InvocationHandler</code> class for managing Avalon
240      * <code>Components</code>.
241      *
242      * <p>
243      *  Components retrieved from an Avalon ComponentManager must be
244      *  returned to the manager when they are no longer required.
245      * </p>
246      *
247      * <p>
248      *  The returning of Components to their ComponentManager is handled
249      *  by a Proxy class which uses the following InvocationHandler.
250      * </p>
251      *
252      * <p>
253      *  Each Component returned by this Provider is wrapped inside a 
254      *  Proxy class which implements all of the Component's interfaces
255      *  including javax.xml.rpc.server.ServiceLifecycle.
256      * </p>
257      *
258      * <p>
259      *  When Axis is finished with the object returned by this provider,
260      *  it invokes ServiceLifecycle.destroy(). This is intercepted by the
261      *  InvocationHandler and the Component is returned at this time back
262      *  to the ComponentManager it was retrieved from.
263      * </p>
264      *
265      * <p>
266      *  <b>Note</b>, when Axis invokes ServiceLifecycle.destroy() is dependant
267      *  on the scope of the service (ie. Request, Session & Application).
268      * </p>
269      */
270     final class Handler implements InvocationHandler
271     {
272         // Constants describing the ServiceLifecycle.destroy method
273         private final String SL_DESTROY = "destroy";
274         private final Class SL_CLASS = ServiceLifecycle.class;
275 
276         // Component & ServiceManager references
277         private final Object m_object;
278         private final ServiceManager m_manager;
279 
280         /***
281          * Simple constructor, sets all internal references
282          *
283          * @param object a <code>Component</code> instance
284          * @param manager a <code>ComponentManager</code> instance
285          * @param log a <code>Logger</code> instance
286          */
287         public Handler(final Object object, final ServiceManager manager)
288         {
289             m_object = object;
290             m_manager = manager;
291         }
292 
293         /***
294          * <code>invoke</code> method, handles all method invocations for this
295          * particular proxy.
296          *
297          * <p>
298          *  Usually the invocation is passed through to the
299          *  actual component the proxy wraps, unless the method belongs to
300          *  the <code>ServiceLifecycle</code> interface where it is handled
301          *  locally.
302          * </p>
303          *
304          * @param proxy the <code>Proxy</code> instance the method was invoked on
305          * @param method the invoked method <code>Method</code> object
306          * @param args an <code>Object[]</code> array of arguments
307          * @return an <code>Object</code> value or null if none
308          * @exception Throwable if an error occurs
309          */
310         public Object invoke(Object proxy, Method method, Object[] args)
311             throws Throwable
312         {
313             try
314             {
315                 // if ServiceLifecycle.destroy() called, return to CM
316                 if (method.getDeclaringClass().equals(SL_CLASS))
317                 {
318                     if (method.getName().equals(SL_DESTROY))
319                     {
320                         m_manager.release(m_object);
321                     }
322 
323                     return null;
324                 }
325                 else // otherwise pass call to the real object
326                 {
327                     return method.invoke(m_object, args);
328                 }
329             }
330             catch ( InvocationTargetException e )
331             {
332                 throw e.getTargetException();
333             }
334         }
335     }
336 }