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 }