001// Copyright 2009, 2010, 2011, 2013 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.test;
016
017import org.apache.commons.cli.*;
018import org.eclipse.jetty.server.Server;
019import org.eclipse.jetty.server.ssl.SslSocketConnector;
020import org.eclipse.jetty.util.ssl.SslContextFactory;
021import org.eclipse.jetty.webapp.WebAppContext;
022
023import java.io.File;
024
025/**
026 * Launches an instance of Jetty.
027 */
028public class JettyRunner implements ServletContainerRunner
029{
030    private Server jettyServer;
031
032    private String description;
033
034    private int port;
035
036    private int sslPort;
037
038    public JettyRunner()
039    {
040        // un-configured runner
041    }
042
043    public JettyRunner(String webappFolder, String contextPath, int port, int sslPort) throws Exception
044    {
045        configure(webappFolder, contextPath, port, sslPort).start();
046    }
047
048    public JettyRunner configure(String webappFolder, String contextPath, int port, int sslPort) throws Exception
049    {
050        this.port = port;
051
052        this.sslPort = sslPort;
053
054        String expandedPath = expand(webappFolder);
055
056        description = String.format("<JettyRunner: %s:%s/%s (%s)", contextPath, port, sslPort, expandedPath);
057
058        jettyServer = new Server(port);
059
060        WebAppContext webapp = new WebAppContext();
061        webapp.setContextPath(contextPath);
062        webapp.setWar(expandedPath);
063
064        // SSL support
065        File keystoreFile = new File(TapestryRunnerConstants.MODULE_BASE_DIR, "src/test/conf/keystore");
066
067        if (keystoreFile.exists())
068        {
069            SslContextFactory sslContextFactory = new SslContextFactory();
070
071            sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath());
072
073            sslContextFactory.setKeyStorePassword("tapestry");
074
075            sslContextFactory.setKeyManagerPassword("tapestry");
076
077            SslSocketConnector sslConnector = new SslSocketConnector(sslContextFactory);
078
079            sslConnector.setPort(sslPort);
080
081            jettyServer.addConnector(sslConnector);
082        }
083
084        jettyServer.setHandler(webapp);
085        return this;
086    }
087
088    public void start() throws Exception
089    {
090        jettyServer.start();
091    }
092
093    /**
094     * Immediately shuts down the server instance.
095     */
096    @Override
097    public void stop()
098    {
099        System.out.printf("Stopping Jetty instance on port %d/%d\n", port, sslPort);
100
101        try
102        {
103            // Stop immediately and not gracefully.
104            jettyServer.stop();
105        } catch (Exception ex)
106        {
107            throw new RuntimeException("Error stopping Jetty instance: " + ex.toString(), ex);
108        }
109
110        System.out.println("Jetty instance has stopped.");
111    }
112
113    public Server getServer()
114    {
115        return jettyServer;
116    }
117
118    @Override
119    public String toString()
120    {
121        return description;
122    }
123
124    /**
125     * Needed inside Maven multi-projects to expand a path relative to the module to a complete
126     * path. If the path already is absolute and points to an existing directory, it will be used
127     * unchanged.
128     *
129     * @param moduleLocalPath
130     * @return expanded path
131     * @see TapestryRunnerConstants#MODULE_BASE_DIR
132     */
133    protected String expand(String moduleLocalPath)
134    {
135        File path = new File(moduleLocalPath);
136
137        // Don't expand if the path provided already exists.
138        if (path.isAbsolute() && path.isDirectory())
139            return moduleLocalPath;
140
141        return new File(TapestryRunnerConstants.MODULE_BASE_DIR, moduleLocalPath).getPath();
142    }
143
144    /**
145     * Main entrypoint used to run the Jetty instance from the command line.
146     *
147     * @since 5.4
148     */
149    public static void main(String[] args) throws Exception
150    {
151        String commandName = JettyRunner.class.getName();
152
153        Options options = new Options();
154
155        String webapp = "src/main/webapp";
156        String context = "/";
157        int httpPort = 8080;
158        int sslPort = 8443;
159
160        options.addOption(OptionBuilder.withLongOpt("directory")
161                .withDescription("Root context directory (defaults to 'src/main/webapp')")
162                .hasArg().withArgName("DIR")
163                .create('d'))
164                .addOption(OptionBuilder.withLongOpt("context")
165                        .withDescription("Context path for application (defaults to '/')")
166                        .hasArg().withArgName("CONTEXT")
167                        .create('c'))
168                .addOption(OptionBuilder.withLongOpt("port")
169                        .withDescription("HTTP port (defaults to 8080)")
170                        .hasArg().withArgName("PORT")
171                        .create('p'))
172                .addOption(OptionBuilder.withLongOpt("secure-port")
173                        .withDescription("HTTPS port (defaults to 8443)")
174                        .hasArg().withArgName("PORT")
175                        .create('s'))
176                .addOption("h", "help", false, "Display command usage");
177
178
179        CommandLine line = new BasicParser().parse(options, args);
180
181        boolean usage = line.hasOption('h');
182
183        if (!usage)
184        {
185            if (line.hasOption('d'))
186            {
187                webapp = line.getOptionValue('d');
188            }
189
190            File folder = new File(webapp);
191
192            if (!folder.exists())
193            {
194                System.err.printf("%s: Directory `%s' does not exist.%n", commandName, webapp);
195                System.exit(-1);
196            }
197
198            if (line.hasOption('p'))
199            {
200                try
201                {
202                    httpPort = Integer.parseInt(line.getOptionValue('p'));
203                } catch (NumberFormatException e)
204                {
205                    usage = true;
206                }
207            }
208
209            if (line.hasOption('s'))
210            {
211                try
212                {
213                    sslPort = Integer.parseInt(line.getOptionValue('s'));
214                } catch (NumberFormatException e)
215                {
216                    usage = true;
217                }
218            }
219
220            if (line.hasOption('c'))
221            {
222                context = line.getOptionValue('c');
223            }
224
225        }
226
227        if (usage)
228        {
229            new HelpFormatter().printHelp(commandName, options);
230            System.exit(-1);
231        }
232
233        new JettyRunner(webapp, context, httpPort, sslPort);
234    }
235}