/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006, JBoss Inc.
*/
package org.jboss.soa.esb.listeners.gateway;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLEncoder;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.LinkRef;
import javax.naming.NamingException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityCollection;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.ContextConfig;
import org.apache.log4j.Logger;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.naming.Util;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.addressing.EPR;
import org.jboss.soa.esb.addressing.eprs.HTTPEpr;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.helpers.KeyValuePair;
import org.jboss.soa.esb.listeners.ListenerTagNames;
import org.jboss.soa.esb.listeners.gateway.http.HttpRequestWrapper;
import org.jboss.soa.esb.listeners.lifecycle.AbstractManagedLifecycle;
import org.jboss.soa.esb.listeners.lifecycle.ManagedLifecycleException;
import org.jboss.soa.esb.listeners.message.UncomposedMessageDeliveryAdapter;
import org.jboss.soa.esb.services.registry.RegistryException;
import org.jboss.soa.esb.services.registry.RegistryFactory;
import org.jboss.soa.esb.util.ClassUtil;
/**
* Tomcat Listener provides the functionality to pass the http request
* to ESB service .
* <p> This class will generate {@link org.apache.catalina.connector.Connector},
* {@link org.apache.catalina.core.StandardHost} and {@link org.apache.catalina.core.StandardContext}
* for user defined http port, host and context. These generated tomcat components will be
* attached to jboss embedded tomcat(aka jboss web). You can monitor and control these new generated
* components through jboss JMX console. When this listener is stopped, these components will be
* destroyed automatically.
*
* <p>The different tomcat listener can be started in same port with different context.for example:
* <code> http://localhost:8765/jbossesb/servicecategory/servicename </code>
*
* <p>This class uses the {@link HttpMessageComposer}
* by default.
* @see org.jboss.soa.esb.listeners.gateway.HttpMessageComposer
* @see org.jboss.soa.esb.listeners.gateway.HttpDispatchServlet
* @author <a href="mailto:ema@redhat.com">Jim Ma</a>
* @deprecated
*/
public class HttpGatewayListener extends AbstractManagedLifecycle {
private static Logger logger = Logger.getLogger(HttpGatewayListener.class);
/**The tag used to read the host value from configuration */
public static final String SERVER_HOST_TAG = "http_host";
/**The tag used to read the port value from configuration */
public static final String SERVER_PORT_TAG = "http_port";
/**The tag used to read the context value from configuration */
public static final String REQUEST_CONTEXT_TAG = "http_context";
/**The tag used to read the dispatch servlet class name value from configuration */
public static final String DISPATCH_SERVLET_CLASS = "dispatch_servlet";
/**Allow http method config attribute name*/
public static final String ALLOW_HTTP_METHOD = "allowHttpMethod";
/**Auth method config attribute name */
public static final String AUTH_METHOD = "authMethod";
/**Security domain config attribute name */
public static final String SECURITY_DOMAIN = "securityDomain";
/**Security role config attribute name */
public static final String SECURITY_ROLE = "securityRole";
/**Http host value*/
public String host = null;
/**Http address value*/
public String address = null;
/**Http port value*/
public String port = null;
/** Default max threads */
public String maxThreads = "5";
/**listener contenxt */
public String httpContext = null;
/** Endpoint reference that presents this listener */
private EPR endpointReference = null;
/**Service category*/
private String serviceCategory = null;
/**Service name*/
private String serviceName = null;
/**The new created tomcat standard context, it reprents a web app*/
private StandardContext ctx = null;
/** The default servlet used to dispatch the http request to ESB service*/
private String dispatchServletClassName = HttpDispatchServlet.class.getName();
/** Protocol value */
private Object protocol = "http";
/** Imply if the port number is the jboss.web used, if yes , the new created context will be attched to jboss web Servlet engine */
private boolean useJBossWebServletEngine = false;
/**Constuct the TomcatGatewyListner
* @param config The listener configuration
* @throws ConfigurationException Exception during construction
*/
@SuppressWarnings("unchecked")
public HttpGatewayListener(ConfigTree config) throws ConfigurationException {
super(config);
host = config.getAttribute(SERVER_HOST_TAG);
try {
address = InetAddress.getByName(host).getHostAddress();
}catch (Exception e) {
throw new ConfigurationException("Invalid host configuration");
}
port = config.getAttribute(SERVER_PORT_TAG);
httpContext = config.getAttribute(REQUEST_CONTEXT_TAG);
serviceCategory = config.getAttribute(ListenerTagNames.TARGET_SERVICE_CATEGORY_TAG);
serviceName = config.getAttribute(ListenerTagNames.TARGET_SERVICE_NAME_TAG);
if (config.getAttribute(DISPATCH_SERVLET_CLASS) != null) {
dispatchServletClassName = config.getAttribute(DISPATCH_SERVLET_CLASS);
}
boolean synchronous = !config.getAttribute("synchronous", "true").equalsIgnoreCase("false");
if (!synchronous) {
String asyncResponse = config.getAttribute("asyncResponse");
if (asyncResponse != null) {
if (ClassUtil.getResourceAsStream(asyncResponse, getClass()) == null) {
throw new ConfigurationException("Asynchronous response resource file '" + asyncResponse
+ "' not found on classpath.");
}
}
}
//validate allow http method configuration
if (config.getAttribute(ALLOW_HTTP_METHOD) != null) {
String allowMethods = config.getAttribute(ALLOW_HTTP_METHOD);
String[] methods = allowMethods.split(",");
List<String> standardMesthods = new ArrayList<String>();
standardMesthods.add("GET");
standardMesthods.add("POST");
standardMesthods.add("DELETE");
standardMesthods.add("PUT");
standardMesthods.add("OPTIONS");
standardMesthods.add("HEAD");
standardMesthods.add("TRACE");
for (String method : methods) {
if (!standardMesthods.contains(method.toUpperCase())) {
throw new ConfigurationException("Invalid allow http method configuration, please specify the specify method list with comma-separated(e.g. POST,GET,PUT,DELETE");
}
}
}
try {
Set ports = HttpServerDelegate.getInstance().queryObjects("jboss.web:port="+ port+",type=Connector,*");
if (ports.size() > 0) {
//When this gateway stared on JBoss default port 8080 or 80, the configured host will be ignored
Set contexts = HttpServerDelegate.getInstance().queryObjects("jboss.web:host=localhost" + ",path=" + httpContext + ",*");
if (contexts.size() > 0) {
throw new ConfigurationException("There is already an http context named " + httpContext + ", choose another one");
}
logger.info("This http gateway listener [" + config.getAttribute(ListenerTagNames.NAME_TAG) + "] will be started on JBoss default port " + port + " and the configured host will be ignored.");
//the created context will be attached jboss.web domain
useJBossWebServletEngine = true;
} else {
//if the port is not the jboss.web used, check if the http context name is duplicate
Set contexts = HttpServerDelegate.getInstance().queryObjects(HttpServerDelegate.DOMAIN_NAME + ":host=" + HttpServerDelegate.defaultVHost + ",path=" + httpContext + ",*");
if (contexts.size() > 0) {
throw new ConfigurationException("There is already an http context named " + httpContext + ", choose another one");
}
}
} catch (Exception e) {
throw new ConfigurationException(e);
}
//Check the http security configuration
if (config.getAttribute(AUTH_METHOD) != null) {
if (config.getAttribute(SECURITY_DOMAIN) == null) {
throw new ConfigurationException("Security domain configuration for this context not found for http authentication method " + config.getAttribute(AUTH_METHOD));
}
if (config.getAttribute(SECURITY_ROLE) == null) {
throw new ConfigurationException("Security role configuration for this context not found for http authentication method " + config.getAttribute(AUTH_METHOD));
}
}
}
/*
* Start the Tomcat listner
*
* @see org.jboss.soa.esb.listeners.lifecycle.AbstractManagedLifecycle#doStart()
*/
protected void doStart() throws ManagedLifecycleException {
try {
startHttpServer();
} catch (Exception e) {
throw new ManagedLifecycleException(
"Failed to start Http gateway listener", e);
}
try {
registerEndpoint();
} catch (Throwable t) {
logger.error("Unable to register service endpoint '" + endpointReference.getAddr().getAddress()
+ "' for service '" + serviceCategory + ":" + serviceName + "'. Stopping Http Listener Server...", t);
try {
stopHttpServer();
} catch (Exception e) {
throw new ManagedLifecycleException(
"Failed to stop Http gateway listener", e);
}
}
}
/*
* Stop the tomcat listener
*
* @see org.jboss.soa.esb.listeners.lifecycle.AbstractManagedLifecycle#doStop()
*/
protected void doStop() throws ManagedLifecycleException {
unregisterEndpoint();
try {
stopHttpServer();
} catch (Exception e) {
throw new ManagedLifecycleException(
"Failed to stop Http gateway listener", e);
}
}
/*
* Initialize the tomcat listener
*
* @see org.jboss.soa.esb.listeners.lifecycle.AbstractManagedLifecycle#doInitialise()
*/
protected void doInitialise() throws ManagedLifecycleException {
}
/*
* Destory the tomcat listener
*
* @see org.jboss.soa.esb.listeners.lifecycle.AbstractManagedLifecycle#doDestroy()
*/
protected void doDestroy() throws ManagedLifecycleException {
}
/**
* Start the tomcat http server.It will check if it really needs to create
* Tomcat connector and host. Then add these new tomcat component to tomcat Engine
* tree through JMX server.
* @throws Exception For error during start tomcat context
*/
@SuppressWarnings("unchecked")
protected void startHttpServer() throws Exception {
ctx = new StandardContext();
final URL[] urls = new URL[]{};
URLClassLoader urlClassLoader = AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() {
public URLClassLoader run() {
return new URLClassLoader(urls, getClass().getClassLoader());
}
});
WebappLoader loader = new WebappLoader(urlClassLoader);
ctx.setLoader(loader);
ContextConfig ctxCfg = new ContextConfig();
ctx.addLifecycleListener(ctxCfg);
ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");
ctx.setPath(httpContext);
ctx.setDocBase(".");
ClassLoader oldloader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(urlClassLoader);
initWebappDefaults(ctx);
Thread.currentThread().setContextClassLoader(oldloader);
String encodedAddr = URLEncoder.encode("/" + address);
String connectorName = HttpServerDelegate.DOMAIN_NAME + ":address=" + encodedAddr + ",port=" + port + ",type=Connector";
if (!useJBossWebServletEngine) {
//Create default Tomcat standard vHost "localhost"
HttpServerDelegate.getInstance().createHost(HttpServerDelegate.defaultVHost);
List<KeyValuePair> properties = getConfig().childPropertyList();
if (getConfig().getAttribute(ListenerTagNames.MAX_THREADS_TAG) != null) {
maxThreads = getConfig().getAttribute(ListenerTagNames.MAX_THREADS_TAG);
}
properties.add(new KeyValuePair("maxThreads", maxThreads));
HttpServerDelegate.getInstance().createConnector(address, port, properties);
HttpServerDelegate.getInstance().addContext(HttpServerDelegate.defaultVHost, ctx);
} else {
//add it to jboss.web servlet engine
connectorName = "jboss.web:port=" + port + ",type=Connector,*";
HttpServerDelegate.getInstance().addContext(new ObjectName("jboss.web:host=localhost,type=Host"), ctx);
}
Set connectors = HttpServerDelegate.getInstance().queryObjects(connectorName);
if (connectors.isEmpty()) {
throw new javax.management.InstanceNotFoundException("ObjectName: "+ connectorName + " Not found");
}
ObjectName obj = (ObjectName)connectors.iterator().next();
try {
protocol = MBeanServerLocator.locateJBoss().getAttribute(obj, "scheme");
} catch (Exception e) {
//do nothing and use default http
protocol = "http";
}
}
/**Register this endpoint
* @throws ConfigurationException For configuration error
* @throws RegistryException For registry error when register this EPR
*/
private void registerEndpoint() throws ConfigurationException, RegistryException {
try {
endpointReference = new HTTPEpr(new URI(protocol + "://"+ address + ":" + port + this.httpContext));
endpointReference.getAddr().addExtension("is-gateway", "true");
} catch (Exception e) {
throw new RegistryException("Tomcat gateway listener registration failed", e);
}
String serviceDescription = getConfig().getAttribute(ListenerTagNames.SERVICE_DESCRIPTION_TAG);
RegistryFactory.getRegistry().registerEPR(serviceCategory, serviceName, serviceDescription,
endpointReference, endpointReference.getAddr().getAddress());
}
/**Unregister this endpoint
*/
private void unregisterEndpoint() {
try {
RegistryFactory.getRegistry().unRegisterEPR(serviceCategory, serviceName, endpointReference);
} catch (Throwable t) {
logger.error("Unable to unregister service endpoint '" + endpointReference.getAddr().getAddress()
+ "' for service '" + serviceCategory + ":" + serviceName + "'.", t);
}
}
/**Stop tomcat server. It will destroy the generated tomcat connector or host .
* Before do it , it will check if the reference count for it is zero .When there
* is no tomcat listener uses the generated host or connector, it will destroy it
* @throws Exception For errors during stop tomcat connector or host
*/
@SuppressWarnings("unchecked")
public void stopHttpServer() throws Exception {
//Destroy the created context
if (!useJBossWebServletEngine) {
HttpServerDelegate.getInstance().destroyContext(HttpServerDelegate.defaultVHost, address, port, httpContext);
} else {
Set<ObjectName> contexts = HttpServerDelegate.getInstance().queryObjects("jboss.web:j2eeType=WebModule,name=//localhost" + httpContext + ",*");
for (ObjectName obName : contexts) {
HttpServerDelegate.getInstance().destroyContext(obName);
}
}
}
/**Get the uncomposed message delivery dapater
* @return uncomposed message delivery adapter
* @throws ConfigurationException For configuation error
*/
protected UncomposedMessageDeliveryAdapter createDeliveryAdapter()
throws ConfigurationException {
return UncomposedMessageDeliveryAdapter.getGatewayDeliveryAdapter(
getConfig(),
new HttpMessageComposer<HttpRequestWrapper>());
}
/**Initialize the StandardContext.By default it will uses
* {@link HttpDispatchServlet} as default servlet mapping
* @param ctx Created tomcat standard context
* @throws Exception For errors during initialization
*/
protected void initWebappDefaults(StandardContext ctx) throws Exception {
if (isHttpAuthConfiured()) {
InitialContext iniCtx = new InitialContext();
Context envCtx;
try {
envCtx = (Context) iniCtx.lookup("java:comp/env");
} catch (NamingException e) {
envCtx = (Context) iniCtx.lookup("java:comp");
envCtx = envCtx.createSubcontext("env");
}
String securityDomain = getConfig().getAttribute(SECURITY_DOMAIN);
Util.bind(envCtx, "security/securityMgr", new LinkRef(securityDomain));
Util.bind(envCtx, "security/realmMapping", new LinkRef(securityDomain));
Util.bind(envCtx, "security/security-domain", new LinkRef(securityDomain));
Util.bind(envCtx, "security/subject", new LinkRef(securityDomain));
}
StandardWrapper sw = (StandardWrapper) ctx.createWrapper();
sw.setServletClass(dispatchServletClassName);
sw.setName("default");
ctx.addChild(sw);
ctx.getServletContext().setAttribute("config", this.getConfig());
sw.addInitParameter("listings", "false");
sw.setLoadOnStartup(1);
try {
sw.start();
} catch (LifecycleException e) {
logger.error("Unexpected error when start the default servlet");
throw e;
}
ctx.addServletMapping("/", "default");
//Add security support
if (isHttpAuthConfiured()) {
String securityRole = this.getConfig().getAttribute(SECURITY_ROLE);
SecurityConstraint constraint = new SecurityConstraint();
constraint.addAuthRole(securityRole);
SecurityCollection collection = new SecurityCollection("all");
collection.addPattern("/*");
constraint.addCollection(collection);
ctx.addConstraint(constraint);
ctx.addSecurityRole(securityRole);
String authMethod = this.getConfig().getAttribute(AUTH_METHOD);
LoginConfig config = new LoginConfig(authMethod, securityRole,
null, null);
ctx.setLoginConfig(config);
}
}
private boolean isHttpAuthConfiured() {
if (getConfig().getAttribute(AUTH_METHOD) != null) {
return true;
}
return false;
}
}