View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.test.rest;
23  
24  import static org.opencastproject.util.data.functions.Misc.chuck;
25  
26  import org.opencastproject.util.UrlSupport;
27  
28  import org.eclipse.jetty.server.Server;
29  import org.eclipse.jetty.servlet.ServletContextHandler;
30  import org.eclipse.jetty.servlet.ServletHolder;
31  import org.glassfish.jersey.server.ResourceConfig;
32  import org.glassfish.jersey.servlet.ServletContainer;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import java.io.IOException;
37  import java.net.URL;
38  import java.util.concurrent.ThreadLocalRandom;
39  
40  /**
41   * Helper environment for creating REST service unit tests.
42   * <p>
43   * The REST endpoint to test needs a no-arg constructor in order to be created by the framework.
44   * <p>
45   * Write REST unit tests using <a href="http://code.google.com/p/rest-assured/">rest assured</a>.
46   * <h2>Example Usage</h2>
47   * 
48   * <pre>
49   *   import static com.jayway.restassured.RestAssured.*;
50   *   import static com.jayway.restassured.matcher.RestAssuredMatchers.*;
51   *   import static org.hamcrest.Matchers.*;
52   *   import static org.opencastproject.rest.RestServiceTestEnv.*
53   *
54   *   public class RestEndpointTest {
55   *   // create a local environment running on some random port
56   *   // use rt.host("/path/to/service") to wrap all URL creations for HTTP request methods
57   *   private static final RestServiceTestEnv rt = testEnvScanAllPackages(localhostRandomPort());
58   *
59   *   \@BeforeClass public static void oneTimeSetUp() {
60   *   env.setUpServer();
61   *   }
62   *
63   *   \@AfterClass public static void oneTimeTearDown() {
64   *   env.tearDownServer();
65   *   }
66   *   }
67   * </pre>
68   * 
69   * Add the following dependencies to your pom
70   * 
71   * <pre>
72   * &lt;dependency&gt;
73   *   &lt;groupId&gt;com.jayway.restassured&lt;/groupId&gt;
74   *   &lt;artifactId&gt;rest-assured&lt;/artifactId&gt;
75   *   &lt;version&gt;1.7.2&lt;/version&gt;
76   *   &lt;scope&gt;test&lt;/scope&gt;
77   * &lt;/dependency&gt;
78   * &lt;dependency&gt;
79   *   &lt;groupId&gt;org.apache.httpcomponents&lt;/groupId&gt;
80   *   &lt;artifactId&gt;httpcore&lt;/artifactId&gt;
81   *   &lt;version&gt;4.2.4&lt;/version&gt;
82   *   &lt;scope&gt;test&lt;/scope&gt;
83   * &lt;/dependency&gt;
84   * </pre>
85   */
86  public final class RestServiceTestEnv {
87    private Server server;
88  
89    private URL baseUrl = null;
90    private final ResourceConfig cfg;
91  
92    private static final Logger logger = LoggerFactory.getLogger(RestServiceTestEnv.class);
93  
94    /**
95     * Create an environment for <code>baseUrl</code>. The base URL should be the URL where the service to test is
96     * mounted, e.g. http://localhost:8090/test
97     */
98    private RestServiceTestEnv(ResourceConfig cfg) {
99      this.cfg = cfg;
100   }
101 
102   public static RestServiceTestEnv testEnvForClasses(Class<?>... restServices) {
103     return new RestServiceTestEnv(new ResourceConfig(restServices));
104   }
105 
106   /** Create a URL suitable for rest-assured's post(), get() e.al. methods. */
107   public String host(String path) {
108     if (baseUrl == null) {
109       throw new RuntimeException("Server not yet started");
110     }
111     return UrlSupport.url(baseUrl, path).toString();
112   }
113 
114   /**
115    * Return the base URL of the HTTP server. <code>http://host:port</code> public URL getBaseUrl() { return baseUrl; }
116    *
117    * Call in @BeforeClass annotated method.
118    */
119   public void setUpServer() {
120     int port = -1;
121     int maxRetries = 100;
122     try {
123       ServletContainer servletContainer = new ServletContainer(cfg);
124       ServletHolder jerseyServlet = new ServletHolder(servletContainer);
125       for (int tries = maxRetries; tries > 0; tries--) {
126         try {
127           port = 3000 + tries + ThreadLocalRandom.current().nextInt(62000);
128           logger.info("Starting http server at port {}", port);
129           server = new Server(port);
130           ServletContextHandler context = new ServletContextHandler(server, "/");
131           context.addServlet(jerseyServlet, "/*");
132           server.start();
133           baseUrl = UrlSupport.url("http", "127.0.0.1", port);
134           return;
135         } catch (IOException e) {
136           logger.error("Couldn't bind server to port {}. Retrying with new port...", port, e);
137           // Rethrow exception after last try
138           if (tries == 1) {
139             logger.error("Couldn't start server after {} tries.", maxRetries);
140             throw e;
141           }
142           Thread.sleep(100);
143         }
144       }
145     } catch (Exception e) {
146       logger.error("Unexpected Exception occurred while setting up http server");
147       chuck(e);
148     }
149   }
150 
151   /** Call in @AfterClass annotated method. */
152   public void tearDownServer() {
153     if (server != null) {
154       logger.info("Stop http server");
155       try {
156         server.stop();
157       } catch (Exception e) {
158         logger.warn("Stop http server - failed {}", e.getMessage());
159       }
160     }
161   }
162 
163 }