/** * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.server.webservices; import org.apache.openejb.AppContext; import org.apache.openejb.BeanContext; import org.apache.openejb.Injection; import org.apache.openejb.assembler.classic.AppInfo; import org.apache.openejb.assembler.classic.Assembler; import org.apache.openejb.assembler.classic.EjbJarInfo; import org.apache.openejb.assembler.classic.EnterpriseBeanInfo; import org.apache.openejb.assembler.classic.IdPropertiesInfo; import org.apache.openejb.assembler.classic.PortInfo; import org.apache.openejb.assembler.classic.ServletInfo; import org.apache.openejb.assembler.classic.SingletonBeanInfo; import org.apache.openejb.assembler.classic.StatelessBeanInfo; import org.apache.openejb.assembler.classic.WebAppInfo; import org.apache.openejb.assembler.classic.WsBuilder; import org.apache.openejb.assembler.classic.event.AssemblerAfterApplicationCreated; import org.apache.openejb.assembler.classic.event.AssemblerBeforeApplicationDestroyed; import org.apache.openejb.assembler.classic.event.NewEjbAvailableAfterApplicationCreated; import org.apache.openejb.assembler.classic.util.PojoUtil; import org.apache.openejb.assembler.classic.util.ServiceConfiguration; import org.apache.openejb.core.CoreContainerSystem; import org.apache.openejb.core.WebContext; import org.apache.openejb.core.webservices.PortAddressRegistry; import org.apache.openejb.core.webservices.PortAddressRegistryImpl; import org.apache.openejb.core.webservices.PortData; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.observer.Observes; import org.apache.openejb.server.SelfManaging; import org.apache.openejb.server.ServerService; import org.apache.openejb.server.ServiceException; import org.apache.openejb.server.httpd.HttpListener; import org.apache.openejb.server.httpd.HttpListenerRegistry; import org.apache.openejb.server.httpd.util.HttpUtil; import org.apache.openejb.spi.ContainerSystem; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.openejb.util.StringTemplate; import javax.naming.Context; import javax.xml.namespace.QName; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.Socket; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @SuppressWarnings("UnusedDeclaration") public abstract class WsService implements ServerService, SelfManaging { public static final Logger logger = Logger.getInstance(LogCategory.OPENEJB_WS, WsService.class); public static final String WS_ADDRESS_FORMAT = "openejb.wsAddress.format"; public static final String WS_FORCE_ADDRESS = "openejb.webservice.deployment.address"; private static final boolean OLD_WEBSERVICE_DEPLOYMENT = SystemInstance.get().getOptions().get("openejb.webservice.old-deployment", false); private StringTemplate wsAddressTemplate; private PortAddressRegistry portAddressRegistry; private CoreContainerSystem containerSystem; private Assembler assembler; private WsRegistry wsRegistry; private String realmName; private String transportGuarantee; private String authMethod; private String virtualHost; private final ConcurrentMap<AppInfo, Collection<BeanContext>> deployedApplications = new ConcurrentHashMap<AppInfo, Collection<BeanContext>>(); private final Set<WebAppInfo> deployedWebApps = new HashSet<WebAppInfo>(); private final Map<String, String> ejbLocations = new TreeMap<String, String>(); private final Map<String, String> ejbAddresses = new TreeMap<String, String>(); private final Map<String, String> servletAddresses = new TreeMap<String, String>(); private final Map<String, List<EndpointInfo>> addressesByApplication = new TreeMap<String, List<EndpointInfo>>(); public WsService() { final String format = SystemInstance.get().getOptions().get(WS_ADDRESS_FORMAT, "/{ejbDeploymentId}"); this.wsAddressTemplate = new StringTemplate(format); } public StringTemplate getWsAddressTemplate() { return wsAddressTemplate; } public void setWsAddressTemplate(final StringTemplate wsAddressTemplate) { this.wsAddressTemplate = wsAddressTemplate; } public String getRealmName() { return realmName; } public void setRealmName(final String realmName) { this.realmName = realmName; } public String getTransportGuarantee() { return transportGuarantee; } public void setTransportGuarantee(final String transportGuarantee) { this.transportGuarantee = transportGuarantee; } public String getAuthMethod() { return authMethod; } public void setAuthMethod(final String authMethod) { this.authMethod = authMethod; } public String getVirtualHost() { return virtualHost; } public void setVirtualHost(final String virtualHost) { this.virtualHost = virtualHost; } @Override public String getIP() { return "n/a"; } @Override public int getPort() { return -1; } @Override public void init(final Properties props) throws Exception { if (props == null) return; final String format = props.getProperty(WS_ADDRESS_FORMAT); if (format != null) { this.wsAddressTemplate = new StringTemplate(format); } realmName = props.getProperty("realmName"); transportGuarantee = props.getProperty("transportGuarantee"); authMethod = props.getProperty("authMethod"); virtualHost = props.getProperty("virtualHost", "localhost"); } @Override public void start() throws ServiceException { wsRegistry = SystemInstance.get().getComponent(WsRegistry.class); if (wsRegistry == null && SystemInstance.get().getComponent(HttpListenerRegistry.class) != null) { wsRegistry = new OpenEJBHttpWsRegistry(); } if (portAddressRegistry == null) { portAddressRegistry = new PortAddressRegistryImpl(); SystemInstance.get().setComponent(PortAddressRegistry.class, portAddressRegistry); } containerSystem = (CoreContainerSystem) SystemInstance.get().getComponent(ContainerSystem.class); portAddressRegistry = SystemInstance.get().getComponent(PortAddressRegistry.class); assembler = SystemInstance.get().getComponent(Assembler.class); SystemInstance.get().setComponent(WsService.class, this); if (assembler != null) { SystemInstance.get().addObserver(this); for (final AppInfo appInfo : assembler.getDeployedApplications()) { final AppContext appContext = containerSystem.getAppContext(appInfo.appId); deploy(new AssemblerAfterApplicationCreated(appInfo, appContext, null)); } } } @Override public void stop() throws ServiceException { if (assembler != null) { SystemInstance.get().removeObserver(this); for (final AppInfo appInfo : new ArrayList<AppInfo>(deployedApplications.keySet())) { undeploy(new AssemblerBeforeApplicationDestroyed(appInfo, null)); } assembler = null; if (SystemInstance.get().getComponent(WsService.class) == this) { SystemInstance.get().removeComponent(WsService.class); } } } protected abstract HttpListener createEjbWsContainer(URL moduleBaseUrl, PortData port, BeanContext beanContext, ServiceConfiguration configuration) throws Exception; protected abstract void destroyEjbWsContainer(String deploymentId); protected abstract HttpListener createPojoWsContainer(ClassLoader loader, URL moduleBaseUrl, PortData port, String serviceId, Class target, Context context, String contextRoot, Map<String, Object> bindings, ServiceConfiguration configuration) throws Exception; protected abstract void destroyPojoWsContainer(String serviceId); // handle webapp ejbs of ears - called before afterApplicationCreated for ear so dont add app to deployedApplications here public void newEjbToDeploy(final @Observes NewEjbAvailableAfterApplicationCreated event) { final AppInfo app = event.getApp(); if (!deployedApplications.containsKey(app)) { deployedApplications.putIfAbsent(app, new LinkedList<BeanContext>()); } deployApp(app, event.getBeanContexts()); } public void deploy(final @Observes AssemblerAfterApplicationCreated event) { final AppInfo appInfo = event.getApp(); if (deployedApplications.put(appInfo, new LinkedList<BeanContext>()) == null) { deployApp(appInfo, event.getContext().getBeanContexts()); } } private void deployApp(final AppInfo appInfo, final Collection<BeanContext> ejbs) { final Collection<BeanContext> alreadyDeployed = deployedApplications.get(appInfo); final Map<String, WebAppInfo> webContextByEjb = new HashMap<>(); for (final WebAppInfo webApp : appInfo.webApps) { for (final String ejb : webApp.ejbWebServices) { webContextByEjb.put(ejb, webApp); } } final Map<String, String> contextData = new HashMap<>(); contextData.put("appId", appInfo.path); for (final EjbJarInfo ejbJar : appInfo.ejbJars) { final Map<String, PortInfo> ports = new TreeMap<>(); for (final PortInfo port : ejbJar.portInfos) { ports.put(port.serviceLink, port); } URL moduleBaseUrl = null; if (ejbJar.path != null) { try { moduleBaseUrl = new File(ejbJar.path).toURI().toURL(); } catch (final MalformedURLException e) { logger.error("Invalid ejb jar location " + ejbJar.path, e); } } StringTemplate deploymentIdTemplate = this.wsAddressTemplate; if (ejbJar.properties.containsKey(WS_ADDRESS_FORMAT)) { final String format = ejbJar.properties.getProperty(WS_ADDRESS_FORMAT); logger.info("Using " + WS_ADDRESS_FORMAT + " '" + format + "'"); deploymentIdTemplate = new StringTemplate(format); } contextData.put("ejbJarId", ejbJar.moduleName); final String host = host(ejbJar, appInfo); for (final EnterpriseBeanInfo bean : ejbJar.enterpriseBeans) { if (bean instanceof StatelessBeanInfo || bean instanceof SingletonBeanInfo) { final BeanContext beanContext = containerSystem.getBeanContext(bean.ejbDeploymentId); if (beanContext == null || (ejbs != null && !ejbs.contains(beanContext))) { continue; } final PortInfo portInfo = ports.get(bean.ejbName); if (portInfo == null || alreadyDeployed.contains(beanContext)) continue; final ClassLoader old = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(beanContext.getClassLoader()); try { final PortData port = WsBuilder.toPortData(portInfo, beanContext.getInjections(), moduleBaseUrl, beanContext.getClassLoader()); final HttpListener container = createEjbWsContainer(moduleBaseUrl, port, beanContext, new ServiceConfiguration(beanContext.getProperties(), appInfo.services)); // generate a location if one was not assigned String location = port.getLocation(); if (location == null) { location = autoAssignWsLocation(bean, port, contextData, deploymentIdTemplate); } if (!location.startsWith("/")) location = "/" + location; ejbLocations.put(bean.ejbDeploymentId, location); final ClassLoader classLoader = beanContext.getClassLoader(); if (wsRegistry != null) { String auth = authMethod; String realm = realmName; String transport = transportGuarantee; if ("BASIC".equals(portInfo.authMethod) || "DIGEST".equals(portInfo.authMethod) || "CLIENT-CERT".equals(portInfo.authMethod)) { auth = portInfo.authMethod; realm = portInfo.realmName; transport = portInfo.transportGuarantee; } final WebAppInfo webAppInfo = webContextByEjb.get(bean.ejbClass); String context = webAppInfo != null ? webAppInfo.contextRoot : null; String moduleId = webAppInfo != null ? webAppInfo.moduleId : null; if (context == null && !OLD_WEBSERVICE_DEPLOYMENT) { context = ejbJar.moduleName; } final List<String> addresses = wsRegistry.addWsContainer(container, classLoader, context, host, location, realm, transport, auth, moduleId); alreadyDeployed.add(beanContext); // one of the registered addresses to be the canonical address final String address = HttpUtil.selectSingleAddress(addresses); if (address != null) { // register wsdl location portAddressRegistry.addPort(portInfo.serviceId, portInfo.wsdlService, portInfo.portId, portInfo.wsdlPort, portInfo.seiInterfaceName, address); setWsdl(container, address); logger.info("Webservice(wsdl=" + address + ", qname=" + port.getWsdlService() + ") --> Ejb(id=" + portInfo.portId + ")"); ejbAddresses.put(bean.ejbDeploymentId, address); addressesForApp(appInfo.appId).add(new EndpointInfo(address, port.getWsdlService(), beanContext.getBeanClass().getName())); } } } catch (final Throwable e) { logger.error("Error deploying JAX-WS Web Service for EJB " + beanContext.getDeploymentID(), e); } finally { Thread.currentThread().setContextClassLoader(old); } } } } if (ejbs == null || appInfo.webAppAlone) { for (final WebAppInfo webApp : appInfo.webApps) { afterApplicationCreated(appInfo, webApp); } } // else called because of ear case where new ejbs are deployed in webapps } private String host(final EjbJarInfo jar, final AppInfo app) { for (final WebAppInfo web : app.webApps) { if (jar.moduleId.equals(web.moduleId)) { if (web.host != null) { return web.host; } break; } } return virtualHost; } protected void setWsdl(final HttpListener listener, final String wsdl) { // no-op } private List<EndpointInfo> addressesForApp(final String appId) { if (!addressesByApplication.containsKey(appId)) { addressesByApplication.put(appId, new ArrayList<EndpointInfo>()); } return addressesByApplication.get(appId); } public void afterApplicationCreated(final AppInfo appInfo, final WebAppInfo webApp) { final WebContext webContext = containerSystem.getWebContextByHost(webApp.moduleId, webApp.host != null ? webApp.host : virtualHost); if (webContext == null) return; // if already deployed skip this webapp if (!deployedWebApps.add(webApp)) return; final Map<String, PortInfo> ports = new TreeMap<String, PortInfo>(); for (final PortInfo port : webApp.portInfos) { ports.put(port.serviceLink, port); } URL moduleBaseUrl = null; try { moduleBaseUrl = new File(webApp.path).toURI().toURL(); } catch (final MalformedURLException e) { logger.error("Invalid ejb jar location " + webApp.path, e); } Collection<IdPropertiesInfo> pojoConfiguration = null; // lazy init for (final ServletInfo servlet : webApp.servlets) { if (servlet.servletName == null) { continue; } final PortInfo portInfo = ports.get(servlet.servletName); if (portInfo == null) { continue; } final ClassLoader old = Thread.currentThread().getContextClassLoader(); final ClassLoader classLoader = webContext.getClassLoader(); Thread.currentThread().setContextClassLoader(classLoader); try { final Collection<Injection> injections = webContext.getInjections(); final Context context = webContext.getJndiEnc(); final Class target = classLoader.loadClass(servlet.servletClass); final Map<String, Object> bindings = webContext.getBindings(); final PortData port = WsBuilder.toPortData(portInfo, injections, moduleBaseUrl, classLoader); pojoConfiguration = PojoUtil.findPojoConfig(pojoConfiguration, appInfo, webApp); final HttpListener container = createPojoWsContainer(classLoader, moduleBaseUrl, port, portInfo.serviceLink, target, context, webApp.contextRoot, bindings, new ServiceConfiguration(PojoUtil.findConfiguration(pojoConfiguration, target.getName()), appInfo.services)); if (wsRegistry != null) { String auth = authMethod; String realm = realmName; String transport = transportGuarantee; if ("BASIC".equals(portInfo.authMethod) || "DIGEST".equals(portInfo.authMethod) || "CLIENT-CERT".equals(portInfo.authMethod)) { auth = portInfo.authMethod; realm = portInfo.realmName; transport = portInfo.transportGuarantee; } // give servlet a reference to the webservice container final List<String> addresses = wsRegistry.setWsContainer(container, classLoader, webApp.contextRoot, host(webApp), servlet, realm, transport, auth, webApp.moduleId); // one of the registered addresses to be the connonical address final String address = HttpUtil.selectSingleAddress(addresses); // add address to global registry portAddressRegistry.addPort(portInfo.serviceId, portInfo.wsdlService, portInfo.portId, portInfo.wsdlPort, portInfo.seiInterfaceName, address); setWsdl(container, address); logger.info("Webservice(wsdl=" + address + ", qname=" + port.getWsdlService() + ") --> Pojo(id=" + portInfo.portId + ")"); servletAddresses.put(webApp.moduleId + "." + servlet.servletName, address); addressesForApp(webApp.moduleId).add(new EndpointInfo(address, port.getWsdlService(), target.getName())); } } catch (final Throwable e) { logger.error("Error deploying CXF webservice for servlet " + portInfo.serviceLink, e); } finally { Thread.currentThread().setContextClassLoader(old); } } } private String host(final WebAppInfo webApp) { return webApp.host == null ? virtualHost : webApp.host; } public void undeploy(@Observes final AssemblerBeforeApplicationDestroyed event) { final AppInfo appInfo = event.getApp(); if (deployedApplications.remove(appInfo) != null) { for (final EjbJarInfo ejbJar : appInfo.ejbJars) { final Map<String, PortInfo> ports = new TreeMap<String, PortInfo>(); for (final PortInfo port : ejbJar.portInfos) { ports.put(port.serviceLink, port); } for (final EnterpriseBeanInfo enterpriseBean : ejbJar.enterpriseBeans) { if (enterpriseBean instanceof StatelessBeanInfo || enterpriseBean instanceof SingletonBeanInfo) { final PortInfo portInfo = ports.get(enterpriseBean.ejbName); if (portInfo == null) { continue; } final BeanContext beanContext = containerSystem.getBeanContext(enterpriseBean.ejbDeploymentId); if (beanContext == null) { continue; } // remove wsdl addresses from global registry final String address = ejbAddresses.remove(enterpriseBean.ejbDeploymentId); addressesForApp(appInfo.appId).remove(new EndpointInfo(address, portInfo.wsdlPort, beanContext.getBeanClass().getName())); if (address != null) { portAddressRegistry.removePort(portInfo.serviceId, portInfo.wsdlService, portInfo.portId, portInfo.seiInterfaceName); } // remove container from web server final String location = ejbLocations.get(enterpriseBean.ejbDeploymentId); if (this.wsRegistry != null && location != null) { this.wsRegistry.removeWsContainer(location, ejbJar.moduleId); } // destroy webservice container destroyEjbWsContainer(enterpriseBean.ejbDeploymentId); ejbLocations.remove(enterpriseBean.ejbDeploymentId); } } } for (final WebAppInfo webApp : appInfo.webApps) { deployedWebApps.remove(webApp); final Map<String, PortInfo> ports = new TreeMap<String, PortInfo>(); for (final PortInfo port : webApp.portInfos) { ports.put(port.serviceLink, port); } for (final ServletInfo servlet : webApp.servlets) { if (servlet.servletClass == null) { continue; } PortInfo portInfo = ports.remove(servlet.servletClass); if (portInfo == null) { portInfo = ports.remove(servlet.servletName); if (portInfo == null) { continue; } } // remove wsdl addresses from global registry final String address = servletAddresses.remove(webApp.moduleId + "." + servlet.servletName); if (address != null) { portAddressRegistry.removePort(portInfo.serviceId, portInfo.wsdlService, portInfo.portId, portInfo.seiInterfaceName); } // clear servlet's reference to the webservice container if (this.wsRegistry != null) { try { this.wsRegistry.clearWsContainer(webApp.contextRoot, host(webApp), servlet, webApp.moduleId); } catch (final IllegalArgumentException ignored) { // no-op } } // destroy webservice container destroyPojoWsContainer(portInfo.serviceLink); } addressesByApplication.remove(webApp.moduleId); } addressesByApplication.remove(appInfo.appId); } } private String autoAssignWsLocation(final EnterpriseBeanInfo bean, final PortData port, final Map<String, String> contextData, final StringTemplate template) { if (bean.properties.containsKey(WS_FORCE_ADDRESS)) { return bean.properties.getProperty(WS_FORCE_ADDRESS); } contextData.put("ejbDeploymentId", bean.ejbDeploymentId); contextData.put("ejbType", getEjbType(bean.type)); contextData.put("ejbClass", bean.ejbClass); contextData.put("ejbClass.simpleName", bean.ejbClass.substring(bean.ejbClass.lastIndexOf('.') + 1)); contextData.put("ejbName", bean.ejbName); contextData.put("portComponentName", port.getPortName().getLocalPart()); contextData.put("wsdlPort", port.getWsdlPort().getLocalPart()); contextData.put("wsdlService", port.getWsdlService().getLocalPart()); return template.apply(contextData); } public static String getEjbType(final int type) { if (type == EnterpriseBeanInfo.STATEFUL) { return "StatefulBean"; } else if (type == EnterpriseBeanInfo.STATELESS) { return "StatelessBean"; } else if (type == EnterpriseBeanInfo.SINGLETON) { return "SingletonBean"; } else if (type == EnterpriseBeanInfo.MANAGED) { return "ManagedBean"; } else if (type == EnterpriseBeanInfo.MESSAGE) { return "MessageDrivenBean"; } else if (type == EnterpriseBeanInfo.ENTITY) { return "StatefulBean"; } else { return "UnknownBean"; } } @Override public void service(final InputStream in, final OutputStream out) throws ServiceException, IOException { throw new UnsupportedOperationException("CxfService cannot be invoked directly"); } @Override public void service(final Socket socket) throws ServiceException, IOException { throw new UnsupportedOperationException("CxfService cannot be invoked directly"); } public Map<String, List<EndpointInfo>> getAddressesByApplication() { return addressesByApplication; } public static class EndpointInfo { public String address; public String portName; public String classname; public EndpointInfo(final String address, final QName portName, final String name) { this.address = address; this.classname = name; if (portName != null) { this.portName = portName.toString(); } else { this.portName = "null"; } } } }