/*
* JBoss, Home of Professional Open Source.
* Copyright 2010, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY 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 along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.osgi.httpservice;
import static org.jboss.as.osgi.httpservice.WebLogger.WEB_LOGGER;
import java.io.File;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Map;
import java.util.WeakHashMap;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.jboss.as.osgi.OSGiMessages;
import org.jboss.as.osgi.httpservice.HttpServiceFactory.GlobalRegistry;
import org.jboss.as.osgi.httpservice.HttpServiceFactory.Registration;
import org.jboss.as.osgi.httpservice.HttpServiceFactory.Registration.Type;
import org.jboss.as.server.ServerEnvironment;
import org.jboss.as.web.host.ApplicationContextWrapper;
import org.jboss.as.web.host.CommonWebServer;
import org.jboss.as.web.host.ServletBuilder;
import org.jboss.as.web.host.WebDeploymentBuilder;
import org.jboss.as.web.host.WebDeploymentController;
import org.jboss.as.web.host.WebHost;
import org.osgi.framework.Bundle;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
/**
* An {@link HttpService} implementation
*
* @author Thomas.Diesler@jboss.com
* @author David Bosschaert
* @since 19-Jul-2012
*/
final class HttpServiceImpl implements HttpService {
private final GlobalRegistry registry;
private final ServerEnvironment serverEnvironment;
private final CommonWebServer webServer;
private final WebHost virtualHost;
private final Bundle bundle;
// This map holds the shared ApplicationContexts to be used with the associated HttpContext.
// It is a WeakHashMap which means that the ApplicationContexts are remembered for as long
// as the HttpContext exists.
private final Map<HttpContext, ShareableContextWrapper> contexts = new WeakHashMap<HttpContext, ShareableContextWrapper>();
HttpServiceImpl(ServerEnvironment serverEnvironment, CommonWebServer webServer, WebHost virtualHost, Bundle bundle) {
this.registry = GlobalRegistry.INSTANCE;
this.virtualHost = virtualHost;
this.webServer = webServer;
this.serverEnvironment = serverEnvironment;
this.bundle = bundle;
}
@Override
@SuppressWarnings("rawtypes")
public void registerServlet(String alias, Servlet servlet, Dictionary initparams, HttpContext httpContext) throws ServletException, NamespaceException {
validateAlias(alias, false);
validateServlet(servlet);
registerInternal(alias, servlet, initparams, httpContext, Type.SERVLET);
}
@Override
public void registerResources(String alias, String name, HttpContext httpContext) throws NamespaceException {
validateAlias(alias, false);
validateName(name);
if (httpContext == null) { httpContext = createDefaultHttpContext(); }
ResourceServlet servlet = new ResourceServlet(name, httpContext);
registerInternal(alias, servlet, null, null, Type.RESOURCE);
}
@SuppressWarnings("rawtypes")
private synchronized ServletBuilder registerInternal(String alias, Servlet servlet, Dictionary initparams, HttpContext httpContext, Type type) throws NamespaceException {
File storageDir = new File(serverEnvironment.getServerTempDir() + File.separator + alias + File.separator + "osgiservlet-root");
storageDir.mkdirs();
//ShareableContext ctx;
WebDeploymentBuilder deploymentBuilder = new WebDeploymentBuilder();
ShareableContextWrapper scWrapper = null;
if (httpContext != null) {
scWrapper = contexts.get(httpContext);
} else {
scWrapper = new ShareableContextWrapper();
httpContext = new DefaultHttpContext(bundle);
}
if(scWrapper == null) {
contexts.put(httpContext, scWrapper);
}
deploymentBuilder.setDocumentRoot(storageDir);
deploymentBuilder.setContextRoot(alias);
deploymentBuilder.setApplicationContextWrapper(scWrapper);
//ctx.addLifecycleListener(new ContextConfig());
deploymentBuilder.setClassLoader(servlet.getClass().getClassLoader());
deploymentBuilder.addMimeMapping("html", "text/html");
deploymentBuilder.addMimeMapping("jpg", "image/jpeg");
deploymentBuilder.addMimeMapping("png", "image/png");
deploymentBuilder.addMimeMapping("gif", "image/gif");
deploymentBuilder.addMimeMapping("css", "text/css");
deploymentBuilder.addMimeMapping("js", "text/javascript");
String wrapperName = alias.substring(1);
ServletBuilder wrapper = new ServletBuilder();
wrapper.setServletName(wrapperName);
wrapper.setServlet(new SecurityServletWrapper(servlet, httpContext));
wrapper.setServletClass(servlet.getClass());
// Init parameters
if (initparams != null) {
Enumeration keys = initparams.keys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String val = (String) initparams.get(key);
wrapper.addInitParam(key, val);
}
}
wrapper.setForceInit(true);
wrapper.addUrlMapping("/*");
deploymentBuilder.addServlet(wrapper);
WebDeploymentController deploymentController;
try {
deploymentController = virtualHost.addWebDeployment(deploymentBuilder);
WEB_LOGGER.registerWebapp(deploymentBuilder.getContextRoot());
deploymentController.create();
} catch (Exception ex) {
throw new NamespaceException(WEB_LOGGER.createContextFailed(), ex);
}
try {
deploymentController.start();
} catch (Exception ex) {
throw new NamespaceException(WEB_LOGGER.startContextFailed(), ex);
}
registry.register(alias, bundle, deploymentController, servlet, type);
// Must be added to the main mapper as no dynamic servlets usually
/*Mapper mapper = webServer.getService().getMapper();
mapper.addWrapper(virtualHost.getName(), ctx.getPath(), pattern, wrapper, false);*/
return wrapper;
}
@Override
public void unregister(String alias) {
try {
validateAlias(alias, true);
} catch (NamespaceException e) {
WEB_LOGGER.errorf(e, "");
return;
}
Registration reg = registry.unregister(alias, bundle);
if (reg != null) {
unregisterInternal(reg);
}
}
@Override
public HttpContext createDefaultHttpContext() {
return new DefaultHttpContext(bundle);
}
void unregisterInternal(Registration reg) {
WebDeploymentController context = reg.getContext();
try {
context.stop();
} catch (Exception e) {
WEB_LOGGER.stopContextFailed(e);
}
try {
context.destroy();
} catch (Exception e) {
WEB_LOGGER.destroyContextFailed(e);
}
}
private void validateAlias(String alias, boolean exists) throws NamespaceException {
// An alias must begin with slash ('/') and must not end with slash ('/'), with the exception
// that an alias of the form "/" is used to denote the root alias
if (alias == null || !alias.startsWith("/")) { throw new IllegalArgumentException(OSGiMessages.MESSAGES.invalidServletAlias(alias)); }
if (alias.length() > 1 && alias.endsWith("/")) { throw new IllegalArgumentException(OSGiMessages.MESSAGES.invalidServletAlias(alias)); }
if (exists && !registry.exists(alias)) { throw new IllegalArgumentException(OSGiMessages.MESSAGES.aliasMappingDoesNotExist(alias)); }
if (!exists && registry.exists(alias)) { throw new NamespaceException(OSGiMessages.MESSAGES.aliasMappingAlreadyExists(alias)); }
}
private void validateName(String name) throws NamespaceException {
// The name parameter must also not end with slash ('/') with the exception
// that a name of the form "/" is used to denote the root of the bundle.
if (name == null || (name.length() > 1 && name.endsWith("/"))) { throw new NamespaceException(OSGiMessages.MESSAGES.invalidResourceName(name)); }
}
private void validateServlet(Servlet servlet) throws ServletException {
// A single servlet instance can only be registered once.
if (registry.contains(servlet)) { throw new ServletException(OSGiMessages.MESSAGES.servletAlreadyRegistered(servlet.getServletInfo())); }
}
static class ShareableContextWrapper implements ApplicationContextWrapper {
private Object sharedContext;
@Override
public synchronized Object wrap(final Object context) {
if(sharedContext == null) {
this.sharedContext = context;
}
return sharedContext;
}
}
/* This wrapper class takes care of handling the security through the HttpContext.
*/
static class SecurityServletWrapper implements Servlet {
private final HttpContext httpContext;
private final Servlet delegate;
SecurityServletWrapper(Servlet servlet, HttpContext ctx) {
if (servlet == null) { throw new NullPointerException(); }
delegate = servlet;
if (ctx == null) { throw new NullPointerException(); }
httpContext = ctx;
}
@Override
public void destroy() {
delegate.destroy();
}
@Override
public ServletConfig getServletConfig() {
return delegate.getServletConfig();
}
@Override
public String getServletInfo() {
return delegate.getServletInfo();
}
@Override
public void init(ServletConfig sc) throws ServletException {
delegate.init(sc);
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (!httpContext.handleSecurity(httpRequest, httpResponse)) { return; }
Object u = httpRequest.getAttribute(HttpContext.REMOTE_USER);
String remoteUser = u instanceof String ? (String) u : null;
Object a = httpRequest.getAttribute(HttpContext.AUTHENTICATION_TYPE);
String authType = a instanceof String ? (String) a : null;
if (remoteUser != null || authType != null) {
request = new SecurityRequestWrapper(remoteUser, authType, httpRequest);
}
}
delegate.service(request, response);
}
}
/* This wrapper class can provide the remote user and auth type information if provided through the HttpContext.
*/
static class SecurityRequestWrapper extends HttpServletRequestWrapper implements HttpServletRequest {
private final String remoteUser;
private final String authType;
SecurityRequestWrapper(String remoteUser, String authType, HttpServletRequest request) {
super(request);
this.remoteUser = remoteUser;
this.authType = authType;
}
@Override
public String getAuthType() {
return authType;
}
@Override
public String getRemoteUser() {
return remoteUser;
}
}
}