blob: 77ea670e5985658c71ef86ba4e477cdf703006aa [file] [log] [blame]
Ray Milkeye8274232014-05-27 16:03:12 -07001package net.onrc.onos.api.rest;
2
3
4import net.floodlightcontroller.restserver.RestletRoutable;
Ray Milkeye8274232014-05-27 16:03:12 -07005import org.restlet.Application;
6import org.restlet.Component;
7import org.restlet.Context;
8import org.restlet.Request;
9import org.restlet.Response;
10import org.restlet.Restlet;
11import org.restlet.Server;
12import org.restlet.data.Protocol;
13import org.restlet.data.Reference;
14import org.restlet.data.Status;
15import org.restlet.ext.jackson.JacksonRepresentation;
16import org.restlet.representation.Representation;
17import org.restlet.routing.Filter;
18import org.restlet.routing.Router;
19import org.restlet.routing.Template;
20import org.restlet.service.StatusService;
21
22import java.util.List;
23
24/**
25 * A REST API server suitible for inclusion in unit tests. Unit tests can
26 * create a server on a given port, then specify the RestletRoutable classes
27 * that are to be tested. The lifecyle for the server is to create it
28 * and then start it during the @Before (setUp) portion of the test and to
29 * shut it down during the @After (tearDown) section.
30 */
31public class TestRestApiServer {
32
33 private List<RestletRoutable> restlets;
34 private RestApplication restApplication;
35 private Server server;
36 private Component component;
37 private int port;
38
39 /**
40 * Hide the default constructor.
41 */
42 @SuppressWarnings("unused")
43 private TestRestApiServer() { }
44
45 /**
46 * Public constructor. Given a port number, create a REST API server on
47 * that port. The server is not running, it can be started using the
48 * startServer() method.
49 * @param serverPort port for the server to listen on.
50 */
51 public TestRestApiServer(final int serverPort) {
52 port = serverPort;
53 }
54
55 /**
56 * The restlet engine requires an Application as a container.
57 */
58 private class RestApplication extends Application {
Ray Milkey531bb232014-06-03 09:08:15 -070059 private final Context context;
Ray Milkeye8274232014-05-27 16:03:12 -070060
61 /**
62 * Initialize the Application along with its Context.
63 */
64 public RestApplication() {
65 super();
66 context = new Context();
67 }
68
69 /**
70 * Add an attribute to the Context for the Application. This is most
71 * often used to specify attributes that allow modules to locate each
72 * other.
73 *
74 * @param name name of the attribute
75 * @param value value of the attribute
76 */
77 public void addAttribute(final String name, final Object value) {
78 context.getAttributes().put(name, value);
79 }
80
81 /**
82 * Sets up the Restlet for the APIs under test using a Router. Also, a
83 * filter is installed to deal with double slashes in URLs.
84 * This code is adapted from
85 * net.floodlightcontroller.restserver.RestApiServer
86 *
87 * @return Router object for the APIs under test.
88 */
89 @Override
90 public Restlet createInboundRoot() {
91 Router baseRouter = new Router(context);
92 baseRouter.setDefaultMatchingMode(Template.MODE_STARTS_WITH);
93 for (RestletRoutable rr : restlets) {
94 baseRouter.attach(rr.basePath(), rr.getRestlet(context));
95 }
96
97 /**
98 * Filter out multiple slashes in URLs to make them a single slash.
99 */
100 Filter slashFilter = new Filter() {
101 @Override
102 protected int beforeHandle(Request request, Response response) {
103 Reference ref = request.getResourceRef();
104 String originalPath = ref.getPath();
105 if (originalPath.contains("//")) {
106 String newPath = originalPath.replaceAll("/+", "/");
107 ref.setPath(newPath);
108 }
109 return Filter.CONTINUE;
110 }
111
112 };
113 slashFilter.setNext(baseRouter);
114
115 return slashFilter;
116 }
117
118 /**
119 * Run the Application on a given port.
120 *
121 * @param restPort port to listen on for inbounde requests
122 */
123 public void run(final int restPort) {
124 setStatusService(new StatusService() {
125 @Override
126 public Representation getRepresentation(Status status,
127 Request request,
128 Response response) {
129 return new JacksonRepresentation<>(status);
130 }
131 });
132
133 // Start listening for REST requests
134 try {
135 component = new Component();
136 server = component.getServers().add(Protocol.HTTP, restPort);
137 component.getDefaultHost().attach(this);
138 component.start();
139 } catch (Exception e) {
140 throw new RuntimeException(e);
141 }
142 }
143 }
144
145 /**
146 * Start up the REST server. A list of the Restlets being tested is
147 * passed in. The usual use of this method is in the @Before (startUp)
148 * of a JUnit test.
149 *
150 * @param restletsUnderTest list of Restlets to run as part of the server.
Ray Milkeye8274232014-05-27 16:03:12 -0700151 */
Ray Milkey531bb232014-06-03 09:08:15 -0700152 public void startServer(final List<RestletRoutable> restletsUnderTest) {
Ray Milkeye8274232014-05-27 16:03:12 -0700153 restlets = restletsUnderTest;
154
155 restApplication = new RestApplication();
156 restApplication.run(port);
157
158 }
159
160 /**
161 * Stop the REST server. The container is stopped, and the server will
162 * no longer respond to requests. The usual use of this is in the @After
Ray Milkey531bb232014-06-03 09:08:15 -0700163 * (tearDown) part of the test.
Ray Milkeye8274232014-05-27 16:03:12 -0700164 */
Ray Milkey531bb232014-06-03 09:08:15 -0700165 public void stopServer() {
166 try {
167 restApplication.stop();
168 server.stop();
169 component.stop();
170 } catch (Exception ex) {
171 // Stopping the server failed, convert to unchecked exception to
172 // abort the calling test with a failure.
173 throw new IllegalStateException(ex);
174 }
Ray Milkeye8274232014-05-27 16:03:12 -0700175 }
176
177
178 /**
179 * Add an attribute to the Context for the Application. This is most
180 * often used to specify attributes that allow modules to locate each
181 * other.
182 *
183 * @param name name of the attribute
184 * @param value value of the attribute
185 */
186 public void addAttribute(final String name, final Object value) {
187 restApplication.addAttribute(name, value);
188 }
189}