Thomas Vachuska
Committed by Ray Milkey

Initial sketch of codecs and REST API approach.

FIxed typos and defects.
ONOS-81

Change-Id: I789444a181abea509c354966545c927e305710d1
Showing 28 changed files with 985 additions and 22 deletions
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.codec;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Context for codecs to use while encoding/decoding.
*/
public interface CodecContext {
/**
* Returns the JSON object mapper.
*
* @return object mapper
*/
ObjectMapper mapper();
/**
* Returns the JSON codec for the specified entity class.
*
* @param entityClass entity class
* @param <T> entity type
* @return JSON codec; null if no codec available for the class
*/
<T> JsonCodec<T> codec(Class<T> entityClass);
/**
* Returns reference to the specified service implementation.
*
* @param serviceClass service class
* @param <T> service type
* @return JSON codec; null if no codec available for the class
*/
<T> T get(Class<T> serviceClass);
}
......@@ -33,9 +33,10 @@ public interface CodecService {
* Returns the JSON codec for the specified entity class.
*
* @param entityClass entity class
* @param <T> entity type
* @return JSON codec; null if no codec available for the class
*/
JsonCodec getCodec(Class<?> entityClass);
<T> JsonCodec<T> getCodec(Class<T> entityClass);
/**
* Registers the specified JSON codec for the given entity class.
......@@ -43,7 +44,7 @@ public interface CodecService {
* @param entityClass entity class
* @param codec JSON codec
*/
void registerCodec(Class<?> entityClass, JsonCodec codec);
<T> void registerCodec(Class<T> entityClass, JsonCodec<T> codec);
/**
* Unregisters the JSON codec for the specified entity class.
......
......@@ -16,7 +16,6 @@
package org.onlab.onos.codec;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
......@@ -32,36 +31,41 @@ public abstract class JsonCodec<T> {
* Encodes the specified entity into JSON.
*
* @param entity entity to encode
* @param mapper object mapper
* @param context encoding context
* @return JSON node
* @throws java.lang.UnsupportedOperationException if the codec does not
* support encode operations
*/
public abstract ObjectNode encode(T entity, ObjectMapper mapper);
public ObjectNode encode(T entity, CodecContext context) {
throw new UnsupportedOperationException("encode() not supported");
}
/**
* Decodes the specified entity from JSON.
*
* @param json JSON to decode
* @param context decoding context
* @return decoded entity
* @throws java.lang.UnsupportedOperationException if the codec does not
* support decode operations
*/
public abstract T decode(ObjectNode json);
public T decode(ObjectNode json, CodecContext context) {
throw new UnsupportedOperationException("decode() not supported");
}
/**
* Encodes the collection of the specified entities.
*
* @param entities collection of entities to encode
* @param mapper object mapper
* @param context encoding context
* @return JSON array
* @throws java.lang.UnsupportedOperationException if the codec does not
* support encode operations
*/
public ArrayNode encode(Iterable<T> entities, ObjectMapper mapper) {
ArrayNode result = mapper.createArrayNode();
public ArrayNode encode(Iterable<T> entities, CodecContext context) {
ArrayNode result = context.mapper().createArrayNode();
for (T entity : entities) {
result.add(encode(entity, mapper));
result.add(encode(entity, context));
}
return result;
}
......@@ -70,14 +74,15 @@ public abstract class JsonCodec<T> {
* Decodes the specified JSON array into a collection of entities.
*
* @param json JSON array to decode
* @param context decoding context
* @return collection of decoded entities
* @throws java.lang.UnsupportedOperationException if the codec does not
* support decode operations
*/
public List<T> decode(ArrayNode json) {
public List<T> decode(ArrayNode json, CodecContext context) {
List<T> result = new ArrayList<>();
for (JsonNode node : json) {
result.add(decode((ObjectNode) node));
result.add(decode((ObjectNode) node, context));
}
return result;
}
......
......@@ -92,6 +92,8 @@ public interface LinkService {
*/
Set<Link> getIngressLinks(ConnectPoint connectPoint);
// FIXME: I don't think this makes sense; discuss and remove or adjust return
// to be a Set<Link> or add Link.Type parameter
/**
* Returns the infrastructure links between the specified source
* and destination connection points.
......
......@@ -58,12 +58,12 @@ public class JsonCodecTest {
private static class FooCodec extends JsonCodec<Foo> {
@Override
public ObjectNode encode(Foo entity, ObjectMapper mapper) {
return mapper.createObjectNode().put("name", entity.name);
public ObjectNode encode(Foo entity, CodecContext context) {
return context.mapper().createObjectNode().put("name", entity.name);
}
@Override
public Foo decode(ObjectNode json) {
public Foo decode(ObjectNode json, CodecContext context) {
return new Foo(json.get("name").asText());
}
}
......@@ -74,9 +74,26 @@ public class JsonCodecTest {
Foo f2 = new Foo("bar");
FooCodec codec = new FooCodec();
ImmutableList<Foo> entities = ImmutableList.of(f1, f2);
ArrayNode json = codec.encode(entities, new ObjectMapper());
List<Foo> foos = codec.decode(json);
ArrayNode json = codec.encode(entities, new TestContext());
List<Foo> foos = codec.decode(json, new TestContext());
assertEquals("incorrect encode/decode", entities, foos);
}
private class TestContext implements CodecContext {
private ObjectMapper mapper = new ObjectMapper();
@Override
public ObjectMapper mapper() {
return mapper;
}
@Override
public <T> JsonCodec<T> codec(Class<T> entityClass) {
return null;
}
@Override
public <T> T get(Class<T> serviceClass) {
return null;
}
}
}
\ No newline at end of file
......
......@@ -80,7 +80,7 @@
<group>
<title>GUI, REST &amp; Command-Line</title>
<packages>
org.onlab.onos.gui:org.onlab.onos.rest:org.onlab.onos.cli:org.onlab.onos.gui.*:org.onlab.onos.rest.*:org.onlab.onos.cli.*
org.onlab.onos.gui:org.onlab.onos.rest:org.onlab.onos.cli:org.onlab.onos.gui.*:org.onlab.onos.rest.*:org.onlab.onos.cli.*:org.onlab.onos.codec.impl
</packages>
</group>
<group>
......
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.api;
/**
* Represents condition where an item is not found or not available.
*/
public class ItemNotFoundException extends RuntimeException {
/**
* Creates a new exception with no message.
*/
public ItemNotFoundException() {
}
/**
* Creates a new exception with the supplied message.
* @param message error message
*/
public ItemNotFoundException(String message) {
super(message);
}
/**
* Creates a new exception with the supplied message and cause.
* @param message error message
* @param cause cause of the error
*/
public ItemNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
......@@ -18,6 +18,8 @@ package org.onlab.rest;
import org.onlab.osgi.DefaultServiceDirectory;
import org.onlab.osgi.ServiceDirectory;
import javax.ws.rs.core.Response;
/**
* Base abstraction of a JAX-RS resource.
*/
......@@ -44,8 +46,12 @@ public abstract class BaseResource {
* @param <T> type of service
* @return service implementation
*/
protected static <T> T get(Class<T> service) {
public <T> T get(Class<T> service) {
return services.get(service);
}
protected static Response.ResponseBuilder ok(Object obj) {
return Response.ok(obj);
}
}
......
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.onos.codec.CodecContext;
import org.onlab.onos.codec.JsonCodec;
import org.onlab.onos.net.Annotated;
import org.onlab.onos.net.Annotations;
/**
* Base JSON codec for annotated entities.
*/
public abstract class AnnotatedCodec<T extends Annotated> extends JsonCodec<T> {
/**
* Adds JSON encoding of the given item annotations to the specified node.
*
* @param node node to add annotations to
* @param entity annotated entity
* @param context encode context
* @return the given node
*/
protected ObjectNode annotate(ObjectNode node, T entity, CodecContext context) {
if (!entity.annotations().keys().isEmpty()) {
JsonCodec<Annotations> codec = context.codec(Annotations.class);
node.set("annotations", codec.encode(entity.annotations(), context));
}
return node;
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.onos.codec.CodecContext;
import org.onlab.onos.codec.JsonCodec;
import org.onlab.onos.net.Annotations;
/**
* Annotations JSON codec.
*/
public class AnnotationsCodec extends JsonCodec<Annotations> {
@Override
public ObjectNode encode(Annotations annotations, CodecContext context) {
ObjectNode result = context.mapper().createObjectNode();
for (String key : annotations.keys()) {
result.put(key, annotations.value(key));
}
return result;
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.codec.impl;
import com.google.common.collect.ImmutableSet;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.codec.CodecService;
import org.onlab.onos.codec.JsonCodec;
import org.onlab.onos.net.Annotations;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.Port;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Implementation of the JSON codec brokering service.
*/
@Component(immediate = true)
@Service
public class CodecManager implements CodecService {
private static Logger log = LoggerFactory.getLogger(CodecManager.class);
private final Map<Class<?>, JsonCodec> codecs = new ConcurrentHashMap<>();
@Activate
public void activate() {
codecs.clear();
registerCodec(Annotations.class, new AnnotationsCodec());
registerCodec(Device.class, new DeviceCodec());
registerCodec(Port.class, new PortCodec());
registerCodec(ConnectPoint.class, new ConnectPointCodec());
registerCodec(Link.class, new LinkCodec());
log.info("Started");
}
@Deactivate
public void deativate() {
codecs.clear();
log.info("Stopped");
}
@Override
public Set<Class<?>> getCodecs() {
return ImmutableSet.copyOf(codecs.keySet());
}
@Override
@SuppressWarnings("unchecked")
public <T> JsonCodec<T> getCodec(Class<T> entityClass) {
return codecs.get(entityClass);
}
@Override
public <T> void registerCodec(Class<T> entityClass, JsonCodec<T> codec) {
codecs.putIfAbsent(entityClass, codec);
}
@Override
public void unregisterCodec(Class<?> entityClass) {
codecs.remove(entityClass);
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.onos.codec.CodecContext;
import org.onlab.onos.codec.JsonCodec;
import org.onlab.onos.net.ConnectPoint;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Connection point JSON codec.
*/
public class ConnectPointCodec extends JsonCodec<ConnectPoint> {
@Override
public ObjectNode encode(ConnectPoint point, CodecContext context) {
checkNotNull(point, "Connect point cannot be null");
return context.mapper().createObjectNode()
.put("device", point.deviceId().toString())
.put("port", point.port().toString());
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.onos.codec.CodecContext;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.device.DeviceService;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Device JSON codec.
*/
public class DeviceCodec extends AnnotatedCodec<Device> {
@Override
public ObjectNode encode(Device device, CodecContext context) {
checkNotNull(device, "Device cannot be null");
DeviceService service = context.get(DeviceService.class);
ObjectNode result = context.mapper().createObjectNode()
.put("id", device.id().toString())
.put("available", service.isAvailable(device.id()))
.put("role", service.getRole(device.id()).toString())
.put("mfr", device.manufacturer())
.put("hw", device.hwVersion())
.put("sw", device.swVersion())
.put("serial", device.serialNumber());
return annotate(result, device, context);
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.onos.codec.CodecContext;
import org.onlab.onos.codec.JsonCodec;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.device.DeviceService;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Link JSON codec.
*/
public class LinkCodec extends AnnotatedCodec<Link> {
@Override
public ObjectNode encode(Link link, CodecContext context) {
checkNotNull(link, "Link cannot be null");
DeviceService service = context.get(DeviceService.class);
JsonCodec<ConnectPoint> codec = context.codec(ConnectPoint.class);
ObjectNode result = context.mapper().createObjectNode();
result.set("src", codec.encode(link.src(), context));
result.set("dst", codec.encode(link.dst(), context));
return annotate(result, link, context);
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.codec.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.onos.codec.CodecContext;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.PortNumber;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Device port JSON codec.
*/
public class PortCodec extends AnnotatedCodec<Port> {
@Override
public ObjectNode encode(Port port, CodecContext context) {
checkNotNull(port, "Port cannot be null");
ObjectNode result = context.mapper().createObjectNode()
.put("port", portName(port.number()))
.put("isEnabled", port.isEnabled())
.put("type", port.type().toString().toLowerCase())
.put("portSpeed", port.portSpeed());
return annotate(result, port, context);
}
private String portName(PortNumber port) {
return port.equals(PortNumber.LOCAL) ? "local" : port.toString();
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Implementations of the codec broker and built-in entity JSON codecs.
*/
package org.onlab.onos.codec.impl;
\ No newline at end of file
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.rest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.api.ItemNotFoundException;
import org.onlab.onos.codec.CodecContext;
import org.onlab.onos.codec.CodecService;
import org.onlab.onos.codec.JsonCodec;
import org.onlab.rest.BaseResource;
/**
* Abstract REST resource.
*/
public class AbstractWebResource extends BaseResource implements CodecContext {
@Override
public ObjectMapper mapper() {
return new ObjectMapper();
}
/**
* Returns the JSON codec for the specified entity class.
*
* @param entityClass entity class
* @param <T> entity type
* @return JSON codec
*/
public <T> JsonCodec<T> codec(Class<T> entityClass) {
return get(CodecService.class).getCodec(entityClass);
}
/**
* Returns JSON object wrapping the array encoding of the specified
* collection of items.
*
* @param codecClass codec item class
* @param field field holding the array
* @param items collection of items to be encoded into array
* @param <T> item type
* @return JSON object
*/
protected <T> ObjectNode encodeArray(Class<T> codecClass, String field,
Iterable<T> items) {
ObjectNode result = mapper().createObjectNode();
result.set(field, codec(codecClass).encode(items, this));
return result;
}
/**
* Returns the specified item if that items is null; otherwise throws
* not found exception.
*
* @param item item to check
* @param message not found message
* @param <T> item type
* @return item if not null
* @throws org.onlab.api.ItemNotFoundException if item is null
*/
protected <T> T nullIsNotFound(T item, String message) {
if (item == null) {
throw new ItemNotFoundException(message);
}
return item;
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.rest;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.device.DeviceService;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.onos.net.DeviceId.deviceId;
/**
* REST resource for interacting with the inventory of infrastructure devices.
*/
@Path("devices")
public class DevicesWebResource extends AbstractWebResource {
public static final String DEVICE_NOT_FOUND = "Device is not found";
@GET
public Response getDevices() {
Iterable<Device> devices = get(DeviceService.class).getDevices();
return ok(encodeArray(Device.class, "devices", devices)).build();
}
@GET
@Path("{id}")
public Response getDevice(@PathParam("id") String id) {
Device device = nullIsNotFound(get(DeviceService.class).getDevice(deviceId(id)),
DEVICE_NOT_FOUND);
return ok(codec(Device.class).encode(device, this)).build();
}
@GET
@Path("{id}/ports")
public Response getDevicePorts(@PathParam("id") String id) {
DeviceService service = get(DeviceService.class);
Device device = nullIsNotFound(service.getDevice(deviceId(id)), DEVICE_NOT_FOUND);
List<Port> ports = checkNotNull(service.getPorts(deviceId(id)), "Ports could not be retrieved");
ObjectNode result = codec(Device.class).encode(device, this);
result.set("ports", codec(Port.class).encode(ports, this));
return ok(result).build();
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.rest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
/**
* JAX-RS Response message body writer.
*/
@Produces("application/json")
public class JsonBodyWriter implements MessageBodyWriter<ObjectNode> {
private ObjectMapper mapper = new ObjectMapper();
@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return type == ObjectNode.class;
}
@Override
public long getSize(ObjectNode node, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return -1;
}
@Override
public void writeTo(ObjectNode node, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException {
mapper.writer().writeValue(entityStream, node);
entityStream.flush();
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.rest;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.link.LinkService;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import static org.onlab.onos.net.DeviceId.deviceId;
import static org.onlab.onos.net.PortNumber.portNumber;
import static org.onlab.onos.rest.LinksWebResource.Direction.valueOf;
/**
* REST resource for interacting with the inventory of infrastructure links.
*/
@Path("links")
public class LinksWebResource extends AbstractWebResource {
enum Direction { ALL, INGRESS, EGRESS }
@GET
public Response getLinks(@QueryParam("device") String deviceId,
@QueryParam("port") String port,
@QueryParam("direction") String direction) {
LinkService service = get(LinkService.class);
Iterable<Link> links;
if (deviceId != null && port != null) {
links = getConnectPointLinks(new ConnectPoint(deviceId(deviceId),
portNumber(port)),
direction, service);
} else if (deviceId != null) {
links = getDeviceLinks(deviceId(deviceId), direction, service);
} else {
links = service.getLinks();
}
return ok(encodeArray(Link.class, "links", links)).build();
}
private Iterable<Link> getConnectPointLinks(ConnectPoint point,
String direction,
LinkService service) {
Direction dir = direction != null ?
valueOf(direction.toUpperCase()) : Direction.ALL;
switch (dir) {
case INGRESS:
return service.getIngressLinks(point);
case EGRESS:
return service.getEgressLinks(point);
default:
return service.getLinks(point);
}
}
private Iterable<Link> getDeviceLinks(DeviceId deviceId,
String direction,
LinkService service) {
Direction dir = direction != null ?
valueOf(direction.toUpperCase()) : Direction.ALL;
switch (dir) {
case INGRESS:
return service.getDeviceIngressLinks(deviceId);
case EGRESS:
return service.getDeviceEgressLinks(deviceId);
default:
return service.getDeviceLinks(deviceId);
}
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.rest.exceptions;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
/**
* Base exception mapper implementation.
*/
public abstract class AbstractMapper<E extends Throwable> implements ExceptionMapper<E> {
/**
* Returns the response status to be given when the exception occurs.
*
* @return response status
*/
protected abstract Response.Status responseStatus();
@Override
public Response toResponse(E exception) {
return response(responseStatus(), exception).build();
}
/**
* Produces a response builder primed with the supplied status code
* and JSON entity with the status code and exception message.
*
* @param status response status
* @param exception exception to encode
* @return response builder
*/
protected Response.ResponseBuilder response(Response.Status status,
Throwable exception) {
ObjectMapper mapper = new ObjectMapper();
ObjectNode result = mapper.createObjectNode()
.put("code", status.getStatusCode())
.put("message", exception.getMessage());
return Response.status(status).entity(result.toString());
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.rest.exceptions;
import org.onlab.api.ItemNotFoundException;
import javax.ws.rs.core.Response;
/**
* Mapper for service not found exceptions to the NOT_FOUND response code.
*/
public class EntityNotFoundMapper extends AbstractMapper<ItemNotFoundException> {
@Override
protected Response.Status responseStatus() {
return Response.Status.NOT_FOUND;
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.rest.exceptions;
import javax.ws.rs.core.Response;
/**
* Mapper for service not found exceptions to the NOT_FOUND response code.
*/
public class ServerErrorMapper extends AbstractMapper<RuntimeException> {
@Override
protected Response.Status responseStatus() {
return Response.Status.INTERNAL_SERVER_ERROR;
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.onos.rest.exceptions;
import org.onlab.osgi.ServiceNotFoundException;
import javax.ws.rs.core.Response;
/**
* Mapper for service not found exceptions to the SERVICE_UNAVAILABLE response code.
*/
public class ServiceNotFoundMapper extends AbstractMapper<ServiceNotFoundException> {
@Override
protected Response.Status responseStatus() {
return Response.Status.SERVICE_UNAVAILABLE;
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Various exception mappers to map errors to proper response status codes.
*/
package org.onlab.onos.rest.exceptions;
\ No newline at end of file
......@@ -24,8 +24,21 @@
<servlet-name>JAX-RS Service</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>org.onlab.onos.rest</param-value>
<param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
<param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.classnames</param-name>
<param-value>
org.onlab.onos.rest.exceptions.EntityNotFoundMapper,
org.onlab.onos.rest.exceptions.ServiceNotFoundMapper,
org.onlab.onos.rest.exceptions.ServerErrorMapper,
org.onlab.onos.rest.JsonBodyWriter,
org.onlab.onos.rest.DevicesWebResource,
org.onlab.onos.rest.LinksWebResource,
org.onlab.onos.rest.ConfigResource
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
......
......@@ -109,6 +109,10 @@
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
......@@ -120,13 +124,15 @@
<Import-Package>
org.slf4j,
org.osgi.framework,
javax.ws.rs,javax.ws.rs.core,
javax.ws.rs,javax.ws.rs.core,javax.ws.rs.ext,
com.sun.jersey.api.core,
com.sun.jersey.spi.container.servlet,
com.sun.jersey.server.impl.container.servlet,
com.fasterxml.jackson.databind,
com.fasterxml.jackson.databind.node,
com.google.common.base.*,
org.onlab.api.*,
org.onlab.osgi.*,
org.onlab.packet.*,
org.onlab.rest.*,
org.onlab.onos.*
......