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.File;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.net.MalformedURLException;
024    import java.net.URL;
025    import java.util.ArrayList;
026    import java.util.Enumeration;
027    import java.util.HashMap;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.jar.JarEntry;
031    import java.util.jar.JarFile;
032    
033    import javax.jws.WebService;
034    import javax.xml.ws.WebServiceProvider;
035    
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    import org.apache.geronimo.common.DeploymentException;
039    import org.apache.geronimo.deployment.DeploymentContext;
040    import org.apache.geronimo.deployment.service.EnvironmentBuilder;
041    import org.apache.geronimo.deployment.util.DeploymentUtil;
042    import org.apache.geronimo.gbean.AbstractName;
043    import org.apache.geronimo.gbean.GBeanData;
044    import org.apache.geronimo.gbean.GBeanInfo;
045    import org.apache.geronimo.j2ee.deployment.EARContext;
046    import org.apache.geronimo.j2ee.deployment.Module;
047    import org.apache.geronimo.j2ee.deployment.WebModule;
048    import org.apache.geronimo.j2ee.deployment.WebServiceBuilder;
049    import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
050    import org.apache.geronimo.jaxws.JAXWSUtils;
051    import org.apache.geronimo.jaxws.PortInfo;
052    import org.apache.geronimo.kernel.GBeanAlreadyExistsException;
053    import org.apache.geronimo.kernel.GBeanNotFoundException;
054    import org.apache.geronimo.kernel.classloader.JarFileClassLoader;
055    import org.apache.geronimo.kernel.repository.Environment;
056    import org.apache.geronimo.openejb.deployment.EjbModule;
057    import org.apache.geronimo.xbeans.javaee.ServletMappingType;
058    import org.apache.geronimo.xbeans.javaee.ServletType;
059    import org.apache.geronimo.xbeans.javaee.WebAppType;
060    import org.apache.openejb.assembler.classic.EnterpriseBeanInfo;
061    import org.apache.xbean.finder.ClassFinder;
062    
063    public abstract class JAXWSServiceBuilder implements WebServiceBuilder {
064        private static final Log LOG = LogFactory.getLog(JAXWSServiceBuilder.class);
065    
066        protected final Environment defaultEnvironment;
067    
068        public JAXWSServiceBuilder(Environment defaultEnvironment) {
069            this.defaultEnvironment = defaultEnvironment;
070        }
071    
072        protected String getKey() {
073            return getClass().getName();
074        }
075        
076        public void findWebServices(Module module,
077                                    boolean isEJB,
078                                    Map servletLocations,
079                                    Environment environment,
080                                    Map sharedContext) throws DeploymentException {
081            Map portMap = null;
082            String path = isEJB ? "META-INF/webservices.xml" : "WEB-INF/webservices.xml";
083            JarFile moduleFile = module.getModuleFile();
084            try {
085                URL wsDDUrl = DeploymentUtil.createJarURL(moduleFile, path);
086                InputStream in = wsDDUrl.openStream();
087                portMap = parseWebServiceDescriptor(in, wsDDUrl, moduleFile, isEJB, servletLocations);
088            } catch (IOException e) {
089                // webservices.xml does not exist
090                portMap = discoverWebServices(module, isEJB, servletLocations);
091            }
092    
093            if (portMap != null && !portMap.isEmpty()) {
094                EnvironmentBuilder.mergeEnvironments(environment, defaultEnvironment);
095                sharedContext.put(getKey(), portMap);
096            }
097    
098        }
099    
100        private Map<String, PortInfo> discoverWebServices(Module module,
101                                                          boolean isEJB,
102                                                          Map correctedPortLocations)
103                throws DeploymentException {
104            Map<String, PortInfo> map = new HashMap<String, PortInfo>();        
105            if (isEJB) {            
106                discoverEJBWebServices(module, correctedPortLocations, map);
107            } else {          
108                discoverPOJOWebServices(module, correctedPortLocations, map);
109            }        
110            return map;
111        }
112        
113        private void discoverPOJOWebServices(Module module,
114                                             Map correctedPortLocations,
115                                             Map<String, PortInfo> map) 
116            throws DeploymentException {
117            ClassLoader classLoader = module.getEarContext().getClassLoader();
118            WebAppType webApp = (WebAppType) module.getSpecDD();
119    
120            // find web services
121            ServletType[] servletTypes = webApp.getServletArray();
122    
123            if (webApp.getDomNode().getChildNodes().getLength() == 0) {
124                // web.xml not present (empty really), discover annotated
125                // classes and update DD
126                List<Class> services = discoverWebServices(module.getModuleFile(), false);
127                String contextRoot = ((WebModule) module).getContextRoot();
128                for (Class service : services) {
129                    // skip interfaces and such
130                    if (!JAXWSUtils.isWebService(service)) {
131                        continue;
132                    }
133    
134                    LOG.debug("Discovered POJO Web Service: " + service.getName());
135                    
136                    // add new <servlet/> element
137                    ServletType servlet = webApp.addNewServlet();
138                    servlet.addNewServletName().setStringValue(service.getName());
139                    servlet.addNewServletClass().setStringValue(service.getName());
140    
141                    // add new <servlet-mapping/> element
142                    String location = "/" + JAXWSUtils.getServiceName(service);
143                    ServletMappingType servletMapping = webApp.addNewServletMapping();
144                    servletMapping.addNewServletName().setStringValue(service.getName());
145                    servletMapping.addNewUrlPattern().setStringValue(location);
146    
147                    // map service
148                    PortInfo portInfo = new PortInfo();
149                    portInfo.setLocation(contextRoot + location);
150                    map.put(service.getName(), portInfo);
151                }
152            } else {
153                // web.xml present, examine servlet classes and check for web
154                // services
155                for (ServletType servletType : servletTypes) {
156                    String servletName = servletType.getServletName().getStringValue().trim();
157                    if (servletType.isSetServletClass()) {
158                        String servletClassName = servletType.getServletClass().getStringValue().trim();
159                        try {
160                            Class servletClass = classLoader.loadClass(servletClassName);
161                            if (JAXWSUtils.isWebService(servletClass)) {
162                                LOG.debug("Found POJO Web Service: " + servletName);
163                                PortInfo portInfo = new PortInfo();
164                                map.put(servletName, portInfo);
165                            }
166                        } catch (Exception e) {
167                            throw new DeploymentException("Failed to load servlet class "
168                                                          + servletClassName, e);
169                        }
170                    }
171                }
172    
173                // update web service locations
174                for (Map.Entry entry : map.entrySet()) {
175                    String servletName = (String) entry.getKey();
176                    PortInfo portInfo = (PortInfo) entry.getValue();
177    
178                    String location = (String) correctedPortLocations.get(servletName);
179                    if (location != null) {
180                        portInfo.setLocation(location);
181                    }
182                }
183            }
184        }       
185                       
186        private void discoverEJBWebServices(Module module,
187                                            Map correctedPortLocations,
188                                            Map<String, PortInfo> map) 
189            throws DeploymentException {
190            ClassLoader classLoader = module.getEarContext().getClassLoader();
191            EjbModule ejbModule = (EjbModule) module;
192            for (EnterpriseBeanInfo bean : ejbModule.getEjbJarInfo().enterpriseBeans) {
193                if (bean.type != EnterpriseBeanInfo.STATELESS) {
194                    continue;
195                }            
196                try {
197                    Class ejbClass = classLoader.loadClass(bean.ejbClass);
198                    if (JAXWSUtils.isWebService(ejbClass)) {
199                        LOG.debug("Found EJB Web Service: " + bean.ejbName);
200                        PortInfo portInfo = new PortInfo();
201                        String location = (String) correctedPortLocations.get(bean.ejbName);
202                        if (location == null) {
203                            // set default location, i.e. /@WebService.serviceName/@WebService.name
204                            location = "/" + JAXWSUtils.getServiceName(ejbClass) + "/" + JAXWSUtils.getName(ejbClass);
205                        }
206                        portInfo.setLocation(location);
207                        map.put(bean.ejbName, portInfo);
208                    }
209                } catch (Exception e) {
210                    throw new DeploymentException("Failed to load ejb class "
211                                                  + bean.ejbName, e);
212                }
213            }
214        }
215        
216        /**
217         * Returns a list of any classes annotated with @WebService or
218         * @WebServiceProvider annotation.
219         */
220        private List<Class> discoverWebServices(JarFile moduleFile,
221                                                boolean isEJB)                                                      
222                throws DeploymentException {
223            LOG.debug("Discovering web service classes");
224    
225            File tmpDir = null;
226            List<URL> urlList = new ArrayList<URL>();
227            if (isEJB) {
228                File jarFile = new File(moduleFile.getName());
229                try {
230                    urlList.add(jarFile.toURL());
231                } catch (MalformedURLException e) {
232                    // this should not happen
233                    throw new DeploymentException(e);
234                }
235            } else {
236                /*
237                 * Can't get ClassLoader to load nested Jar files, so
238                 * unpack the module Jar file and discover all nested Jar files
239                 * within it and the classes/ directory.
240                 */
241                try {
242                    tmpDir = DeploymentUtil.createTempDir();
243                    /*
244                     * This is needed becuase DeploymentUtil.unzipToDirectory()
245                     * always closes the passed JarFile.
246                     */
247                    JarFile module = new JarFile(moduleFile.getName());
248                    DeploymentUtil.unzipToDirectory(module, tmpDir);
249                } catch (IOException e) {
250                    if (tmpDir != null) {
251                        DeploymentUtil.recursiveDelete(tmpDir);
252                    }
253                    throw new DeploymentException("Failed to expand the module archive", e);
254                }
255    
256                // create URL list
257                Enumeration<JarEntry> jarEnum = moduleFile.entries();
258                while (jarEnum.hasMoreElements()) {
259                    JarEntry entry = jarEnum.nextElement();
260                    String name = entry.getName();
261                    if (name.equals("WEB-INF/classes/")) {
262                        // ensure it is first
263                        File classesDir = new File(tmpDir, "WEB-INF/classes/");
264                        try {
265                            urlList.add(0, classesDir.toURL());
266                        } catch (MalformedURLException e) {
267                            // this should not happen, ignore
268                        }
269                    } else if (name.startsWith("WEB-INF/lib/")
270                            && name.endsWith(".jar")) {
271                        File jarFile = new File(tmpDir, name);
272                        try {
273                            urlList.add(jarFile.toURL());
274                        } catch (MalformedURLException e) {
275                            // this should not happen, ignore
276                        }
277                    }
278                }
279            }
280            
281            URL[] urls = urlList.toArray(new URL[] {});
282            JarFileClassLoader tempClassLoader = new JarFileClassLoader(null, urls, this.getClass().getClassLoader());
283            ClassFinder classFinder = new ClassFinder(tempClassLoader, urlList);
284    
285            List<Class> classes = new ArrayList<Class>();
286    
287            classes.addAll(classFinder.findAnnotatedClasses(WebService.class));
288            classes.addAll(classFinder.findAnnotatedClasses(WebServiceProvider.class));       
289    
290            tempClassLoader.destroy();
291    
292            if (tmpDir != null) {
293                DeploymentUtil.recursiveDelete(tmpDir);
294            }
295    
296            return classes;
297        }
298    
299        protected abstract Map<String, PortInfo> parseWebServiceDescriptor(InputStream in,
300                                                                           URL wsDDUrl,
301                                                                           JarFile moduleFile,
302                                                                           boolean isEJB,
303                                                                           Map correctedPortLocations)
304                throws DeploymentException;
305    
306        public boolean configurePOJO(GBeanData targetGBean,
307                                     String servletName,
308                                     Module module,
309                                     String servletClassName,
310                                     DeploymentContext context)
311                throws DeploymentException {
312            Map sharedContext = ((WebModule) module).getSharedContext();
313            Map portInfoMap = (Map) sharedContext.get(getKey());
314            if (portInfoMap == null) {
315                // not ours
316                return false;
317            }
318            PortInfo portInfo = (PortInfo) portInfoMap.get(servletName);
319            if (portInfo == null) {
320                // not ours
321                return false;
322            }
323    
324            // verify that the class is loadable and is a JAX-WS web service
325            ClassLoader classLoader = context.getClassLoader();
326            Class servletClass = loadClass(servletClassName, classLoader);
327            if (!JAXWSUtils.isWebService(servletClass)) {
328                return false;
329            }
330            
331            Map componentContext = null;
332            try {
333                GBeanData moduleGBean = context.getGBeanInstance(context.getModuleName());
334                componentContext = (Map)moduleGBean.getAttribute("componentContext");
335            } catch (GBeanNotFoundException e) {
336                LOG.warn("ModuleGBean not found. JNDI resource injection will not work.");
337            }
338    
339            String location = portInfo.getLocation();
340            LOG.info("Configuring JAX-WS Web Service: " + servletName + " at " + location);
341    
342            AbstractName containerFactoryName = context.getNaming().createChildName(targetGBean.getAbstractName(), getContainerFactoryGBeanInfo().getName(), NameFactory.GERONIMO_SERVICE);
343            GBeanData containerFactoryData = new GBeanData(containerFactoryName, getContainerFactoryGBeanInfo());
344            containerFactoryData.setAttribute("portInfo", portInfo);
345            containerFactoryData.setAttribute("endpointClassName", servletClassName);
346            containerFactoryData.setAttribute("componentContext", componentContext);
347            try {
348                context.addGBean(containerFactoryData);
349            } catch (GBeanAlreadyExistsException e) {
350                throw new DeploymentException("Could not add web service container factory gbean", e);
351            }
352    
353            targetGBean.setReferencePattern("WebServiceContainerFactory", containerFactoryName);
354            // our web container does not use that property
355            targetGBean.setAttribute("pojoClassName", "java.lang.Object");
356    
357            if (context instanceof EARContext) {
358                containerFactoryData.setReferencePattern("TransactionManager",
359                                                         ((EARContext)context).getTransactionManagerName());
360            }
361    
362            initialize(containerFactoryData, servletClass, portInfo, module);
363            
364            return true;
365        }
366    
367        protected abstract GBeanInfo getContainerFactoryGBeanInfo();
368    
369        public boolean configureEJB(GBeanData targetGBean,
370                                    String ejbName,
371                                    Module module,
372                                    Map sharedContext,
373                                    ClassLoader classLoader)
374                throws DeploymentException {        
375            Map portInfoMap = (Map) sharedContext.get(getKey());
376            if (portInfoMap == null) {
377                // not ours
378                return false;
379            }
380            PortInfo portInfo = (PortInfo) portInfoMap.get(ejbName);
381            if (portInfo == null) {
382                // not ours
383                return false;
384            }
385           
386            String beanClassName = (String)targetGBean.getAttribute("ejbClass");
387            // verify that the class is loadable and is a JAX-WS web service
388            Class beanClass = loadClass(beanClassName, classLoader);
389            if (!JAXWSUtils.isWebService(beanClass)) {
390                return false;
391            }
392            
393            String location = portInfo.getLocation();
394            if (location == null) {                   
395                throw new DeploymentException("Endpoint URI for EJB WebService is missing");
396            }
397    
398            LOG.info("Configuring EJB JAX-WS Web Service: " + ejbName + " at " + location);
399            
400            targetGBean.setAttribute("portInfo", portInfo);
401            
402            initialize(targetGBean, beanClass, portInfo, module);
403            
404            return true;
405        }
406        
407        protected void initialize(GBeanData targetGBean, Class wsClass, PortInfo info, Module module) 
408            throws DeploymentException {
409        }
410        
411        Class<?> loadClass(String className, ClassLoader loader) throws DeploymentException {
412            try {
413                return loader.loadClass(className);
414            } catch (ClassNotFoundException ex) {
415                throw new DeploymentException("Unable to load Web Service class: " + className, ex);
416            }
417        }
418    }