001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.jaxws.builder; 019 020 import java.io.ByteArrayOutputStream; 021 import java.io.File; 022 import java.io.FileFilter; 023 import java.io.IOException; 024 import java.io.InputStream; 025 import java.io.OutputStream; 026 import java.net.URL; 027 import java.net.MalformedURLException; 028 import java.util.ArrayList; 029 import java.util.Arrays; 030 import java.util.Collection; 031 import java.util.List; 032 import java.util.Random; 033 import java.util.Set; 034 035 import javax.xml.namespace.QName; 036 037 import org.apache.commons.logging.Log; 038 import org.apache.commons.logging.LogFactory; 039 import org.apache.geronimo.common.DeploymentException; 040 import org.apache.geronimo.deployment.DeploymentConfigurationManager; 041 import org.apache.geronimo.deployment.DeploymentContext; 042 import org.apache.geronimo.j2ee.deployment.Module; 043 import org.apache.geronimo.jaxws.PortInfo; 044 import org.apache.geronimo.kernel.config.Configuration; 045 import org.apache.geronimo.kernel.config.ConfigurationResolver; 046 import org.apache.geronimo.kernel.config.NoSuchConfigException; 047 import org.apache.geronimo.kernel.repository.Repository; 048 049 public class WsdlGenerator { 050 051 private static final Log LOG = LogFactory.getLog(WsdlGenerator.class); 052 053 private final static String ADD_TO_CLASSPATH_WSGEN_PROPERTY = 054 "org.apache.geronimo.jaxws.wsgen.addToClassPath"; 055 056 private final static String FORK_WSGEN_PROPERTY = 057 "org.apache.geronimo.jaxws.wsgen.fork"; 058 059 private final static String FORK_TIMEOUT_WSGEN_PROPERTY = 060 "org.apache.geronimo.jaxws.wsgen.fork.timeout"; 061 062 private final static long FORK_POLL_FREQUENCY = 1000 * 2; // 2 seconds 063 064 private QName wsdlService; 065 private QName wsdlPort; 066 private boolean forkWsgen = getForkWsgen(); 067 private long forkTimeout = getForTimeout(); 068 private boolean addToClassPath = getDefaultAddToClassPath(); 069 private JAXWSTools jaxwsTools; 070 071 private static boolean getForkWsgen() { 072 String value = System.getProperty(FORK_WSGEN_PROPERTY); 073 if (value != null) { 074 return Boolean.valueOf(value).booleanValue(); 075 } else { 076 String osName = System.getProperty("os.name"); 077 if (osName == null) { 078 return false; 079 } 080 osName = osName.toLowerCase(); 081 // Fork on Windows only 082 return (osName.indexOf("windows") != -1); 083 } 084 } 085 086 private static long getForTimeout() { 087 String value = System.getProperty(FORK_TIMEOUT_WSGEN_PROPERTY); 088 if (value != null) { 089 return Long.parseLong(value); 090 } else { 091 return 1000 * 60; // 60 seconds 092 } 093 } 094 095 private static boolean getDefaultAddToClassPath() { 096 String value = System.getProperty(ADD_TO_CLASSPATH_WSGEN_PROPERTY); 097 if (value == null) { 098 return true; 099 } else { 100 return Boolean.parseBoolean(value); 101 } 102 } 103 104 public WsdlGenerator() { 105 this.jaxwsTools = new JAXWSTools(); 106 } 107 108 public void setSunSAAJ() { 109 this.jaxwsTools.setUseSunSAAJ(); 110 } 111 112 public void setAxis2SAAJ() { 113 this.jaxwsTools.setUseAxis2SAAJ(); 114 } 115 116 public void setWsdlService(QName name) { 117 this.wsdlService = name; 118 } 119 120 public QName getWsdlService() { 121 return this.wsdlService; 122 } 123 124 public void setWsdlPort(QName port) { 125 this.wsdlPort = port; 126 } 127 128 public QName getWsdlPort() { 129 return this.wsdlPort; 130 } 131 132 public void setAddToClassPath(boolean addToClassPath) { 133 this.addToClassPath = addToClassPath; 134 } 135 136 public boolean getAddToClassPath() { 137 return this.addToClassPath; 138 } 139 140 private URL[] getWsgenClasspath(DeploymentContext context) throws Exception { 141 DeploymentConfigurationManager cm = (DeploymentConfigurationManager)context.getConfigurationManager(); 142 Collection<? extends Repository> repositories = cm.getRepositories(); 143 File[] jars = this.jaxwsTools.getClasspath(repositories); 144 return JAXWSTools.toURL(jars); 145 } 146 147 private static void getModuleClasspath(Module module, DeploymentContext context, StringBuilder classpath) throws DeploymentException { 148 getModuleClasspath(classpath, module.getEarContext()); 149 if (module.getRootEarContext() != module.getEarContext()) { 150 getModuleClasspath(classpath, module.getRootEarContext()); 151 } 152 } 153 154 private static void getModuleClasspath(StringBuilder classpath, DeploymentContext deploymentContext) throws DeploymentException { 155 Configuration configuration = deploymentContext.getConfiguration(); 156 ConfigurationResolver resolver = configuration.getConfigurationResolver(); 157 List<String> moduleClassPath = configuration.getClassPath(); 158 for (String pattern : moduleClassPath) { 159 try { 160 Set<URL> files = resolver.resolve(pattern); 161 for (URL url: files) { 162 String path = toFileName(url); 163 classpath.append(path).append(File.pathSeparator); 164 } 165 } catch (MalformedURLException e) { 166 throw new DeploymentException("Could not resolve pattern: " + pattern, e); 167 } catch (NoSuchConfigException e) { 168 throw new DeploymentException("Could not resolve pattern: " + pattern, e); 169 } 170 } 171 } 172 173 private static File toFile(URL url) { 174 if (url == null || !url.getProtocol().equals("file")) { 175 return null; 176 } else { 177 String filename = toFileName(url); 178 return new File(filename); 179 } 180 } 181 182 private static String toFileName(URL url) { 183 String filename = url.getFile().replace('/', File.separatorChar); 184 int pos =0; 185 while ((pos = filename.indexOf('%', pos)) >= 0) { 186 if (pos + 2 < filename.length()) { 187 String hexStr = filename.substring(pos + 1, pos + 3); 188 char ch = (char) Integer.parseInt(hexStr, 16); 189 filename = filename.substring(0, pos) + ch + filename.substring(pos + 3); 190 } 191 } 192 return filename; 193 } 194 195 private String[] buildArguments(String sei, String classPath, File moduleBaseDir, PortInfo portInfo) { 196 List<String> arguments = new ArrayList<String>(); 197 198 arguments.add("-cp"); 199 arguments.add(classPath); 200 arguments.add("-keep"); 201 arguments.add("-wsdl"); 202 arguments.add("-d"); 203 arguments.add(moduleBaseDir.getAbsolutePath()); 204 205 QName serviceName = getWsdlService(); 206 if (serviceName != null) { 207 arguments.add("-servicename"); 208 arguments.add(serviceName.toString()); 209 } 210 211 QName portName = getWsdlPort(); 212 if (portName != null) { 213 arguments.add("-portname"); 214 arguments.add(portName.toString()); 215 } 216 217 arguments.add(sei); 218 219 return arguments.toArray(new String[]{}); 220 } 221 222 private File getFirstWsdlFile(File baseDir) throws IOException { 223 LOG.debug("Looking for service wsdl file in " + baseDir.getAbsolutePath()); 224 File[] files = baseDir.listFiles(new FileFilter() { 225 public boolean accept(File file) { 226 return (file.isFile() && file.getName().endsWith(".wsdl")); 227 } 228 }); 229 230 if (files.length == 1) { 231 return files[0]; 232 } else { 233 return null; 234 } 235 } 236 237 private File findWsdlFile(File baseDir, PortInfo portInfo) throws IOException { 238 QName serviceName = getWsdlService(); 239 240 if (serviceName != null) { 241 // check if serviceName.wsdl locates at the baseDir, if so, return its path. 242 String wsdlFileName = serviceName.getLocalPart() + ".wsdl"; 243 if (Character.isLowerCase(wsdlFileName.charAt(0))) { 244 wsdlFileName = Character.toUpperCase(wsdlFileName.charAt(0)) + wsdlFileName.substring(1); 245 } 246 File wsdlFile = new File(baseDir, wsdlFileName); 247 if (wsdlFile.exists()) { 248 return wsdlFile; 249 } else { 250 return getFirstWsdlFile(baseDir); 251 } 252 } else { 253 return getFirstWsdlFile(baseDir); 254 } 255 } 256 257 private static String getRelativeNameOrURL(File baseDir, File file) { 258 String basePath = baseDir.getAbsolutePath(); 259 String path = file.getAbsolutePath(); 260 261 if (path.startsWith(basePath)) { 262 if (File.separatorChar == path.charAt(basePath.length())) { 263 return path.substring(basePath.length() + 1); 264 } else { 265 return path.substring(basePath.length()); 266 } 267 } else { 268 return file.toURI().toString(); 269 } 270 } 271 272 private static File createTempDirectory(File baseDir) throws IOException { 273 Random rand = new Random(); 274 while(true) { 275 String dirName = String.valueOf(Math.abs(rand.nextInt())); 276 File dir = new File(baseDir, dirName); 277 if (!dir.exists()) { 278 if (!dir.mkdir()) { 279 throw new IOException("Failed to create temporary directory: " + dir); 280 } else { 281 return dir; 282 } 283 } 284 } 285 } 286 287 public String generateWsdl(Module module, 288 String serviceClass, 289 DeploymentContext context, 290 PortInfo portInfo) throws DeploymentException { 291 //call wsgen tool to generate the wsdl file based on the bindingtype. 292 //let's set the outputDir as the module base directory in server repository. 293 File moduleBase = module.getEarContext().getInPlaceConfigurationDir(); 294 if (moduleBase == null) { 295 moduleBase = module.getEarContext().getBaseDir(); 296 } 297 File moduleBaseDir = (moduleBase.isFile()) ? moduleBase.getParentFile() : moduleBase; 298 File baseDir; 299 300 try { 301 baseDir = createTempDirectory(moduleBaseDir); 302 } catch (IOException e) { 303 throw new DeploymentException(e); 304 } 305 306 URL[] urls; 307 StringBuilder classPath = new StringBuilder(); 308 //let's figure out the classpath for wsgen tools 309 try { 310 urls = getWsgenClasspath(context); 311 } catch (Exception e) { 312 throw new DeploymentException("Failed to generate the wsdl file using wsgen: unable to get the location of the required artifact(s).", e); 313 } 314 //let's figure out the classpath string for the module and wsgen tools. 315 if (urls != null && urls.length > 0) { 316 for (URL url : urls) { 317 classPath.append(toFile(url).getAbsolutePath()).append(File.pathSeparator); 318 } 319 } 320 getModuleClasspath(module, context, classPath); 321 322 //create arguments; 323 String[] arguments = buildArguments(serviceClass, classPath.toString(), baseDir, portInfo); 324 325 try { 326 boolean result = false; 327 328 if (this.forkWsgen) { 329 result = forkWsgen(classPath, arguments); 330 } else { 331 result = invokeWsgen(urls, arguments); 332 } 333 334 if (result) { 335 //check to see if the file is created. 336 File wsdlFile = findWsdlFile(baseDir, portInfo); 337 if (wsdlFile == null) { 338 throw new DeploymentException("Unable to find the service wsdl file"); 339 } 340 if (this.addToClassPath) { 341 context.getConfiguration().addToClassPath(baseDir.getName()); 342 } 343 return getRelativeNameOrURL(moduleBase, wsdlFile); 344 } else { 345 throw new DeploymentException("WSDL generation failed"); 346 } 347 348 } catch (DeploymentException e) { 349 throw e; 350 } catch (Exception e) { 351 throw new DeploymentException("Unable to generate the wsdl file using wsgen.", e); 352 } 353 } 354 355 private boolean invokeWsgen(URL[] jars, String[] arguments) throws Exception { 356 ByteArrayOutputStream os = new ByteArrayOutputStream(); 357 boolean rs = this.jaxwsTools.invokeWsgen(jars, os, arguments); 358 os.close(); 359 360 if (LOG.isDebugEnabled()) { 361 byte [] arr = os.toByteArray(); 362 String wsgenOutput = new String(arr, 0, arr.length); 363 LOG.debug("wsgen output: " + wsgenOutput); 364 } 365 366 return rs; 367 } 368 369 private boolean forkWsgen(StringBuilder classPath, String[] arguments) throws Exception { 370 List<String> cmd = new ArrayList<String>(); 371 String javaHome = System.getProperty("java.home"); 372 String java = javaHome + File.separator + "bin" + File.separator + "java"; 373 cmd.add(java); 374 cmd.add("-classpath"); 375 cmd.add(classPath.toString()); 376 cmd.add("com.sun.tools.ws.WsGen"); 377 cmd.addAll(Arrays.asList(arguments)); 378 379 if (LOG.isDebugEnabled()) { 380 LOG.debug("Executing wsgen: " + cmd); 381 } 382 383 ProcessBuilder builder = new ProcessBuilder(cmd); 384 builder.redirectErrorStream(true); 385 386 Process process = builder.start(); 387 return waitFor(process); 388 } 389 390 private boolean waitFor(Process process) throws DeploymentException { 391 CaptureOutputThread outputThread = new CaptureOutputThread(process.getInputStream()); 392 outputThread.start(); 393 394 long sleepTime = 0; 395 while(sleepTime < this.forkTimeout) { 396 try { 397 int errorCode = process.exitValue(); 398 if (errorCode == 0) { 399 if (LOG.isDebugEnabled()) { 400 LOG.debug("wsgen output: " + outputThread.getOutput()); 401 } 402 return true; 403 } else { 404 LOG.error("WSDL generation process failed"); 405 LOG.error(outputThread.getOutput()); 406 return false; 407 } 408 } catch (IllegalThreadStateException e) { 409 // still running 410 try { 411 Thread.sleep(FORK_POLL_FREQUENCY); 412 } catch (InterruptedException ee) { 413 // interrupted 414 process.destroy(); 415 throw new DeploymentException("WSDL generation process was interrupted"); 416 } 417 sleepTime += FORK_POLL_FREQUENCY; 418 } 419 } 420 421 // timeout; 422 process.destroy(); 423 424 LOG.error("WSDL generation process timed out"); 425 LOG.error(outputThread.getOutput()); 426 427 throw new DeploymentException("WSDL generation process timed out"); 428 } 429 430 private static class CaptureOutputThread extends Thread { 431 432 private InputStream in; 433 private ByteArrayOutputStream out; 434 435 public CaptureOutputThread(InputStream in) { 436 this.in = in; 437 this.out = new ByteArrayOutputStream(); 438 } 439 440 public String getOutput() { 441 // make sure the thread is done 442 try { 443 join(10 * 1000); 444 445 // if it's still not done, interrupt it 446 if (isAlive()) { 447 interrupt(); 448 } 449 } catch (InterruptedException e) { 450 // that's ok 451 } 452 453 // get the output 454 byte [] arr = this.out.toByteArray(); 455 String output = new String(arr, 0, arr.length); 456 return output; 457 } 458 459 public void run() { 460 try { 461 copyAll(this.in, this.out); 462 } catch (IOException e) { 463 // ignore 464 } finally { 465 try { this.out.close(); } catch (IOException ee) {} 466 try { this.in.close(); } catch (IOException ee) {} 467 } 468 } 469 470 private static void copyAll(InputStream in, OutputStream out) throws IOException { 471 byte[] buffer = new byte[4096]; 472 int count; 473 while ((count = in.read(buffer)) > 0) { 474 out.write(buffer, 0, count); 475 } 476 out.flush(); 477 } 478 } 479 }