Bug #60 : JSON list responses from the NB-APIs returns native object (instead of...
[controller.git] / opendaylight / northbound / topology / src / main / java / org / opendaylight / controller / topology / northbound / TopologyNorthboundJAXRS.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.topology.northbound;
10
11 import java.util.ArrayList;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Set;
15 import java.util.concurrent.ConcurrentMap;
16
17 import javax.ws.rs.Consumes;
18 import javax.ws.rs.DELETE;
19 import javax.ws.rs.GET;
20 import javax.ws.rs.PUT;
21 import javax.ws.rs.Path;
22 import javax.ws.rs.PathParam;
23 import javax.ws.rs.Produces;
24 import javax.ws.rs.core.Context;
25 import javax.ws.rs.core.MediaType;
26 import javax.ws.rs.core.Response;
27 import javax.ws.rs.core.SecurityContext;
28 import javax.xml.bind.JAXBElement;
29
30 import org.codehaus.enunciate.jaxrs.ResponseCode;
31 import org.codehaus.enunciate.jaxrs.StatusCodes;
32 import org.codehaus.enunciate.jaxrs.TypeHint;
33 import org.opendaylight.controller.northbound.commons.RestMessages;
34 import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException;
35 import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
36 import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException;
37 import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils;
38 import org.opendaylight.controller.sal.authorization.Privilege;
39 import org.opendaylight.controller.sal.core.Edge;
40 import org.opendaylight.controller.sal.core.Property;
41 import org.opendaylight.controller.sal.utils.ServiceHelper;
42 import org.opendaylight.controller.sal.utils.Status;
43 import org.opendaylight.controller.topologymanager.ITopologyManager;
44 import org.opendaylight.controller.topologymanager.TopologyUserLinkConfig;
45
46 /**
47  * Topology Northbound REST API
48  *
49  * <br>
50  * <br>
51  * Authentication scheme : <b>HTTP Basic</b><br>
52  * Authentication realm : <b>opendaylight</b><br>
53  * Transport : <b>HTTP and HTTPS</b><br>
54  * <br>
55  * HTTPS Authentication is disabled by default. Administrator can enable it in
56  * tomcat-server.xml after adding a proper keystore / SSL certificate from a
57  * trusted authority.<br>
58  * More info :
59  * http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html#Configuration
60  */
61
62 @Path("/")
63 public class TopologyNorthboundJAXRS {
64
65     private String username;
66
67     @Context
68     public void setSecurityContext(SecurityContext context) {
69         if (context != null && context.getUserPrincipal() != null) username = context.getUserPrincipal().getName();
70     }
71
72     protected String getUserName() {
73         return username;
74     }
75
76     /**
77      *
78      * Retrieve the Topology
79      *
80      * @param containerName
81      *            The container for which we want to retrieve the topology (Eg.
82      *            'default')
83      *
84      * @return A List of EdgeProps each EdgeProp represent an Edge of the grap
85      *         with the corresponding properties attached to it.
86      *
87      *         <pre>
88      *
89      * Example:
90      *
91      * RequestURL:
92      * http://localhost:8080/controller/nb/v2/topology/default
93      *
94      * Response in XML:
95      * &lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
96      * &lt;topology&gt;
97      *     &lt;edgeProperties&gt;
98      *         &lt;edge&gt;
99      *             &lt;tailNodeConnector&gt;
100      *                 &lt;node&gt;
101      *                     &lt;id&gt;00:00:00:00:00:00:00:02&lt;/id&gt;
102      *                     &lt;type&gt;OF&lt;/type&gt;
103      *                 &lt;/node&gt;
104      *                 &lt;id&gt;2&lt;/id&gt;
105      *                 &lt;type&gt;OF&lt;/type&gt;
106      *             &lt;/tailNodeConnector&gt;
107      *             &lt;headNodeConnector&gt;
108      *                 &lt;node&gt;
109      *                     &lt;id&gt;00:00:00:00:00:00:00:51&lt;/id&gt;
110      *                     &lt;type&gt;OF&lt;/type&gt;
111      *                 &lt;/node&gt;
112      *                 &lt;id&gt;2&lt;/id&gt;
113      *                 &lt;type&gt;OF&lt;/type&gt;
114      *             &lt;/headNodeConnector&gt;
115      *         &lt;/edge&gt;
116      *         &lt;properties&gt;
117      *             &lt;state&gt;
118      *                 &lt;value&gt;1&lt;/value&gt;
119      *             &lt;/state&gt;
120      *             &lt;config&gt;
121      *                 &lt;value&gt;1&lt;/value&gt;
122      *             &lt;/config&gt;
123      *             &lt;name&gt;
124      *                 &lt;value&gt;C1_2-L2_2&lt;/value&gt;
125      *             &lt;/name&gt;
126      *             &lt;timeStamp&gt;
127      *                 &lt;value&gt;1377279422032&lt;/value&gt;
128      *                 &lt;name&gt;creation&lt;/name&gt;
129      *             &lt;/timeStamp&gt;
130      *         &lt;/properties&gt;
131      *     &lt;/edgeProperties&gt;
132      *     &lt;edgeProperties&gt;
133      *         &lt;edge&gt;
134      *             &lt;tailNodeConnector&gt;
135      *                 &lt;node&gt;
136      *                     &lt;id&gt;00:00:00:00:00:00:00:51&lt;/id&gt;
137      *                     &lt;type&gt;OF&lt;/type&gt;
138      *                 &lt;/node&gt;
139      *                 &lt;id&gt;2&lt;/id&gt;
140      *                 &lt;type&gt;OF&lt;/type&gt;
141      *             &lt;/tailNodeConnector&gt;
142      *             &lt;headNodeConnector&gt;
143      *                 &lt;node&gt;
144      *                     &lt;id&gt;00:00:00:00:00:00:00:02&lt;/id&gt;
145      *                     &lt;type&gt;OF&lt;/type&gt;
146      *                 &lt;/node&gt;
147      *                 &lt;id&gt;2&lt;/id&gt;
148      *                 &lt;type&gt;OF&lt;/type&gt;
149      *             &lt;/headNodeConnector&gt;
150      *         &lt;/edge&gt;
151      *         &lt;properties&gt;
152      *             &lt;state&gt;
153      *                 &lt;value&gt;1&lt;/value&gt;
154      *             &lt;/state&gt;
155      *             &lt;name&gt;
156      *                 &lt;value&gt;L2_2-C1_2&lt;/value&gt;
157      *             &lt;/name&gt;
158      *             &lt;config&gt;
159      *                 &lt;value&gt;1&lt;/value&gt;
160      *             &lt;/config&gt;
161      *             &lt;timeStamp&gt;
162      *                 &lt;value&gt;1377279423564&lt;/value&gt;
163      *                 &lt;name&gt;creation&lt;/name&gt;
164      *             &lt;/timeStamp&gt;
165      *         &lt;/properties&gt;
166      *     &lt;/edgeProperties&gt;
167      * &lt;/topology&gt;
168      *
169      * Response in JSON:
170      * {"edgeProperties":[{"edge":{"tailNodeConnector":{"node":{"id":"00:00:00:00:00:00:00:02","type":"OF"},
171      * "id":"2","type":"OF"},"headNodeConnector":{"node":{"id":"00:00:00:00:00:00:00:51","type":"OF"},"id":
172      * "2","type":"OF"}},"properties":{"timeStamp":{"value":"1377278961017","name":"creation"}}},
173      * {"edge":{"tailNodeConnector":{"node":{"id":"00:00:00:00:00:00:00:51","type":"OF"},"id":
174      * "2","type":"OF"}},"headNodeConnector":{"node":{"id":"00:00:00:00:00:00:00:02","type":"OF"},
175      * "id":"2","type":"OF"}},"properties":{"timeStamp":{"value":"1377278961018","name":"creation"}}}]}
176      *
177      * </pre>
178      */
179     @Path("/{containerName}")
180     @GET
181     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
182     @TypeHint(Topology.class)
183     @StatusCodes({ @ResponseCode(code = 404, condition = "The Container Name was not found") })
184     public Topology getTopology(@PathParam("containerName") String containerName) {
185
186         if (!NorthboundUtils.isAuthorized(
187                 getUserName(), containerName, Privilege.READ, this)) {
188             throw new UnauthorizedException(
189                     "User is not authorized to perform this operation on container "
190                             + containerName);
191         }
192         ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
193                 .getInstance(ITopologyManager.class, containerName, this);
194         if (topologyManager == null) {
195             throw new ResourceNotFoundException(
196                     RestMessages.NOCONTAINER.toString());
197         }
198
199         Map<Edge, Set<Property>> topo = topologyManager.getEdges();
200         if (topo != null) {
201             List<EdgeProperties> res = new ArrayList<EdgeProperties>();
202             for (Map.Entry<Edge, Set<Property>> entry : topo.entrySet()) {
203                 EdgeProperties el = new EdgeProperties(entry.getKey(), entry.getValue());
204                 res.add(el);
205             }
206             return new Topology(res);
207         }
208
209         return null;
210     }
211
212     /**
213      * Retrieve the user configured links
214      *
215      * @param containerName
216      *            The container for which we want to retrieve the user links (Eg. 'default')
217      *
218      * @return A List of user configured links
219      *
220      * <pre>
221      *
222      * Example:
223      *
224      * RequestURL:
225      * http://localhost:8080/controller/nb/v2/topology/default/user-link
226      *
227      * Response in XML:
228      * &lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
229      * &lt;topologyUserLinks&gt;
230      * &lt;userLinks&gt;
231      * &lt;status&gt;Success&lt;/status&gt;
232      * &lt;name&gt;link1&lt;/name&gt;
233      * &lt;srcNodeConnector&gt;OF|2@OF|00:00:00:00:00:00:00:02&lt;/srcNodeConnector&gt;
234      * &lt;dstNodeConnector&gt;OF|2@OF|00:00:00:00:00:00:00:51&lt;/dstNodeConnector&gt;
235      * &lt;/userLinks&gt;
236      * &lt;/topologyUserLinks&gt;
237      *
238      * Response in JSON:
239      * {"userLinks":{"status":"Success","name":"link1","srcNodeConnector":
240      * "OF|2@OF|00:00:00:00:00:00:00:02","dstNodeConnector":"OF|2@OF|00:00:00:00:00:00:00:51"}}
241      *
242      * </pre>
243      */
244     @Path("/{containerName}/user-link")
245     @GET
246     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
247     @TypeHint(TopologyUserLinks.class)
248     @StatusCodes({ @ResponseCode(code = 404, condition = "The Container Name was not found") })
249     public TopologyUserLinks getUserLinks(
250             @PathParam("containerName") String containerName) {
251
252         if (!NorthboundUtils.isAuthorized(
253                 getUserName(), containerName, Privilege.READ, this)) {
254             throw new UnauthorizedException(
255                     "User is not authorized to perform this operation on container "
256                             + containerName);
257         }
258         ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
259                 .getInstance(ITopologyManager.class, containerName, this);
260         if (topologyManager == null) {
261             throw new ResourceNotFoundException(
262                     RestMessages.NOCONTAINER.toString());
263         }
264
265         ConcurrentMap<String, TopologyUserLinkConfig> userLinks = topologyManager
266                 .getUserLinks();
267         if ((userLinks != null) && (userLinks.values() != null)) {
268             List<TopologyUserLinkConfig> res = new ArrayList<TopologyUserLinkConfig>(
269                     userLinks.values());
270             return new TopologyUserLinks(res);
271         }
272
273         return null;
274     }
275
276     /**
277      * Add an User Link
278      *
279      * @param containerName
280      *            Name of the Container (Eg. 'default')
281      * @param TopologyUserLinkConfig
282      *            in JSON or XML format
283      * @return Response as dictated by the HTTP Response Status code
284      *
285      * <pre>
286      *
287      * Example:
288      *
289      * RequestURL:
290      * http://localhost:8080/controller/nb/v2/topology/default/user-link
291      *
292      * Request in XML:
293      * &lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
294      * &lt;topologyUserLinks&gt;
295      * &lt;status&gt;Success&lt;/status&gt;
296      * &lt;name&gt;link1&lt;/name&gt;
297      * &lt;srcNodeConnector&gt;OF|2@OF|00:00:00:00:00:00:00:02&lt;/srcNodeConnector&gt;
298      * &lt;dstNodeConnector&gt;OF|2@OF|00:00:00:00:00:00:00:51&lt;/dstNodeConnector&gt;
299      * &lt;/topologyUserLinks&gt;
300      *
301      * Request in JSON:
302      * {"status":"Success","name":"link1","srcNodeConnector":"OF|2@OF|00:00:00:00:00:00:00:02","dstNodeConnector":"OF|2@OF|00:00:00:00:00:00:00:51"}
303      *
304      * </pre>
305      */
306     @Path("/{containerName}/user-link")
307     @PUT
308     @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
309     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
310     @StatusCodes({
311             @ResponseCode(code = 201, condition = "User Link added successfully"),
312             @ResponseCode(code = 404, condition = "The Container Name was not found"),
313             @ResponseCode(code = 409, condition = "Failed to add User Link due to Conflicting Name"),
314             @ResponseCode(code = 500, condition = "Failed to add User Link. Failure Reason included in HTTP Error response"),
315             @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
316     public Response addUserLink(
317             @PathParam(value = "containerName") String containerName,
318             @TypeHint(TopologyUserLinkConfig.class) TopologyUserLinkConfig userLinkConfig) {
319
320         if (!NorthboundUtils.isAuthorized(
321                 getUserName(), containerName, Privilege.WRITE, this)) {
322             throw new UnauthorizedException(
323                     "User is not authorized to perform this operation on container "
324                             + containerName);
325         }
326         ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
327                 .getInstance(ITopologyManager.class, containerName, this);
328         if (topologyManager == null) {
329             throw new ResourceNotFoundException(
330                     RestMessages.NOCONTAINER.toString());
331         }
332
333         Status status = topologyManager.addUserLink(userLinkConfig);
334         if (status.isSuccess()) {
335             NorthboundUtils.auditlog("User Link", username, "added", userLinkConfig.getName(), containerName);
336             return Response.status(Response.Status.CREATED).build();
337         }
338         throw new InternalServerErrorException(status.getDescription());
339     }
340
341     /**
342      * Delete an User Link
343      *
344      * @param containerName
345      *            Name of the Container (Eg. 'default')
346      * @param name
347      *            Name of the Link Configuration (Eg. 'config1')
348      * @return Response as dictated by the HTTP Response Status code
349      *
350      * <pre>
351      *
352      * Example:
353      *
354      * RequestURL:
355      * http://localhost:8080/controller/nb/v2/topology/default/user-link/config1
356      *
357      * </pre>
358      */
359     @Path("/{containerName}/user-link/{name}")
360     @DELETE
361     @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
362     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
363     @StatusCodes({
364             @ResponseCode(code = 200, condition = "Operation successful"),
365             @ResponseCode(code = 404, condition = "The Container Name or Link Configuration Name was not found"),
366             @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
367     public Response deleteUserLink(
368             @PathParam("containerName") String containerName,
369             @PathParam("name") String name) {
370
371         if (!NorthboundUtils.isAuthorized(
372                 getUserName(), containerName, Privilege.WRITE, this)) {
373             throw new UnauthorizedException(
374                     "User is not authorized to perform this operation on container "
375                             + containerName);
376         }
377         ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
378                 .getInstance(ITopologyManager.class, containerName, this);
379         if (topologyManager == null) {
380             throw new ResourceNotFoundException(
381                     RestMessages.NOCONTAINER.toString());
382         }
383
384         Status ret = topologyManager.deleteUserLink(name);
385         if (ret.isSuccess()) {
386             NorthboundUtils.auditlog("User Link", username, "removed", name, containerName);
387             return Response.ok().build();
388         }
389         throw new ResourceNotFoundException(ret.getDescription());
390     }
391 }