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
20 package org.apache.axis2.jaxws.description.builder;
21
22 import org.apache.axis2.AxisFault;
23 import org.apache.axis2.Constants;
24 import org.apache.axis2.dataretrieval.SchemaSupplier;
25 import org.apache.axis2.dataretrieval.WSDLSupplier;
26 import org.apache.axis2.deployment.util.Utils;
27 import org.apache.axis2.description.AxisService;
28 import org.apache.axis2.description.Parameter;
29 import org.apache.axis2.engine.AxisConfiguration;
30 import org.apache.axis2.java.security.AccessController;
31 import org.apache.axis2.jaxws.catalog.JAXWSCatalogManager;
32 import org.apache.axis2.jaxws.catalog.impl.OASISCatalogManager;
33 import org.apache.axis2.jaxws.description.EndpointDescription;
34 import org.apache.axis2.jaxws.util.CatalogURIResolver;
35 import org.apache.axis2.transport.http.HTTPConstants;
36 import org.apache.axis2.util.SchemaUtil;
37 import org.apache.axis2.wsdl.WSDLConstants;
38 import org.apache.commons.logging.Log;
39 import org.apache.commons.logging.LogFactory;
40 import org.apache.ws.commons.schema.XmlSchema;
41 import org.apache.ws.commons.schema.XmlSchemaCollection;
42 import org.xml.sax.InputSource;
43
44 import javax.servlet.ServletConfig;
45 import javax.servlet.ServletContext;
46 import javax.wsdl.Definition;
47 import javax.wsdl.WSDLException;
48 import javax.wsdl.factory.WSDLFactory;
49 import javax.wsdl.xml.WSDLReader;
50 import javax.xml.ws.WebServiceException;
51 import javax.xml.ws.soap.SOAPBinding;
52 import java.io.ByteArrayOutputStream;
53 import java.io.File;
54 import java.io.FileFilter;
55 import java.io.FileInputStream;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.io.OutputStream;
59 import java.io.UnsupportedEncodingException;
60 import java.lang.reflect.Method;
61 import java.net.URL;
62 import java.net.URLClassLoader;
63 import java.net.URLDecoder;
64 import java.security.PrivilegedAction;
65 import java.util.ArrayList;
66 import java.util.HashMap;
67 import java.util.HashSet;
68 import java.util.Iterator;
69 import java.util.List;
70 import java.util.StringTokenizer;
71 import java.util.jar.Attributes;
72 import java.util.jar.JarFile;
73 import java.util.jar.JarInputStream;
74 import java.util.jar.Manifest;
75
76 /**
77 * This class will implement an interface that is defined by the
78 * MDQ code. It will be registered within the MDQ framework, and the
79 * MDQ code will call this when it finds an application that was
80 * deployed without WSDL. This class will use the WsGen tool to
81 * generate a WSDL Definition based on the Java source for the application.
82 */
83 public class JAXWSRIWSDLGenerator implements SchemaSupplier, WSDLSupplier {
84
85 private static final Log log = LogFactory.getLog(JAXWSRIWSDLGenerator.class);
86
87 private String classPath;
88
89 private AxisService axisService;
90 private boolean init = false;
91 private HashMap<String, XmlSchema> docMap;
92 private HashMap<String, Definition> wsdlDefMap;
93
94 public JAXWSRIWSDLGenerator(AxisService axisService) {
95 this.axisService = axisService;
96 }
97
98 /**
99 * This method will drive the call to WsGen to generate a WSDL file for
100 * applications deployed without WSDL. We will then read this file in from
101 * disk and create a Definition. After we are done with the file we will
102 * remove it from disk.
103 */
104 public void generateWsdl(String className, String bindingType) throws WebServiceException {
105 generateWsdl(className, bindingType, null);
106 }
107
108 /**
109 * This method will drive the call to WsGen to generate a WSDL file for
110 * applications deployed without WSDL. We will then read this file in from
111 * disk and create a Definition. After we are done with the file we will
112 * remove it from disk. This method accepts a CatalogManager as a parameter
113 * for the eventual use in by an XMLSchemaCollection.
114 */
115 public void generateWsdl(String className, String bindingType, JAXWSCatalogManager catalogManager) throws
116 WebServiceException {
117
118 AxisConfiguration axisConfiguration = axisService.getAxisConfiguration();
119 File tempFile = (File) axisConfiguration.getParameterValue(
120 Constants.Configuration.ARTIFACTS_TEMP_DIR);
121 if (tempFile == null) {
122 tempFile = new File(getProperty_doPriv("java.io.tmpdir"), "_axis2");
123 }
124
125 Parameter servletConfigParam = axisConfiguration
126 .getParameter(HTTPConstants.HTTP_SERVLETCONFIG);
127
128 String webBase = null;
129 if (servletConfigParam != null) {
130 Object obj = servletConfigParam.getValue();
131 ServletContext servletContext;
132
133 if (obj instanceof ServletConfig) {
134 ServletConfig servletConfig = (ServletConfig) obj;
135 servletContext = servletConfig.getServletContext();
136 webBase = servletContext.getRealPath("/WEB-INF");
137 }
138 }
139
140 if(classPath == null) {
141 this.classPath = getDefaultClasspath(webBase);
142 }
143 if (log.isDebugEnabled()) {
144 log.debug("For implementation class " + className +
145 " WsGen classpath: " +
146 classPath);
147 }
148 String localOutputDirectory = tempFile.getAbsolutePath() + className;
149 if (log.isDebugEnabled()) {
150 log.debug("Output directory for generated WSDL file: " + localOutputDirectory);
151 }
152 boolean errorOnRead = false;
153 try {
154
155 if (log.isDebugEnabled()) {
156 log.debug("Generating new WSDL Definition");
157 }
158
159 createOutputDirectory(localOutputDirectory);
160 Class clazz;
161 try {
162 // Try the one in JDK16
163 clazz = Class.forName("com.sun.tools.internal.ws.spi.WSToolsObjectFactory");
164 } catch (Throwable t){
165 // Look for the RI
166 clazz = Class.forName("com.sun.tools.ws.spi.WSToolsObjectFactory");
167 }
168 Method m1 = clazz.getMethod("newInstance", new Class[]{});
169 Object factory = m1.invoke(new Object[]{});
170 String[] arguments = getWsGenArguments(className, bindingType, localOutputDirectory);
171 OutputStream os = new ByteArrayOutputStream();
172 Method m2 = clazz.getMethod("wsgen", new Class[]{OutputStream.class, String[].class});
173 m2.invoke(factory, os, arguments);
174 os.close();
175 wsdlDefMap = readInWSDL(localOutputDirectory);
176 if (wsdlDefMap.isEmpty()) {
177 throw new Exception("A WSDL Definition could not be generated for " +
178 "the implementation class: " + className);
179 }
180 docMap = readInSchema(localOutputDirectory, catalogManager);
181 }
182 catch (Throwable t) {
183 String msg =
184 "Error occurred generating WSDL file for Web service implementation class " +
185 "{" + className + "}: {" + t + "}";
186 log.error(msg, t);
187 throw new WebServiceException(msg, t);
188 }
189 }
190
191 /**
192 * This will set up the arguments that will be used by the WsGen tool.
193 */
194 private String[] getWsGenArguments(String className, String bindingType, String localOutputDirectory) throws
195 WebServiceException {
196 String[] arguments = null;
197 if (bindingType == null || bindingType.equals("") || bindingType.equals(
198 SOAPBinding.SOAP11HTTP_BINDING) || bindingType.equals(
199 SOAPBinding.SOAP11HTTP_MTOM_BINDING)) {
200 if (log.isDebugEnabled()) {
201 log.debug("Generating WSDL with SOAP 1.1 binding type");
202 }
203 arguments = new String[]{"-cp", classPath, className, "-keep", "-wsdl:soap1.1", "-d",
204 localOutputDirectory};
205 } else if (bindingType.equals(SOAPBinding.SOAP12HTTP_BINDING) || bindingType.equals(
206 SOAPBinding.SOAP12HTTP_MTOM_BINDING)) {
207 if (log.isDebugEnabled()) {
208 log.debug("Generating WSDL with SOAP 1.2 binding type");
209 }
210 arguments = new String[]{"-cp", classPath, className, "-keep", "-extension",
211 "-wsdl:Xsoap1.2", "-d", localOutputDirectory};
212 } else {
213 throw new WebServiceException("The binding " + bindingType + " specified by the " +
214 "class " + className + " cannot be used to generate a WSDL. Please choose " +
215 "a supported binding type.");
216 }
217 return arguments;
218 }
219
220 /**
221 * This method will be used to create a Definition based on the
222 * WSDL file generated by WsGen.
223 */
224 private HashMap<String, Definition> readInWSDL(String localOutputDirectory) throws Exception {
225 List<File> wsdlFiles = getWSDLFiles(localOutputDirectory);
226 HashMap<String, Definition> wsdlDefMap = new HashMap<String, Definition>();
227 for (File wsdlFile : wsdlFiles) {
228 if (wsdlFile != null) {
229 try {
230 WSDLFactory wsdlFactory = WSDLFactory.newInstance();
231 WSDLReader wsdlReader = wsdlFactory.newWSDLReader();
232 InputStream is = wsdlFile.toURL().openStream();
233 Definition definition = wsdlReader.readWSDL(localOutputDirectory,
234 new InputSource(is));
235 try {
236 definition.setDocumentBaseURI(wsdlFile.toURI().toString());
237 if (log.isDebugEnabled()) {
238 log.debug("Set base document URI for generated WSDL: " +
239 wsdlFile.toURI().toString());
240 }
241 }
242 catch (Throwable t) {
243 if (log.isDebugEnabled()) {
244 log.debug("Could not set base document URI for generated " +
245 "WSDL: " + wsdlFile.getAbsolutePath() + " : " +
246 t.toString());
247 }
248 }
249 wsdlDefMap.put(wsdlFile.getName().toLowerCase(), definition);
250 }
251 catch (WSDLException e) {
252 String msg = "Error occurred while attempting to create Definition from " +
253 "generated WSDL file {" + wsdlFile.getName() + "}: {" + e + "}";
254 log.error(msg);
255 throw new Exception(msg);
256 }
257 catch (IOException e) {
258 String msg = "Error occurred while attempting to create Definition from " +
259 "generated WSDL file {" + wsdlFile.getName() + "}: {" + e + "}";
260 log.error(msg);
261 throw new Exception(msg);
262 }
263 }
264 }
265 return wsdlDefMap;
266 }
267
268 /**
269 * This method will be used to locate the WSDL file that was
270 * generated by WsGen. There should be only one file with the
271 * ".wsdl" extension in this directory.
272 */
273 private List<File> getWSDLFiles(String localOutputDirectory) {
274 File classDirectory = new File(localOutputDirectory);
275 ArrayList<File> wsdlFiles = new ArrayList<File>();
276 if (classDirectory.isDirectory()) {
277 File[] files = classDirectory.listFiles();
278 for (File file : files) {
279 String fileName = file.getName();
280 if (fileName.endsWith(".wsdl")) {
281 if (log.isDebugEnabled()) {
282 log.debug("Located generated WSDL file: " + fileName);
283 }
284 wsdlFiles.add(file);
285 }
286 }
287 }
288 return wsdlFiles;
289 }
290
291 /**
292 * This file will create the directory we will use as the output
293 * directory argument in our call to WsGen.
294 */
295 private void createOutputDirectory(String localOutputDirectory) {
296 File directory = new File(localOutputDirectory);
297 if (!directory.isDirectory()) {
298 directory.mkdirs();
299 }
300 }
301
302 /**
303 * This method will read in all of the schema files that were generated
304 * for a given application.
305 */
306 private HashMap<String, XmlSchema> readInSchema(String localOutputDirectory,
307 JAXWSCatalogManager catalogManager) throws Exception {
308 try {
309
310 XmlSchemaCollection schemaCollection = new XmlSchemaCollection();
311 if (catalogManager != null)
312 schemaCollection.setSchemaResolver(new CatalogURIResolver(catalogManager));
313 schemaCollection.setBaseUri(new File(localOutputDirectory).getAbsolutePath());
314
315 HashMap<String, XmlSchema> docMap = new HashMap<String, XmlSchema>();
316 List<File> schemaFiles = getSchemaFiles(localOutputDirectory);
317 for (File schemaFile : schemaFiles) {
318 XmlSchema doc = schemaCollection.read(new InputSource(schemaFile.toURL().toString()), null);
319 if (log.isDebugEnabled()) {
320 log.debug("Read in schema file: " + schemaFile.getName());
321 }
322 docMap.put(schemaFile.getName(), doc);
323 }
324 return docMap;
325 }
326 catch (Exception e) {
327 String msg =
328 "Error occurred while attempting to read generated schema file {" + e + "}";
329 log.error(msg);
330 throw new Exception(msg);
331 }
332 }
333
334 /**
335 * This method will return a list of file objects that represent all the
336 * schema files in the current directory.
337 */
338 private List<File> getSchemaFiles(String localOutputDirectory) {
339 ArrayList<File> schemaFiles = new ArrayList<File>();
340 File classDirectory = new File(localOutputDirectory);
341 if (classDirectory.isDirectory()) {
342 File[] files = classDirectory.listFiles();
343 for (File file : files) {
344 String fileName = file.getName();
345 if (fileName.endsWith(".xsd")) {
346 if (log.isDebugEnabled()) {
347 log.debug("Located generated schema file: " + fileName);
348 }
349 schemaFiles.add(file);
350 }
351 }
352 }
353 return schemaFiles;
354 }
355
356 public Definition getWSDL(AxisService service) throws AxisFault {
357 Parameter wsdlParameter = service.getParameter(WSDLConstants.WSDL_4_J_DEFINITION);
358 if (wsdlParameter != null) {
359 Object value = wsdlParameter.getValue();
360 if (value != null) {
361 return (Definition) value;
362 }
363 }
364 initialize(service);
365 return wsdlDefMap.values().iterator().next();
366 }
367
368 private synchronized void initialize(AxisService service) {
369 String className = (String) axisService.getParameter(Constants.SERVICE_CLASS).getValue();
370 if (!init) {
371 generateWsdl(className, SOAPBinding.SOAP11HTTP_BINDING, getCatalogManager(service));
372 init = true;
373 }
374 }
375
376 public XmlSchema getSchema(AxisService service, String xsd) throws AxisFault {
377 Parameter wsdlParameter = service.getParameter(WSDLConstants.WSDL_4_J_DEFINITION);
378 if (wsdlParameter != null) {
379 ArrayList list = service.getSchema();
380 if (list.size() > 0) {
381 if (xsd == null || xsd.length() == 0) {
382 return (XmlSchema) list.get(0);
383 }
384
385 for (Iterator iterator = list.iterator(); iterator.hasNext();) {
386 XmlSchema schema = (XmlSchema) iterator.next();
387 XmlSchema[] schemas = SchemaUtil.getAllSchemas(schema);
388 for (int i = 0; i < schemas.length; i++) {
389 String uri = schemas[i].getSourceURI();
390 if (uri != null && uri.endsWith(xsd)) {
391 return schemas[i];
392 }
393 }
394 }
395 return (XmlSchema) list.get(0);
396 }
397 }
398 initialize(service);
399 XmlSchema schema = docMap.get(xsd);
400 if (schema == null) {
401 docMap.values().iterator().next();
402 }
403 return schema;
404 }
405
406 /**
407 * Expand a directory path or list of directory paths (File.pathSeparator
408 * delimited) into a list of file paths of all the jar files in those
409 * directories.
410 *
411 * @param dirPaths The string containing the directory path or list of
412 * directory paths.
413 * @return The file paths of the jar files in the directories. This is an
414 * empty string if no files were found, and is terminated by an
415 * additional pathSeparator in all other cases.
416 */
417 public static String expandDirs(String dirPaths) {
418 StringTokenizer st = new StringTokenizer(dirPaths, File.pathSeparator);
419 StringBuffer buffer = new StringBuffer();
420 while (st.hasMoreTokens()) {
421 String d = st.nextToken();
422 File dir = new File(d);
423 if (dir.isDirectory()) {
424 File[] files = dir.listFiles(new JavaArchiveFilter());
425 for (int i = 0; i < files.length; i++) {
426 buffer.append(files[i]).append(File.pathSeparator);
427 }
428 }
429 }
430 return buffer.toString();
431 }
432
433 /**
434 * Check if this inputstream is a jar/zip
435 *
436 * @param is
437 * @return true if inputstream is a jar
438 */
439 public static boolean isJar(InputStream is) {
440 try {
441 JarInputStream jis = new JarInputStream(is);
442 if (jis.getNextEntry() != null) {
443 return true;
444 }
445 } catch (IOException ioe) {
446 }
447 return false;
448 }
449
450 /**
451 * Get the CatalogManager associated with an AxisService
452 * @return the CatalogManager in use for this AxisService
453 */
454 public static JAXWSCatalogManager getCatalogManager(AxisService service) {
455 Parameter param = service.getParameter(EndpointDescription.AXIS_SERVICE_PARAMETER);
456
457 if (param != null) {
458 EndpointDescription ed = (EndpointDescription)param.getValue();
459 return ed.getServiceDescription().getCatalogManager();
460 } else
461 return new OASISCatalogManager();
462 }
463
464 /**
465 * Get the default classpath from various thingies in the message context
466 *
467 * @param msgContext
468 * @return default classpath
469 */
470 public String getDefaultClasspath(String webBase) {
471 HashSet classpath = new HashSet();
472 ClassLoader cl = Thread.currentThread().getContextClassLoader();
473 fillClassPath(cl, classpath);
474
475 // Just to be safe (the above doesn't seem to return the webapp
476 // classpath in all cases), manually do this:
477 if (webBase != null) {
478 addPath(classpath, webBase + File.separatorChar + "classes");
479 try {
480 String libBase = webBase + File.separatorChar + "lib";
481 File libDir = new File(libBase);
482 String[] jarFiles = libDir.list();
483 for (int i = 0; i < jarFiles.length; i++) {
484 String jarFile = jarFiles[i];
485 if (jarFile.endsWith(".jar")) {
486 addPath(classpath, libBase +
487 File.separatorChar +
488 jarFile);
489 }
490 }
491 } catch (Exception e) {
492 // Oh well. No big deal.
493 }
494 }
495
496 URL serviceArchive = axisService.getFileName();
497 if(serviceArchive != null) {
498 try {
499 classpath.add(Utils.toFile(serviceArchive).getCanonicalPath());
500 } catch (UnsupportedEncodingException e) {
501 log.error(e.getMessage(), e);
502 } catch (IOException e) {
503 log.error(e.getMessage(), e);
504 }
505 }
506
507 // axis.ext.dirs can be used in any appserver
508 getClassPathFromDirectoryProperty(classpath, "axis.ext.dirs");
509
510 // classpath used by Jasper
511 getClassPathFromProperty(classpath, "org.apache.catalina.jsp_classpath");
512
513 // websphere stuff.
514 getClassPathFromProperty(classpath, "ws.ext.dirs");
515 getClassPathFromProperty(classpath, "com.ibm.websphere.servlet.application.classpath");
516
517 // java class path
518 getClassPathFromProperty(classpath, "java.class.path");
519
520 // Load jars from java external directory
521 getClassPathFromDirectoryProperty(classpath, "java.ext.dirs");
522
523 // boot classpath isn't found in above search
524 getClassPathFromProperty(classpath, "sun.boot.class.path");
525
526 StringBuffer path = new StringBuffer();
527 for (Iterator iterator = classpath.iterator(); iterator.hasNext();) {
528 String s = (String) iterator.next();
529 path.append(s);
530 path.append(File.pathSeparatorChar);
531 }
532 log.debug(path);
533 return path.toString();
534 }
535
536 private static void addPath(HashSet classpath, String s) {
537 String path = s.replace(((File.separatorChar == '/') ? '\\' : '/'), File.separatorChar).trim();
538 File file = new File(path);
539 if (fileExists(file)) {
540 path = file.getAbsolutePath();
541 classpath.add(path);
542 }
543 }
544
545 /**
546 * Add all files in the specified directory to the classpath
547 *
548 * @param classpath
549 * @param property
550 */
551 private static void getClassPathFromDirectoryProperty(HashSet classpath, String property) {
552 String dirs = getProperty_doPriv(property);
553 String path = null;
554 try {
555 path = expandDirs(dirs);
556 } catch (Exception e) {
557 // Oh well. No big deal.
558 }
559 if (path != null) {
560 addPath(classpath, path);
561 }
562 }
563
564 private static String getProperty_doPriv(final String property) {
565 return (String)
566 AccessController.doPrivileged(
567 new PrivilegedAction() {
568
569 public Object run() {
570 try {
571 return System.getProperty(property);
572 } catch (Throwable t) {
573 return null;
574 }
575 }
576 });
577 }
578 /**
579 * Add a classpath stored in a property.
580 *
581 * @param classpath
582 * @param property
583 */
584 private static void getClassPathFromProperty(HashSet classpath, String property) {
585 String path = getProperty_doPriv(property);
586 if (path != null) {
587 addPath(classpath, path);
588 }
589 }
590
591 /**
592 * Walk the classloader hierarchy and add to the classpath
593 *
594 * @param cl
595 * @param classpath
596 */
597 private static void fillClassPath(ClassLoader cl, HashSet classpath) {
598 while (cl != null) {
599 if (cl instanceof URLClassLoader) {
600 URL[] urls = ((URLClassLoader) cl).getURLs();
601 for (int i = 0; (urls != null) && i < urls.length; i++) {
602 String path = urls[i].getPath();
603 //If it is a drive letter, adjust accordingly.
604 if (path.length() >= 3 && path.charAt(0) == '/' && path.charAt(2) == ':')
605 path = path.substring(1);
606 addPath(classpath, URLDecoder.decode(path));
607
608 // if its a jar extract Class-Path entries from manifest
609 File file = new File(urls[i].getFile());
610 if (file.isFile()) {
611 FileInputStream fis = null;
612 try {
613 fis = new FileInputStream(file);
614 if (isJar(fis)) {
615 JarFile jar = new JarFile(file);
616 Manifest manifest = jar.getManifest();
617 if (manifest != null) {
618 Attributes attributes = manifest.getMainAttributes();
619 if (attributes != null) {
620 String s = attributes.getValue(Attributes.Name.CLASS_PATH);
621 String base = file.getParent();
622 if (s != null) {
623 StringTokenizer st = new StringTokenizer(s, " ");
624 while (st.hasMoreTokens()) {
625 String t = st.nextToken();
626 addPath(classpath, base + File.separatorChar + t);
627 }
628 }
629 }
630 }
631 }
632 } catch (IOException ioe) {
633 } finally {
634 if (fis != null) {
635 try {
636 fis.close();
637 } catch (IOException ioe2) {
638 }
639 }
640 }
641 }
642 }
643 }
644 cl = cl.getParent();
645 }
646 }
647
648 /**
649 * Filter for zip/jar
650 */
651 private static class JavaArchiveFilter implements FileFilter {
652 public boolean accept(File file) {
653 String name = file.getName().toLowerCase();
654 return (name.endsWith(".jar") || name.endsWith(".zip"));
655 }
656 }
657
658 static private Boolean fileExists (final File file) {
659 Boolean exists = (Boolean) AccessController.doPrivileged(
660 new PrivilegedAction() {
661 public Object run() {
662 return new Boolean(file.exists());
663 }
664 }
665 );
666 return exists;
667 }
668
669 }