001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.test;
020
021import org.apache.oozie.servlet.ErrorServlet;
022import org.eclipse.jetty.server.Connector;
023import org.eclipse.jetty.server.HttpConfiguration;
024import org.eclipse.jetty.server.HttpConnectionFactory;
025import org.eclipse.jetty.server.Server;
026import org.eclipse.jetty.server.ServerConnector;
027import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
028import org.eclipse.jetty.servlet.FilterHolder;
029import org.eclipse.jetty.servlet.ServletContextHandler;
030import org.eclipse.jetty.servlet.ServletHolder;
031
032import javax.servlet.DispatcherType;
033import javax.servlet.Filter;
034import javax.servlet.Servlet;
035import javax.servlet.http.HttpServletResponse;
036import java.net.InetAddress;
037import java.util.EnumSet;
038import java.util.Map;
039
040/**
041 * An embedded servlet container for testing purposes. <p> It provides reduced functionality, it supports only
042 * Servlets. <p> The servlet container is started in a free port.
043 */
044public class EmbeddedServletContainer {
045    private Server server;
046    private String host = null;
047    private int port = -1;
048    private String contextPath;
049    private ServletContextHandler context;
050
051    /**
052     * Create a servlet container.
053     *
054     * @param contextPath context path for the servlet, it must not be prefixed or append with "/", for the default
055     * context use ""
056     */
057    public EmbeddedServletContainer(String contextPath) {
058        this.contextPath = contextPath;
059        server = new Server(0);
060        context = new ServletContextHandler();
061        context.setContextPath("/" + contextPath);
062        context.setErrorHandler(getErrorHandler());
063        this.addServletEndpoint("/error/*", ErrorServlet.class);
064        server.setHandler(context);
065    }
066
067    /**
068     * Add a servlet to the container.
069     *
070     * @param servletPath servlet path for the servlet, it should be prefixed with '/", it may contain a wild card at
071     * the end.
072     * @param servletClass servlet class
073     * @param initParams a mapping of init parameters for the servlet, or null
074     */
075    public void addServletEndpoint(String servletPath, Class<? extends Servlet> servletClass, Map<String, String> initParams) {
076        ServletHolder holder = new ServletHolder(servletClass);
077        if (initParams != null) {
078            holder.setInitParameters(initParams);
079        }
080        context.addServlet(holder, servletPath);
081    }
082
083    /**
084     * Add a servlet to the container.
085     *
086     * @param servletPath servlet path for the servlet, it should be prefixed with '/", it may contain a wild card at
087     * the end.
088     * @param servletClass servlet class
089     */
090    public void addServletEndpoint(String servletPath, Class<? extends Servlet> servletClass) {
091        addServletEndpoint(servletPath, servletClass, null);
092    }
093
094    /**
095     * Add a servlet instance to the container.
096     *
097     * @param servletPath servlet path for the servlet, it should be prefixed with '/", it may contain a wild card at
098     * the end.
099     * @param servlet servlet instance
100     */
101    public void addServletEndpoint(String servletPath, Servlet servlet) {
102        ServletHolder holder = new ServletHolder(servlet);
103        context.addServlet(holder, servletPath);
104    }
105
106    /**
107     * Add a filter to the container.
108     *
109     * @param filterPath path for the filter, it should be prefixed with '/", it may contain a wild card at
110     * the end.
111     * @param filterClass servlet class
112     */
113    public void addFilter(String filterPath, Class<? extends Filter> filterClass) {
114        context.addFilter(new FilterHolder(filterClass), filterPath, EnumSet.of(DispatcherType.REQUEST));
115    }
116
117    /**
118     * Start the servlet container. <p> The container starts on a free port.
119     *
120     * @throws Exception thrown if the container could not start.
121     */
122    public void start() throws Exception {
123        host = InetAddress.getLocalHost().getHostName();
124        ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(new HttpConfiguration()));
125        connector.setHost(host);
126        server.setConnectors(new Connector[] { connector });
127        server.start();
128        port = connector.getLocalPort();
129        System.out.println("Running embedded servlet container at: http://" + host + ":" + port);
130    }
131
132    /**
133     * Return the hostname the servlet container is bound to.
134     *
135     * @return the hostname.
136     */
137    public String getHost() {
138        return host;
139    }
140
141    /**
142     * Return the port number the servlet container is bound to.
143     *
144     * @return the port number.
145     */
146    public int getPort() {
147        return port;
148    }
149
150    /**
151     * Return the full URL (including protocol, host, port, context path, servlet path) for the context path.
152     *
153     * @return URL to the context path.
154     */
155    public String getContextURL() {
156        return "http://" + host + ":" + port + "/" + contextPath;
157    }
158
159    /**
160     * Return the full URL (including protocol, host, port, context path, servlet path) for a servlet path.
161     *
162     * @param servletPath the path which will be expanded to a full URL.
163     * @return URL to the servlet.
164     */
165    public String getServletURL(String servletPath) {
166        String path = servletPath;
167        if (path.endsWith("*")) {
168            path = path.substring(0, path.length() - 1);
169        }
170        return getContextURL() + path;
171    }
172
173    /**
174     * Stop the servlet container.
175     */
176    public void stop() {
177        try {
178            server.stop();
179        }
180        catch (Exception e) {
181            // ignore exception
182        }
183
184        try {
185            server.destroy();
186        }
187        catch (Exception e) {
188            // ignore exception
189        }
190
191        host = null;
192        port = -1;
193    }
194
195    /**
196     * Returns an error page handler
197     * @return
198     */
199    private ErrorPageErrorHandler getErrorHandler() {
200        ErrorPageErrorHandler errorHandler = new ErrorPageErrorHandler();
201        errorHandler.addErrorPage(HttpServletResponse.SC_BAD_REQUEST, "/error");
202        errorHandler.addErrorPage(HttpServletResponse.SC_UNAUTHORIZED, "/error");
203        errorHandler.addErrorPage(HttpServletResponse.SC_FORBIDDEN, "/error");
204        errorHandler.addErrorPage(HttpServletResponse.SC_NOT_FOUND, "/error");
205        errorHandler.addErrorPage(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "/error");
206        errorHandler.addErrorPage(HttpServletResponse.SC_CONFLICT, "/error");
207        errorHandler.addErrorPage(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "/error");
208        errorHandler.addErrorPage(HttpServletResponse.SC_NOT_IMPLEMENTED, "/error");
209        errorHandler.addErrorPage(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "/error");
210        errorHandler.addErrorPage("java.lang.Throwable", "/error");
211        return errorHandler;
212    }
213}