Initial sketch of codecs and REST API approach.
FIxed typos and defects.
ONOS-81

Change-Id: I789444a181abea509c354966545c927e305710d1
diff --git a/core/api/src/main/java/org/onlab/onos/codec/CodecContext.java b/core/api/src/main/java/org/onlab/onos/codec/CodecContext.java
new file mode 100644
index 0000000..6c8493e
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/codec/CodecContext.java
@@ -0,0 +1,50 @@
+/*
+ * 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);
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/codec/CodecService.java b/core/api/src/main/java/org/onlab/onos/codec/CodecService.java
index b8f3285..100a28c 100644
--- a/core/api/src/main/java/org/onlab/onos/codec/CodecService.java
+++ b/core/api/src/main/java/org/onlab/onos/codec/CodecService.java
@@ -33,9 +33,10 @@
      * 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 @@
      * @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.
diff --git a/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java b/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java
index 5dcf25c..8a8f426 100644
--- a/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java
+++ b/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java
@@ -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;
 
@@ -31,37 +30,42 @@
     /**
      * Encodes the specified entity into JSON.
      *
-     * @param entity entity to encode
-     * @param mapper object mapper
+     * @param entity  entity to encode
+     * @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 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;
     }
@@ -69,15 +73,16 @@
     /**
      * Decodes the specified JSON array into a collection of entities.
      *
-     * @param json JSON array to decode
+     * @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;
     }
diff --git a/core/api/src/main/java/org/onlab/onos/net/link/LinkService.java b/core/api/src/main/java/org/onlab/onos/net/link/LinkService.java
index 70775f0..2039aa5 100644
--- a/core/api/src/main/java/org/onlab/onos/net/link/LinkService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/link/LinkService.java
@@ -92,6 +92,8 @@
      */
     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.
diff --git a/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java b/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java
index f482a48..05460fb 100644
--- a/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java
+++ b/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java
@@ -58,12 +58,12 @@
 
     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 @@
         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
diff --git a/docs/pom.xml b/docs/pom.xml
index c0d1fcd..655cc31 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -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>
diff --git a/utils/misc/src/main/java/org/onlab/api/ItemNotFoundException.java b/utils/misc/src/main/java/org/onlab/api/ItemNotFoundException.java
new file mode 100644
index 0000000..fe484d4
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/api/ItemNotFoundException.java
@@ -0,0 +1,46 @@
+/*
+ * 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);
+    }
+
+}
diff --git a/utils/pom.xml b/utils/pom.xml
index 54a5467..f46bc61 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -38,7 +38,7 @@
         <module>nio</module>
         <module>osgi</module>
         <module>rest</module>
-	<module>thirdparty</module>
+        <module>thirdparty</module>
     </modules>
 
     <dependencies>
diff --git a/utils/rest/src/main/java/org/onlab/rest/BaseResource.java b/utils/rest/src/main/java/org/onlab/rest/BaseResource.java
index 5886afe..50b4d0f 100644
--- a/utils/rest/src/main/java/org/onlab/rest/BaseResource.java
+++ b/utils/rest/src/main/java/org/onlab/rest/BaseResource.java
@@ -18,6 +18,8 @@
 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 @@
      * @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);
+    }
+
 }
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotatedCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotatedCodec.java
new file mode 100644
index 0000000..1a6c511
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotatedCodec.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotationsCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotationsCodec.java
new file mode 100644
index 0000000..a8d2907
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/AnnotationsCodec.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/CodecManager.java b/web/api/src/main/java/org/onlab/onos/codec/impl/CodecManager.java
new file mode 100644
index 0000000..30be730
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/CodecManager.java
@@ -0,0 +1,86 @@
+/*
+ * 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);
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/ConnectPointCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/ConnectPointCodec.java
new file mode 100644
index 0000000..3b5bf02
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/ConnectPointCodec.java
@@ -0,0 +1,38 @@
+/*
+ * 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());
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/DeviceCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/DeviceCodec.java
new file mode 100644
index 0000000..5835382
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/DeviceCodec.java
@@ -0,0 +1,45 @@
+/*
+ * 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);
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/LinkCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/LinkCodec.java
new file mode 100644
index 0000000..ad09663
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/LinkCodec.java
@@ -0,0 +1,43 @@
+/*
+ * 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);
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/PortCodec.java b/web/api/src/main/java/org/onlab/onos/codec/impl/PortCodec.java
new file mode 100644
index 0000000..4bfcc6a
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/PortCodec.java
@@ -0,0 +1,45 @@
+/*
+ * 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();
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/codec/impl/package-info.java b/web/api/src/main/java/org/onlab/onos/codec/impl/package-info.java
new file mode 100644
index 0000000..31db517
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/codec/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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
diff --git a/web/api/src/main/java/org/onlab/onos/rest/AbstractWebResource.java b/web/api/src/main/java/org/onlab/onos/rest/AbstractWebResource.java
new file mode 100644
index 0000000..ee9adb0
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/AbstractWebResource.java
@@ -0,0 +1,81 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/DevicesWebResource.java b/web/api/src/main/java/org/onlab/onos/rest/DevicesWebResource.java
new file mode 100644
index 0000000..52fca58
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/DevicesWebResource.java
@@ -0,0 +1,65 @@
+/*
+ * 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();
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/JsonBodyWriter.java b/web/api/src/main/java/org/onlab/onos/rest/JsonBodyWriter.java
new file mode 100644
index 0000000..d34e30e
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/JsonBodyWriter.java
@@ -0,0 +1,58 @@
+/*
+ * 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();
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/LinksWebResource.java b/web/api/src/main/java/org/onlab/onos/rest/LinksWebResource.java
new file mode 100644
index 0000000..479b6a0
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/LinksWebResource.java
@@ -0,0 +1,89 @@
+/*
+ * 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);
+        }
+    }
+
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/exceptions/AbstractMapper.java b/web/api/src/main/java/org/onlab/onos/rest/exceptions/AbstractMapper.java
new file mode 100644
index 0000000..50c27ae
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/exceptions/AbstractMapper.java
@@ -0,0 +1,57 @@
+/*
+ * 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());
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/exceptions/EntityNotFoundMapper.java b/web/api/src/main/java/org/onlab/onos/rest/exceptions/EntityNotFoundMapper.java
new file mode 100644
index 0000000..4c86fe6
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/exceptions/EntityNotFoundMapper.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServerErrorMapper.java b/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServerErrorMapper.java
new file mode 100644
index 0000000..a9d95d0
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServerErrorMapper.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServiceNotFoundMapper.java b/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServiceNotFoundMapper.java
new file mode 100644
index 0000000..297882b
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/exceptions/ServiceNotFoundMapper.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/exceptions/package-info.java b/web/api/src/main/java/org/onlab/onos/rest/exceptions/package-info.java
new file mode 100644
index 0000000..5f733e7
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/exceptions/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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
diff --git a/web/api/src/main/webapp/WEB-INF/web.xml b/web/api/src/main/webapp/WEB-INF/web.xml
index c634728..1342203 100644
--- a/web/api/src/main/webapp/WEB-INF/web.xml
+++ b/web/api/src/main/webapp/WEB-INF/web.xml
@@ -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>
diff --git a/web/pom.xml b/web/pom.xml
index 0efbd59..f846078 100644
--- a/web/pom.xml
+++ b/web/pom.xml
@@ -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.*