Home » apache-cxf-2.1.1-src » org.apache » cxf » endpoint » dynamic » [javadoc | source]
    1   /**
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements. See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership. The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License. You may obtain a copy of the License at
    9    *
   10    * http://www.apache.org/licenses/LICENSE-2.0
   11    *
   12    * Unless required by applicable law or agreed to in writing,
   13    * software distributed under the License is distributed on an
   14    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   15    * KIND, either express or implied. See the License for the
   16    * specific language governing permissions and limitations
   17    * under the License.
   18    */
   19   package org.apache.cxf.endpoint.dynamic;
   20   
   21   import java.io.File;
   22   import java.io.FileInputStream;
   23   import java.io.IOException;
   24   import java.net.MalformedURLException;
   25   import java.net.URI;
   26   import java.net.URISyntaxException;
   27   import java.net.URL;
   28   import java.net.URLClassLoader;
   29   import java.util.Collection;
   30   import java.util.Collections;
   31   import java.util.Iterator;
   32   import java.util.List;
   33   import java.util.Map;
   34   import java.util.StringTokenizer;
   35   import java.util.jar.Attributes;
   36   import java.util.jar.JarFile;
   37   import java.util.logging.Level;
   38   import java.util.logging.Logger;
   39   
   40   import javax.xml.bind.JAXBContext;
   41   import javax.xml.bind.JAXBException;
   42   import javax.xml.namespace.QName;
   43   
   44   import org.w3c.dom.Element;
   45   
   46   import org.xml.sax.EntityResolver;
   47   import org.xml.sax.InputSource;
   48   import org.xml.sax.SAXException;
   49   import org.xml.sax.SAXParseException;
   50   
   51   import com.sun.codemodel.JCodeModel;
   52   import com.sun.codemodel.JDefinedClass;
   53   import com.sun.codemodel.JPackage;
   54   import com.sun.codemodel.writer.FileCodeWriter;
   55   import com.sun.tools.xjc.Options;
   56   import com.sun.tools.xjc.api.ErrorListener;
   57   import com.sun.tools.xjc.api.S2JJAXBModel;
   58   import com.sun.tools.xjc.api.SchemaCompiler;
   59   import com.sun.tools.xjc.api.XJC;
   60   
   61   import org.apache.cxf.Bus;
   62   import org.apache.cxf.bus.CXFBusFactory;
   63   import org.apache.cxf.common.i18n.Message;
   64   import org.apache.cxf.common.logging.LogUtils;
   65   import org.apache.cxf.common.util.StringUtils;
   66   import org.apache.cxf.endpoint.Client;
   67   import org.apache.cxf.endpoint.ClientImpl;
   68   import org.apache.cxf.endpoint.EndpointImplFactory;
   69   import org.apache.cxf.endpoint.SimpleEndpointImplFactory;
   70   import org.apache.cxf.helpers.FileUtils;
   71   import org.apache.cxf.jaxb.JAXBDataBinding;
   72   import org.apache.cxf.resource.URIResolver;
   73   import org.apache.cxf.service.Service;
   74   import org.apache.cxf.service.factory.ServiceConstructionException;
   75   import org.apache.cxf.service.model.SchemaInfo;
   76   import org.apache.cxf.service.model.ServiceInfo;
   77   /**
   78    * This class reads a WSDL and creates a dynamic client from it.
   79    * 
   80    * Use {@link #newInstance} to obtain an instance, and then
   81    * {@link #createClient(String)} (or other overloads) to create a client.
   82    * 
   83    * It uses the JAXB data binding. It does not set up complex interceptors for 
   84    * features such as attachments. 
   85    * See {@link org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory}
   86    * for an alternative that sets up JAX-WS endpoints.
   87    *
   88    * This class may be subclassed to allow for other endpoints or behaviors.
   89    */
   90   public class DynamicClientFactory {
   91   
   92       private static final Logger LOG = LogUtils.getL7dLogger(DynamicClientFactory.class);
   93   
   94       private Bus bus;
   95   
   96       private String tmpdir = System.getProperty("java.io.tmpdir");
   97   
   98       private boolean simpleBindingEnabled = true;
   99       
  100       private Map<String, Object> jaxbContextProperties;
  101       
  102       protected DynamicClientFactory(Bus bus) {
  103           this.bus = bus;
  104       }
  105       
  106       protected EndpointImplFactory getEndpointImplFactory() {
  107           return SimpleEndpointImplFactory.getSingleton();
  108       }
  109   
  110       public void setTemporaryDirectory(String dir) {
  111           tmpdir = dir;
  112       }
  113   
  114       /**
  115        * Create a new instance using a specific <tt>Bus</tt>.
  116        * 
  117        * @param b the <tt>Bus</tt> to use in subsequent operations with the
  118        *            instance
  119        * @return the new instance
  120        */
  121       public static DynamicClientFactory newInstance(Bus b) {
  122           return new DynamicClientFactory(b);
  123       }
  124   
  125       /**
  126        * Create a new instance using a default <tt>Bus</tt>.
  127        * 
  128        * @return the new instance
  129        * @see CXFBusFactory#getDefaultBus()
  130        */
  131       public static DynamicClientFactory newInstance() {
  132           Bus bus = CXFBusFactory.getThreadDefaultBus();
  133           return new DynamicClientFactory(bus);
  134       }
  135   
  136       /**
  137        * Create a new <code>Client</code> instance using the WSDL to be loaded
  138        * from the specified URL and using the current classloading context.
  139        * 
  140        * @param wsdlURL the URL to load
  141        * @return
  142        */
  143       public Client createClient(String wsdlUrl) {
  144           return createClient(wsdlUrl, (QName)null, (QName)null);
  145       }
  146   
  147       /**
  148        * Create a new <code>Client</code> instance using the WSDL to be loaded
  149        * from the specified URL and with the specified <code>ClassLoader</code>
  150        * as parent.
  151        * 
  152        * @param wsdlUrl
  153        * @param classLoader
  154        * @return
  155        */
  156       public Client createClient(String wsdlUrl, ClassLoader classLoader) {
  157           return createClient(wsdlUrl, null, classLoader, null);
  158       }
  159   
  160       public Client createClient(String wsdlUrl, QName service) {
  161           return createClient(wsdlUrl, service, null);
  162       }
  163   
  164       public Client createClient(String wsdlUrl, QName service, QName port) {
  165           return createClient(wsdlUrl, service, null, port);
  166       }
  167   
  168       public Client createClient(String wsdlUrl, QName service, ClassLoader classLoader, QName port) {
  169           if (classLoader == null) {
  170               classLoader = Thread.currentThread().getContextClassLoader();
  171           }
  172           URL u = composeUrl(wsdlUrl);
  173           LOG.log(Level.FINE, "Creating client from URL " + u.toString());
  174           ClientImpl client = new ClientImpl(bus, u, service, port,
  175                                              getEndpointImplFactory());
  176   
  177           Service svc = client.getEndpoint().getService();
  178           //all SI's should have the same schemas
  179           Collection<SchemaInfo> schemas = svc.getServiceInfos().get(0).getSchemas();
  180   
  181           SchemaCompiler compiler = XJC.createSchemaCompiler();
  182           ErrorListener elForRun = new InnerErrorListener(wsdlUrl);
  183           compiler.setErrorListener(elForRun);
  184   
  185           addSchemas(wsdlUrl, schemas, compiler);
  186           
  187           S2JJAXBModel intermediateModel = compiler.bind();
  188           JCodeModel codeModel = intermediateModel.generateCode(null, elForRun);
  189           StringBuilder sb = new StringBuilder();
  190           boolean firstnt = false;
  191   
  192           for (Iterator<JPackage> packages = codeModel.packages(); packages.hasNext();) {
  193               JPackage jpackage = packages.next();
  194               if (!isValidPackage(jpackage)) {
  195                   continue;
  196               }
  197               if (firstnt) {
  198                   sb.append(':');
  199               } else {
  200                   firstnt = true;
  201               }
  202               sb.append(jpackage.name());
  203           }
  204           outputDebug(codeModel);
  205           
  206           String packageList = sb.toString();
  207   
  208           // our hashcode + timestamp ought to be enough.
  209           String stem = toString() + "-" + System.currentTimeMillis();
  210           File src = new File(tmpdir, stem + "-src");
  211           if (!src.mkdir()) {
  212               throw new IllegalStateException("Unable to create working directory " + src.getPath());
  213           }
  214           try {
  215               FileCodeWriter writer = new FileCodeWriter(src);
  216               codeModel.build(writer);
  217           } catch (IOException e) {
  218               throw new IllegalStateException("Unable to write generated Java files for schemas: "
  219                                               + e.getMessage(), e);
  220           }
  221           File classes = new File(tmpdir, stem + "-classes");
  222           if (!classes.mkdir()) {
  223               throw new IllegalStateException("Unable to create working directory " + src.getPath());
  224           }
  225           StringBuilder classPath = new StringBuilder();
  226           try {
  227               setupClasspath(classPath, classLoader);
  228           } catch (Exception ex) {
  229               throw new RuntimeException(ex);
  230           }
  231           
  232           List<File> srcFiles = FileUtils.getFilesRecurse(src, ".+\\.java$"); 
  233           if (!compileJavaSrc(classPath.toString(), srcFiles, classes.toString())) {
  234               LOG.log(Level.SEVERE , new Message("COULD_NOT_COMPILE_SRC", LOG, wsdlUrl).toString());
  235           }
  236           FileUtils.removeDir(src);
  237           URLClassLoader cl;
  238           try {
  239               cl = new URLClassLoader(new URL[] {classes.toURI().toURL()}, classLoader);
  240           } catch (MalformedURLException mue) {
  241               throw new IllegalStateException("Internal error; a directory returns a malformed URL: "
  242                                               + mue.getMessage(), mue);
  243           }
  244   
  245           JAXBContext context;
  246           Map<String, Object> contextProperties = jaxbContextProperties;
  247           
  248           if (contextProperties == null) {
  249               contextProperties = Collections.emptyMap();
  250           }
  251           
  252           try {
  253               if (StringUtils.isEmpty(packageList)) {
  254                   context = JAXBContext.newInstance(new Class[0], contextProperties);
  255               } else {
  256                   context = JAXBContext.newInstance(packageList, cl, contextProperties);
  257               }
  258           } catch (JAXBException jbe) {
  259               throw new IllegalStateException("Unable to create JAXBContext for generated packages: "
  260                                               + jbe.getMessage(), jbe);
  261           }
  262            
  263           JAXBDataBinding databinding = new JAXBDataBinding();
  264           databinding.setContext(context);
  265           svc.setDataBinding(databinding);
  266   
  267           ServiceInfo svcfo = client.getEndpoint().getEndpointInfo().getService();
  268   
  269           // Setup the new classloader!
  270           Thread.currentThread().setContextClassLoader(cl);
  271   
  272           TypeClassInitializer visitor = new TypeClassInitializer(svcfo, intermediateModel);
  273           visitor.walk();
  274           // delete the classes files
  275           FileUtils.removeDir(classes);
  276           return client;
  277       }
  278   
  279       private boolean isValidPackage(JPackage jpackage) {
  280           if (jpackage == null) {
  281               return false;
  282           }
  283           String name = jpackage.name();
  284           if ("org.w3._2001.xmlschema".equals(name)
  285               || "java.lang".equals(name)
  286               || "java.io".equals(name)
  287               || "generated".equals(name)) {
  288               return false;
  289           }
  290           Iterator<JDefinedClass> i = jpackage.classes();
  291           while (i.hasNext()) {
  292               JDefinedClass current = i.next();
  293               if ("ObjectFactory".equals(current.name())) { 
  294                   return true;
  295               }
  296           }
  297           return false;
  298       }
  299   
  300       private void outputDebug(JCodeModel codeModel) {
  301           if (!LOG.isLoggable(Level.INFO)) {
  302               return;
  303           }
  304           
  305           StringBuffer sb = new StringBuffer();
  306           boolean first = true;
  307           for (Iterator<JPackage> itr = codeModel.packages(); itr.hasNext();) {
  308               JPackage package1 = itr.next();
  309               
  310               for (Iterator<JDefinedClass> citr = package1.classes(); citr.hasNext();) {
  311                   if (!first) {
  312                       sb.append(", ");
  313                   } else {
  314                       first = false;
  315                   }
  316                   sb.append(citr.next().fullName());
  317               }
  318           }
  319           
  320           LOG.log(Level.INFO, "Created classes: " + sb.toString());
  321           
  322       }
  323   
  324       private void addSchemas(String wsdlUrl, Collection<SchemaInfo> schemas, SchemaCompiler compiler) {
  325           int num = 1;
  326           for (SchemaInfo schema : schemas) {
  327               Element el = schema.getElement();
  328               
  329               compiler.parseSchema(wsdlUrl + "#types" + num, el);
  330               num++;
  331           }
  332           
  333           if (simpleBindingEnabled && isJaxb21()) {
  334               String id = "/org/apache/cxf/endpoint/dynamic/simple-binding.xjb";
  335               LOG.info("Loading the JAXB 2.1 simple binding for client.");
  336               InputSource source = new InputSource(getClass().getResourceAsStream(id));
  337               source.setSystemId(id);
  338               compiler.parseSchema(source);
  339           }
  340       }
  341       
  342       private boolean isJaxb21() {
  343           String id = Options.getBuildID();
  344           StringTokenizer st = new StringTokenizer(id, ".");
  345           String minor = null;
  346           
  347           // major version
  348           if (st.hasMoreTokens()) {
  349               st.nextToken();
  350           }
  351           
  352           if (st.hasMoreTokens()) {
  353               minor = st.nextToken();
  354           }
  355           
  356           try {
  357               int i = Integer.valueOf(minor);
  358               if (i >= 1) {
  359                   return true;
  360               }
  361           } catch (NumberFormatException e) {
  362               // do nothing;
  363           }
  364           
  365           return false;
  366       }
  367   
  368       public boolean isSimpleBindingEnabled() {
  369           return simpleBindingEnabled;
  370       }
  371   
  372       public void setSimpleBindingEnabled(boolean simpleBindingEnabled) {
  373           this.simpleBindingEnabled = simpleBindingEnabled;
  374       }
  375   
  376       static boolean compileJavaSrc(String classPath, List<File> srcList, String dest) {
  377           String[] javacCommand = new String[srcList.size() + 7];
  378           
  379           javacCommand[0] = "javac";
  380           javacCommand[1] = "-classpath";
  381           javacCommand[2] = classPath;        
  382           javacCommand[3] = "-d";
  383           javacCommand[4] = dest;
  384           javacCommand[5] = "-target";
  385           javacCommand[6] = "1.5";
  386           
  387           int i = 7;
  388           for (File f : srcList) {
  389               javacCommand[i++] = f.getAbsolutePath();            
  390           }
  391           org.apache.cxf.common.util.Compiler javaCompiler 
  392               = new org.apache.cxf.common.util.Compiler();
  393           
  394           return javaCompiler.internalCompile(javacCommand, 7); 
  395       }
  396       
  397       static void addClasspathFromManifest(StringBuilder classPath, File file) 
  398           throws URISyntaxException, IOException {
  399           
  400           JarFile jar = new JarFile(file);
  401           Attributes attr = null;
  402           if (jar.getManifest() != null) {
  403               attr = jar.getManifest().getMainAttributes();
  404           }
  405           if (attr != null) {
  406               String cp = attr.getValue("Class-Path");
  407               while (cp != null) {
  408                   String fileName = cp;
  409                   int idx = fileName.indexOf(' ');
  410                   if (idx != -1) {
  411                       fileName = fileName.substring(0, idx);
  412                       cp =  cp.substring(idx + 1).trim();
  413                   } else {
  414                       cp = null;
  415                   }
  416                   URI uri = new URI(fileName);
  417                   File f2;
  418                   if (uri.isAbsolute()) {
  419                       f2 = new File(uri);
  420                   } else {
  421                       f2 = new File(file, fileName);
  422                   }
  423                   if (f2.exists()) {
  424                       classPath.append(f2.getAbsolutePath());
  425                       classPath.append(System.getProperty("path.separator"));
  426                   }
  427               }
  428           }         
  429       }
  430   
  431       static void setupClasspath(StringBuilder classPath, ClassLoader classLoader)
  432           throws URISyntaxException, IOException {
  433           
  434           ClassLoader scl = ClassLoader.getSystemClassLoader();        
  435           ClassLoader tcl = classLoader;
  436           do {
  437               if (tcl instanceof URLClassLoader) {
  438                   URL[] urls = ((URLClassLoader)tcl).getURLs();
  439                   if (urls == null) {
  440                       urls = new URL[0];
  441                   }
  442                   for (URL url : urls) {
  443                       if (url.getProtocol().startsWith("file")) {
  444                           File file; 
  445                           try { 
  446                               file = new File(url.toURI().getPath()); 
  447                           } catch (URISyntaxException urise) { 
  448                               file = new File(url.getPath()); 
  449                           } 
  450   
  451                           if (file.exists()) { 
  452                               classPath.append(file.getAbsolutePath()) 
  453                                   .append(System 
  454                                           .getProperty("path.separator")); 
  455   
  456                               if (file.getName().endsWith(".jar")) { 
  457                                   addClasspathFromManifest(classPath, file); 
  458                               }                         
  459                           }     
  460                       }
  461                   }
  462               }
  463               tcl = tcl.getParent();
  464               if (null == tcl) {
  465                   break;
  466               }
  467           } while(!tcl.equals(scl.getParent()));
  468       }
  469   
  470       private URL composeUrl(String s) {
  471           try {
  472               URIResolver resolver = new URIResolver(null, s, getClass());
  473   
  474               if (resolver.isResolved()) {
  475                   return resolver.getURI().toURL();
  476               } else {
  477                   throw new ServiceConstructionException(new Message("COULD_NOT_RESOLVE_URL", LOG, s));
  478               }
  479           } catch (IOException e) {
  480               throw new ServiceConstructionException(new Message("COULD_NOT_RESOLVE_URL", LOG, s), e);
  481           }
  482       }
  483   
  484       private class InnerErrorListener implements ErrorListener {
  485   
  486           private String url;
  487   
  488           InnerErrorListener(String url) {
  489               this.url = url;
  490           }
  491   
  492           public void error(SAXParseException arg0) {
  493               throw new RuntimeException("Error compiling schema from WSDL at {" + url + "}: "
  494                                          + arg0.getMessage(), arg0);
  495           }
  496   
  497           public void fatalError(SAXParseException arg0) {
  498               throw new RuntimeException("Fatal error compiling schema from WSDL at {" + url + "}: "
  499                                          + arg0.getMessage(), arg0);
  500           }
  501   
  502           public void info(SAXParseException arg0) {
  503               // ignore
  504           }
  505   
  506           public void warning(SAXParseException arg0) {
  507               // ignore
  508           }
  509       }
  510   
  511       // sorry, but yuck. Try a file first?!?
  512       static class RelativeEntityResolver implements EntityResolver {
  513           private String baseURI;
  514   
  515           public RelativeEntityResolver(String baseURI) {
  516               super();
  517               this.baseURI = baseURI;
  518           }
  519   
  520           public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
  521               // the system id is null if the entity is in the wsdl.
  522               if (systemId != null) {
  523                   File file = new File(baseURI, systemId);
  524                   if (file.exists()) {
  525                       return new InputSource(new FileInputStream(file));
  526                   } else {
  527                       return new InputSource(systemId);
  528                   }
  529               }
  530               return null;
  531           }
  532       }
  533   
  534       /**
  535        * Return the map of JAXB context properties used at the time that we create new contexts.
  536        * @return the map
  537        */
  538       public Map<String, Object> getJaxbContextProperties() {
  539           return jaxbContextProperties;
  540       }
  541   
  542       /**
  543        * Set the map of JAXB context properties used at the time that we create new contexts.
  544        * @param jaxbContextProperties
  545        */
  546       public void setJaxbContextProperties(Map<String, Object> jaxbContextProperties) {
  547           this.jaxbContextProperties = jaxbContextProperties;
  548       }
  549   }

Save This Page
Home » apache-cxf-2.1.1-src » org.apache » cxf » endpoint » dynamic » [javadoc | source]