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();