View Javadoc

1   /*
2   Copyright (C) 2000 - 2007 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  package com.gridsystems.innergrid.api;
18  
19  import java.io.File;
20  import java.io.InputStream;
21  import java.lang.reflect.Proxy;
22  import java.net.URL;
23  import java.rmi.Remote;
24  import java.util.Hashtable;
25  import java.util.Map;
26  import java.util.concurrent.ConcurrentHashMap;
27  import java.util.concurrent.ConcurrentMap;
28  
29  import javax.xml.namespace.QName;
30  import javax.xml.rpc.ServiceException;
31  import javax.xml.rpc.Stub;
32  import javax.xml.rpc.encoding.DeserializerFactory;
33  import javax.xml.rpc.encoding.SerializerFactory;
34  import javax.xml.rpc.encoding.TypeMapping;
35  
36  import com.gridsystems.utils.FileUtils;
37  
38  import org.apache.axis.MessageContext;
39  import org.apache.axis.WSDDEngineConfiguration;
40  import org.apache.axis.client.AxisClient;
41  import org.apache.axis.client.Call;
42  import org.apache.axis.client.Service;
43  import org.apache.axis.configuration.FileProvider;
44  import org.apache.axis.deployment.wsdd.WSDDDeployment;
45  import org.apache.axis.deployment.wsdd.WSDDDocument;
46  import org.apache.axis.encoding.TypeMappingRegistry;
47  import org.apache.axis.encoding.ser.BeanDeserializerFactory;
48  import org.apache.axis.encoding.ser.BeanSerializerFactory;
49  import org.apache.axis.handlers.soap.SOAPService;
50  import org.apache.axis.transport.http.HTTPConstants;
51  import org.apache.axis.utils.XMLUtils;
52  import org.apache.commons.discovery.ResourceNameIterator;
53  import org.apache.commons.discovery.resource.ClassLoaders;
54  import org.apache.commons.discovery.resource.names.DiscoverServiceNames;
55  import org.apache.commons.logging.Log;
56  import org.apache.commons.logging.LogFactory;
57  
58  /**
59   * ApiClientFactory type.
60   *
61   * @author Rodrigo Ruiz
62   */
63  public abstract class ClientApiFactory extends HTTPConstants {
64  
65    /**
66     * Client API registry entry.
67     */
68    public static class ApiEntry {
69      /** Factory for this API stubs. */
70      public ClientApiFactory factory;
71  
72      /** API interface. */
73      public Class< ? > iface;
74  
75      /** API WSDL. */
76      public URL wsdl;
77  
78      /** WSDD. */
79      public URL wsdd;
80  
81      /** API Service name in WSDL. */
82      public QName serviceName;
83  
84      /** API Port name in WSDL. */
85      public QName portName;
86    }
87  
88    /**
89     * A Service subclass that uses a singleton EngineConfiguration and
90     * AxisClient.
91     */
92    private static class FuraService extends Service {
93      /**
94       * <code>serialVersionUID</code> attribute.
95       */
96      private static final long serialVersionUID = 1L;
97  
98      /**
99       * Singleton engine configuration.
100      */
101     private static final WSDDEngineConfiguration CONFIG;
102 
103     /**
104      * Singleton client engine.
105      */
106     private static final AxisClient ENGINE;
107 
108     static {
109       // Fura client APIs use their own configuration
110       CONFIG = new FileProvider("fura-client-config.wsdd");
111       ENGINE = new AxisClient(CONFIG);
112     }
113 
114     /**
115      * Creates a new instance.
116      *
117      * @param wsdl          The URL to the Service WSDL
118      * @param serviceName   The Service name in the WSDL
119      * @throws ServiceException If an error occurs
120      */
121     public FuraService(URL wsdl, QName serviceName) throws ServiceException {
122       super(wsdl, serviceName);
123     }
124 
125     /**
126      * {@inheritDoc}
127      */
128     @Override protected AxisClient getAxisClient() {
129       return ENGINE;
130     }
131   }
132 
133   /**
134    * Class logger.
135    */
136   private static final Log LOG = LogFactory.getLog(ClientApiFactory.class);
137 
138   /**
139    * Initial capacity for the factory map.
140    */
141   private static final int INITIAL_CAPACITY = 15;
142 
143   /**
144    * Load factor for the factory map.
145    */
146   private static final float LOAD_FACTOR = 0.75f;
147 
148   /**
149    * Concurrency level for the factory map.
150    */
151   private static final int CONCURRENCY_LEVEL = 5;
152 
153   /**
154    * Keeps the time at which this class has been loaded, so WSDL files cached
155    * before this moment can be discarded.
156    */
157   private static final long WSDL_CACHE_STARTUP = System.currentTimeMillis();
158 
159   /**
160    * WSDL Parser map.
161    */
162   private static final ConcurrentMap<Class<?>, QName> API_NAMES;
163 
164   /**
165    * WSDL Parser map.
166    */
167   private static final ConcurrentMap<QName, ApiEntry> API_ENTRIES;
168 
169   static {
170     API_NAMES = new ConcurrentHashMap<Class<?>, QName>(
171       INITIAL_CAPACITY, LOAD_FACTOR, CONCURRENCY_LEVEL);
172 
173     API_ENTRIES = new ConcurrentHashMap<QName, ApiEntry>(
174       INITIAL_CAPACITY, LOAD_FACTOR, CONCURRENCY_LEVEL);
175 
176     // Discover factories
177 
178     final String spi = ClientApiFactory.class.getName();
179     LOG.info("Discovering API factories for '" + spi + "'");
180     final ClassLoaders loaders = new ClassLoaders();
181     loaders.put(Thread.currentThread().getContextClassLoader());
182     final DiscoverServiceNames discover = new DiscoverServiceNames(loaders);
183     ResourceNameIterator it = discover.findResourceNames(spi);
184     while (it.hasNext()) {
185       String factoryName = it.nextResourceName();
186       try {
187         Class<?> factoryClass = Class.forName(factoryName);
188         if (ClientApiFactory.class.isAssignableFrom(factoryClass)) {
189           // Creating a new instance will trigger the registry process
190           LOG.info("Registering " + factoryName);
191           ((ClientApiFactory)factoryClass.newInstance()).registerApis();
192         }
193       } catch (Exception e) {
194         LOG.warn("Could not register factory '" + factoryName + "'", e);
195       }
196     }
197   }
198 
199   /**
200    * Registers this instance into the FACTORIES map.
201    *
202    * @param api API entry descriptor
203    */
204   public static void register(ApiEntry api) {
205     API_NAMES.putIfAbsent(api.iface, api.serviceName);
206     API_ENTRIES.putIfAbsent(api.serviceName, api);
207 
208     if (api.wsdd != null && FuraService.CONFIG != null) {
209       LOG.debug("Registering WSDD '" + api.wsdd + "'");
210       InputStream is = null;
211       try {
212         is = api.wsdd.openStream();
213         WSDDDeployment deployment = FuraService.CONFIG.getDeployment();
214         WSDDDocument doc = new WSDDDocument(XMLUtils.newDocument(is));
215         doc.deploy(deployment);
216 
217         FuraService.ENGINE.refreshGlobalOptions();
218       } catch (Exception e) {
219         LOG.warn("Could not register API '" + api.iface.getName() + "'", e);
220       } finally {
221         if (is != null) {
222           try {
223             is.close();
224           } catch (Exception e) { }
225         }
226       }
227     }
228   }
229 
230   /**
231    * Creates a new instance.
232    */
233   protected ClientApiFactory() {
234     //registerApis();
235   }
236 
237   /**
238    * Hook for registering the APIs this factory is responsible for.
239    */
240   protected abstract void registerApis();
241 
242   /**
243    * Creates a new instance using a dynamic proxy.
244    *
245    * @param entry  The API descriptor
246    * @param con    Used to configure target endpoint and authentication
247    * @return       An implementation of "iface"
248    * @throws ServiceException If an error occurs
249    */
250   protected Remote newInstance(ApiEntry entry, Connection con) throws ServiceException {
251     ClassLoader loader = Thread.currentThread().getContextClassLoader();
252     Class< ? >[] ifaces = { entry.iface, javax.xml.rpc.Stub.class };
253 
254     Stub port = (Stub)getPort(entry, con);
255 
256     return (Remote)Proxy.newProxyInstance(loader, ifaces,
257       new ClientApiProxy(entry.iface, port, con));
258   }
259 
260   /**
261    * Creates a stub instance for the given API.
262    *
263    * @param entry The API descriptor
264    * @param con    Used to configure target endpoint and authentication
265    * @return The stub
266    * @throws ServiceException If an error occurs
267    */
268   @SuppressWarnings("unchecked")
269   public final Remote getPort(ApiEntry entry, Connection con) throws ServiceException {
270     fixWsdl(entry, con);
271 
272     Service service = new FuraService(entry.wsdl, entry.serviceName);
273     Remote remote = service.getPort(entry.iface);
274 
275     // Initialise the stub
276     Stub stub = (Stub)remote;
277     Map headers = (Map)stub._getProperty(REQUEST_HEADERS);
278     if (headers == null) {
279       headers = new Hashtable();
280       stub._setProperty(REQUEST_HEADERS, headers);
281     }
282 
283     return (Remote)stub;
284   }
285 
286   /**
287    * Downloads a service WSDL descriptor from a remote server into the user temp
288    * folder, and updates the specified entry.
289    *
290    * @param entry The API descriptor
291    * @param con    Used to configure target endpoint and authentication
292    */
293   private void fixWsdl(ApiEntry entry, Connection con) {
294     if (entry.wsdl == null && con != null && entry.serviceName != null) {
295       String tempDir = System.getProperty("java.io.tmpdir");
296       File temp = new File(tempDir, "ClientApiFactory");
297       temp.mkdirs();
298 
299       String ns = entry.serviceName.getNamespaceURI();
300       String name = entry.serviceName.getLocalPart();
301       ns = ns.replaceAll("[:/]+", "_");
302       String wsdlName = ns + "_" + name + ".wsdl";
303       File f = new File(temp, wsdlName);
304 
305       if (f.isFile() && WSDL_CACHE_STARTUP < f.lastModified()) {
306         try {
307           entry.wsdl = f.toURI().toURL();
308         } catch (Exception e) {
309           throw new RuntimeException("Could not build URL to service WSDL", e);
310         }
311       } else {
312         try {
313           StringBuffer sbUrl = new StringBuffer();
314           sbUrl.append((con.isSecured()) ? "https://" : "http://");
315           sbUrl.append(con.getHost());
316           sbUrl.append(':');
317           sbUrl.append(con.getPort());
318           sbUrl.append("/kernel/api/");
319           sbUrl.append(entry.portName.getLocalPart());
320           sbUrl.append("?wsdl");
321           URL url = new URL(sbUrl.toString());
322           FileUtils.createFile(url.openStream(), f);
323 
324           entry.wsdl = f.toURI().toURL();
325         } catch (Exception e) {
326           f.delete();
327           e.printStackTrace();
328           throw new RuntimeException(entry.serviceName + " WSDL download failed", e);
329         }
330       }
331     }
332   }
333 
334   /**
335    * Creates a new API stub instance.
336    *
337    * @param <T>      The stub type
338    * @param iface    The stub interface class
339    * @param con      The connection
340    * @return A stub
341    * @throws Exception If an error occurs
342    */
343   public static <T extends Remote> T newApi(Class<T> iface, Connection con)
344     throws Exception {
345     if (iface == null) {
346       return null;
347     } else {
348       QName name = API_NAMES.get(iface);
349       if (name == null) {
350         throw new CKernelException("CLT015", iface.getName());
351       } else {
352         return newApi(iface, API_NAMES.get(iface), con);
353       }
354     }
355   }
356 
357   /**
358    * Creates a new API stub instance.
359    *
360    * @param <T>         Stub type
361    * @param iface       Stub interface class
362    * @param serviceName Service qualified name
363    * @param con         Connection
364    * @return A stub instance
365    * @throws Exception If an error occurs
366    */
367   public static <T extends Remote> T newApi(Class<T> iface, QName serviceName,
368     Connection con) throws Exception {
369     ApiEntry entry = API_ENTRIES.get(serviceName);
370 
371     if (entry == null) {
372       throw new CKernelException("CLT015", iface.getName());
373     } else {
374       return (T)entry.factory.newInstance(entry, con);
375     }
376   }
377 
378 
379   /**
380    * Gets the back-end engine configuration.
381    *
382    * @return The engine configuration
383    */
384   public static WSDDEngineConfiguration getConfig() {
385     return FuraService.CONFIG;
386   }
387 
388   /**
389    * Gets the back-end engine.
390    *
391    * @return The engine
392    */
393   public static AxisClient getEngine() {
394     return FuraService.ENGINE;
395   }
396 
397   /**
398    * Moves a type mapping defined in the "" encoding to the default encoding.
399    *
400    * @param svcName  The service name
401    * @param javaType The java class
402    * @param xmlType  The QName of the XML type
403    */
404   public static void fixTypeMapping(String svcName, Class<?> javaType, QName xmlType) {
405     try {
406       SOAPService svc = ClientApiFactory.getEngine().getService(svcName);
407       TypeMappingRegistry tmr = svc.getTypeMappingRegistry();
408 
409       SerializerFactory sf = null;
410       DeserializerFactory df = null;
411 
412       String[] styleURIs = tmr.getRegisteredEncodingStyleURIs();
413       for (String uri : styleURIs) {
414         TypeMapping tm = tmr.getTypeMapping(uri);
415         sf = tm.getSerializer(javaType, xmlType);
416         df = tm.getDeserializer(javaType, xmlType);
417         if (sf != null && df != null) {
418           break;
419         }
420       }
421 
422       if (sf == null || df == null) {
423         // No serialiser found. We assume the type is a bean
424         sf = new BeanSerializerFactory(javaType, xmlType);
425         df = new BeanDeserializerFactory(javaType, xmlType);
426       }
427 
428       // Register the type in all type mappings
429       tmr.getDefaultTypeMapping().register(javaType, xmlType, sf, df);
430       for (String uri : styleURIs) {
431         TypeMapping tm = tmr.getTypeMapping(uri);
432         tm.register(javaType, xmlType, sf, df);
433       }
434     } catch (Exception e) {
435       throw new RuntimeException(e);
436     }
437   }
438 }