001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */package org.apache.cxf.endpoint.dynamic;
019:
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.IOException;
023: import java.net.MalformedURLException;
024: import java.net.URISyntaxException;
025: import java.net.URL;
026: import java.net.URLClassLoader;
027: import java.util.Collection;
028: import java.util.Iterator;
029: import java.util.StringTokenizer;
030: import java.util.logging.Level;
031: import java.util.logging.Logger;
032:
033: import javax.xml.bind.JAXBContext;
034: import javax.xml.bind.JAXBException;
035: import javax.xml.namespace.QName;
036:
037: import org.w3c.dom.Element;
038:
039: import org.xml.sax.EntityResolver;
040: import org.xml.sax.InputSource;
041: import org.xml.sax.SAXException;
042: import org.xml.sax.SAXParseException;
043:
044: import com.sun.codemodel.JCodeModel;
045: import com.sun.codemodel.JDefinedClass;
046: import com.sun.codemodel.JPackage;
047: import com.sun.codemodel.writer.FileCodeWriter;
048: import com.sun.tools.xjc.Options;
049: import com.sun.tools.xjc.api.ErrorListener;
050: import com.sun.tools.xjc.api.S2JJAXBModel;
051: import com.sun.tools.xjc.api.SchemaCompiler;
052: import com.sun.tools.xjc.api.XJC;
053:
054: import org.apache.cxf.Bus;
055: import org.apache.cxf.bus.CXFBusFactory;
056: import org.apache.cxf.common.i18n.Message;
057: import org.apache.cxf.common.util.StringUtils;
058: import org.apache.cxf.endpoint.Client;
059: import org.apache.cxf.endpoint.ClientImpl;
060: import org.apache.cxf.helpers.FileUtils;
061: import org.apache.cxf.jaxb.JAXBDataBinding;
062: import org.apache.cxf.resource.URIResolver;
063: import org.apache.cxf.service.Service;
064: import org.apache.cxf.service.factory.ServiceConstructionException;
065: import org.apache.cxf.service.model.SchemaInfo;
066: import org.apache.cxf.service.model.ServiceInfo;
067: import org.apache.tools.ant.Project;
068: import org.apache.tools.ant.types.DirSet;
069: import org.apache.tools.ant.types.FileSet;
070: import org.apache.tools.ant.types.Path;
071:
072: /**
073: *
074: *
075: */
076: public final class DynamicClientFactory {
077:
078: private static final Logger LOG = Logger
079: .getLogger(DynamicClientFactory.class.getName());
080:
081: private Bus bus;
082:
083: private String tmpdir = System.getProperty("java.io.tmpdir");
084:
085: private boolean simpleBindingEnabled = true;
086:
087: private DynamicClientFactory(Bus bus) {
088: this .bus = bus;
089: }
090:
091: public void setTemporaryDirectory(String dir) {
092: tmpdir = dir;
093: }
094:
095: /**
096: * Create a new instance using a specific <tt>Bus</tt>.
097: *
098: * @param b the <tt>Bus</tt> to use in subsequent operations with the
099: * instance
100: * @return the new instance
101: */
102: public static DynamicClientFactory newInstance(Bus b) {
103: return new DynamicClientFactory(b);
104: }
105:
106: /**
107: * Create a new instance using a default <tt>Bus</tt>.
108: *
109: * @return the new instance
110: * @see CXFBusFactory#getDefaultBus()
111: */
112: public static DynamicClientFactory newInstance() {
113: Bus bus = CXFBusFactory.getThreadDefaultBus();
114: return new DynamicClientFactory(bus);
115: }
116:
117: /**
118: * Create a new <code>Client</code> instance using the WSDL to be loaded
119: * from the specified URL and using the current classloading context.
120: *
121: * @param wsdlURL the URL to load
122: * @return
123: */
124: public Client createClient(String wsdlUrl) {
125: return createClient(wsdlUrl, (QName) null, (QName) null);
126: }
127:
128: /**
129: * Create a new <code>Client</code> instance using the WSDL to be loaded
130: * from the specified URL and with the specified <code>ClassLoader</code>
131: * as parent.
132: *
133: * @param wsdlUrl
134: * @param classLoader
135: * @return
136: */
137: public Client createClient(String wsdlUrl, ClassLoader classLoader) {
138: return createClient(wsdlUrl, null, classLoader, null);
139: }
140:
141: public Client createClient(String wsdlUrl, QName service) {
142: return createClient(wsdlUrl, service, null);
143: }
144:
145: public Client createClient(String wsdlUrl, QName service, QName port) {
146: return createClient(wsdlUrl, service, null, port);
147: }
148:
149: public Client createClient(String wsdlUrl, QName service,
150: ClassLoader classLoader, QName port) {
151: if (classLoader == null) {
152: classLoader = Thread.currentThread()
153: .getContextClassLoader();
154: }
155: URL u = composeUrl(wsdlUrl);
156: LOG.log(Level.FINE, "Creating client from URL " + u.toString());
157: ClientImpl client = new ClientImpl(bus, u, service, port);
158:
159: Service svc = client.getEndpoint().getService();
160: //all SI's should have the same schemas
161: Collection<SchemaInfo> schemas = svc.getServiceInfos().get(0)
162: .getSchemas();
163:
164: SchemaCompiler compiler = XJC.createSchemaCompiler();
165: ErrorListener elForRun = new InnerErrorListener(wsdlUrl);
166: compiler.setErrorListener(elForRun);
167:
168: addSchemas(wsdlUrl, schemas, compiler);
169:
170: S2JJAXBModel intermediateModel = compiler.bind();
171: JCodeModel codeModel = intermediateModel.generateCode(null,
172: elForRun);
173: StringBuilder sb = new StringBuilder();
174: boolean firstnt = false;
175:
176: for (Iterator<JPackage> packages = codeModel.packages(); packages
177: .hasNext();) {
178: JPackage packadge = packages.next();
179: String name = packadge.name();
180: if ("org.w3._2001.xmlschema".equals(name)) {
181: continue;
182: }
183: if (firstnt) {
184: sb.append(':');
185: } else {
186: firstnt = true;
187: }
188: sb.append(packadge.name());
189: }
190: outputDebug(codeModel);
191:
192: String packageList = sb.toString();
193:
194: // our hashcode + timestamp ought to be enough.
195: String stem = toString() + "-" + System.currentTimeMillis();
196: File src = new File(tmpdir, stem + "-src");
197: if (!src.mkdir()) {
198: throw new IllegalStateException(
199: "Unable to create working directory "
200: + src.getPath());
201: }
202: try {
203: FileCodeWriter writer = new FileCodeWriter(src);
204: codeModel.build(writer);
205: } catch (IOException e) {
206: throw new IllegalStateException(
207: "Unable to write generated Java files for schemas: "
208: + e.getMessage(), e);
209: }
210: File classes = new File(tmpdir, stem + "-classes");
211: if (!classes.mkdir()) {
212: throw new IllegalStateException(
213: "Unable to create working directory "
214: + src.getPath());
215: }
216: Project project = new Project();
217: project.setBaseDir(new File(tmpdir));
218: Path classPath = new Path(project);
219: setupClasspath(classPath, classLoader);
220: Path srcPath = new Path(project);
221: FileSet fileSet = new FileSet();
222: fileSet.setDir(src);
223: srcPath.addFileset(fileSet);
224:
225: if (!compileJavaSrc(classPath, srcPath, classes.toString())) {
226: LOG.log(Level.SEVERE, new Message("COULD_NOT_COMIPLE_SRC",
227: LOG, wsdlUrl).toString());
228: }
229: FileUtils.removeDir(src);
230: URLClassLoader cl;
231: try {
232: cl = new URLClassLoader(
233: new URL[] { classes.toURI().toURL() }, classLoader);
234: } catch (MalformedURLException mue) {
235: throw new IllegalStateException(
236: "Internal error; a directory returns a malformed URL: "
237: + mue.getMessage(), mue);
238: }
239:
240: JAXBContext context;
241:
242: try {
243: if (StringUtils.isEmpty(packageList)) {
244: context = JAXBContext.newInstance(new Class[0]);
245: } else {
246: context = JAXBContext.newInstance(packageList, cl);
247: }
248: } catch (JAXBException jbe) {
249: throw new IllegalStateException(
250: "Unable to create JAXBContext for generated packages: "
251: + jbe.getMessage(), jbe);
252: }
253:
254: JAXBDataBinding databinding = new JAXBDataBinding();
255: databinding.setContext(context);
256: svc.setDataBinding(databinding);
257:
258: ServiceInfo svcfo = client.getEndpoint().getEndpointInfo()
259: .getService();
260:
261: // Setup the new classloader!
262: Thread.currentThread().setContextClassLoader(cl);
263:
264: TypeClassInitializer visitor = new TypeClassInitializer(svcfo,
265: intermediateModel);
266: visitor.walk();
267: // delete the classes files
268: FileUtils.removeDir(classes);
269: return client;
270: }
271:
272: private void outputDebug(JCodeModel codeModel) {
273: if (!LOG.isLoggable(Level.INFO)) {
274: return;
275: }
276:
277: StringBuffer sb = new StringBuffer();
278: boolean first = true;
279: for (Iterator<JPackage> itr = codeModel.packages(); itr
280: .hasNext();) {
281: JPackage package1 = itr.next();
282:
283: for (Iterator<JDefinedClass> citr = package1.classes(); citr
284: .hasNext();) {
285: if (!first) {
286: sb.append(", ");
287: } else {
288: first = false;
289: }
290: sb.append(citr.next().fullName());
291: }
292: }
293:
294: LOG.log(Level.INFO, "Created classes: " + sb.toString());
295:
296: }
297:
298: private void addSchemas(String wsdlUrl,
299: Collection<SchemaInfo> schemas, SchemaCompiler compiler) {
300: int num = 1;
301: for (SchemaInfo schema : schemas) {
302: Element el = schema.getElement();
303:
304: compiler.parseSchema(wsdlUrl + "#types" + num, el);
305: num++;
306: }
307:
308: if (simpleBindingEnabled && isJaxb21()) {
309: String id = "/org/apache/cxf/endpoint/dynamic/simple-binding.xjb";
310: LOG.info("Loading the JAXB 2.1 simple binding for client.");
311: InputSource source = new InputSource(getClass()
312: .getResourceAsStream(id));
313: source.setSystemId(id);
314: compiler.parseSchema(source);
315: }
316: }
317:
318: private boolean isJaxb21() {
319: String id = Options.getBuildID();
320: StringTokenizer st = new StringTokenizer(id, ".");
321: String minor = null;
322:
323: // major version
324: if (st.hasMoreTokens()) {
325: st.nextToken();
326: }
327:
328: if (st.hasMoreTokens()) {
329: minor = st.nextToken();
330: }
331:
332: try {
333: int i = Integer.valueOf(minor);
334: if (i >= 1) {
335: System.out.println("Found JAXB 2.1");
336: return true;
337: }
338: } catch (NumberFormatException e) {
339: // do nothing;
340: }
341:
342: return false;
343: }
344:
345: public boolean isSimpleBindingEnabled() {
346: return simpleBindingEnabled;
347: }
348:
349: public void setSimpleBindingEnabled(boolean simpleBindingEnabled) {
350: this .simpleBindingEnabled = simpleBindingEnabled;
351: }
352:
353: static boolean compileJavaSrc(Path classPath, Path srcPath,
354: String dest) {
355: String[] srcList = srcPath.list();
356: String[] javacCommand = new String[srcList.length + 7];
357:
358: javacCommand[0] = "javac";
359: javacCommand[1] = "-classpath";
360: javacCommand[2] = classPath.toString();
361: javacCommand[3] = "-d";
362: javacCommand[4] = dest.toString();
363: javacCommand[5] = "-target";
364: javacCommand[6] = "1.5";
365:
366: for (int i = 0; i < srcList.length; i++) {
367: javacCommand[7 + i] = srcList[i];
368: }
369: org.apache.cxf.tools.util.Compiler javaCompiler = new org.apache.cxf.tools.util.Compiler();
370:
371: return javaCompiler.internalCompile(javacCommand, 7);
372: }
373:
374: static void setupClasspath(Path classPath, ClassLoader classLoader) {
375: ClassLoader scl = ClassLoader.getSystemClassLoader();
376: ClassLoader tcl = classLoader;
377: do {
378: if (tcl instanceof URLClassLoader) {
379: URL[] urls = ((URLClassLoader) tcl).getURLs();
380: for (URL url : urls) {
381: if (url.getProtocol().startsWith("file")) {
382: try {
383: File file = new File(url.toURI().getPath());
384: if (file.isDirectory()) {
385: DirSet ds = new DirSet();
386: ds.setFile(file);
387: classPath.addDirset(ds);
388: } else {
389: FileSet fs = new FileSet();
390: fs.setFile(file);
391: classPath.addFileset(fs);
392: }
393: } catch (URISyntaxException e) {
394: // TODO Auto-generated catch block
395: e.printStackTrace();
396: }
397: }
398: }
399: }
400: tcl = tcl.getParent();
401: if (null == tcl) {
402: break;
403: }
404: } while (!tcl.equals(scl));
405: }
406:
407: private URL composeUrl(String s) {
408: try {
409: URIResolver resolver = new URIResolver(null, s, getClass());
410:
411: if (resolver.isResolved()) {
412: return resolver.getURI().toURL();
413: } else {
414: throw new ServiceConstructionException(new Message(
415: "COULD_NOT_RESOLVE_URL", LOG, s));
416: }
417: } catch (IOException e) {
418: throw new ServiceConstructionException(new Message(
419: "COULD_NOT_RESOLVE_URL", LOG, s), e);
420: }
421: }
422:
423: private class InnerErrorListener implements ErrorListener {
424:
425: private String url;
426:
427: InnerErrorListener(String url) {
428: this .url = url;
429: }
430:
431: public void error(SAXParseException arg0) {
432: throw new RuntimeException(
433: "Error compiling schema from WSDL at {" + url
434: + "}: " + arg0.getMessage(), arg0);
435: }
436:
437: public void fatalError(SAXParseException arg0) {
438: throw new RuntimeException(
439: "Fatal error compiling schema from WSDL at {" + url
440: + "}: " + arg0.getMessage(), arg0);
441: }
442:
443: public void info(SAXParseException arg0) {
444: // ignore
445: }
446:
447: public void warning(SAXParseException arg0) {
448: // ignore
449: }
450: }
451:
452: // sorry, but yuck. Try a file first?!?
453: static class RelativeEntityResolver implements EntityResolver {
454: private String baseURI;
455:
456: public RelativeEntityResolver(String baseURI) {
457: super ();
458: this .baseURI = baseURI;
459: }
460:
461: public InputSource resolveEntity(String publicId,
462: String systemId) throws SAXException, IOException {
463: // the system id is null if the entity is in the wsdl.
464: if (systemId != null) {
465: File file = new File(baseURI, systemId);
466: if (file.exists()) {
467: return new InputSource(new FileInputStream(file));
468: } else {
469: return new InputSource(systemId);
470: }
471: }
472: return null;
473: }
474: }
475: }
|