/*
* Data Hub Service (DHuS) - For Space data distribution.
* Copyright (C) 2013,2014,2015 GAEL Systems
*
* This file is part of DHuS software sources.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.gael.dhus.server.http;
import com.google.common.io.Files;
import fr.gael.dhus.server.ScalabilityManager;
import fr.gael.dhus.server.http.webapp.WebApplication;
import fr.gael.dhus.system.config.ConfigurationManager;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import org.apache.catalina.Container;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.startup.Catalina;
import org.apache.catalina.startup.Constants;
import org.apache.catalina.startup.ContextConfig;
import org.apache.catalina.startup.Tomcat.DefaultWebXmlListener;
import org.apache.catalina.valves.RemoteAddrValve;
import org.apache.catalina.valves.RemoteIpValve;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.scan.StandardJarScanner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class TomcatServer
{
private static final Logger LOGGER = LogManager.getLogger(TomcatServer.class);
@Autowired
private ConfigurationManager configurationManager;
@Autowired
private ScalabilityManager scalabilityManager;
private String tomcatpath;
private Catalina cat;
/**
* Initialize Tomcat inner datasets.
*/
public void init () throws TomcatException
{
tomcatpath = configurationManager.getTomcatConfiguration ().getPath ();
final String extractDirectory = tomcatpath;
File extractDirectoryFile = new File (extractDirectory);
LOGGER.info("Starting tomcat in " + extractDirectoryFile.getPath());
try
{
extract (extractDirectoryFile, extractDirectory);
// create tomcat various paths
new File (extractDirectory, "conf").mkdirs ();
File cfg =
new File (ClassLoader.getSystemResource ("server.xml").toURI ());
Files.copy (cfg, new File (extractDirectory, "conf/server.xml"));
new File (extractDirectory, "logs").mkdirs ();
new File (extractDirectory, "webapps").mkdirs ();
new File (extractDirectory, "work").mkdirs ();
File tmpDir = new File (extractDirectory, "temp");
tmpDir.mkdirs ();
System.setProperty ("java.io.tmpdir", tmpDir.getAbsolutePath ());
System.setProperty ("catalina.base",
extractDirectoryFile.getAbsolutePath ());
System.setProperty ("catalina.home",
extractDirectoryFile.getAbsolutePath ());
cat = new Catalina ();
}
catch (Exception e)
{
throw new TomcatException ("Cannot initalize Tomcat environment.", e);
}
Runtime.getRuntime ().addShutdownHook (new TomcatShutdownHook ());
}
/**
* This method Starts the Tomcat server.
*/
public void start () throws TomcatException
{
if (cat == null) init ();
cat.start ();
}
/**
* This method Stops the Tomcat server.
*/
public void stop () throws TomcatException
{
// Stop the embedded server
cat.stop ();
cat = null;
}
public boolean isRunning ()
{
return cat != null;
}
protected class TomcatShutdownHook extends Thread
{
protected TomcatShutdownHook ()
{
}
@Override
public void run ()
{
try
{
TomcatServer.this.stop ();
}
catch (Throwable ex)
{
ExceptionUtils.handleThrowable (ex);
LOGGER.error("Fail to properly shutdown Tomcat:" + ex.getMessage ());
}
}
}
protected void extract (File extract_directory_file, String extract_directory)
throws Exception
{
if (extract_directory_file.exists ())
{
LOGGER.debug("Clean extractDirectory");
FileUtils.deleteDirectory (extract_directory_file);
}
if ( !extract_directory_file.exists ())
{
boolean created = extract_directory_file.mkdirs ();
if ( !created)
{
throw new Exception ("FATAL: impossible to create directory:" +
extract_directory_file.getPath ());
}
}
// ensure webapp dir is here
boolean created = new File (extract_directory, "webapps").mkdirs ();
if ( !created)
{
throw new Exception ("FATAL: impossible to create directory:" +
extract_directory_file.getPath () + "/webapps");
}
expandConfigurationFile ("web.xml", extract_directory_file);
}
private static void expandConfigurationFile (String file_name,
File extract_directory) throws Exception
{
InputStream inputStream = null;
try
{
inputStream =
Thread.currentThread ().getContextClassLoader ()
.getResourceAsStream ("conf/" + file_name);
if (inputStream != null)
{
File confDirectory = new File (extract_directory, "conf");
if ( !confDirectory.exists ())
{
confDirectory.mkdirs ();
}
expand (inputStream, new File (confDirectory, file_name));
}
}
finally
{
if (inputStream != null)
{
inputStream.close ();
}
}
}
private static void expand (InputStream input, File file) throws IOException
{
BufferedOutputStream output = null;
try
{
output = new BufferedOutputStream (new FileOutputStream (file));
byte buffer[] = new byte[2048];
while (true)
{
int n = input.read (buffer);
if (n <= 0)
{
break;
}
output.write (buffer, 0, n);
}
}
finally
{
if (output != null)
{
try
{
output.close ();
}
catch (IOException e)
{
// Ignore
}
}
}
}
public void install (WebApplication web_application)
throws TomcatException
{
if (web_application.isPartOfScalability () && !scalabilityManager.isActive ())
{
LOGGER.info ("Scalability - Skipping '"+web_application+"', because scalability is disabled");
return;
}
LOGGER.info ("Installing webapp " + web_application);
String appName = web_application.getName ();
String folder;
if (appName.trim ().isEmpty ())
{
folder = "ROOT";
}
else
{
folder = appName;
}
try
{
if (web_application.hasWarStream ())
{
InputStream stream = web_application.getWarStream ();
if (stream == null)
{
throw new TomcatException ("Cannot install webApplication " +
web_application.getName () +
". The referenced war file does not exist.");
}
JarInputStream jis = new JarInputStream (stream);
File destDir = new File (tomcatpath, "webapps/" + folder);
byte[] buffer = new byte[4096];
JarEntry file;
while ( (file = jis.getNextJarEntry ()) != null)
{
File f =
new File (destDir + java.io.File.separator + file.getName ());
if (file.isDirectory ())
{ // if its a directory, create it
f.mkdirs ();
continue;
}
if ( !f.getParentFile ().exists ())
{
f.getParentFile ().mkdirs ();
}
java.io.FileOutputStream fos = new java.io.FileOutputStream (f);
int read;
while ( (read = jis.read (buffer)) != -1)
{
fos.write (buffer, 0, read);
}
fos.flush ();
fos.close ();
}
jis.close ();
}
web_application.configure (new File (tomcatpath, "webapps/" + folder)
.getPath ());
StandardEngine engine =
(StandardEngine) cat.getServer ().findServices ()[0]
.getContainer ();
Container container = engine.findChild (engine.getDefaultHost ());
StandardContext ctx = new StandardContext ();
String url =
(web_application.getName () == "" ? "" : "/") +
web_application.getName ();
ctx.setName (url);
ctx.setPath (url);
ctx.setDocBase (new File (tomcatpath, "webapps/" + folder).getPath ());
ctx.addLifecycleListener (new DefaultWebXmlListener ());
ctx.setConfigFile (getWebappConfigFile (new File (tomcatpath,
"webapps/" + folder).getPath (), url));
ContextConfig ctxCfg = new ContextConfig ();
ctx.addLifecycleListener (ctxCfg);
ctxCfg.setDefaultWebXml("fr/gael/dhus/server/http/global-web.xml");
StandardJarScanner.class.cast(ctx.getJarScanner()).setScanClassPath(false);
container.addChild (ctx);
List<String> welcomeFiles = web_application.getWelcomeFiles ();
for (String welcomeFile : welcomeFiles)
{
ctx.addWelcomeFile (welcomeFile);
}
if (web_application.getAllow () != null ||
web_application.getDeny () != null)
{
RemoteIpValve valve = new RemoteIpValve ();
valve.setRemoteIpHeader ("x-forwarded-for");
valve.setProxiesHeader ("x-forwarded-by");
valve.setProtocolHeader ("x-forwarded-proto");
ctx.addValve (valve);
RemoteAddrValve valve_addr = new RemoteAddrValve ();
valve_addr.setAllow (web_application.getAllow ());
valve_addr.setDeny (web_application.getDeny ());
ctx.addValve (valve_addr);
}
web_application.checkInstallation ();
}
catch (Exception e)
{
throw new TomcatException ("Cannot install webApplication " +
web_application.getName (), e);
}
}
public void await ()
{
cat.getServer ().await ();
}
public int getPort ()
{
Connector connector =
cat.getServer ().findServices ()[0].findConnectors ()[0];
return connector.getPort ();
}
public int getAltPort()
{
Connector[] connectors = cat.getServer().findServices()[0].findConnectors();
if (connectors.length >= 2)
{
return connectors[1].getPort();
}
return connectors[0].getPort();
}
public String getPath ()
{
return this.tomcatpath;
}
protected URL getWebappConfigFile (String path, String url)
{
File docBase = new File (path);
if (docBase.isDirectory ())
{
return getWebappConfigFileFromDirectory (docBase, url);
}
else
{
return getWebappConfigFileFromJar (docBase, url);
}
}
private URL getWebappConfigFileFromDirectory (File docBase, String url)
{
URL result = null;
File webAppContextXml =
new File (docBase, Constants.ApplicationContextXml);
if (webAppContextXml.exists ())
{
try
{
result = webAppContextXml.toURI ().toURL ();
}
catch (MalformedURLException e)
{
LOGGER.warn("Unable to determine web application context.xml " + docBase, e);
}
}
return result;
}
private URL getWebappConfigFileFromJar (File docBase, String url)
{
URL result = null;
JarFile jar = null;
try
{
jar = new JarFile (docBase);
JarEntry entry = jar.getJarEntry (Constants.ApplicationContextXml);
if (entry != null)
{
result =
new URL ("jar:" + docBase.toURI ().toString () + "!/" +
Constants.ApplicationContextXml);
}
}
catch (IOException e)
{
LOGGER.warn("Unable to determine web application context.xml " + docBase, e);
}
finally
{
if (jar != null)
{
try
{
jar.close ();
}
catch (IOException e)
{
// ignore
}
}
}
return result;
}
}