/**
*
* 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.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.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
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 Set<AppInfo> deployedApplications = new HashSet<AppInfo>();
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()) {
afterApplicationCreated(new AssemblerAfterApplicationCreated(appInfo, null));
}
}
}
@Override
public void stop() throws ServiceException {
if (assembler != null) {
SystemInstance.get().removeObserver(this);
for (final AppInfo appInfo : new ArrayList<AppInfo>(deployedApplications)) {
beforeApplicationDestroyed(new AssemblerBeforeApplicationDestroyed(appInfo));
}
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) {
deployApp(event.getApp(), event.getBeanContexts());
}
public void afterApplicationCreated(final @Observes AssemblerAfterApplicationCreated event) {
final AppInfo appInfo = event.getApp();
if (deployedApplications.add(appInfo)) {
deployApp(appInfo, event.getDeployedEjbs());
}
}
private void deployApp(final AppInfo appInfo, final Collection<BeanContext> ejbs) {
final Map<String, String> webContextByEjb = new HashMap<String, String>();
for (final WebAppInfo webApp : appInfo.webApps) {
for (final String ejb : webApp.ejbWebServices) {
webContextByEjb.put(ejb, webApp.contextRoot);
}
}
final Map<String, String> contextData = new HashMap<String, String>();
contextData.put("appId", appInfo.path);
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);
}
URL moduleBaseUrl = null;
if (ejbJar.path != null) {
try {
moduleBaseUrl = new File(ejbJar.path).toURI().toURL();
} catch (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);
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)
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;
}
String context = webContextByEjb.get(bean.ejbClass);
if (context == null && !OLD_WEBSERVICE_DEPLOYMENT) {
context = ejbJar.moduleName;
}
final List<String> addresses = wsRegistry.addWsContainer(container, classLoader, context, virtualHost, location, realm, transport, auth);
// 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 (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
}
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.getWebContext(webApp.moduleId);
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 (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, virtualHost, servlet, realm, transport, auth);
// 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 (Throwable e) {
logger.error("Error deploying CXF webservice for servlet " + portInfo.serviceLink, e);
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
}
public void beforeApplicationDestroyed(
@Observes
final AssemblerBeforeApplicationDestroyed event) {
final AppInfo appInfo = event.getApp();
if (deployedApplications.remove(appInfo)) {
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);
}
// 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, virtualHost, servlet);
} catch (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";
}
}
}
}