WIP: Refactoring the Intent REST API.
NOTE: All changes are Work-in-Progress, and they are in the process
of being actively updated and refactored.

 * The new implementation is in onos/core/intent/runtime/web
   The old implementation in the onos/core/datagrid/web is kept until
   the refactoring is completed.

 * The new REST API base path is /wm/onos/intent

 * The initial set of (new) APIs is the following:
   - /wm/onos/intent/high
     GET all high-level intent
     POST (create) a collection of high-level intents
     DELETE all intents

   - /wm/onos/intent/high/{intent-id}
     GET a high-level intent object
     DELETE a high-level intent object

   - /wm/onos/intent/low
     GET all low-level intents

   - /wm/onos/intent/low/{intent-id}
     GET a low-level intent object

   - /wm/onos/intent/path/switch/{src-dpid}/shortest-path/{dst-dpid}
     GET a Shortest Path between two Switch DPIDs

  * The Application-Level Intent object is specified in class
    onos/api/intent/ApplicationIntent.java

TODO (list incomplete):
 - Return the appropriate REST codes and return values for each REST operation
 - Add the appropriate Java APIs so each REST call would make a single
   Java call.
 - If necessary, rename API class ApplicationIntent to something more
   appropriate, and use it in the Java API.
 - Re-think/refactor the ApplicationIntent so it becomes a more solid base for
   all (high-level) intents.
 - The corresponding Java APIs for each REST call should be synchronous
   (for some definition of the expected operation outcome).
 - Implement intent/intents Java API delete operation that requires a
   single call instead of two calls (delete and purge)
 - Refactor the High and Low Level intents, such that they don't use
   inheritance from a common base class.
 - Cleanup the return Intent objects representation in JSON. E.g.,
   the Dpid JSON should be just "dpid": <value> instead of
   "dpid": {
        "value": <value>
    }

Change-Id: Ia994a2026f57a1f176c5321c1952325e3b986097
diff --git a/src/main/java/net/onrc/onos/api/intent/ApplicationIntent.java b/src/main/java/net/onrc/onos/api/intent/ApplicationIntent.java
new file mode 100644
index 0000000..113e6cd
--- /dev/null
+++ b/src/main/java/net/onrc/onos/api/intent/ApplicationIntent.java
@@ -0,0 +1,216 @@
+package net.onrc.onos.api.intent;
+
+/**
+ * An object used by the application to specify an intent.
+ */
+public class ApplicationIntent {
+    private String intentId;            // The Intent ID
+    private String intentType;          // The Intent type
+
+    // If true, don't update the path when topology changes
+    private boolean isStaticPath;
+
+    private String srcSwitchDpid;       // Flow Path Source Switch DPID
+    private int srcSwitchPort;          // Flow Path Source Switch Port
+    private String dstSwitchDpid;       // Flow Path Destination Switch DPID
+    private int dstSwitchPort;          // Flow Path Destination Switch Port
+    private double bandwidth;        // Bandwidth for Constrained Shortest Path
+
+    // Matching Fields
+    private String matchSrcMac;         // Matching source MAC address
+    private String matchDstMac;         // Matching destination MAC address
+
+    /**
+     * Gets the Intent ID.
+     *
+     * @return the Intent ID.
+     */
+    public String intentId() {
+        return this.intentId;
+    }
+
+    /**
+     * Sets the Intent ID.
+     *
+     * @param intentId the Intent ID to set.
+     */
+    public void setIntentId(String intentId) {
+        this.intentId = intentId;
+    }
+
+    /**
+     * Gets the Intent type.
+     *
+     * Currently, the following strings are recognized:
+     *   - "SHORTEST_PATH"
+     *   - "CONSTRAINED_SHORTEST_PATH"
+     *
+     * @return the Intent type.
+     */
+    public String intentType() {
+        return this.intentType;
+    }
+
+    /**
+     * Sets the Intent type.
+     *
+     * Currently, the following strings are recognized:
+     *   - "SHORTEST_PATH"
+     *   - "CONSTRAINED_SHORTEST_PATH"
+     *
+     * @param intentType the Intent type to set.
+     */
+    public void setIntentType(String intentType) {
+        this.intentType = intentType;
+    }
+
+    /**
+     * Gets the "staticPath" flag for the intent.
+     *
+     * A path for an intent is defined as "static" if it shouldn't be updated
+     * when the topology changes.
+     *
+     * @return true if the intent path is static, otherwise false.
+     */
+    public boolean isStaticPath() {
+        return this.isStaticPath;
+    }
+
+    /**
+     * Sets the "staticPath" flag for the intent.
+     *
+     * A path for an intent is defined as "static" if it shouldn't be updated
+     * when the topology changes.
+     *
+     * @param staticPath true if the intent path is static, otherwise false.
+     */
+    public void setStaticPath(boolean staticPath) {
+        this.isStaticPath = staticPath;
+    }
+
+    /**
+     * Gets the Source Switch DPID.
+     *
+     * @return the Source Switch DPID.
+     */
+    public String srcSwitchDpid() {
+        return this.srcSwitchDpid;
+    }
+
+    /**
+     * Sets the Source Switch DPID.
+     *
+     * @param srcSwitchDpid the Source Switch DPID to set.
+     */
+    public void setSrcSwitchDpid(String srcSwitchDpid) {
+        this.srcSwitchDpid = srcSwitchDpid;
+    }
+
+    /**
+     * Gets the Source Switch Port.
+     *
+     * @return the Source Switch Port.
+     */
+    public int srcSwitchPort() {
+        return this.srcSwitchPort;
+    }
+
+    /**
+     * Sets the Source Switch Port.
+     *
+     * @param srcSwitchPort the Source Switch Port to set.
+     */
+    public void setSrcSwitchPort(int srcSwitchPort) {
+        this.srcSwitchPort = srcSwitchPort;
+    }
+
+    /**
+     * Gets the Destination Switch DPID.
+     *
+     * @return the Destination Switch DPID.
+     */
+    public String dstSwitchDpid() {
+        return this.dstSwitchDpid;
+    }
+
+    /**
+     * Sets the Destination Switch DPID.
+     *
+     * @param dstSwitchDpid the Destination Switch DPID to set.
+     */
+    public void setDstSwitchDpid(String dstSwitchDpid) {
+        this.dstSwitchDpid = dstSwitchDpid;
+    }
+
+    /**
+     * Gets the Destination Switch Port.
+     *
+     * @return the Destination Switch Port.
+     */
+    public int dstSwitchPort() {
+        return this.dstSwitchPort;
+    }
+
+    /**
+     * Sets the Destination Switch Port.
+     *
+     * @param dstSwitchPort the Destination Switch Port to set.
+     */
+    public void setDstSwitchPort(int dstSwitchPort) {
+        this.dstSwitchPort = dstSwitchPort;
+    }
+
+    /**
+     * Gets the bandwidth for Constrained Shortest Path.
+     *
+     * @return the bandwidth for Constrained Shortest Path.
+     */
+    public double bandwidth() {
+        return this.bandwidth;
+    }
+
+    /**
+     * Sets the bandwidth for Constrained Shortest Path.
+     *
+     * @param bandwidth the bandwidth for Constrained Shortest Path
+     */
+    public void setBandwidth(double bandwidth) {
+        this.bandwidth = bandwidth;
+    }
+
+    /**
+     * Gets the matching source MAC address.
+     *
+     * @return the matching source MAC address.
+     */
+    public String matchSrcMac() {
+        return this.matchSrcMac;
+    }
+
+    /**
+     * Sets the matching source MAC address.
+     *
+     * @param matchSrcMac the matching source MAC address to set.
+     */
+    public void setMatchSrcMac(String matchSrcMac) {
+        this.matchSrcMac = matchSrcMac;
+    }
+
+    /**
+     * Gets the matching destination MAC address.
+     *
+     * @return the matching destination MAC address.
+     */
+    public String matchDstMac() {
+        return this.matchDstMac;
+    }
+
+    /**
+     * Sets the matching destination MAC address.
+     *
+     * @param matchDstMac the matching destination MAC address to set.
+     */
+    public void setMatchDstMac(String matchDstMac) {
+        this.matchDstMac = matchDstMac;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java b/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
index a9ba287..1e6ae00 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
@@ -14,6 +14,8 @@
 import net.floodlightcontroller.core.module.FloodlightModuleException;
 import net.floodlightcontroller.core.module.IFloodlightModule;
 import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.restserver.IRestApiService;
+
 import net.onrc.onos.core.datagrid.IDatagridService;
 import net.onrc.onos.core.datagrid.IEventChannel;
 import net.onrc.onos.core.datagrid.IEventChannelListener;
@@ -26,6 +28,7 @@
 import net.onrc.onos.core.intent.PathIntent;
 import net.onrc.onos.core.intent.PathIntentMap;
 import net.onrc.onos.core.intent.ShortestPathIntent;
+import net.onrc.onos.core.intent.runtime.web.IntentWebRoutable;
 import net.onrc.onos.core.registry.IControllerRegistryService;
 import net.onrc.onos.core.topology.DeviceEvent;
 import net.onrc.onos.core.topology.INetworkGraphListener;
@@ -82,6 +85,7 @@
     private PathIntentMap pathIntents;
     private IControllerRegistryService controllerRegistry;
     private PersistIntent persistIntent;
+    private IRestApiService restApi;
 
     private IEventChannel<Long, IntentOperationList> opEventChannel;
     private final ReentrantLock lock = new ReentrantLock();
@@ -137,6 +141,7 @@
         Collection<Class<? extends IFloodlightService>> l = new ArrayList<>(2);
         l.add(IDatagridService.class);
         l.add(INetworkGraphService.class);
+        l.add(IRestApiService.class);
         return l;
     }
 
@@ -145,6 +150,7 @@
         datagridService = context.getServiceImpl(IDatagridService.class);
         networkGraphService = context.getServiceImpl(INetworkGraphService.class);
         controllerRegistry = context.getServiceImpl(IControllerRegistryService.class);
+        restApi = context.getServiceImpl(IRestApiService.class);
     }
 
     @Override
@@ -156,6 +162,7 @@
         datagridService.addListener(INTENT_STATE_EVENT_CHANNEL_NAME, this, Long.class, IntentStateList.class);
         networkGraphService.registerNetworkGraphListener(this);
         persistIntent = new PersistIntent(controllerRegistry);
+        restApi.addRestletRoutable(new IntentWebRoutable());
     }
 
     // ================================================================================
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java
new file mode 100644
index 0000000..c4e2ac4
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java
@@ -0,0 +1,89 @@
+package net.onrc.onos.core.intent.runtime.web;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import net.onrc.onos.core.intent.Intent;
+import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.IntentOperation;
+import net.onrc.onos.core.intent.IntentOperationList;
+import net.onrc.onos.core.intent.runtime.IPathCalcRuntimeService;
+
+import org.restlet.resource.Delete;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class to access a single high-level intent.
+ */
+public class IntentHighObjectResource extends ServerResource {
+    private static final Logger log = LoggerFactory.getLogger(IntentHighObjectResource.class);
+    // TODO need to assign proper application id.
+    private static final String APPLN_ID = "1";
+
+    /**
+     * Gets a single high-level intent.
+     *
+     * @return a Collection with the single high-level intent if found,
+     * otherwise null.
+     */
+    @Get("json")
+    public Collection<Intent> retrieve() throws IOException {
+        IPathCalcRuntimeService pathRuntime = (IPathCalcRuntimeService) getContext().
+                getAttributes().get(IPathCalcRuntimeService.class.getCanonicalName());
+        Collection<Intent> intents = null;
+
+        String intentId = (String) getRequestAttributes().get("intent-id");
+        if (intentId == null) {
+            return null;        // Missing Intent ID
+        }
+
+        //
+        // Get a single high-level Intent: use the Intent ID to find it
+        //
+        IntentMap intentMap = pathRuntime.getHighLevelIntents();
+        String applnIntentId = APPLN_ID + ":" + intentId;
+        Intent intent = intentMap.getIntent(applnIntentId);
+        if (intent != null) {
+            intents = new LinkedList<>();
+            intents.add(intent);
+        }
+
+        return intents;
+    }
+
+    /**
+     * Deletes a single high-level intent.
+     *
+     * @return the status of the operation (TBD).
+     */
+    @Delete("json")
+    public String store() {
+        IPathCalcRuntimeService pathRuntime = (IPathCalcRuntimeService) getContext().
+                getAttributes().get(IPathCalcRuntimeService.class.getCanonicalName());
+
+        String intentId = (String) getRequestAttributes().get("intent-id");
+        if (intentId == null) {
+            return null;        // Missing Intent ID
+        }
+
+        //
+        // Remove a single high-level Intent: use the Intent ID to find it
+        //
+        //
+        // TODO: The implementation below is broken - waiting for the Java API
+        // TODO: The deletion should use synchronous Java API?
+        IntentMap intentMap = pathRuntime.getHighLevelIntents();
+        String applnIntentId = APPLN_ID + ":" + intentId;
+        Intent intent = intentMap.getIntent(applnIntentId);
+        if (intent != null) {
+            IntentOperationList operations = new IntentOperationList();
+            operations.add(IntentOperation.Operator.REMOVE, intent);
+            pathRuntime.executeIntentOperations(operations);
+        }
+        return "";      // TODO no reply yet from the purge intents call
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java
new file mode 100644
index 0000000..ede564f
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java
@@ -0,0 +1,149 @@
+package net.onrc.onos.core.intent.runtime.web;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.api.intent.ApplicationIntent;
+import net.onrc.onos.core.intent.ConstrainedShortestPathIntent;
+import net.onrc.onos.core.intent.Intent;
+import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.IntentOperation;
+import net.onrc.onos.core.intent.IntentOperationList;
+import net.onrc.onos.core.intent.ShortestPathIntent;
+import net.onrc.onos.core.intent.runtime.IPathCalcRuntimeService;
+import net.onrc.onos.core.util.Dpid;
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.restlet.resource.Delete;
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class to access the high-level intents.
+ */
+public class IntentHighResource extends ServerResource {
+    private static final Logger log = LoggerFactory.getLogger(IntentHighResource.class);
+    // TODO need to assign proper application id.
+    private static final String APPLN_ID = "1";
+
+    /**
+     * Gets all high-level intents.
+     *
+     * @return a collection with all high-level intents.
+     */
+    @Get("json")
+    public Collection<Intent> retrieve() throws IOException {
+        IPathCalcRuntimeService pathRuntime = (IPathCalcRuntimeService) getContext().
+                getAttributes().get(IPathCalcRuntimeService.class.getCanonicalName());
+
+        IntentMap intentMap = pathRuntime.getHighLevelIntents();
+        Collection<Intent> intents = intentMap.getAllIntents();
+
+        return intents;
+    }
+
+    /**
+     * Adds a collection of high-level intents.
+     *
+     * @return the status of the operation (TBD).
+     */
+    @Post("json")
+    public String store(String jsonIntent) throws IOException {
+        IPathCalcRuntimeService pathRuntime = (IPathCalcRuntimeService) getContext()
+                .getAttributes().get(IPathCalcRuntimeService.class.getCanonicalName());
+        if (pathRuntime == null) {
+            log.warn("Failed to get path calc runtime");
+            return "";
+        }
+
+        String reply = "";
+        ObjectMapper mapper = new ObjectMapper();
+        ApplicationIntent[] addOperations = null;
+        try {
+            addOperations = mapper.readValue(jsonIntent, ApplicationIntent[].class);
+        } catch (JsonParseException ex) {
+            log.error("JsonParseException occurred", ex);
+        } catch (JsonMappingException ex) {
+            log.error("JsonMappingException occurred", ex);
+        } catch (IOException ex) {
+            log.error("IOException occurred", ex);
+        }
+        if (addOperations == null) {
+            return "";
+        }
+
+        //
+        // Process all intents one-by-one
+        //
+        // TODO: The Intent Type should be enum instead of a string,
+        // and we should use a switch statement below to process the
+        // different type of intents.
+        //
+        IntentOperationList intentOperations = new IntentOperationList();
+        for (ApplicationIntent oper : addOperations) {
+            String applnIntentId = APPLN_ID + ":" + oper.intentId();
+
+            IntentOperation.Operator operator = IntentOperation.Operator.ADD;
+            Dpid srcSwitchDpid = new Dpid(oper.srcSwitchDpid());
+            Dpid dstSwitchDpid = new Dpid(oper.dstSwitchDpid());
+
+            if (oper.intentType().equals("SHORTEST_PATH")) {
+                //
+                // Process Shortest-Path Intent
+                //
+                ShortestPathIntent spi =
+                    new ShortestPathIntent(applnIntentId,
+                                           srcSwitchDpid.value(),
+                                           oper.srcSwitchPort(),
+                                           MACAddress.valueOf(oper.matchSrcMac()).toLong(),
+                                           dstSwitchDpid.value(),
+                                           oper.dstSwitchPort(),
+                                           MACAddress.valueOf(oper.matchDstMac()).toLong());
+                spi.setPathFrozen(oper.isStaticPath());
+                intentOperations.add(operator, spi);
+            } else {
+                //
+                // Process Constrained Shortest-Path Intent
+                //
+                ConstrainedShortestPathIntent cspi =
+                    new ConstrainedShortestPathIntent(applnIntentId,
+                                                      srcSwitchDpid.value(),
+                                                      oper.srcSwitchPort(),
+                                                      MACAddress.valueOf(oper.matchSrcMac()).toLong(),
+                                                      dstSwitchDpid.value(),
+                                                      oper.dstSwitchPort(),
+                                                      MACAddress.valueOf(oper.matchDstMac()).toLong(),
+                                                      oper.bandwidth());
+                cspi.setPathFrozen(oper.isStaticPath());
+                intentOperations.add(operator, cspi);
+            }
+        }
+        // Apply the Intent Operations
+        pathRuntime.executeIntentOperations(intentOperations);
+
+        return reply;
+    }
+
+    /**
+     * Deletes all high-level intents.
+     *
+     * @return the status of the operation (TBD).
+     */
+    @Delete("json")
+    public String store() {
+        IPathCalcRuntimeService pathRuntime = (IPathCalcRuntimeService) getContext().
+                getAttributes().get(IPathCalcRuntimeService.class.getCanonicalName());
+
+        // Delete all intents
+        // TODO: The implementation below is broken - waiting for the Java API
+        // TODO: The deletion should use synchronous Java API?
+        pathRuntime.purgeIntents();
+        return "";      // TODO no reply yet from the purge intents call
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowObjectResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowObjectResource.java
new file mode 100644
index 0000000..4f5cc8a
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowObjectResource.java
@@ -0,0 +1,54 @@
+package net.onrc.onos.core.intent.runtime.web;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import net.onrc.onos.core.intent.Intent;
+import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.runtime.IPathCalcRuntimeService;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class to access a single low-level intent.
+ */
+public class IntentLowObjectResource extends ServerResource {
+    private static final Logger log = LoggerFactory.getLogger(IntentLowObjectResource.class);
+    // TODO need to assign proper application id.
+    private static final String APPLN_ID = "1";
+
+    /**
+     * Gets a single low-level intent.
+     *
+     * @return a Collection with the single low-level intent if found,
+     * otherwise null.
+     */
+    @Get("json")
+    public Collection<Intent> retrieve() throws IOException {
+        IPathCalcRuntimeService pathRuntime = (IPathCalcRuntimeService) getContext().
+                getAttributes().get(IPathCalcRuntimeService.class.getCanonicalName());
+        Collection<Intent> intents = null;
+
+        String intentId = (String) getRequestAttributes().get("intent-id");
+        if (intentId == null) {
+            return null;        // Missing Intent ID
+        }
+
+        //
+        // Get a single low-level Intent: use the Intent ID to find it
+        //
+        IntentMap intentMap = pathRuntime.getPathIntents();
+        String applnIntentId = APPLN_ID + ":" + intentId;
+        Intent intent = intentMap.getIntent(applnIntentId);
+        if (intent != null) {
+            intents = new LinkedList<>();
+            intents.add(intent);
+        }
+
+        return intents;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java
new file mode 100644
index 0000000..bf0b527
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java
@@ -0,0 +1,39 @@
+package net.onrc.onos.core.intent.runtime.web;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import net.onrc.onos.core.intent.Intent;
+import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.runtime.IPathCalcRuntimeService;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class to access the low-level intents.
+ */
+public class IntentLowResource extends ServerResource {
+    private static final Logger log = LoggerFactory.getLogger(IntentLowResource.class);
+
+    /**
+     * Gets all low-level intents.
+     *
+     * @return a collection of all low-leve intents.
+     */
+    @Get("json")
+    public Collection<Intent> retrieve() throws IOException {
+        IPathCalcRuntimeService pathRuntime = (IPathCalcRuntimeService) getContext().
+                getAttributes().get(IPathCalcRuntimeService.class.getCanonicalName());
+
+        //
+        // Get all low-level intents
+        //
+        IntentMap intentMap = pathRuntime.getPathIntents();
+        Collection<Intent> intents = intentMap.getAllIntents();
+
+        return intents;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentWebRoutable.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentWebRoutable.java
new file mode 100644
index 0000000..4c32c47
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentWebRoutable.java
@@ -0,0 +1,49 @@
+package net.onrc.onos.core.intent.runtime.web;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+/**
+ * REST API implementation for the Intents.
+ */
+public class IntentWebRoutable implements RestletRoutable {
+    /**
+     * Creates the Restlet router and bind to the proper resources.
+     */
+    @Override
+    public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        // GET all high-level intents
+        // POST (create) a collection of high-level intents
+        // DELETE all intents (TODO: delete a collection of high-level intents)
+        router.attach("/high", IntentHighResource.class);
+
+        // GET a high-level intent object
+        // PUT (update) a high-level intent object (entire object) (LATER?)
+        // DELETE a high-level intent object
+        router.attach("/high/{intent-id}", IntentHighObjectResource.class);
+
+        // GET all low-level intents
+        router.attach("/low", IntentLowResource.class);
+
+        // GET a low-level intent object
+        router.attach("/low/{intent-id}", IntentLowObjectResource.class);
+
+        // GET a Shortest Path between two Switch DPIDs
+        router.attach("/path/switch/{src-dpid}/shortest-path/{dst-dpid}",
+                      ShortestPathResource.class);
+
+        return router;
+    }
+
+    /**
+     * Sets the base path for the Intents.
+     */
+    @Override
+    public String basePath() {
+        return "/wm/onos/intent";
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/ShortestPathResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/ShortestPathResource.java
new file mode 100644
index 0000000..6338d52
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/ShortestPathResource.java
@@ -0,0 +1,96 @@
+package net.onrc.onos.core.intent.runtime.web;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.onrc.onos.core.intent.ConstrainedBFSTree;
+import net.onrc.onos.core.intent.Path;
+import net.onrc.onos.core.topology.INetworkGraphService;
+import net.onrc.onos.core.topology.Link;
+import net.onrc.onos.core.topology.LinkEvent;
+import net.onrc.onos.core.topology.NetworkGraph;
+import net.onrc.onos.core.topology.Switch;
+import net.onrc.onos.core.topology.serializers.LinkSerializer;
+import net.onrc.onos.core.util.Dpid;
+
+import org.codehaus.jackson.Version;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.module.SimpleModule;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class to access Shortest-Path information between switches.
+ */
+public class ShortestPathResource extends ServerResource {
+
+    private static final Logger log = LoggerFactory.getLogger(ShortestPathResource.class);
+
+    /**
+     * Gets the Shortest-Path infomration between switches.
+     *
+     * @return a JSON string with the Shortest-Path information between
+     * switches. The Shortest-Path information is an ordered collection of
+     * Links.
+     */
+    @Get("json")
+    public String retrieve() {
+        INetworkGraphService networkGraphService =
+                (INetworkGraphService) getContext().getAttributes().
+                        get(INetworkGraphService.class.getCanonicalName());
+
+        NetworkGraph graph = networkGraphService.getNetworkGraph();
+
+        //
+        // Prepare the JSON serializer
+        //
+        ObjectMapper mapper = new ObjectMapper();
+        SimpleModule module = new SimpleModule("module", new Version(1, 0, 0, null));
+        module.addSerializer(new LinkSerializer());
+        mapper.registerModule(module);
+
+        //
+        // Fetch the attributes
+        //
+        String srcDpidStr = (String) getRequestAttributes().get("src-dpid");
+        String dstDpidStr = (String) getRequestAttributes().get("dst-dpid");
+        Dpid srcDpid = new Dpid(srcDpidStr);
+        Dpid dstDpid = new Dpid(dstDpidStr);
+        log.debug("Getting Shortest Path {}--{}", srcDpidStr, dstDpidStr);
+
+        //
+        // Do the Shortest Path computation and return the result: a list of
+        // links.
+        //
+        graph.acquireReadLock();
+        try {
+            Switch srcSwitch = graph.getSwitch(srcDpid.value());
+            Switch dstSwitch = graph.getSwitch(dstDpid.value());
+            if ((srcSwitch == null) || (dstSwitch == null)) {
+                return "";
+            }
+            ConstrainedBFSTree bfsTree = new ConstrainedBFSTree(srcSwitch);
+            Path path = bfsTree.getPath(dstSwitch);
+            List<Link> links = new LinkedList<>();
+            for (LinkEvent linkEvent : path) {
+                Link link = graph.getLink(linkEvent.getSrc().getDpid(),
+                        linkEvent.getSrc().getNumber(),
+                        linkEvent.getDst().getDpid(),
+                        linkEvent.getDst().getNumber());
+                if (link == null) {
+                    return "";
+                }
+                links.add(link);
+            }
+            return mapper.writeValueAsString(links);
+        } catch (IOException e) {
+            log.error("Error writing Shortest Path to JSON", e);
+            return "";
+        } finally {
+            graph.releaseReadLock();
+        }
+    }
+}
diff --git a/src/test/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModuleTest.java b/src/test/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModuleTest.java
index 17f5788..5e1a11d 100644
--- a/src/test/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModuleTest.java
+++ b/src/test/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModuleTest.java
@@ -19,6 +19,8 @@
 
 import net.floodlightcontroller.core.module.FloodlightModuleContext;
 import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.restserver.IRestApiService;
+
 import net.onrc.onos.core.datagrid.IDatagridService;
 import net.onrc.onos.core.datagrid.IEventChannel;
 import net.onrc.onos.core.datagrid.IEventChannelListener;
@@ -29,6 +31,7 @@
 import net.onrc.onos.core.intent.IntentOperationList;
 import net.onrc.onos.core.intent.MockNetworkGraph;
 import net.onrc.onos.core.intent.ShortestPathIntent;
+import net.onrc.onos.core.intent.runtime.web.IntentWebRoutable;
 import net.onrc.onos.core.registry.IControllerRegistryService;
 import net.onrc.onos.core.topology.DeviceEvent;
 import net.onrc.onos.core.topology.INetworkGraphListener;
@@ -69,6 +72,7 @@
     private INetworkGraphService networkGraphService;
     private IControllerRegistryService controllerRegistryService;
     private PersistIntent persistIntent;
+    private IRestApiService restApi;
     private MockNetworkGraph graph;
     private IEventChannel<Long, IntentOperationList> intentOperationChannel;
     private IEventChannel<Long, IntentStateList> intentStateChannel;
@@ -86,6 +90,7 @@
         intentOperationChannel = createMock(IEventChannel.class);
         intentStateChannel = createMock(IEventChannel.class);
         persistIntent = PowerMock.createMock(PersistIntent.class);
+        restApi = createMock(IRestApiService.class);
 
         PowerMock.expectNew(PersistIntent.class,
                 anyObject(IControllerRegistryService.class)).andReturn(persistIntent);
@@ -100,6 +105,8 @@
         expect(persistIntent.persistIfLeader(eq(1L),
                 anyObject(IntentOperationList.class))).andReturn(true)
                 .anyTimes();
+        expect(modContext.getServiceImpl(IRestApiService.class))
+                .andReturn(restApi).once();
 
         expect(networkGraphService.getNetworkGraph()).andReturn(graph)
                 .anyTimes();
@@ -117,12 +124,14 @@
                 eq(Long.class),
                 eq(IntentStateList.class)))
                 .andReturn(intentStateChannel).once();
+        restApi.addRestletRoutable(anyObject(IntentWebRoutable.class));
 
         replay(datagridService);
         replay(networkGraphService);
         replay(modContext);
         replay(controllerRegistryService);
         PowerMock.replay(persistIntent, PersistIntent.class);
+        replay(restApi);
     }
 
 
@@ -235,6 +244,7 @@
         verify(modContext);
         verify(controllerRegistryService);
         PowerMock.verify(persistIntent, PersistIntent.class);
+        verify(restApi);
     }
 
     /**
diff --git a/src/test/java/net/onrc/onos/core/intent/runtime/UseCaseTest.java b/src/test/java/net/onrc/onos/core/intent/runtime/UseCaseTest.java
index 4a0df3a..f3c9aaa 100644
--- a/src/test/java/net/onrc/onos/core/intent/runtime/UseCaseTest.java
+++ b/src/test/java/net/onrc/onos/core/intent/runtime/UseCaseTest.java
@@ -14,6 +14,8 @@
 
 import net.floodlightcontroller.core.module.FloodlightModuleContext;
 import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.restserver.IRestApiService;
+
 import net.onrc.onos.core.datagrid.IDatagridService;
 import net.onrc.onos.core.datagrid.IEventChannel;
 import net.onrc.onos.core.datagrid.IEventChannelListener;
@@ -27,6 +29,7 @@
 import net.onrc.onos.core.intent.PathIntent;
 import net.onrc.onos.core.intent.PathIntentMap;
 import net.onrc.onos.core.intent.ShortestPathIntent;
+import net.onrc.onos.core.intent.runtime.web.IntentWebRoutable;
 import net.onrc.onos.core.registry.IControllerRegistryService;
 import net.onrc.onos.core.topology.DeviceEvent;
 import net.onrc.onos.core.topology.INetworkGraphListener;
@@ -60,6 +63,7 @@
     private INetworkGraphService networkGraphService;
     private IControllerRegistryService controllerRegistryService;
     private PersistIntent persistIntent;
+    private IRestApiService restApi;
     private IEventChannel<Long, IntentOperationList> intentOperationChannel;
     private IEventChannel<Long, IntentStateList> intentStateChannel;
 
@@ -79,6 +83,7 @@
         intentOperationChannel = createMock(IEventChannel.class);
         intentStateChannel = createMock(IEventChannel.class);
         persistIntent = PowerMock.createMock(PersistIntent.class);
+        restApi = createMock(IRestApiService.class);
 
         PowerMock.expectNew(PersistIntent.class,
                 anyObject(IControllerRegistryService.class)).andReturn(persistIntent);
@@ -92,6 +97,8 @@
         expect(persistIntent.getKey()).andReturn(1L).anyTimes();
         expect(persistIntent.persistIfLeader(eq(1L),
                 anyObject(IntentOperationList.class))).andReturn(true).anyTimes();
+        expect(modContext.getServiceImpl(IRestApiService.class))
+                .andReturn(restApi).once();
 
         expect(networkGraphService.getNetworkGraph()).andReturn(g).anyTimes();
         networkGraphService.registerNetworkGraphListener(anyObject(INetworkGraphListener.class));
@@ -106,12 +113,14 @@
                 eq(Long.class),
                 eq(IntentStateList.class)))
                 .andReturn(intentStateChannel).once();
+        restApi.addRestletRoutable(anyObject(IntentWebRoutable.class));
 
         replay(datagridService);
         replay(networkGraphService);
         replay(modContext);
         replay(controllerRegistryService);
         PowerMock.replay(persistIntent, PersistIntent.class);
+        replay(restApi);
     }
 
     @After
@@ -121,6 +130,7 @@
         verify(modContext);
         verify(controllerRegistryService);
         PowerMock.verify(persistIntent, PersistIntent.class);
+        verify(restApi);
     }
 
     private void showResult(PathIntentMap intents) {