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 }