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 * <dependency>
73 * <groupId>com.jayway.restassured</groupId>
74 * <artifactId>rest-assured</artifactId>
75 * <version>1.7.2</version>
76 * <scope>test</scope>
77 * </dependency>
78 * <dependency>
79 * <groupId>org.apache.httpcomponents</groupId>
80 * <artifactId>httpcore</artifactId>
81 * <version>4.2.4</version>
82 * <scope>test</scope>
83 * </dependency>
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 }