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    }