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.OutputStream;
025    import java.lang.reflect.Method;
026    import java.net.MalformedURLException;
027    import java.net.URL;
028    import java.net.URLClassLoader;
029    import java.util.ArrayList;
030    import java.util.Collection;
031    import java.util.List;
032    import java.util.Random;
033    import java.util.SortedSet;
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.EARContext;
043    import org.apache.geronimo.j2ee.deployment.Module;
044    import org.apache.geronimo.jaxws.PortInfo;
045    import org.apache.geronimo.kernel.repository.Artifact;
046    import org.apache.geronimo.kernel.repository.ListableRepository;
047    import org.apache.geronimo.kernel.repository.Version;
048    
049    public class WsdlGenerator {
050    
051        private static final Log LOG = LogFactory.getLog(WsdlGenerator.class);
052        
053        private final static Artifact AXIS2_JAXWS_API_ARTIFACT = new Artifact("org.apache.axis2","axis2-jaxws-api", (Version)null, "jar");
054        private final static Artifact AXIS2_SAAJ_API_ARTIFACT = new Artifact("org.apache.axis2","axis2-saaj-api", (Version)null, "jar");
055        private final static Artifact AXIS2_SAAJ_IMPL_ARTIFACT = new Artifact("org.apache.axis2","axis2-saaj", (Version)null, "jar");
056        private final static Artifact JAXB_API_ARTIFACT = new Artifact("javax.xml.bind","jaxb-api", (Version)null, "jar");
057        private final static Artifact JAXB_IMPL_ARTIFACT = new Artifact("com.sun.xml.bind","jaxb-impl", (Version)null, "jar");
058        private final static Artifact JAXB_XJC_ARTIFACT = new Artifact("com.sun.xml.bind","jaxb-xjc", (Version)null, "jar");    
059        private final static Artifact JAXWS_TOOLS_ARTIFACT = new Artifact("com.sun.xml.ws","jaxws-tools", (Version)null, "jar");
060        private final static Artifact JAXWS_RT_ARTIFACT = new Artifact("com.sun.xml.ws","jaxws-rt", (Version)null, "jar");
061        private final static Artifact GERONIMO_ACTIVATION_SPEC_ARTIFACT = new Artifact("org.apache.geronimo.specs","geronimo-activation_1.1_spec", (Version)null, "jar");    
062        private final static Artifact GERONIMO_ANNOTATION_ARTIFACT = new Artifact("org.apache.geronimo.specs","geronimo-annotation_1.0_spec", (Version)null, "jar");     
063        private final static Artifact GERONIMO_WS_METADATA_ARTIFACT = new Artifact("org.apache.geronimo.specs","geronimo-ws-metadata_2.0_spec", (Version)null, "jar");  
064        private final static Artifact GERONIMO_EJB_SPEC_ARTIFACT = new Artifact("org.apache.geronimo.specs","geronimo-ejb_3.0_spec", (Version)null, "jar");
065        private final static Artifact SUN_SAAJ_IMPL_ARTIFACT = new Artifact("com.sun.xml.messaging.saaj","saaj-impl", (Version)null, "jar");
066        private final static String TOOLS = "tools.jar";
067    
068        private Artifact saajImpl;
069        private QName wsdlService;
070        private QName wsdlPort;
071            
072        public WsdlGenerator() {
073        }
074        
075        public void setSunSAAJ() {
076            this.saajImpl = SUN_SAAJ_IMPL_ARTIFACT;
077        }
078        
079        public void setAxis2SAAJ() {
080            this.saajImpl = AXIS2_SAAJ_IMPL_ARTIFACT;
081        }
082        
083        public void setWsdlService(QName name) {
084            this.wsdlService = name;
085        }
086        
087        public QName getWsdlService() {
088            return this.wsdlService;        
089        }
090        
091        public void setWsdlPort(QName port) {
092            this.wsdlPort = port;
093        }
094        
095        public QName getWsdlPort() {
096            return this.wsdlPort;
097        }
098        
099        private URL[] getWsgenClasspath(DeploymentContext context) 
100            throws DeploymentException, MalformedURLException {
101            ArrayList<URL> jars = new ArrayList<URL>();
102            
103            DeploymentConfigurationManager cm = (DeploymentConfigurationManager)context.getConfigurationManager();
104            Collection<ListableRepository> repositories = cm.getRepositories();
105    
106            jars.add(getLocation(repositories, JAXB_API_ARTIFACT));
107            jars.add(getLocation(repositories, JAXB_IMPL_ARTIFACT));
108            jars.add(getLocation(repositories, JAXB_XJC_ARTIFACT));
109            jars.add(getLocation(repositories, JAXWS_TOOLS_ARTIFACT));
110            jars.add(getLocation(repositories, JAXWS_RT_ARTIFACT));
111            jars.add(getLocation(repositories, AXIS2_JAXWS_API_ARTIFACT));
112            jars.add(getLocation(repositories, AXIS2_SAAJ_API_ARTIFACT));
113            jars.add(getLocation(repositories, GERONIMO_ACTIVATION_SPEC_ARTIFACT));
114            jars.add(getLocation(repositories, GERONIMO_ANNOTATION_ARTIFACT));
115            jars.add(getLocation(repositories, GERONIMO_WS_METADATA_ARTIFACT));
116            jars.add(getLocation(repositories, GERONIMO_EJB_SPEC_ARTIFACT));
117            if (this.saajImpl != null) {
118                jars.add(getLocation(repositories, this.saajImpl));
119            }
120            jars.add(new File(getToolsJarLoc()).toURL());
121             
122            return jars.toArray(new URL[jars.size()]);        
123        }
124        
125        private static String getModuleClasspath(Module module, DeploymentContext context) throws DeploymentException {
126            File moduleBase = module.getEarContext().getBaseDir();
127            File moduleBaseDir = (moduleBase.isFile()) ? moduleBase.getParentFile() : moduleBase;
128            String baseDir = moduleBaseDir.getAbsolutePath();
129            List<String> moduleClassPath = context.getConfiguration().getClassPath();
130            StringBuilder classpath = new StringBuilder();
131            for (String s : moduleClassPath) {          
132                s = s.replace("/", File.separator);
133                            
134                classpath.append(baseDir);
135                classpath.append(File.separator);
136                classpath.append(s);
137                classpath.append(File.pathSeparator);
138            }
139            return classpath.toString();
140        }
141        
142        private static URL getLocation(Collection<ListableRepository> repositories, Artifact artifactQuery) throws DeploymentException, MalformedURLException {
143            File file = null;
144            
145            for (ListableRepository repository : repositories) {
146                SortedSet artifactSet = repository.list(artifactQuery);
147                // if we have exactly one artifact found
148                if (artifactSet.size() == 1) {
149                    file = repository.getLocation((Artifact) artifactSet.first());
150                    return file.getAbsoluteFile().toURL();
151                } else if (artifactSet.size() > 1) {// if we have more than 1 artifacts found use the latest one.
152                    file = repository.getLocation((Artifact) artifactSet.last());
153                    return file.getAbsoluteFile().toURL();
154                } 
155            }
156            if (file == null) {
157                throw new DeploymentException("Missing artifact in repositories: " + artifactQuery.toString());
158            }
159            return null;
160        }
161        
162        private static String getToolsJarLoc() throws DeploymentException {
163            //create a new File then check exists()
164            String jreHomePath = System.getProperty("java.home");
165            String javaHomePath = "";
166            int jreHomePathLength = jreHomePath.length();
167            if (jreHomePathLength > 0) {
168                int i = jreHomePath.substring(0, jreHomePathLength -1).lastIndexOf(java.io.File.separator);
169                javaHomePath = jreHomePath.substring(0, i);
170            }
171            File jdkhomelib = new File(javaHomePath + java.io.File.separator + "lib");
172            if (!jdkhomelib.exists()) {
173                throw new DeploymentException("Missing " + jdkhomelib.getAbsolutePath() 
174                        + ". This is required for wsgen to run. ");
175            }
176            else {
177                File tools = new File(jdkhomelib + java.io.File.separator + TOOLS);
178                if (!tools.exists()) {
179                    throw new DeploymentException("Missing tools.jar in" + jdkhomelib.getAbsolutePath() 
180                            + ". This is required for wsgen to run. ");                
181                } else {
182                    return tools.getAbsolutePath();
183                }               
184            }
185        }
186        
187        private static File toFile(URL url) {
188            if (url == null || !url.getProtocol().equals("file")) {
189                return null;
190            } else {
191                String filename = url.getFile().replace('/', File.separatorChar);
192                int pos =0;
193                while ((pos = filename.indexOf('%', pos)) >= 0) {
194                    if (pos + 2 < filename.length()) {
195                        String hexStr = filename.substring(pos + 1, pos + 3);
196                        char ch = (char) Integer.parseInt(hexStr, 16);
197                        filename = filename.substring(0, pos) + ch + filename.substring(pos + 3);
198                    }
199                }
200                return new File(filename);
201            }
202        }
203        
204        private String[] buildArguments(String sei, String classPath, File moduleBaseDir, PortInfo portInfo) {
205            List<String> arguments = new ArrayList<String>();
206            
207            arguments.add("-cp");
208            arguments.add(classPath);
209            arguments.add("-keep");
210            arguments.add("-wsdl");
211            arguments.add("-d");
212            arguments.add(moduleBaseDir.getAbsolutePath());
213            
214            QName serviceName = getWsdlService();
215            if (serviceName != null) {
216                arguments.add("-servicename");
217                arguments.add(serviceName.toString());
218            }
219    
220            QName portName = getWsdlPort();
221            if (portName != null) {
222                arguments.add("-portname");
223                arguments.add(portName.toString());
224            }
225            
226            arguments.add(sei);
227            
228            return arguments.toArray(new String[]{});
229        }
230        
231        private File getFirstWsdlFile(File baseDir) throws IOException {
232            LOG.debug("Looking for service wsdl file in " + baseDir.getAbsolutePath());
233            File[] files = baseDir.listFiles(new FileFilter() {
234                public boolean accept(File file) {
235                    return (file.isFile() && file.getName().endsWith(".wsdl"));
236                }
237            });
238    
239            if (files.length == 1) {
240                return files[0];
241            } else {
242                return null;
243            }
244        }
245        
246        private File findWsdlFile(File baseDir, PortInfo portInfo) throws IOException {
247            QName serviceName = getWsdlService();
248    
249            if (serviceName != null) {
250                // check if serviceName.wsdl locates at the baseDir, if so, return its path.
251                String wsdlFileName = serviceName.getLocalPart() + ".wsdl";
252                if (Character.isLowerCase(wsdlFileName.charAt(0))) {
253                    wsdlFileName = Character.toUpperCase(wsdlFileName.charAt(0)) + wsdlFileName.substring(1);
254                }
255                File wsdlFile = new File(baseDir, wsdlFileName);
256                if (wsdlFile.exists()) {
257                    return wsdlFile;
258                } else {
259                    return getFirstWsdlFile(baseDir);
260                }
261            } else {
262                return getFirstWsdlFile(baseDir);
263            }
264        }
265        
266        private static String getRelativeNameOrURL(File baseDir, File file) {
267            String basePath = baseDir.getAbsolutePath();
268            String path = file.getAbsolutePath();
269            
270            if (path.startsWith(basePath)) {
271                if (File.separatorChar == path.charAt(basePath.length())) {
272                    return path.substring(basePath.length() + 1);
273                } else {
274                    return path.substring(basePath.length());
275                }
276            } else {
277                return file.toURI().toString();
278            }
279        }
280        
281        private static File createTempDirectory(File baseDir) throws IOException {
282            Random rand = new Random();       
283            while(true) {
284                String dirName = String.valueOf(Math.abs(rand.nextInt()));        
285                File dir = new File(baseDir, dirName);
286                if (!dir.exists()) {
287                    if (!dir.mkdir()) {
288                        throw new IOException("Failed to create temporary directory: " + dir);
289                    } else {
290                        return dir;
291                    }
292                }
293            }               
294        }
295        
296        public String generateWsdl(Module module, 
297                                   String serviceClass, 
298                                   DeploymentContext context, 
299                                   PortInfo portInfo) throws DeploymentException {
300            //call wsgen tool to generate the wsdl file based on the bindingtype.
301            //let's set the outputDir as the module base directory in server repository.
302            File moduleBase = module.getEarContext().getBaseDir();
303            File moduleBaseDir = (moduleBase.isFile()) ? moduleBase.getParentFile() : moduleBase;
304            File baseDir;
305            
306            try {
307                baseDir = createTempDirectory(moduleBaseDir);
308            } catch (IOException e) {
309                throw new DeploymentException(e);
310            }
311            
312            URL[] urls;
313            StringBuilder classPath = new StringBuilder();
314            //let's figure out the classpath for wsgen tools
315            try {
316                 urls = getWsgenClasspath(context);
317            } catch (MalformedURLException e) {
318                throw new DeploymentException("unable to generate the wsdl file using wsgen. - unable to get the location of the required artifact(s).", e);
319            } 
320            //let's figure out the classpath string for the module and wsgen tools.
321            if (urls != null && urls.length > 0) {
322                for (int i = 0; i< urls.length; i++) {
323                    classPath.append(toFile(urls[i]).getAbsolutePath() + File.pathSeparator);
324                }
325            }
326            classPath.append(getModuleClasspath(module, context));
327    
328            //create arguments;
329            String[] arguments = buildArguments(serviceClass, classPath.toString(), baseDir, portInfo);
330            
331            try {
332                URLClassLoader loader = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
333                Class clazz = loader.loadClass("com.sun.tools.ws.spi.WSToolsObjectFactory");
334                Method method = clazz.getMethod("newInstance");
335                Object factory = method.invoke(null);
336                Method method2 = clazz.getMethod("wsgen", OutputStream.class, String[].class);
337                ByteArrayOutputStream os = new ByteArrayOutputStream();
338                Boolean result = (Boolean) method2.invoke(factory, os, arguments);
339                os.close();
340                
341                byte [] arr = os.toByteArray();
342                String wsgenOutput = new String(arr, 0, arr.length);
343                
344                if (LOG.isDebugEnabled()) {
345                    LOG.debug("wsgen output: " + wsgenOutput);
346                }
347                
348                if (result) {
349                    //check to see if the file is created.
350                    File wsdlFile = findWsdlFile(baseDir, portInfo);
351                    if (wsdlFile == null) {
352                        throw new DeploymentException("Unable to find the service wsdl file");
353                    }
354                    return getRelativeNameOrURL(moduleBase, wsdlFile);
355                } else {
356                    throw new DeploymentException("wsgen failed: " + wsgenOutput);
357                }
358            } catch (DeploymentException e) {
359                throw e;
360            } catch (Exception e) {
361                throw new DeploymentException("Unable to generate the wsdl file using wsgen.", e);
362            }
363        }
364    }