blob: 75afb2021569c81264b5bb119c3a74075de3afb2 [file] [log] [blame]
/*
* 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.netbeans.modules.httpserver;
import java.io.Externalizable;
import java.io.File;
import java.net.MalformedURLException;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import org.apache.tomcat.context.WebXmlReader;
import org.apache.tomcat.startup.EmbededTomcat;
import org.apache.tomcat.core.ContextManager;
import org.apache.tomcat.core.Context;
import org.apache.tomcat.logging.TomcatLogger;
import org.apache.tomcat.service.PoolTcpConnector;
import org.openide.filesystems.FileUtil;
import org.openide.modules.ModuleInstall;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
/**
* Module installation class for Http Server
*
* @author Petr Jiricka
*/
public class HttpServerModule extends ModuleInstall implements Externalizable {
private static ContextManager server;
/** listener that reloads context when systemClassLoader changes */
private static ContextReloader reloader;
private static Thread serverThread;
private static boolean inSetRunning = false;
/** Module is being closed. */
public void close () {
// stop the server, don't set the running status
synchronized (HttpServerSettings.httpLock ()) {
stopHTTPServer();
}
}
/** initiates HTTPServer so it runs */
static void initHTTPServer() {
if (inSetRunning)
return;
synchronized (HttpServerSettings.httpLock()) {
if (inSetRunning)
return;
inSetRunning = true;
try {
if ((serverThread != null) && (!httpserverSettings().running)) {
// another thread is trying to start the server, wait for a while and then stop it if it's still bad
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {}
if ((serverThread != null) && (!httpserverSettings().running)) {
serverThread.stop();
serverThread = null;
}
}
if (serverThread == null) {
serverThread = new Thread("HTTPServer") { // NOI18N
public void run() {
try {
server = buildServer();
server.start();
httpserverSettings().runSuccess();
reloader.activate();
// this is not a debug message, this is a server startup message
if (httpserverSettings().isStartStopMessages())
System.out.println(NbBundle.getMessage(HttpServerModule.class, "CTL_ServerStarted", new Object[] {Integer.valueOf(httpserverSettings().getPort())}));
} catch (ThreadDeath td) {
throw td;
} catch (Throwable ex) {
Logger.getLogger("global").log(Level.INFO, null, ex);
// couldn't start
serverThread = null;
inSetRunning = false;
httpserverSettings().runFailure(ex);
} finally {
httpserverSettings().setStartStopMessages(true);
}
}
};
serverThread.start();
}
// wait for the other thread to start the server
try {
HttpServerSettings.httpLock().wait(HttpServerSettings.SERVER_STARTUP_TIMEOUT);
} catch (Exception e) {
Logger.getLogger("global").log(Level.INFO, null, e);
}
} finally {
inSetRunning = false;
}
}
}
public void uninstalled () {
stopHTTPServer();
}
/** stops the HTTP server */
@SuppressWarnings("deprecation")
static void stopHTTPServer() {
if (inSetRunning)
return;
synchronized (HttpServerSettings.httpLock ()) {
if (inSetRunning)
return;
inSetRunning = true;
try {
if (reloader != null) {
reloader.deactivate ();
reloader = null;
}
if ((serverThread != null) && (server != null)) {
try {
server.stop();
serverThread.join();
}
catch (InterruptedException e) {
serverThread.stop();
/* deprecated, but this really is the last resort,
only if everything else failed */
}
catch (Exception e) {
//e.printStackTrace();
serverThread.stop();
/* deprecated, but this really is the last resort,
only if everything else failed */
}
serverThread = null;
// this is not a debug message, this is a server shutdown message
if (httpserverSettings ().isStartStopMessages())
System.out.println(NbBundle.getBundle(HttpServerModule.class).
getString("CTL_ServerStopped"));
}
}
finally {
inSetRunning = false;
}
}
}
private static ContextManager getContextManager(EmbededTomcat tc) {
try {
java.lang.reflect.Field fm = EmbededTomcat.class.getDeclaredField("contextM"); // NOI18N
fm.setAccessible(true);
return (ContextManager)fm.get(tc);
}
catch (NoSuchFieldException e) {
return null;
}
catch (IllegalAccessException e) {
return null;
}
}
/** Removes WebXmlReader interceptor to avoid attempt
* to load JspServlet that processes jsp file and produces confusing message
*/
private static void removeWebXmlReader (EmbededTomcat tc) {
try {
java.lang.reflect.Field fm = EmbededTomcat.class.getDeclaredField("contextInt"); // NOI18N
fm.setAccessible(true);
Vector contextInt = (Vector)fm.get(tc);
Iterator it = contextInt.iterator ();
while (it.hasNext ()) {
Object o = it.next ();
if (o instanceof WebXmlReader) {
contextInt.remove (o);
break;
}
}
}
catch (NoSuchFieldException e) {
return;
}
catch (IllegalAccessException e) {
return;
}
}
private static ContextManager buildServer() throws Exception {
HttpServerSettings op = httpserverSettings ();
NbLogger logger = new NbLogger();
logger.setName("tc_log"); // NOI18N
final EmbededTomcat tc=new EmbededTomcat();
File wd = FileUtil.toFile (FileUtil.getConfigRoot());
wd = new File(wd, "httpwork"); // NOI18N
tc.setWorkDir(wd.getAbsolutePath());
// install interceptors which need to be initialized BEFORE the default server interceptors
NbLoaderInterceptor nbL =new NbLoaderInterceptor();
tc.addContextInterceptor( nbL );
// hack - force initialization of default interceptors, so our interceptor is after them
tc.addApplicationAdapter(null);
// install interceptors which need to be initialized AFTER the default server interceptors
NbServletsInterceptor nbI =new NbServletsInterceptor();
tc.addContextInterceptor( nbI );
removeWebXmlReader (tc);
ServletContext sctx;
sctx=tc.addContext("", wd.toURI().toURL()); // NOI18N
tc.initContext( sctx );
//ctxt.getServletLoader().setParentLoader(TopManager.getDefault().systemClassLoader());
tc.addEndpoint( op.getPort(), null, null);
final ContextManager cm = getContextManager(tc);
reloader = new ContextReloader (tc, cm, sctx);
// reduce number of threads
Enumeration e = cm.getConnectors ();
while (e.hasMoreElements ()) {
Object o = e.nextElement ();
if (o instanceof PoolTcpConnector) {
org.apache.tomcat.core.ServerConnector conn = (PoolTcpConnector)o;
conn.setAttribute (PoolTcpConnector.MIN_SPARE_THREADS, "0"); // NOI18N
conn.setAttribute (PoolTcpConnector.MAX_SPARE_THREADS, "1"); // NOI18N
conn.setAttribute (PoolTcpConnector.MAX_THREADS, "3"); // NOI18N
}
}
return cm;
}
private static class NbLogger extends TomcatLogger {
public NbLogger() {
super();
}
protected void realLog(String message) {
}
protected void realLog(String message, Throwable t) {
}
public void flush() {
}
}
/**
* Obtains settings of this module
*/
static HttpServerSettings httpserverSettings () {
return HttpServerSettings.getDefault();
}
/** Listener for change of system class loader to reinitialize context
* running on HTTP server.
* The purpose is to force usage of up-to-date classes even in the case
* of module reloading.
*/
private static class ContextReloader implements LookupListener, Runnable {
private ServletContext ide_ctx;
private EmbededTomcat tc;
private ContextManager cm;
private Lookup.Result<ClassLoader> res;
public ContextReloader (EmbededTomcat tc, ContextManager cm, ServletContext ctx) {
ide_ctx = ctx;
this.tc = tc;
this.cm = cm;
}
/** Starts to listen on class loader changes */
public void activate () {
res = Lookup.getDefault().lookup(new Lookup.Template<ClassLoader> (ClassLoader.class));
res.addLookupListener (this);
}
/** Stops listening. */
public void deactivate () {
if (res != null) {
res.removeLookupListener (this);
res = null;
}
}
public void resultChanged (LookupEvent evt) {
RequestProcessor.getDefault ().post (this);
}
public void run () {
ClassLoader cl = (ClassLoader)res.allInstances ().iterator ().next ();
cm.setParentClassLoader (cl);
File wd = FileUtil.toFile (FileUtil.getConfigRoot());
wd = new File(wd, "httpwork"); // NOI18N
Enumeration e = cm.getContexts ();
while (e.hasMoreElements ()) {
Object o = e.nextElement ();
if (o instanceof Context) {
Context ctx = (Context)o;
// PENDING why this is in loop?
tc.removeContext (ide_ctx);
try {
ide_ctx=tc.addContext ("", wd.toURI().toURL ()); // NOI18N
}
catch (MalformedURLException ex) {
// ErrorManager.getDefault ().log (ErrorManager.INFORMATIONAL, ex);
}
tc.initContext ( ide_ctx );
}
}
}
}
}