/*******************************************************************************
* Copyright (c) 2007-2008 Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* File: $Source$
* Created by: Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>)
* Created on: Nov 3, 2007
* Revision: $Id$
*
* Contributors:
* Cambridge Semantics Incorporated - initial API and implementation
*******************************************************************************/
package org.openanzo.servlet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import org.eclipse.jetty.servlets.GzipFilter;
import org.eclipse.jetty.util.URIUtil;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.security.keystore.ISecretKeystore;
import org.openanzo.services.IAuthenticationService;
import org.openanzo.services.ServicesDictionary;
import org.ops4j.pax.web.service.WebContainer;
import org.osgi.framework.BundleContext;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for ServletComponent implementations. This class helps implementors of the {@link IServletComponent} interface by providing stock implementation
* of many of the methods. Its initialize method will read the various properties from the configuration graph.
*
* @author Jordi Albornoz Mulligan (<a href="mailto:jordi@cambridgesemantics.com">jordi@cambridgesemantics.com</a>)
* @author Matthew Roy (<a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>)
*/
public abstract class BaseServletComponent implements IServletComponent {
private static final Logger log = LoggerFactory.getLogger(BaseServletComponent.class);
protected AuthenticationMode authenticationMode = AuthenticationMode.NONE;
protected SecurityConstraint securityConstraint = SecurityConstraint.NONE;
protected Collection<PathSpec> pathSpec = new ArrayList<PathSpec>();
protected Collection<PathSpec> protectedPathSpec = new ArrayList<PathSpec>();
protected String contextPath;
protected String docRoot;
protected Properties servletProperties = new Properties();
protected String loginPage;
protected String errorPage;
protected int tokenTimeout = -1;
protected int tokenRefreshWindow = -1;
protected Boolean customTokenRefreshEnabled;
protected Boolean gzipOutputEnabled;
protected HttpContext context;
protected BundleContext bundleContext;
protected GzipFilter filter;
protected WelcomeFilesFilter welcomeFilter;
protected Set<String> instances = new HashSet<String>();
public void registerServlet(WebContainer webContainer, IAuthenticationService authenticationService, ISecretKeystore keystore) {
setUpContext(authenticationService, keystore);
try {
// Properties contextProperties = new Properties();
// contextProperties.put(WebContainerConstants.CONTEXT_NAME, (this.contextPath.startsWith("/") ? this.contextPath.substring(1) : this.contextPath));
// webContainer.setContextParam(contextProperties, context);
if (getServlet() != null) {
ArrayList<String> paths = new ArrayList<String>();
if (getPathSpec() != null) {
for (PathSpec spec : getPathSpec()) {
paths.add(spec.spec + ((spec.wildCard) ? "/*" : ""));
}
}
if (getProtectedPathSpec() != null) {
for (PathSpec spec : getProtectedPathSpec()) {
paths.add(spec.spec + ((spec.wildCard) ? "/*" : ""));
}
}
if (log.isInfoEnabled()) {
log.info(LogUtils.LIFECYCLE_MARKER, "Registering Servlet:" + getServlet() + " paths:" + Arrays.toString(paths.toArray(new String[0])));
}
ClassLoader currentClassLoader = null;
currentClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getServlet().getClass().getClassLoader());
try {
webContainer.registerServlet(getServlet(), paths.toArray(new String[0]), servletProperties, context);
} finally {
Thread.currentThread().setContextClassLoader(currentClassLoader);
}
} else if (getDocRoot() != null) {
ArrayList<String> allPaths = new ArrayList<String>();
ArrayList<String> allRoots = new ArrayList<String>();
if (getPathSpec() != null) {
for (PathSpec spec : getPathSpec()) {
try {
if (log.isInfoEnabled()) {
log.info(LogUtils.LIFECYCLE_MARKER, "Registering Resources:" + spec.spec + " docRoot:" + docRoot);
}
webContainer.registerResources(spec.spec, docRoot, context);
allPaths.add(spec.spec + ((spec.wildCard) ? "/*" : ""));
allRoots.add(spec.spec);
} catch (NamespaceException nse) {
log.error(LogUtils.LIFECYCLE_MARKER, "Error registering static resource: |" + spec.spec + "|", nse);
}
}
}
if (getProtectedPathSpec() != null) {
for (PathSpec spec : getProtectedPathSpec()) {
try {
if (log.isInfoEnabled()) {
log.info(LogUtils.LIFECYCLE_MARKER, "Registering Resources:" + spec.spec + " docRoot:" + docRoot);
}
webContainer.registerResources(spec.spec, docRoot, context);
allPaths.add(spec.spec + ((spec.wildCard) ? "/*" : ""));
allRoots.add(spec.spec);
} catch (NamespaceException nse) {
log.error(LogUtils.LIFECYCLE_MARKER, "Error registering static resource", nse);
}
}
welcomeFilter = new WelcomeFilesFilter(allRoots, new String[] { "index.html", "index.htm" }, true);
webContainer.registerFilter(welcomeFilter, allPaths.toArray(new String[0]), null, null, context);
}
if ((gzipOutputEnabled == null || gzipOutputEnabled) && allPaths.size() > 0) {
filter = new GzipFilter();
webContainer.registerFilter(filter, allPaths.toArray(new String[0]), null, servletProperties, context);
}
}
} catch (ServletException se) {
log.error(LogUtils.LIFECYCLE_MARKER, "Error registering servlet at:" + getContextPath(), se);
throw new RuntimeException(se);
}
}
/**
* Initializes the HttpContext depending on the authentication mode
*/
protected void setUpContext(IAuthenticationService authenticationService, ISecretKeystore keystore) {
switch (getAuthenticationMode()) {
case NONE:
context = new BasicContext(bundleContext, getSecurityConstraint(), getDocRoot());
break;
case BASIC: {
IAuthenticatorRealm relm = new ServerRealm(authenticationService);
context = new BasicAuthenticator(bundleContext, getSecurityConstraint(), relm, getDocRoot(), getPathSpec(), getProtectedPathSpec());
}
break;
case ENCRYPTED_TOKEN: {
ServerRealm relm = new ServerRealm(authenticationService);
EncryptedTokenAuthenticator authenticator = new EncryptedTokenAuthenticator(bundleContext, getSecurityConstraint(), relm, keystore, docRoot, getPathSpec(), getProtectedPathSpec());
authenticator.setLoginPage(getLoginPage());
authenticator.setErrorPage(getErrorPage());
if (tokenTimeout != -1)
authenticator.setTokenTimeout(getTokenTimeout());
if (customTokenRefreshEnabled != null)
authenticator.setCustomTokenRefresh(isCustomTokenRefreshEnabled());
if (tokenRefreshWindow != -1)
authenticator.setTokenRefreshWindow(getTokenRefreshWindow());
if (docRoot != null) {
authenticator.setDocRoot(docRoot);
}
context = authenticator;
}
break;
}
}
public void unregisterServlet(WebContainer webContainer) {
if (getServlet() != null) {
webContainer.unregisterServlet(getServlet());
} else if (getDocRoot() != null) {
try {
if (welcomeFilter != null && webContainer != null) {
webContainer.unregisterFilter(welcomeFilter);
}
} catch (IllegalArgumentException iae) {
log.debug(LogUtils.LIFECYCLE_MARKER, "Error unregistering welcome filter", iae);
}
if (webContainer != null && filter != null) {
try {
webContainer.unregisterFilter(filter);
} catch (IllegalArgumentException iae) {
log.debug(LogUtils.LIFECYCLE_MARKER, "Error unregistering filter", iae);
}
}
if (webContainer != null && getPathSpec() != null) {
for (PathSpec spec : getPathSpec()) {
try {
webContainer.unregister(spec.spec);
} catch (IllegalArgumentException iae) {
log.debug(LogUtils.LIFECYCLE_MARKER, "Error unregistering pathspec", iae);
}
}
}
if (webContainer != null && getProtectedPathSpec() != null) {
for (PathSpec spec : getProtectedPathSpec()) {
try {
webContainer.unregister(spec.spec);
} catch (IllegalArgumentException iae) {
log.debug(LogUtils.LIFECYCLE_MARKER, "Error unregistering protected pathsec", iae);
}
}
}
}
}
/**
* Initialize the hosting of a servlet
*
* @param bundleContext
* bundle context for servlet
* @param configProperties
* configuration properties
* @throws AnzoException
*/
public void initialize(BundleContext bundleContext, Dictionary<?, ?> configProperties) throws AnzoException {
if (configProperties == null) {
log.warn(LogUtils.LIFECYCLE_MARKER, "configProperties is null while initializing base servlet component.");
} else {
String instancesString = ServicesDictionary.getInstanceURI(configProperties);
if (instancesString != null) {
if (instancesString.equals("*")) {
//Do nothing since null equals all
} else {
StringTokenizer st = new StringTokenizer(instancesString, ",");
while (st.hasMoreTokens()) {
instances.add(st.nextToken());
}
}
}
this.bundleContext = bundleContext;
String authmode = ServletDictionary.getAuthorizationType(configProperties);
this.authenticationMode = (authmode != null) ? AuthenticationMode.valueOf(authmode) : AuthenticationMode.NONE;
String sc = ServletDictionary.getSecurityConstraint(configProperties);
this.securityConstraint = (sc != null) ? SecurityConstraint.valueOf(sc) : SecurityConstraint.NONE;
Boolean requireSSL = ServicesDictionary.getRequireSSL(configProperties);
boolean reqSSL = (requireSSL != null) ? requireSSL.booleanValue() : false;
if (reqSSL) {
this.securityConstraint = SecurityConstraint.INTEGRAL;
}
this.contextPath = ServletDictionary.getContextPath(configProperties);
this.docRoot = ServletDictionary.getDocRoot(configProperties);
if (this.docRoot != null) {
this.docRoot = this.docRoot.replace('\\', '/'); // The web container doesn't like backslashes in the docroot. So convert them to forward slashes.
}
if (docRoot != null && docRoot.startsWith("./")) {
String rootPath = getRootPath();
rootPath = rootPath.replace('\\', '/');
docRoot = URIUtil.addPaths(rootPath, docRoot.substring(Math.min(docRoot.length() - 1, 2)));
} else if (docRoot != null && docRoot.startsWith("../")) {
String rootPath = getRootPath();
rootPath = rootPath.replace('\\', '/');
docRoot = URIUtil.addPaths(rootPath, docRoot);
}
String pathSpec = ServletDictionary.getPathSpec(configProperties);
if (pathSpec != null) {
StringTokenizer st = new StringTokenizer(pathSpec, ",");
while (st.hasMoreTokens()) {
String path = st.nextToken();
if (path != null) {
this.pathSpec.add(new PathSpec(URIUtil.addPaths(((getContextPath() != null) ? getContextPath() : ""), path)));
}
}
}
String protectedPathSpec = ServletDictionary.getProtectedPathSpec(configProperties);
if (protectedPathSpec != null) {
StringTokenizer st = new StringTokenizer(protectedPathSpec, ",");
while (st.hasMoreTokens()) {
String path = st.nextToken();
if (path != null) {
this.protectedPathSpec.add(new PathSpec(URIUtil.addPaths(((getContextPath() != null) ? getContextPath() : ""), path)));
}
}
}
this.loginPage = ServletDictionary.getLoginPage(configProperties);
if (this.loginPage != null) {
this.loginPage = URIUtil.addPaths(this.contextPath, loginPage);
}
this.errorPage = ServletDictionary.getErrorPage(configProperties);
if (this.errorPage != null) {
this.errorPage = URIUtil.addPaths(this.contextPath, errorPage);
}
this.customTokenRefreshEnabled = ServletDictionary.getCustomTokenRefreshEnabled(configProperties);
this.gzipOutputEnabled = ServletDictionary.getGzipOutput(configProperties);
if (configProperties.get(ServletDictionary.KEY_AUTH_TOKEN_TIMEOUT) != null) {
this.tokenTimeout = ServletDictionary.getAuthTokenTimeout(configProperties);
}
if (configProperties.get(ServletDictionary.KEY_AUTH_TOKEN_REFRESH_WINDOW) != null) {
this.tokenRefreshWindow = ServletDictionary.getAuthTokenRefreshWindow(configProperties);
}
initializeServletProperties(configProperties);
// Log some info for help debugging configuration issues
if (log.isInfoEnabled()) {
StringBuilder msg = new StringBuilder("Initialize BaseServletComponent with values: contextPath:");
msg.append(this.contextPath);
msg.append(" docRoot:");
msg.append(this.docRoot);
msg.append(" pathSpec:");
if (this.pathSpec != null) {
boolean first = true;
for (PathSpec spec : this.pathSpec) {
if (!first) {
msg.append(",");
}
msg.append(spec.toString());
first = false;
}
}
msg.append(" authenticationMode:");
msg.append(this.authenticationMode.toString());
msg.append(" protectedPathSpec:");
if (this.protectedPathSpec != null) {
boolean first = true;
for (PathSpec spec : this.protectedPathSpec) {
if (!first) {
msg.append(",");
}
msg.append(spec.toString());
first = false;
}
}
msg.append(" loginPage:");
msg.append(this.loginPage);
msg.append(" errorPage:");
msg.append(this.errorPage);
msg.append(" customTokenRefreshEnabled:");
msg.append(this.customTokenRefreshEnabled);
msg.append(" gzipOutputEnabled:");
msg.append(this.gzipOutputEnabled);
msg.append(" tokenTimeout:");
msg.append(this.tokenTimeout);
msg.append(" tokenRefreshWindow:");
msg.append(this.tokenRefreshWindow);
log.info(LogUtils.LIFECYCLE_MARKER, msg.toString());
}
}
}
private String getRootPath() {
String path = System.getProperty("ANZO_SERVER_HOME");
if (path == null) {
path = System.getenv("ANZO_SERVER_HOME");
}
if (path == null) {
path = System.getProperty("ANZO_HOME");
}
if (path == null) {
path = System.getenv("ANZO_HOME");
}
return path;
}
public Set<String> getWebcontainerInstanceURIs() {
return instances;
}
protected void initializeServletProperties(Dictionary<?, ?> configProperties) {
for (Enumeration<?> keys = configProperties.keys(); keys.hasMoreElements();) {
Object key = keys.nextElement();
servletProperties.put(key, configProperties.get(key));
}
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#getContextPath()
*/
public String getContextPath() {
return this.contextPath;
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#getDocRoot()
*/
public String getDocRoot() {
return this.docRoot;
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#getInitProperties()
*/
public Properties getInitProperties() {
return this.servletProperties;
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#getIsAuthenticationMode()
*/
public AuthenticationMode getAuthenticationMode() {
return this.authenticationMode;
}
/**
* @return the securityConstraint
*/
public SecurityConstraint getSecurityConstraint() {
return securityConstraint;
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#getPathSpec()
*/
public Collection<PathSpec> getPathSpec() {
return this.pathSpec;
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#getProtectedPathSpec()
*/
public Collection<PathSpec> getProtectedPathSpec() {
return this.protectedPathSpec;
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#getErrorPage()
*/
public String getErrorPage() {
return this.errorPage;
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#getLoginPage()
*/
public String getLoginPage() {
return this.loginPage;
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#getTokenTimeout()
*/
public int getTokenTimeout() {
return this.tokenTimeout;
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#getTokenRefreshWindow()
*/
public int getTokenRefreshWindow() {
return this.tokenRefreshWindow;
}
/* (non-Javadoc)
* @see org.openanzo.server.endpoint.IServletComponent#isCustomTokenRefreshEnabled()
*/
public Boolean isCustomTokenRefreshEnabled() {
return this.customTokenRefreshEnabled;
}
}