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