/*******************************************************************************
*
* Copyright (c) 2004-2012 Oracle Corporation.
*
* 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
*
* Contributors:
*
* Kohsuke Kawaguchi, Winston Prakash, Jean-Baptiste Quenot, Tom Huybrechts
*
*******************************************************************************/
package org.eclipse.hudson;
import com.google.common.base.internal.Finalizer;
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import com.thoughtworks.xstream.core.JVM;
import hudson.EnvVars;
import hudson.model.Hudson;
import hudson.util.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.Security;
import java.util.Locale;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletResponse;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import org.apache.tools.ant.types.FileSet;
import org.eclipse.hudson.WebAppController.DefaultInstallStrategy;
import org.eclipse.hudson.graph.ChartUtil;
import org.eclipse.hudson.init.InitialSetup;
import org.eclipse.hudson.init.InitialSetupLogin;
import org.eclipse.hudson.security.HudsonSecurityEntitiesHolder;
import org.eclipse.hudson.security.HudsonSecurityManager;
import org.jvnet.localizer.LocaleProvider;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.jelly.JellyFacet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Entry point when Hudson is used as a webapp.
*
* @author Kohsuke Kawaguchi, Winston Prakash
*/
public final class HudsonServletContextListener implements ServletContextListener {
private Logger logger = LoggerFactory.getLogger(InitialSetup.class);
private final RingBufferLogHandler handler = new RingBufferLogHandler();
/**
* Creates the sole instance of {@link Hudson} and register it to the
* {@link ServletContext}.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
try {
final ServletContext servletContext = event.getServletContext();
// Install the current servlet context, unless its already been set
final WebAppController controller = WebAppController.get();
try {
// Attempt to set the context
controller.setContext(servletContext);
} catch (IllegalStateException e) {
// context already set ignore
}
// Setup the default install strategy if not already configured
try {
controller.setInstallStrategy(new DefaultInstallStrategy());
} catch (IllegalStateException e) {
// strategy already set ignore
}
// use the current request to determine the language
LocaleProvider.setProvider(new LocaleProvider() {
@Override
public Locale get() {
Locale locale = null;
StaplerRequest req = Stapler.getCurrentRequest();
if (req != null) {
locale = req.getLocale();
}
if (locale == null) {
locale = Locale.getDefault();
}
return locale;
}
});
// quick check to see if we (seem to) have enough permissions to run. (see #719)
JVM jvm;
try {
jvm = new JVM();
new URLClassLoader(new URL[0], getClass().getClassLoader());
} catch (SecurityException e) {
controller.install(new InsufficientPermissionDetected(e));
return;
}
try {
// remove Sun PKCS11 provider if present. See http://wiki.hudson-ci.org/display/HUDSON/Solaris+Issue+6276483
Security.removeProvider("SunPKCS11-Solaris");
} catch (SecurityException e) {
// ignore this error.
}
installLogger();
File dir = getHomeDir(event);
try {
dir = dir.getCanonicalFile();
} catch (IOException e) {
dir = dir.getAbsoluteFile();
}
final File home = dir;
home.mkdirs();
logger.info("Home directory: " + home);
// check that home exists (as mkdirs could have failed silently), otherwise throw a meaningful error
if (!home.exists()) {
controller.install(new NoHomeDir(home));
return;
}
// make sure that we are using XStream in the "enhanced" (JVM-specific) mode
if (jvm.bestReflectionProvider().getClass() == PureJavaReflectionProvider.class) {
// nope
controller.install(new IncompatibleVMDetected());
return;
}
// make sure this is servlet 2.4 container or above
try {
ServletResponse.class.getMethod("setCharacterEncoding", String.class);
} catch (NoSuchMethodException e) {
controller.install(new IncompatibleServletVersionDetected(ServletResponse.class));
return;
}
// make sure that we see Ant 1.7
try {
FileSet.class.getMethod("getDirectoryScanner");
} catch (NoSuchMethodException e) {
controller.install(new IncompatibleAntVersionDetected(FileSet.class));
return;
}
//make sure AWT is functioning. Needed for Graphing framework to work properly
if (ChartUtil.awtProblemCause != null) {
controller.install(new AWTProblem(ChartUtil.awtProblemCause));
return;
}
// some containers (in particular Tomcat) doesn't abort a launch
// even if the temp directory doesn't exist.
// check that and report an error
try {
File f = File.createTempFile("test", "test");
f.delete();
} catch (IOException e) {
controller.install(new NoTempDir(e));
return;
}
// Tomcat breaks XSLT with JDK 5.0 and onward. Check if that's the case, and if so,
// try to correct it
try {
TransformerFactory.newInstance();
// if this works we are all happy
} catch (TransformerFactoryConfigurationError x) {
// no it didn't.
logger.warn("XSLT not configured correctly. Hudson will try to fix this. See http://issues.apache.org/bugzilla/show_bug.cgi?id=40895 for more details", x);
System.setProperty(TransformerFactory.class.getName(), "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
try {
TransformerFactory.newInstance();
logger.info("XSLT is set to the JAXP RI in JRE");
} catch (TransformerFactoryConfigurationError y) {
logger.error("Failed to correct the problem.");
}
}
com.google.inject.util.GuiceRuntime.setExecutorClassName(TrackedDaemonExecutor.NonInterruptableExecutor.class.getName());
installExpressionFactory(event);
// Do the initial setup (if needed) before actually starting Hudson
boolean securityLoadFailed = false;
try {
// Create the Security Manager temporarily. Since the plugins are not loaded yet
// all permissions may not be loaded. The Security manager will reload when Hudson
// fully starts later
HudsonSecurityEntitiesHolder.setHudsonSecurityManager(new HudsonSecurityManager(home));
} catch (Exception ex) {
ex.printStackTrace();
securityLoadFailed = true;
logger.info("Failed to load Security. " + ex.getLocalizedMessage() + ". Disablbling security and continuing.. ");
}
InitialSetup initSetup = new InitialSetup(home, servletContext);
if (initSetup.needsInitSetup()) {
logger.info("\n\n\n================>\n\nInitial setup required. Please go to the Hudson Dashboard and complete the setup.\n\n<================\n\n\n");
if (HudsonSecurityEntitiesHolder.getHudsonSecurityManager().isUseSecurity() && !securityLoadFailed) {
controller.install(new InitialSetupLogin(initSetup));
} else {
controller.install(initSetup);
}
} else {
initSetup.invokeHudson();
ThreadLocalUtils.removeThreadLocals();
}
} catch (Exception exc) {
logger.error("Failed to initialize Hudson", exc);
} catch (Error e) {
logger.error("Failed to initialize Hudson", e);
}
}
public static void installExpressionFactory(ServletContextEvent event) {
JellyFacet.setExpressionFactory(event, new ExpressionFactory2());
}
/**
* Installs log handler to monitor all Hudson logs.
*/
private void installLogger() {
Hudson.logRecords = handler.getView();
java.util.logging.Logger.getLogger("hudson").addHandler(handler);
}
/**
* Determines the home directory for Hudson.
*
* People makes configuration mistakes, so we are trying to be nice with
* those by doing {@link String#trim()}.
*/
private File getHomeDir(ServletContextEvent event) {
// check JNDI for the home directory first
try {
InitialContext iniCtxt = new InitialContext();
Context env = (Context) iniCtxt.lookup("java:comp/env");
String value = (String) env.lookup("HUDSON_HOME");
if (value != null && value.trim().length() > 0) {
return new File(value.trim());
}
// look at one more place. See issue #1314
value = (String) iniCtxt.lookup("HUDSON_HOME");
if (value != null && value.trim().length() > 0) {
return new File(value.trim());
}
} catch (NamingException e) {
// ignore
}
// finally check the system property
String sysProp = System.getProperty("HUDSON_HOME");
if (sysProp != null) {
return new File(sysProp.trim());
}
// look at the env var next
String env = EnvVars.masterEnvVars.get("HUDSON_HOME");
if (env != null) {
return new File(env.trim()).getAbsoluteFile();
}
// otherwise pick a place by ourselves
String root = event.getServletContext().getRealPath("/WEB-INF/workspace");
if (root != null) {
File ws = new File(root.trim());
if (ws.exists()) // Hudson <1.42 used to prefer this before ~/.hudson, so
// check the existence and if it's there, use it.
// otherwise if this is a new installation, prefer ~/.hudson
{
return ws;
}
}
// if for some reason we can't put it within the webapp, use home directory.
return new File(new File(System.getProperty("user.home")), ".hudson");
}
public void contextDestroyed(ServletContextEvent event) {
Hudson instance = Hudson.getInstance();
if (instance != null) {
instance.cleanUp();
}
ClassLoader loader = InitialSetup.getHudsonContextClassLoader();
try {
Finalizer.cleanupThreads(loader);
} catch (Throwable t) {
logger.error("Finalizer cleanup unsuccessful", t);
}
try {
TrackedDaemonExecutor.shutdownAll(loader);
} catch (Throwable t) {
logger.error("Executor shutdown unsuccessful", t);
}
ThreadLocalUtils.removeThreadLocals();
// Logger is in the system classloader, so if we don't do this
// the whole web app will never be undepoyed.
java.util.logging.Logger.getLogger("hudson").removeHandler(handler);
}
}