001/*
002 * #%L
003 * Netarchivesuite - common
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 *
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023package dk.netarkivet.common.webinterface;
024
025import java.io.File;
026
027import javax.servlet.ServletException;
028
029import org.apache.catalina.core.StandardContext;
030import org.apache.catalina.core.StandardHost;
031import org.apache.catalina.startup.Tomcat;
032import org.apache.tomcat.util.scan.Constants;
033import org.apache.tomcat.util.scan.StandardJarScanFilter;
034
035
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039import dk.netarkivet.common.CommonSettings;
040import dk.netarkivet.common.exceptions.IOFailure;
041import dk.netarkivet.common.utils.CleanupIF;
042import dk.netarkivet.common.utils.FileUtils;
043import dk.netarkivet.common.utils.Settings;
044import dk.netarkivet.common.utils.StringUtils;
045
046/**
047 * A class representing an HttpServer. This class loads web applications as given in settings.
048 */
049public class GUIWebServer implements CleanupIF {
050    /**
051     * The unique instance of this class.
052     */
053    private static GUIWebServer instance;
054    /**
055     * The Tomcat server.
056     */
057    private Tomcat server;
058    /**
059     * Logger for this class.
060     */
061    private static final Logger log = LoggerFactory.getLogger(GUIWebServer.class);
062
063    /** The lower limit of acceptable HTTP port-numbers. */
064    private static final int HTTP_PORT_NUMBER_LOWER_LIMIT = 1025;
065
066    /** The upper limit of acceptable HTTP port-numbers. */
067    private static final int HTTP_PORT_NUMBER_UPPER_LIMIT = 65535;
068
069    /**
070     * Initialises a GUI Web Server and adds web applications.
071     *
072     * @throws IOFailure on trouble starting server.
073     */
074    public GUIWebServer() {
075        // Read and log settings.
076
077        int port = Integer.parseInt(Settings.get(CommonSettings.HTTP_PORT_NUMBER));
078        if (port < HTTP_PORT_NUMBER_LOWER_LIMIT || port > HTTP_PORT_NUMBER_UPPER_LIMIT) {
079            throw new IOFailure("Port must be in the range [" + HTTP_PORT_NUMBER_LOWER_LIMIT + ", "
080                    + HTTP_PORT_NUMBER_UPPER_LIMIT + "], not " + port);
081        }
082        // TODO Replace with just one setting. See issue NAS-1687
083        String[] webApps = Settings.getAll(CommonSettings.SITESECTION_WEBAPPLICATION);
084        String[] classes = Settings.getAll(CommonSettings.SITESECTION_CLASS);
085        if (webApps.length != classes.length) {
086            throw new IOFailure("Number of webapplications and number of classes defining "
087                    + "the webapps do not match. " + "Webapps: [" + StringUtils.conjoin(",", webApps) + "]. "
088                    + "]. Classes: [" + StringUtils.conjoin(",", classes) + "]");
089        }
090
091        log.info("Starting webserver. Port: " + port + " deployment directories: '" + StringUtils.conjoin(",", webApps)
092                + "' classes: '" + StringUtils.conjoin(",", classes) + "'");
093
094        // Get a tomcat server.
095        server = new Tomcat();
096
097
098        System.setProperty("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE", "true");
099
100        // Use directory in commontempdir for cache
101        final File tempDir = FileUtils.getTempDir();
102        log.debug("GUI using tempdir " + tempDir.getAbsolutePath());
103        File basedir = tempDir.getAbsoluteFile().getParentFile();
104        if(tempDir.isAbsolute()) {
105                basedir = new File("");
106        }
107        log.debug("GUI using basedir " + basedir.getAbsolutePath());
108        server.setBaseDir(basedir.getAbsolutePath());
109
110        //File webapps = new File(basedir, "/webapps");
111        File webapps = tempDir;
112        if (webapps.exists()) {
113            FileUtils.removeRecursively(webapps);
114            log.info("Deleted existing tempdir '" + webapps.getAbsolutePath() + "'");
115        }
116
117        webapps.mkdirs();
118        server.getHost().setAppBase(webapps.getAbsolutePath());
119        server.getHost().setAutoDeploy(true);
120        ((StandardHost) server.getHost()).setUnpackWARs(true);
121        //set the port on which tomcat should run
122        server.setPort(port);
123        boolean taglibsScanningDisabled = false;
124
125        if (System.getProperty(Constants.SKIP_JARS_PROPERTY) == null) {
126            log.info("Scanning for taglibs is disabled as " + Constants.SKIP_JARS_PROPERTY + " is unset."); // Only log this once for all contexts
127            taglibsScanningDisabled = true;
128        }
129
130        //add webapps to tomcat
131        for (String webapp: webApps) {
132            // Construct webbase from the name of the webapp.
133            // (1) If the webapp is webpages/History, the webbase is /History
134            // (2) If the webapp is webpages/History.war, the webbase is /History
135            String webappFilename = new File(webapp).getName();
136            String webbase = "/" + webappFilename;
137            final String warSuffix = ".war";
138            if (webappFilename.toLowerCase().endsWith(warSuffix)) {
139                webbase = "/" + webappFilename.substring(0, webappFilename.length() - warSuffix.length());
140            }
141
142            for (SiteSection section : SiteSection.getSections()) {
143                if (webbase.equals("/" + section.getDirname())) {
144                    section.initialize();
145                    break;
146                }
147            }
148            try {
149                //add the jar file to tomcat
150                final File warfileFile = new File(basedir.getAbsolutePath(), webapp);
151                if (!warfileFile.exists() || !warfileFile.isFile()) {
152                    throw new IOFailure("Could not find expected file " + warfileFile.getAbsolutePath());
153                }
154                String warfile = warfileFile.getAbsolutePath();
155                log.info("Deploying webapp with context {} at docbase {}.", webbase, warfile);
156                StandardContext ctx = (StandardContext) server.addWebapp(webbase, warfile);
157                if (taglibsScanningDisabled) {
158                    StandardJarScanFilter jarScanFilter = (StandardJarScanFilter) ctx.getJarScanner().getJarScanFilter();
159                    // Disable scanning for taglibs
160                    jarScanFilter.setTldSkip("*");
161                }
162                if (webapp.equals(webApps[0])) {
163                    //Re-add the 1st context as also the root context
164                    StandardContext rootCtx = (StandardContext) server.addWebapp("/", warfile);
165                    if (taglibsScanningDisabled) {
166                        StandardJarScanFilter jarScanFilter = (StandardJarScanFilter) rootCtx.getJarScanner().getJarScanFilter();
167                        // Disable scanning for taglibs
168                        jarScanFilter.setTldSkip("*");
169                    }
170                }
171            }
172            catch (ServletException e) {
173                log.error("Unable to add webapp " + webapp, e);
174            }
175        }
176    }
177
178
179    /**
180     * Returns the unique instance of this class. If instance is new, starts a GUI web server.
181     *
182     * @return the instance
183     */
184    public static synchronized GUIWebServer getInstance() {
185        if (instance == null) {
186            instance = new GUIWebServer();
187            instance.startServer();
188        }
189        return instance;
190    }
191
192
193
194    /**
195     * Starts a Tomcat server.
196     *
197     * @throws IOFailure if the server for any reason cannot be started.
198     */
199    public void startServer() {
200        // start the server.
201        try {
202            server.start();
203
204        } catch (Exception e) {
205            cleanup();
206            log.warn("Could not start GUI", e);
207        }
208    }
209
210    /**
211     * Closes the GUI webserver, and nullifies this instance.
212     */
213    public void cleanup() {
214
215        try {
216            server.stop();
217            server.destroy();
218            SiteSection.cleanup();
219            log.info("GUI webserver has been stopped.");
220        } catch (Exception e) {
221            //throw new IOFailure("Error while stopping server", e);
222                log.warn("Error while stopping server, Trying to ignore it", e);
223        }
224        
225
226        resetInstance();
227    }
228
229    /** resetClassInstance. */
230    private static synchronized void resetInstance() {
231        instance = null;
232    }
233}