Better handling of extensions in PiPipeconf

Now built using a URL, while input streams are generated on-demand.
Before it could happen that the input stream was completelly read by
someone, leaving it unusable by others.

Change-Id: I61a76bf8b8c1d2f6e2d987661025e0323d59e1c7
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java b/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java
index e457052..e6f9406 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java
@@ -19,12 +19,16 @@
 import com.google.common.collect.ImmutableMap;
 import org.onosproject.net.driver.Behaviour;
 
+import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Optional;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
 
 /**
  * Default pipeconf implementation.
@@ -34,11 +38,11 @@
     private final PiPipeconfId id;
     private final PiPipelineModel pipelineModel;
     private final Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours;
-    private final Map<ExtensionType, InputStream> extensions;
+    private final Map<ExtensionType, URL> extensions;
 
     private DefaultPiPipeconf(PiPipeconfId id, PiPipelineModel pipelineModel,
                               Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours,
-                              Map<ExtensionType, InputStream> extensions) {
+                              Map<ExtensionType, URL> extensions) {
         this.id = id;
         this.pipelineModel = pipelineModel;
         this.behaviours = behaviours;
@@ -72,7 +76,15 @@
 
     @Override
     public Optional<InputStream> extension(ExtensionType type) {
-        return Optional.ofNullable(extensions.get(type));
+        if (extensions.containsKey(type)) {
+            try {
+                return Optional.of(extensions.get(type).openStream());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        } else {
+            return Optional.empty();
+        }
     }
 
     /**
@@ -93,7 +105,7 @@
         private PiPipelineModel pipelineModel;
         private ImmutableMap.Builder<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviourMapBuilder
                 = ImmutableMap.builder();
-        private ImmutableMap.Builder<ExtensionType, InputStream> extensionMapBuilder = ImmutableMap.builder();
+        private ImmutableMap.Builder<ExtensionType, URL> extensionMapBuilder = ImmutableMap.builder();
 
         /**
          * Sets the identifier of this pipeconf.
@@ -134,17 +146,27 @@
         /**
          * Adds an extension to this pipeconf.
          *
-         * @param type        extension type
-         * @param inputStream input stream pointing at the extension
+         * @param type extension type
+         * @param url  url pointing at the extension file
          * @return this
          */
-        public Builder addExtension(ExtensionType type, InputStream inputStream) {
+        public Builder addExtension(ExtensionType type, URL url) {
             checkNotNull(type);
-            checkNotNull(inputStream);
-            extensionMapBuilder.put(type, inputStream);
+            checkNotNull(url);
+            checkArgument(checkUrl(url), format("Extension url %s seems to be empty/non existent", url.toString()));
+            extensionMapBuilder.put(type, url);
             return this;
         }
 
+        private boolean checkUrl(URL url) {
+            try {
+                int byteCount = url.openStream().available();
+                return byteCount > 0;
+            } catch (IOException e) {
+                return false;
+            }
+        }
+
         /**
          * Creates a new pipeconf.
          *
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
index f94ae6e..389da5c 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
@@ -68,9 +68,9 @@
     boolean hasBehaviour(Class<? extends Behaviour> behaviourClass);
 
     /**
-     * Returns, if present, an input stream of ad device-specific or control
-     * protocol-specific extension of this configuration. For example, if requesting a
-     * target-specific P4 binary, this will return the same bytes produced by the P4 compiler.
+     * Returns, if present, an input stream pointing at the beginning of a file representing a device-specific or
+     * control protocol-specific extension of this configuration. For example, if requesting a target-specific P4
+     * binary, this will return the same bytes produced by the P4 compiler.
      *
      * @param type extension type
      * @return extension input stream
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorTest.java b/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorTest.java
index f060125..894865e 100644
--- a/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorTest.java
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorTest.java
@@ -61,7 +61,7 @@
     public void setUp() throws Exception {
         pipeconf = DefaultPiPipeconf.builder()
                 .withId(new PiPipeconfId("mock-pipeconf"))
-                .withPipelineModel(Bmv2PipelineModelParser.parse(this.getClass().getResourceAsStream(BMV2_JSON_PATH)))
+                .withPipelineModel(Bmv2PipelineModelParser.parse(this.getClass().getResource(BMV2_JSON_PATH)))
                 .addBehaviour(PiPipelineInterpreter.class, MockInterpreter.class)
                 .build();
     }
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/PiPipeconfManagerTest.java b/core/net/src/test/java/org/onosproject/net/pi/impl/PiPipeconfManagerTest.java
index a0a6c04..9850004 100644
--- a/core/net/src/test/java/org/onosproject/net/pi/impl/PiPipeconfManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/PiPipeconfManagerTest.java
@@ -54,6 +54,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -71,7 +72,7 @@
     private static final String PIPECONF_ID = "org.project.pipeconf.default";
     private static final String BMV2_JSON_PATH = "/org/onosproject/net/pi/impl/default.json";
 
-    private final InputStream bmv2JsonConfigStream = this.getClass().getResourceAsStream(BMV2_JSON_PATH);
+    private final URL bmv2JsonConfigUrl = this.getClass().getResource(BMV2_JSON_PATH);
 
     private static final DeviceId DEVICE_ID = DeviceId.deviceId("test:test");
     private static final String BASE_DRIVER = "baseDriver";
@@ -106,7 +107,7 @@
         piPipeconfService = new PiPipeconfManager();
         piPipeconf = DefaultPiPipeconf.builder()
                 .withId(new PiPipeconfId(PIPECONF_ID))
-                .withPipelineModel(Bmv2PipelineModelParser.parse(bmv2JsonConfigStream))
+                .withPipelineModel(Bmv2PipelineModelParser.parse(bmv2JsonConfigUrl))
                 .addBehaviour(Pipeliner.class, PipelinerAdapter.class)
                 .build();
         completeDriverName = BASE_DRIVER + ":" + piPipeconf.id();
diff --git a/drivers/bmv2/BUCK b/drivers/bmv2/BUCK
index b406b08..3726829 100644
--- a/drivers/bmv2/BUCK
+++ b/drivers/bmv2/BUCK
@@ -27,6 +27,7 @@
     '//incubator/bmv2/model:onos-incubator-bmv2-model',
     '//incubator/grpc-dependencies:grpc-core-repkg-1.3.0',
     '//lib:grpc-netty-1.3.0',
+    '//drivers/default:onos-drivers-default',
 ]
 
 TEST_DEPS = [
@@ -43,6 +44,7 @@
     '//protocols/grpc/ctl:onos-protocols-grpc-ctl',
     '//protocols/grpc/proto:onos-protocols-grpc-proto',
     '//incubator/bmv2/model:onos-incubator-bmv2-model',
+    '//drivers/default:onos-drivers-default',
 ] + GRPC_DEPS
 
 osgi_jar_with_tests(
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java
index 4903c3e..3004627 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java
@@ -17,12 +17,14 @@
 package org.onosproject.drivers.bmv2;
 
 import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
+import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
+import org.onosproject.net.behaviour.Pipeliner;
 import org.onosproject.net.pi.model.DefaultPiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconfId;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 
-import java.io.InputStream;
+import java.net.URL;
 
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
@@ -42,16 +44,16 @@
 
     static PiPipeconf get() {
 
-        final InputStream jsonConfigStream = Bmv2DefaultPipeconfFactory.class.getResourceAsStream(JSON_PATH);
-        final InputStream p4InfoStream = Bmv2DefaultPipeconfFactory.class.getResourceAsStream(P4INFO_PATH);
+        final URL jsonUrl = Bmv2DefaultPipeconfFactory.class.getResource(JSON_PATH);
+        final URL p4InfoUrl = Bmv2DefaultPipeconfFactory.class.getResource(P4INFO_PATH);
 
         return DefaultPiPipeconf.builder()
                 .withId(new PiPipeconfId(PIPECONF_ID))
-                .withPipelineModel(Bmv2PipelineModelParser.parse(jsonConfigStream))
-                // TODO: reuse default single table pipeliner.
+                .withPipelineModel(Bmv2PipelineModelParser.parse(jsonUrl))
                 .addBehaviour(PiPipelineInterpreter.class, Bmv2DefaultInterpreter.class)
-                .addExtension(P4_INFO_TEXT, p4InfoStream)
-                .addExtension(BMV2_JSON, jsonConfigStream)
+                .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
+                .addExtension(P4_INFO_TEXT, p4InfoUrl)
+                .addExtension(BMV2_JSON, jsonUrl)
                 .build();
     }
 }
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModelParser.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModelParser.java
index b7a1f15c..470ea78 100644
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModelParser.java
+++ b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModelParser.java
@@ -30,10 +30,13 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.net.URL;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -75,13 +78,16 @@
     /**
      * Parse the input stream pointing to a BMv2 JSON configuration, to a Bmv2PipelineModel object.
      *
-     * @param jsonInputStream input stream pointing to a BMv2 JSON configuration
+     * @param url URL pointing to a BMv2 JSON configuration
      * @return Bmv2PipelineModel BMv2 pipeline model object
      */
-    public static Bmv2PipelineModel parse(InputStream jsonInputStream) {
+    public static Bmv2PipelineModel parse(URL url) {
+        checkNotNull(url, "Url cannot be null");
         try {
+            InputStream inputStream = url.openStream();
+            checkArgument(inputStream.available() > 0, "Empty or non-existent JSON at " + url.toString());
             return parse(Json.parse(new BufferedReader(
-                    new InputStreamReader(jsonInputStream))).asObject());
+                    new InputStreamReader(inputStream))).asObject());
         } catch (IOException e) {
             throw new RuntimeException(e);
         }