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 }