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.io.InputStream;
25 import java.lang.reflect.Method;
26 import java.net.MalformedURLException;
27 import java.net.URI;
28 import java.net.URISyntaxException;
29 import java.net.URL;
30 import java.net.URLClassLoader;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.StringTokenizer;
38 import java.util.jar.Attributes;
39 import java.util.jar.JarFile;
40 import java.util.logging.Level;
41 import java.util.logging.Logger;
42
43 import javax.xml.bind.JAXBContext;
44 import javax.xml.bind.JAXBException;
45 import javax.xml.namespace.QName;
46
47 import org.w3c.dom.Element;
48
49 import org.xml.sax.EntityResolver;
50 import org.xml.sax.InputSource;
51 import org.xml.sax.SAXException;
52 import org.xml.sax.SAXParseException;
53
54 import org.apache.cxf.Bus;
55 import org.apache.cxf.bus.CXFBusFactory;
56 import org.apache.cxf.common.i18n.Message;
57 import org.apache.cxf.common.logging.LogUtils;
58 import org.apache.cxf.common.util.ReflectionInvokationHandler;
59 import org.apache.cxf.common.util.StringUtils;
60 import org.apache.cxf.endpoint.Client;
61 import org.apache.cxf.endpoint.ClientImpl;
62 import org.apache.cxf.endpoint.EndpointImplFactory;
63 import org.apache.cxf.endpoint.SimpleEndpointImplFactory;
64 import org.apache.cxf.helpers.FileUtils;
65 import org.apache.cxf.jaxb.JAXBDataBinding;
66 import org.apache.cxf.jaxb.JAXBUtils;
67 import org.apache.cxf.jaxb.JAXBUtils.JCodeModel;
68 import org.apache.cxf.jaxb.JAXBUtils.JDefinedClass;
69 import org.apache.cxf.jaxb.JAXBUtils.JPackage;
70 import org.apache.cxf.jaxb.JAXBUtils.S2JJAXBModel;
71 import org.apache.cxf.jaxb.JAXBUtils.SchemaCompiler;
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 public Client createClient(String wsdlUrl, List<String> bindingFiles) {
147 return createClient(wsdlUrl, (QName)null, (QName)null, bindingFiles);
148 }
149
150
151 /**
152 * Create a new <code>Client</code> instance using the WSDL to be loaded
153 * from the specified URL and using the current classloading context.
154 *
155 * @param wsdlURL the URL to load
156 * @return
157 */
158 public Client createClient(URL wsdlUrl) {
159 return createClient(wsdlUrl, (QName)null, (QName)null);
160 }
161 public Client createClient(URL wsdlUrl, List<String> bindingFiles) {
162 return createClient(wsdlUrl, (QName)null, (QName)null, bindingFiles);
163 }
164
165 /**
166 * Create a new <code>Client</code> instance using the WSDL to be loaded
167 * from the specified URL and with the specified <code>ClassLoader</code>
168 * as parent.
169 *
170 * @param wsdlUrl
171 * @param classLoader
172 * @return
173 */
174 public Client createClient(String wsdlUrl, ClassLoader classLoader) {
175 return createClient(wsdlUrl, null, classLoader, null);
176 }
177 public Client createClient(String wsdlUrl, ClassLoader classLoader, List<String> bindingFiles) {
178 return createClient(wsdlUrl, null, classLoader, null, bindingFiles);
179 }
180
181 public Client createClient(String wsdlUrl, QName service) {
182 return createClient(wsdlUrl, service, (QName)null);
183 }
184 public Client createClient(String wsdlUrl, QName service, List<String> bindingFiles) {
185 return createClient(wsdlUrl, service, null, bindingFiles);
186 }
187
188 public Client createClient(String wsdlUrl, QName service, QName port) {
189 return createClient(wsdlUrl, service, null, port);
190 }
191 public Client createClient(String wsdlUrl, QName service, QName port, List<String> bindingFiles) {
192 return createClient(wsdlUrl, service, null, port, bindingFiles);
193 }
194
195 public Client createClient(String wsdlUrl, QName service, ClassLoader classLoader, QName port) {
196 return createClient(wsdlUrl, service, classLoader, port, null);
197 }
198
199
200 /**
201 * Create a new <code>Client</code> instance using the WSDL to be loaded
202 * from the specified URL and with the specified <code>ClassLoader</code>
203 * as parent.
204 *
205 * @param wsdlUrl
206 * @param classLoader
207 * @return
208 */
209 public Client createClient(URL wsdlUrl, ClassLoader classLoader) {
210 return createClient(wsdlUrl, null, classLoader, null);
211 }
212 public Client createClient(URL wsdlUrl, ClassLoader classLoader, List<String> bindingFiles) {
213 return createClient(wsdlUrl.toString(), null, classLoader, null, bindingFiles);
214 }
215
216 public Client createClient(URL wsdlUrl, QName service) {
217 return createClient(wsdlUrl, service, (QName)null);
218 }
219 public Client createClient(URL wsdlUrl, QName service, List<String> bindingFiles) {
220 return createClient(wsdlUrl, service, null, bindingFiles);
221 }
222
223 public Client createClient(URL wsdlUrl, QName service, QName port) {
224 return createClient(wsdlUrl, service, null, port);
225 }
226 public Client createClient(URL wsdlUrl, QName service, QName port, List<String> bindingFiles) {
227 return createClient(wsdlUrl.toString(), service, null, port, bindingFiles);
228 }
229
230 public Client createClient(URL wsdlUrl, QName service, ClassLoader classLoader, QName port) {
231 return createClient(wsdlUrl.toString(), service, classLoader, port, null);
232 }
233
234 public Client createClient(URL wsdlUrl,
235 QName service,
236 ClassLoader classLoader,
237 QName port,
238 List<String> bindingFiles) {
239 return createClient(wsdlUrl.toString(), service, classLoader, port, bindingFiles);
240 }
241
242
243
244 public Client createClient(String wsdlUrl, QName service,
245 ClassLoader classLoader, QName port,
246 List<String> bindingFiles) {
247
248 if (classLoader == null) {
249 classLoader = Thread.currentThread().getContextClassLoader();
250 }
251 URL u = composeUrl(wsdlUrl);
252 LOG.log(Level.FINE, "Creating client from URL " + u.toString());
253 ClientImpl client = new ClientImpl(bus, u, service, port,
254 getEndpointImplFactory());
255
256 Service svc = client.getEndpoint().getService();
257 //all SI's should have the same schemas
258 Collection<SchemaInfo> schemas = svc.getServiceInfos().get(0).getSchemas();
259
260 SchemaCompiler compiler =
261 JAXBUtils.createSchemaCompilerWithDefaultAllocator(new HashSet<String>());
262
263 Object elForRun = ReflectionInvokationHandler
264 .createProxyWrapper(new InnerErrorListener(wsdlUrl),
265 JAXBUtils.getParamClass(compiler, "setErrorListener"));
266
267 compiler.setErrorListener(elForRun);
268
269 addSchemas(wsdlUrl, schemas, compiler);
270 addBindingFiles(bindingFiles, compiler);
271 S2JJAXBModel intermediateModel = compiler.bind();
272 JCodeModel codeModel = intermediateModel.generateCode(null, elForRun);
273 StringBuilder sb = new StringBuilder();
274 boolean firstnt = false;
275
276 for (Iterator<JPackage> packages = codeModel.packages(); packages.hasNext();) {
277 JPackage jpackage = packages.next();
278 if (!isValidPackage(jpackage)) {
279 continue;
280 }
281 if (firstnt) {
282 sb.append(':');
283 } else {
284 firstnt = true;
285 }
286 sb.append(jpackage.name());
287 }
288 JAXBUtils.logGeneratedClassNames(LOG, codeModel);
289
290 String packageList = sb.toString();
291
292 // our hashcode + timestamp ought to be enough.
293 String stem = toString() + "-" + System.currentTimeMillis();
294 File src = new File(tmpdir, stem + "-src");
295 if (!src.mkdir()) {
296 throw new IllegalStateException("Unable to create working directory " + src.getPath());
297 }
298 try {
299 Object writer = JAXBUtils.createFileCodeWriter(src);
300 codeModel.build(writer);
301 } catch (Exception e) {
302 throw new IllegalStateException("Unable to write generated Java files for schemas: "
303 + e.getMessage(), e);
304 }
305 File classes = new File(tmpdir, stem + "-classes");
306 if (!classes.mkdir()) {
307 throw new IllegalStateException("Unable to create working directory " + classes.getPath());
308 }
309 StringBuilder classPath = new StringBuilder();
310 try {
311 setupClasspath(classPath, classLoader);
312 } catch (Exception ex) {
313 throw new RuntimeException(ex);
314 }
315
316 List<File> srcFiles = FileUtils.getFilesRecurse(src, ".+\\.java$");
317 if (!compileJavaSrc(classPath.toString(), srcFiles, classes.toString())) {
318 LOG.log(Level.SEVERE , new Message("COULD_NOT_COMPILE_SRC", LOG, wsdlUrl).toString());
319 }
320 FileUtils.removeDir(src);
321 URLClassLoader cl;
322 try {
323 cl = new URLClassLoader(new URL[] {classes.toURI().toURL()}, classLoader);
324 } catch (MalformedURLException mue) {
325 throw new IllegalStateException("Internal error; a directory returns a malformed URL: "
326 + mue.getMessage(), mue);
327 }
328
329 JAXBContext context;
330 Map<String, Object> contextProperties = jaxbContextProperties;
331
332 if (contextProperties == null) {
333 contextProperties = Collections.emptyMap();
334 }
335
336 try {
337 if (StringUtils.isEmpty(packageList)) {
338 context = JAXBContext.newInstance(new Class[0], contextProperties);
339 } else {
340 context = JAXBContext.newInstance(packageList, cl, contextProperties);
341 }
342 } catch (JAXBException jbe) {
343 throw new IllegalStateException("Unable to create JAXBContext for generated packages: "
344 + jbe.getMessage(), jbe);
345 }
346
347 JAXBDataBinding databinding = new JAXBDataBinding();
348 databinding.setContext(context);
349 svc.setDataBinding(databinding);
350
351 ServiceInfo svcfo = client.getEndpoint().getEndpointInfo().getService();
352
353 // Setup the new classloader!
354 Thread.currentThread().setContextClassLoader(cl);
355
356 TypeClassInitializer visitor = new TypeClassInitializer(svcfo,
357 intermediateModel,
358 allowWrapperOps());
359 visitor.walk();
360 // delete the classes files
361 FileUtils.removeDir(classes);
362 return client;
363 }
364 protected boolean allowWrapperOps() {
365 return false;
366 }
367
368 private void addBindingFiles(List<String> bindingFiles, SchemaCompiler compiler) {
369 if (bindingFiles != null) {
370 for (String s : bindingFiles) {
371 URL url = composeUrl(s);
372 try {
373 InputStream ins = url.openStream();
374 InputSource is = new InputSource(ins);
375 is.setSystemId(url.toString());
376 is.setPublicId(url.toString());
377 compiler.getOptions().addBindFile(is);
378 } catch (IOException e) {
379 throw new RuntimeException(e);
380 }
381 }
382 }
383 }
384
385 private boolean isValidPackage(JPackage jpackage) {
386 if (jpackage == null) {
387 return false;
388 }
389 String name = jpackage.name();
390 if ("org.w3._2001.xmlschema".equals(name)
391 || "java.lang".equals(name)
392 || "java.io".equals(name)
393 || "generated".equals(name)) {
394 return false;
395 }
396 Iterator<JDefinedClass> i = jpackage.classes();
397 while (i.hasNext()) {
398 JDefinedClass current = i.next();
399 if ("ObjectFactory".equals(current.name())) {
400 return true;
401 }
402 }
403 return false;
404 }
405
406 private void addSchemas(String wsdlUrl, Collection<SchemaInfo> schemas, SchemaCompiler compiler) {
407 int num = 1;
408 for (SchemaInfo schema : schemas) {
409 Element el = schema.getElement();
410 String key = schema.getSystemId();
411 if (StringUtils.isEmpty(key)) {
412 key = wsdlUrl + "#types" + num;
413 }
414
415 //For JAXB 2.1.8
416 InputSource is = new InputSource((InputStream)null);
417 is.setSystemId(key);
418 is.setPublicId(key);
419 compiler.getOptions().addGrammar(is);
420
421 compiler.parseSchema(key, el);
422 num++;
423 }
424
425 if (simpleBindingEnabled && isJaxb21(compiler)) {
426 String id = "/org/apache/cxf/endpoint/dynamic/simple-binding.xjb";
427 LOG.info("Loading the JAXB 2.1 simple binding for client.");
428 InputSource source = new InputSource(getClass().getResourceAsStream(id));
429 source.setSystemId(id);
430 compiler.parseSchema(source);
431 }
432 }
433
434 private boolean isJaxb21(SchemaCompiler sc) {
435 String id = sc.getOptions().getBuildID();
436 StringTokenizer st = new StringTokenizer(id, ".");
437 String minor = null;
438
439 // major version
440 if (st.hasMoreTokens()) {
441 st.nextToken();
442 }
443
444 if (st.hasMoreTokens()) {
445 minor = st.nextToken();
446 }
447
448 try {
449 int i = Integer.valueOf(minor);
450 if (i >= 1) {
451 return true;
452 }
453 } catch (NumberFormatException e) {
454 // do nothing;
455 }
456
457 return false;
458 }
459
460 public boolean isSimpleBindingEnabled() {
461 return simpleBindingEnabled;
462 }
463
464 public void setSimpleBindingEnabled(boolean simpleBindingEnabled) {
465 this.simpleBindingEnabled = simpleBindingEnabled;
466 }
467
468 protected boolean compileJavaSrc(String classPath, List<File> srcList, String dest) {
469 String[] javacCommand = new String[srcList.size() + 7];
470
471 javacCommand[0] = "javac";
472 javacCommand[1] = "-classpath";
473 javacCommand[2] = classPath;
474 javacCommand[3] = "-d";
475 javacCommand[4] = dest;
476 javacCommand[5] = "-target";
477 javacCommand[6] = "1.5";
478
479 int i = 7;
480 for (File f : srcList) {
481 javacCommand[i++] = f.getAbsolutePath();
482 }
483 org.apache.cxf.common.util.Compiler javaCompiler
484 = new org.apache.cxf.common.util.Compiler();
485
486 return javaCompiler.internalCompile(javacCommand, 7);
487 }
488
489 static void addClasspathFromManifest(StringBuilder classPath, File file)
490 throws URISyntaxException, IOException {
491
492 JarFile jar = new JarFile(file);
493 Attributes attr = null;
494 if (jar.getManifest() != null) {
495 attr = jar.getManifest().getMainAttributes();
496 }
497 if (attr != null) {
498 String cp = attr.getValue("Class-Path");
499 while (cp != null) {
500 String fileName = cp;
501 int idx = fileName.indexOf(' ');
502 if (idx != -1) {
503 fileName = fileName.substring(0, idx);
504 cp = cp.substring(idx + 1).trim();
505 } else {
506 cp = null;
507 }
508 URI uri = new URI(fileName);
509 File f2;
510 if (uri.isAbsolute()) {
511 f2 = new File(uri);
512 } else {
513 f2 = new File(file, fileName);
514 }
515 if (f2.exists()) {
516 classPath.append(f2.getAbsolutePath());
517 classPath.append(System.getProperty("path.separator"));
518 }
519 }
520 }
521 }
522
523 static void setupClasspath(StringBuilder classPath, ClassLoader classLoader)
524 throws URISyntaxException, IOException {
525
526 ClassLoader scl = ClassLoader.getSystemClassLoader();
527 ClassLoader tcl = classLoader;
528 do {
529 if (tcl instanceof URLClassLoader) {
530 URL[] urls = ((URLClassLoader)tcl).getURLs();
531 if (urls == null) {
532 urls = new URL[0];
533 }
534 for (URL url : urls) {
535 if (url.getProtocol().startsWith("file")) {
536 File file;
537 if (url.toURI().getPath() == null) {
538 continue;
539 }
540 try {
541 file = new File(url.toURI().getPath());
542 } catch (URISyntaxException urise) {
543 if (url.getPath() == null) {
544 continue;
545 }
546 file = new File(url.getPath());
547 }
548
549 if (file.exists()) {
550 classPath.append(file.getAbsolutePath())
551 .append(System
552 .getProperty("path.separator"));
553
554 if (file.getName().endsWith(".jar")) {
555 addClasspathFromManifest(classPath, file);
556 }
557 }
558 }
559 }
560 } else if (tcl.getClass().getName().contains("weblogic")) {
561 // CXF-2549: Wrong classpath for dynamic client compilation in Weblogic
562 try {
563 Method method = tcl.getClass().getMethod("getClassPath");
564 Object weblogicClassPath = method.invoke(tcl);
565 classPath.append(weblogicClassPath)
566 .append(System.getProperty("path.separator"));
567 } catch (Exception e) {
568 LOG.log(Level.FINE, "unsuccessfully tried getClassPath method", e);
569 }
570 }
571 tcl = tcl.getParent();
572 if (null == tcl) {
573 break;
574 }
575 } while(!tcl.equals(scl.getParent()));
576 }
577
578 private URL composeUrl(String s) {
579 try {
580 URIResolver resolver = new URIResolver(null, s, getClass());
581
582 if (resolver.isResolved()) {
583 return resolver.getURI().toURL();
584 } else {
585 throw new ServiceConstructionException(new Message("COULD_NOT_RESOLVE_URL", LOG, s));
586 }
587 } catch (IOException e) {
588 throw new ServiceConstructionException(new Message("COULD_NOT_RESOLVE_URL", LOG, s), e);
589 }
590 }
591
592 class InnerErrorListener {
593
594 private String url;
595
596 InnerErrorListener(String url) {
597 this.url = url;
598 }
599
600 public void error(SAXParseException arg0) {
601 throw new RuntimeException("Error compiling schema from WSDL at {" + url + "}: "
602 + arg0.getMessage(), arg0);
603 }
604
605 public void fatalError(SAXParseException arg0) {
606 throw new RuntimeException("Fatal error compiling schema from WSDL at {" + url + "}: "
607 + arg0.getMessage(), arg0);
608 }
609
610 public void info(SAXParseException arg0) {
611 // ignore
612 }
613
614 public void warning(SAXParseException arg0) {
615 // ignore
616 }
617 }
618
619 // sorry, but yuck. Try a file first?!?
620 static class RelativeEntityResolver implements EntityResolver {
621 private String baseURI;
622
623 public RelativeEntityResolver(String baseURI) {
624 super();
625 this.baseURI = baseURI;
626 }
627
628 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
629 // the system id is null if the entity is in the wsdl.
630 if (systemId != null) {
631 File file = new File(baseURI, systemId);
632 if (file.exists()) {
633 return new InputSource(new FileInputStream(file));
634 } else {
635 return new InputSource(systemId);
636 }
637 }
638 return null;
639 }
640 }
641
642 /**
643 * Return the map of JAXB context properties used at the time that we create new contexts.
644 * @return the map
645 */
646 public Map<String, Object> getJaxbContextProperties() {
647 return jaxbContextProperties;
648 }
649
650 /**
651 * Set the map of JAXB context properties used at the time that we create new contexts.
652 * @param jaxbContextProperties
653 */
654 public void setJaxbContextProperties(Map<String, Object> jaxbContextProperties) {
655 this.jaxbContextProperties = jaxbContextProperties;
656 }
657
658 }