Integrating YANG live compilation into YANG runtime.

- Bumped ONOS dependency on ONOS YANG tools 2.2.0-b4.
- Added CLI to compile YANG models.
- Added GUI capability to compile YANG models via drag-n-drop or file upload.
- Fixed defect in propagating self-contained JAR apps through the cluster.

Change-Id: Icbd2a588bf1ffe0282e12d3d10a117e0957c3084
diff --git a/apps/yang-gui/src/main/java/org/onosproject/yang/gui/YangModelMessageHandler.java b/apps/yang-gui/src/main/java/org/onosproject/yang/gui/YangModelMessageHandler.java
index 8bf6876..b048744 100644
--- a/apps/yang-gui/src/main/java/org/onosproject/yang/gui/YangModelMessageHandler.java
+++ b/apps/yang-gui/src/main/java/org/onosproject/yang/gui/YangModelMessageHandler.java
@@ -161,7 +161,7 @@
         // FIXME: Hack to properly resolve the YANG source resource
         private InputStream getSource(String modelId, YangModule module) {
             try {
-                module.getYangSource(); // trigger exception
+                return module.getYangSource(); // trigger exception
             } catch (ModelException e) {
                 // Strip the YANG source file base-name and then use it to access
                 // the corresponding resource in the correct run-time context.
@@ -172,7 +172,6 @@
                 return loader == null ? null :
                         loader.getResourceAsStream("/yang/resources" + baseName);
             }
-            return null;
         }
 
         private void addSource(ArrayNode source, InputStream yangSource) {
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel-theme.css b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel-theme.css
index c26ade5..971e133 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel-theme.css
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel-theme.css
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
+/* -- Drag-n-Drop YANG/ZIP files -- */
+div.dropping {
+    border: solid 3px #0095d6;
+}
+
 .light #yang-model-details-panel .src-frame {
     background-color: #f4f4f4;
 }
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
index bf3dce6..f65e636 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
@@ -14,11 +14,23 @@
  * limitations under the License.
  */
 
-
 #ov-yang-model h2 {
     display: inline-block;
 }
 
+#ov-yang-model div.ctrl-btns {
+    width: 250px;
+}
+
+/* -- Drag-n-Drop file upload -- */
+#ov-yang-model form#inputYangFileForm,
+#ov-yang-model input#uploadYangFile {
+    display: none;
+}
+
+.dropping {
+
+}
 
 #yang-model-details-panel.floatpanel {
     z-index: 0;
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.html b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.html
index b49bcd8..6cbb0e1 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.html
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.html
@@ -1,5 +1,5 @@
 <!-- YANG Model partial HTML -->
-<div id="ov-yang-model">
+<div id="ov-yang-model" yangfiledrop on-file-drop="yangDropped()">
 
     <div class="tabular-header">
         <h2>YANG Models ({{tableData.length}} total)</h2>
@@ -8,6 +8,19 @@
                  icon icon-id="refresh" icon-size="42"
                  tooltip tt-msg="autoRefreshTip"
                  ng-click="toggleRefresh()"></div>
+
+            <div class="separator"></div>
+
+            <form id="inputYangFileForm">
+                <input id="uploadYangFile"
+                       type="file" size="50" accept=".zip, *.jar, *.yang"
+                       yang-file-model="yangFile">
+            </form>
+
+            <div icon icon-size="42" icon-id="nav_yang"
+                 class="active" trigger-yang-form
+                 tooltip tt-msg="uploadTip">
+            </div>
         </div>
     </div>
 
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
index 00b6643..42fa343 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
@@ -44,7 +44,8 @@
     // constants
     var pName = 'yang-model-details-panel',
         detailsReq = 'yangModelDetailsRequest',
-        detailsResp = 'yangModelDetailsResponse';
+        detailsResp = 'yangModelDetailsResponse',
+        fileUploadUrl = '/onos/yang/models?modelId=';
 
 
     function createDetailsPanel() {
@@ -135,11 +136,12 @@
     // defines view controller
     angular.module('ovYangModel', [])
     .controller('OvYangModelCtrl', [
-        '$log', '$scope', 'TableBuilderService', 'TableDetailService',
+        '$log', '$scope', '$http', '$timeout',
+        'TableBuilderService', 'TableDetailService',
         'FnService', 'MastService', 'PanelService', 'WebSocketService',
-        'IconService',
+        'IconService', 'UrlFnService', 'FlashService',
 
-        function (_$log_, _$scope_, tbs, tds, _fs_, _mast_, _ps_, _wss_, _is_) {
+        function (_$log_, _$scope_, $http, $timeout, tbs, tds, _fs_, _mast_, _ps_, _wss_, _is_, ufs, _flash_) {
             var handlers = {};
 
             $log = _$log_;
@@ -176,6 +178,45 @@
                 selCb: selCb
             });
 
+            $scope.$on('YangFileChanged', function () {
+                var formData = new FormData(),
+                    url, modelId, finished = false;
+
+                if ($scope.yangFile) {
+                    modelId = $scope.yangFile.name;
+                    modelId = modelId.substr(0, modelId.lastIndexOf("."));
+                    url = fileUploadUrl + modelId;
+                    formData.append('file', $scope.yangFile);
+                    $log.info('Compiling', $scope.yangFile);
+                    d3.select('#frame').classed('dropping', false);
+
+                    // FIXME: Replace this with dialog that shows progress...
+                    for (var i = 0; i < 10; i++) {
+                        $timeout(function () {
+                            if (!finished) _flash_.flash('Compiling ' + modelId);
+                        }, i * 1100);
+                    }
+
+                    $http.post(url, formData, {
+                        transformRequest: angular.identity,
+                        headers: {
+                            'Content-Type': undefined
+                        }
+                    })
+                    .finally(function () {
+                        finished = true;
+                        _flash_.flash('Compile completed for ' + modelId);
+                        $scope.sortCallback($scope.sortParams);
+                        document.getElementById('inputYangFileForm').reset();
+                    });
+                }
+            });
+
+            $scope.yangDropped = function() {
+                $scope.$emit('YangFileChanged');
+                $scope.yangFile = null;
+            };
+
             $scope.$on('$destroy', function () {
                 wss.unbindHandlers(handlers);
             });
@@ -184,6 +225,78 @@
         }
     ])
 
+    // triggers the input form to appear when button is clicked
+    .directive('triggerYangForm', function () {
+        return {
+            restrict: 'A',
+            link: function (scope, elem) {
+                elem.bind('click', function () {
+                    document.getElementById('uploadYangFile')
+                        .dispatchEvent(new MouseEvent('click'));
+                });
+            }
+        };
+    })
+
+    // binds the model file to the scope in scope.yangFile
+    // sends upload request to the server
+    .directive('yangFileModel', ['$parse',
+        function ($parse) {
+            return {
+                restrict: 'A',
+                link: function (scope, elem, attrs) {
+                    var model = $parse(attrs.yangFileModel),
+                        modelSetter = model.assign;
+
+                    elem.bind('change', function () {
+                        scope.$apply(function () {
+                            modelSetter(scope, elem[0].files[0]);
+                        });
+                        scope.$emit('YangFileChanged');
+                    });
+                }
+            };
+        }
+     ])
+
+    .directive("yangfiledrop", ['$parse', '$document', function ($parse, $document) {
+        return {
+            restrict: "A",
+            link: function (scope, element, attrs) {
+                var onYangDrop = $parse(attrs.onFileDrop);
+
+                // When an item is dragged over the document
+                var onDragOver = function (e) {
+                    d3.select('#frame').classed('dropping', true);
+                    e.preventDefault();
+                };
+
+                // When the user leaves the window, cancels the drag or drops the item
+                var onDragEnd = function (e) {
+                    d3.select('#frame').classed('dropping', false);
+                    e.preventDefault();
+                };
+
+                // When a file is dropped
+                var loadFile = function (file) {
+                    scope.yangFile = file;
+                    scope.$apply(onYangDrop(scope));
+                };
+
+                // Dragging begins on the document
+                $document.bind("dragover", onDragOver);
+
+                // Dragging ends on the overlay, which takes the whole window
+                element.bind("dragleave", onDragEnd)
+                    .bind("drop", function (e) {
+                        $log.info('Drag leave', e);
+                        loadFile(e.dataTransfer.files[0]);
+                        onDragEnd(e);
+                    });
+            }
+        };
+    }])
+
     .directive('yangModelDetailsPanel', [
         '$rootScope', '$window', '$timeout', 'KeyService',