zeppelin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From m...@apache.org
Subject [3/6] zeppelin git commit: [ZEPPELIN-732] Helium Application
Date Mon, 04 Jul 2016 15:33:00 GMT
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/main/java/org/apache/zeppelin/resource/ResourcePoolUtils.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/resource/ResourcePoolUtils.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/resource/ResourcePoolUtils.java
index 1825bfe..9878d7e 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/resource/ResourcePoolUtils.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/resource/ResourcePoolUtils.java
@@ -19,6 +19,7 @@ package org.apache.zeppelin.resource;
 
 import com.google.gson.Gson;
 import org.apache.zeppelin.interpreter.InterpreterGroup;
+import org.apache.zeppelin.interpreter.remote.RemoteInterpreterManagedProcess;
 import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcess;
 import org.apache.zeppelin.interpreter.thrift.RemoteInterpreterService;
 import org.slf4j.Logger;
@@ -134,3 +135,4 @@ public class ResourcePoolUtils {
     }
   }
 }
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/main/java/org/apache/zeppelin/resource/WellKnownResourceName.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/resource/WellKnownResourceName.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/resource/WellKnownResourceName.java
index 2d14fd4..4613c62 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/resource/WellKnownResourceName.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/resource/WellKnownResourceName.java
@@ -20,7 +20,8 @@ package org.apache.zeppelin.resource;
  * Well known resource names in ResourcePool
  */
 public enum WellKnownResourceName {
-  ParagraphResult("zeppelin.paragraph.result");     // paragraph run result
+  ZeppelinReplResult("zeppelin.repl.result"),                 // last object of repl
+  ZeppelinTableResult("zeppelin.paragraph.result.table");     // paragraph run result
 
   String name;
   WellKnownResourceName(String name) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/RemoteScheduler.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/RemoteScheduler.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/RemoteScheduler.java
index 33a3ca6..28c7437 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/RemoteScheduler.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/scheduler/RemoteScheduler.java
@@ -20,6 +20,7 @@ package org.apache.zeppelin.scheduler;
 import org.apache.thrift.TException;
 import org.apache.zeppelin.interpreter.InterpreterResult;
 import org.apache.zeppelin.interpreter.InterpreterResult.Code;
+import org.apache.zeppelin.interpreter.remote.RemoteInterpreterManagedProcess;
 import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcess;
 import org.apache.zeppelin.interpreter.thrift.RemoteInterpreterService.Client;
 import org.apache.zeppelin.scheduler.Job.Status;
@@ -49,8 +50,8 @@ public class RemoteScheduler implements Scheduler {
   private RemoteInterpreterProcess interpreterProcess;
 
   public RemoteScheduler(String name, ExecutorService executor, String noteId,
-      RemoteInterpreterProcess interpreterProcess, SchedulerListener listener,
-      int maxConcurrency) {
+                         RemoteInterpreterProcess interpreterProcess, SchedulerListener listener,
+                         int maxConcurrency) {
     this.name = name;
     this.executor = executor;
     this.listener = listener;

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/main/thrift/RemoteInterpreterService.thrift
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/thrift/RemoteInterpreterService.thrift b/zeppelin-interpreter/src/main/thrift/RemoteInterpreterService.thrift
index 6c3fc36..32be4a4 100644
--- a/zeppelin-interpreter/src/main/thrift/RemoteInterpreterService.thrift
+++ b/zeppelin-interpreter/src/main/thrift/RemoteInterpreterService.thrift
@@ -48,7 +48,8 @@ enum RemoteInterpreterEventType {
   RESOURCE_GET = 7
   OUTPUT_APPEND = 8,
   OUTPUT_UPDATE = 9,
-  ANGULAR_REGISTRY_PUSH=10
+  ANGULAR_REGISTRY_PUSH = 10,
+  APP_STATUS_UPDATE = 11,
 }
 
 struct RemoteInterpreterEvent {
@@ -56,6 +57,11 @@ struct RemoteInterpreterEvent {
   2: string data      // json serialized data
 }
 
+struct RemoteApplicationResult {
+  1: bool success,
+  2: string msg
+}
+
 /*
  * The below variables(name, value) will be connected to getCompletions in paragraph.controller.js
  *
@@ -99,4 +105,8 @@ service RemoteInterpreterService {
   void angularObjectAdd(1: string name, 2: string noteId, 3: string paragraphId, 4: string object);
   void angularObjectRemove(1: string name, 2: string noteId, 3: string paragraphId);
   void angularRegistryPush(1: string registry);
+
+  RemoteApplicationResult loadApplication(1: string applicationInstanceId, 2: string packageInfo, 3: string noteId, 4: string paragraphId);
+  RemoteApplicationResult unloadApplication(1: string applicationInstanceId);
+  RemoteApplicationResult runApplication(1: string applicationInstanceId);
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java
new file mode 100644
index 0000000..06c4a6b
--- /dev/null
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/ApplicationLoaderTest.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.helium;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.zeppelin.dep.DependencyResolver;
+import org.apache.zeppelin.interpreter.InterpreterOutput;
+import org.apache.zeppelin.interpreter.InterpreterOutputListener;
+import org.apache.zeppelin.resource.LocalResourcePool;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+public class ApplicationLoaderTest {
+  private File tmpDir;
+
+  @Before
+  public void setUp() {
+    tmpDir = new File(System.getProperty("java.io.tmpdir") + "/ZeppelinLTest_" + System.currentTimeMillis());
+    tmpDir.mkdirs();
+  }
+
+  @After
+  public void tearDown() throws IOException {
+    FileUtils.deleteDirectory(tmpDir);
+  }
+
+  @Test
+  public void loadUnloadApplication() throws Exception {
+    // given
+    LocalResourcePool resourcePool = new LocalResourcePool("pool1");
+    DependencyResolver dep = new DependencyResolver(tmpDir.getAbsolutePath());
+    ApplicationLoader appLoader = new ApplicationLoader(resourcePool, dep);
+
+    HeliumPackage pkg1 = createPackageInfo(MockApplication1.class.getName(), "artifact1");
+    ApplicationContext context1 = createContext("note1", "paragraph1", "app1");
+
+    // when load application
+    MockApplication1 app = (MockApplication1) ((ClassLoaderApplication)
+        appLoader.load(pkg1, context1)).getInnerApplication();
+
+    // then
+    assertFalse(app.isUnloaded());
+    assertEquals(0, app.getNumRun());
+
+    // when unload
+    app.unload();
+
+    // then
+    assertTrue(app.isUnloaded());
+    assertEquals(0, app.getNumRun());
+  }
+
+  public HeliumPackage createPackageInfo(String className, String artifact) {
+    HeliumPackage app1 = new HeliumPackage(
+        HeliumPackage.Type.APPLICATION,
+        "name1",
+        "desc1",
+        artifact,
+        className,
+        new String[][]{{}});
+    return app1;
+  }
+
+  public ApplicationContext createContext(String noteId, String paragraphId, String appInstanceId) {
+    ApplicationContext context1 = new ApplicationContext(
+        noteId,
+        paragraphId,
+        appInstanceId,
+        null,
+        new InterpreterOutput(new InterpreterOutputListener() {
+          @Override
+          public void onAppend(InterpreterOutput out, byte[] line) {
+
+          }
+
+          @Override
+          public void onUpdate(InterpreterOutput out, byte[] output) {
+
+          }
+        }));
+    return context1;
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/MockApplication1.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/MockApplication1.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/MockApplication1.java
new file mode 100644
index 0000000..df3afef
--- /dev/null
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/helium/MockApplication1.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.helium;
+
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.resource.ResourceSet;
+
+/**
+ * Mock application
+ */
+public class MockApplication1 extends Application {
+  boolean unloaded;
+  int run;
+
+  public MockApplication1(ApplicationContext context) {
+    super(context);
+    unloaded = false;
+    run = 0;
+  }
+
+  @Override
+  public void run(ResourceSet args) {
+    run++;
+  }
+
+  @Override
+  public void unload() {
+    unloaded = true;
+  }
+
+  public boolean isUnloaded() {
+    return unloaded;
+  }
+
+  public int getNumRun() {
+    return run;
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteAngularObjectTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteAngularObjectTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteAngularObjectTest.java
index 7ffa170..5def888 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteAngularObjectTest.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteAngularObjectTest.java
@@ -73,6 +73,7 @@ public class RemoteAngularObjectTest implements AngularObjectRegistryListener {
         "fakeRepo",
         env,
         10 * 1000,
+        null,
         null
     );
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterOutputTestStream.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterOutputTestStream.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterOutputTestStream.java
index 4a473f3..74649b1 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterOutputTestStream.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterOutputTestStream.java
@@ -70,7 +70,8 @@ public class RemoteInterpreterOutputTestStream implements RemoteInterpreterProce
         "fakeRepo",
         env,
         10 * 1000,
-        this);
+        this,
+        null);
 
     intpGroup.get("note").add(intp);
     intp.setInterpreterGroup(intpGroup);

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterProcessTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterProcessTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterProcessTest.java
index f9d7d39..0158282 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterProcessTest.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterProcessTest.java
@@ -41,9 +41,9 @@ public class RemoteInterpreterProcessTest {
   @Test
   public void testStartStop() {
     InterpreterGroup intpGroup = new InterpreterGroup();
-    RemoteInterpreterProcess rip = new RemoteInterpreterProcess(
+    RemoteInterpreterManagedProcess rip = new RemoteInterpreterManagedProcess(
         INTERPRETER_SCRIPT, "nonexists", "fakeRepo", new HashMap<String, String>(),
-        10 * 1000, null);
+        10 * 1000, null, null);
     assertFalse(rip.isRunning());
     assertEquals(0, rip.referenceCount());
     assertEquals(1, rip.reference(intpGroup));
@@ -58,7 +58,7 @@ public class RemoteInterpreterProcessTest {
   @Test
   public void testClientFactory() throws Exception {
     InterpreterGroup intpGroup = new InterpreterGroup();
-    RemoteInterpreterProcess rip = new RemoteInterpreterProcess(
+    RemoteInterpreterManagedProcess rip = new RemoteInterpreterManagedProcess(
         INTERPRETER_SCRIPT, "nonexists", "fakeRepo", new HashMap<String, String>(),
         mock(RemoteInterpreterEventPoller.class), 10 * 1000);
     rip.reference(intpGroup);
@@ -96,8 +96,14 @@ public class RemoteInterpreterProcessTest {
     InterpreterGroup intpGroup = mock(InterpreterGroup.class);
     when(intpGroup.getProperty()).thenReturn(properties);
     when(intpGroup.containsKey(Constants.EXISTING_PROCESS)).thenReturn(true);
-    RemoteInterpreterProcess rip = new RemoteInterpreterProcess(INTERPRETER_SCRIPT, "nonexists",
-        "fakeRepo", new HashMap<String, String>(), 10 * 1000, null);
+
+    RemoteInterpreterProcess rip = new RemoteInterpreterManagedProcess(
+        INTERPRETER_SCRIPT,
+        "nonexists",
+        "fakeRepo",
+        new HashMap<String, String>(),
+        mock(RemoteInterpreterEventPoller.class)
+        , 10 * 1000);
     assertFalse(rip.isRunning());
     assertEquals(0, rip.referenceCount());
     assertEquals(1, rip.reference(intpGroup));

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterTest.java
index ea0bbf4..af1c447 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterTest.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterTest.java
@@ -89,6 +89,7 @@ public class RemoteInterpreterTest {
             "fakeRepo",
             env,
             10 * 1000,
+            null,
             null);
   }
 
@@ -106,6 +107,7 @@ public class RemoteInterpreterTest {
             "fakeRepo",
             env,
             10 * 1000,
+            null,
             null);
   }
 
@@ -204,6 +206,7 @@ public class RemoteInterpreterTest {
         "fakeRepo",
         env,
         10 * 1000,
+        null,
         null);
 
 
@@ -219,6 +222,7 @@ public class RemoteInterpreterTest {
         "fakeRepo",
         env,
         10 * 1000,
+        null,
         null);
 
     intpGroup.get("note").add(intpB);
@@ -683,7 +687,7 @@ public class RemoteInterpreterTest {
     //Given
     final Client client = Mockito.mock(Client.class);
     final RemoteInterpreter intr = new RemoteInterpreter(new Properties(), "noteId",
-            MockInterpreterA.class.getName(), "runner", "path","localRepo", env, 10 * 1000, null);
+            MockInterpreterA.class.getName(), "runner", "path","localRepo", env, 10 * 1000, null, null);
     final AngularObjectRegistry registry = new AngularObjectRegistry("spark", null);
     registry.add("name", "DuyHai DOAN", "nodeId", "paragraphId");
     final InterpreterGroup interpreterGroup = new InterpreterGroup("groupId");
@@ -723,11 +727,12 @@ public class RemoteInterpreterTest {
         p,
         "note",
         MockInterpreterEnv.class.getName(),
-        new File("../bin/interpreter.sh").getAbsolutePath(),
+        new File(INTERPRETER_SCRIPT).getAbsolutePath(),
         "fake",
         "fakeRepo",
         env,
         10 * 1000,
+        null,
         null);
 
     intpGroup.put("note", new LinkedList<Interpreter>());

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/test/java/org/apache/zeppelin/resource/DistributedResourcePoolTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/resource/DistributedResourcePoolTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/resource/DistributedResourcePoolTest.java
index ae5e6f5..02dba20 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/resource/DistributedResourcePoolTest.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/resource/DistributedResourcePoolTest.java
@@ -69,6 +69,7 @@ public class DistributedResourcePoolTest {
         "fakeRepo",
         env,
         10 * 1000,
+        null,
         null
     );
 
@@ -86,6 +87,7 @@ public class DistributedResourcePoolTest {
         "fakeRepo",        
         env,
         10 * 1000,
+        null,
         null
     );
 
@@ -110,11 +112,11 @@ public class DistributedResourcePoolTest {
     intp1.open();
     intp2.open();
 
-    eventPoller1 = new RemoteInterpreterEventPoller(null);
+    eventPoller1 = new RemoteInterpreterEventPoller(null, null);
     eventPoller1.setInterpreterGroup(intpGroup1);
     eventPoller1.setInterpreterProcess(intpGroup1.getRemoteInterpreterProcess());
 
-    eventPoller2 = new RemoteInterpreterEventPoller(null);
+    eventPoller2 = new RemoteInterpreterEventPoller(null, null);
     eventPoller2.setInterpreterGroup(intpGroup2);
     eventPoller2.setInterpreterProcess(intpGroup2.getRemoteInterpreterProcess());
 
@@ -140,13 +142,12 @@ public class DistributedResourcePoolTest {
     InterpreterResult ret;
     intp1.interpret("put key1 value1", context);
     intp2.interpret("put key2 value2", context);
-    int numInterpreterResult = 2;
 
     ret = intp1.interpret("getAll", context);
-    assertEquals(numInterpreterResult + 2, gson.fromJson(ret.message(), ResourceSet.class).size());
+    assertEquals(2, gson.fromJson(ret.message(), ResourceSet.class).size());
 
     ret = intp2.interpret("getAll", context);
-    assertEquals(numInterpreterResult + 2, gson.fromJson(ret.message(), ResourceSet.class).size());
+    assertEquals(2, gson.fromJson(ret.message(), ResourceSet.class).size());
 
     ret = intp1.interpret("get key1", context);
     assertEquals("value1", gson.fromJson(ret.message(), String.class));
@@ -218,16 +219,15 @@ public class DistributedResourcePoolTest {
     intp2.interpret("put note2:paragraph1:key1 value1", context);
     intp2.interpret("put note2:paragraph2:key2 value2", context);
 
-    int numInterpreterResult = 2;
 
     // then get all resources.
-    assertEquals(numInterpreterResult + 4, ResourcePoolUtils.getAllResources().size());
+    assertEquals(4, ResourcePoolUtils.getAllResources().size());
 
     // when remove all resources from note1
     ResourcePoolUtils.removeResourcesBelongsToNote("note1");
 
     // then resources should be removed.
-    assertEquals(numInterpreterResult + 2, ResourcePoolUtils.getAllResources().size());
+    assertEquals(2, ResourcePoolUtils.getAllResources().size());
     assertEquals("", gson.fromJson(
         intp1.interpret("get note1:paragraph1:key1", context).message(),
         String.class));
@@ -240,7 +240,7 @@ public class DistributedResourcePoolTest {
     ResourcePoolUtils.removeResourcesBelongsToParagraph("note2", "paragraph1");
 
     // then 1
-    assertEquals(numInterpreterResult + 1, ResourcePoolUtils.getAllResources().size());
+    assertEquals(1, ResourcePoolUtils.getAllResources().size());
     assertEquals("value2", gson.fromJson(
         intp1.interpret("get note2:paragraph2:key2", context).message(),
         String.class));

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-interpreter/src/test/java/org/apache/zeppelin/scheduler/RemoteSchedulerTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/scheduler/RemoteSchedulerTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/scheduler/RemoteSchedulerTest.java
index 40dcef2..f17d88d 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/scheduler/RemoteSchedulerTest.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/scheduler/RemoteSchedulerTest.java
@@ -80,7 +80,8 @@ public class RemoteSchedulerTest implements RemoteInterpreterProcessListener {
         "fakeRepo",
         env,
         10 * 1000,
-        this);
+        this,
+        null);
 
     intpGroup.put("note", new LinkedList<Interpreter>());
     intpGroup.get("note").add(intpA);
@@ -168,7 +169,8 @@ public class RemoteSchedulerTest implements RemoteInterpreterProcessListener {
         "fakeRepo",
         env,
         10 * 1000,
-        this);
+        this,
+        null);
 
     intpGroup.put("note", new LinkedList<Interpreter>());
     intpGroup.get("note").add(intpA);

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java
new file mode 100644
index 0000000..062f5b9
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/HeliumRestApi.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.rest;
+
+import com.google.gson.Gson;
+import org.apache.zeppelin.helium.Helium;
+import org.apache.zeppelin.helium.HeliumApplicationFactory;
+import org.apache.zeppelin.helium.HeliumPackage;
+import org.apache.zeppelin.notebook.Note;
+import org.apache.zeppelin.notebook.Notebook;
+import org.apache.zeppelin.notebook.Paragraph;
+import org.apache.zeppelin.server.JsonResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.Response;
+
+/**
+ * Helium Rest Api
+ */
+@Path("/helium")
+@Produces("application/json")
+public class HeliumRestApi {
+  Logger logger = LoggerFactory.getLogger(HeliumRestApi.class);
+
+  private Helium helium;
+  private HeliumApplicationFactory applicationFactory;
+  private Notebook notebook;
+  private Gson gson = new Gson();
+
+  public HeliumRestApi() {
+  }
+
+  public HeliumRestApi(Helium helium,
+                       HeliumApplicationFactory heliumApplicationFactory,
+                       Notebook notebook) {
+    this.helium  = helium;
+    this.applicationFactory = heliumApplicationFactory;
+    this.notebook = notebook;
+  }
+
+  /**
+   * Get all packages
+   * @return
+   */
+  @GET
+  @Path("all")
+  public Response getAll() {
+    return new JsonResponse(Response.Status.OK, "", helium.getAllPackageInfo()).build();
+  }
+
+  @GET
+  @Path("suggest/{noteId}/{paragraphId}")
+  public Response suggest(@PathParam("noteId") String noteId,
+                          @PathParam("paragraphId") String paragraphId) {
+    Note note = notebook.getNote(noteId);
+    if (note == null) {
+      return new JsonResponse(Response.Status.NOT_FOUND, "Note " + noteId + " not found").build();
+    }
+
+    Paragraph paragraph = note.getParagraph(paragraphId);
+    if (paragraph == null) {
+      return new JsonResponse(Response.Status.NOT_FOUND, "Paragraph " + paragraphId + " not found")
+          .build();
+    }
+
+    return new JsonResponse(Response.Status.OK, "", helium.suggestApp(paragraph)).build();
+  }
+
+  @POST
+  @Path("load/{noteId}/{paragraphId}")
+  public Response suggest(@PathParam("noteId") String noteId,
+                          @PathParam("paragraphId") String paragraphId,
+                          String heliumPackage) {
+
+    Note note = notebook.getNote(noteId);
+    if (note == null) {
+      return new JsonResponse(Response.Status.NOT_FOUND, "Note " + noteId + " not found").build();
+    }
+
+    Paragraph paragraph = note.getParagraph(paragraphId);
+    if (paragraph == null) {
+      return new JsonResponse(Response.Status.NOT_FOUND, "Paragraph " + paragraphId + " not found")
+          .build();
+    }
+    HeliumPackage pkg = gson.fromJson(heliumPackage, HeliumPackage.class);
+
+    String appId = applicationFactory.loadAndRun(pkg, paragraph);
+    return new JsonResponse(Response.Status.OK, "", appId).build();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
index 0ff0dc6..0f7d8a1 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
@@ -17,10 +17,23 @@
 
 package org.apache.zeppelin.server;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import javax.net.ssl.SSLContext;
+import javax.servlet.DispatcherType;
+import javax.ws.rs.core.Application;
+
 import org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.dep.DependencyResolver;
+import org.apache.zeppelin.helium.Helium;
+import org.apache.zeppelin.helium.HeliumApplicationFactory;
 import org.apache.zeppelin.interpreter.InterpreterFactory;
 import org.apache.zeppelin.notebook.Notebook;
 import org.apache.zeppelin.notebook.NotebookAuthorization;
@@ -46,14 +59,6 @@ import org.eclipse.jetty.webapp.WebAppContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.servlet.DispatcherType;
-import javax.ws.rs.core.Application;
-import java.io.File;
-import java.io.IOException;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Set;
-
 /**
  * Main class of Zeppelin.
  */
@@ -63,6 +68,8 @@ public class ZeppelinServer extends Application {
   public static Notebook notebook;
   public static Server jettyWebServer;
   public static NotebookServer notebookWsServer;
+  public static Helium helium;
+  public static HeliumApplicationFactory heliumApplicationFactory;
 
   private SchedulerFactory schedulerFactory;
   private InterpreterFactory replFactory;
@@ -77,9 +84,12 @@ public class ZeppelinServer extends Application {
 
     this.depResolver = new DependencyResolver(
         conf.getString(ConfVars.ZEPPELIN_INTERPRETER_LOCALREPO));
+
+    this.helium = new Helium(conf.getHeliumConfPath(), conf.getHeliumDefaultLocalRegistryPath());
+    this.heliumApplicationFactory = new HeliumApplicationFactory();
     this.schedulerFactory = new SchedulerFactory();
     this.replFactory = new InterpreterFactory(conf, notebookWsServer,
-            notebookWsServer, depResolver);
+        notebookWsServer, heliumApplicationFactory, depResolver);
     this.notebookRepo = new NotebookRepoSync(conf);
     this.notebookIndex = new LuceneSearch();
     this.notebookAuthorization = new NotebookAuthorization(conf);
@@ -87,6 +97,13 @@ public class ZeppelinServer extends Application {
     notebook = new Notebook(conf,
         notebookRepo, schedulerFactory, replFactory, notebookWsServer,
             notebookIndex, notebookAuthorization, credentials);
+
+    // to update notebook from application event from remote process.
+    heliumApplicationFactory.setNotebook(notebook);
+    // to update fire websocket event on application event.
+    heliumApplicationFactory.setApplicationEventListener(notebookWsServer);
+
+    notebook.addNotebookEventListener(heliumApplicationFactory);
   }
 
   public static void main(String[] args) throws InterruptedException {
@@ -294,6 +311,9 @@ public class ZeppelinServer extends Application {
     NotebookRestApi notebookApi = new NotebookRestApi(notebook, notebookWsServer, notebookIndex);
     singletons.add(notebookApi);
 
+    HeliumRestApi heliumApi = new HeliumRestApi(helium, heliumApplicationFactory, notebook);
+    singletons.add(heliumApi);
+
     InterpreterRestApi interpreterApi = new InterpreterRestApi(replFactory);
     singletons.add(interpreterApi);
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
index 1c3220b..4261a65 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
@@ -34,6 +34,8 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.display.AngularObject;
 import org.apache.zeppelin.display.AngularObjectRegistry;
 import org.apache.zeppelin.display.AngularObjectRegistryListener;
+import org.apache.zeppelin.helium.ApplicationEventListener;
+import org.apache.zeppelin.helium.HeliumPackage;
 import org.apache.zeppelin.interpreter.InterpreterGroup;
 import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry;
 import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
@@ -61,7 +63,7 @@ import org.slf4j.LoggerFactory;
  */
 public class NotebookServer extends WebSocketServlet implements
         NotebookSocketListener, JobListenerFactory, AngularObjectRegistryListener,
-        RemoteInterpreterProcessListener {
+        RemoteInterpreterProcessListener, ApplicationEventListener {
   /**
    * Job manager service type
    */
@@ -759,6 +761,7 @@ public class NotebookServer extends WebSocketServlet implements
         if (interpreterGroupId.equals(setting.getInterpreterGroup(note.id()).getId())) {
           AngularObjectRegistry angularObjectRegistry = setting
               .getInterpreterGroup(note.id()).getAngularObjectRegistry();
+
           // first trying to get local registry
           ao = angularObjectRegistry.get(varName, noteId, paragraphId);
           if (ao == null) {
@@ -1136,7 +1139,6 @@ public class NotebookServer extends WebSocketServlet implements
             .put("noteId", noteId)
             .put("paragraphId", paragraphId)
             .put("data", output);
-    Paragraph paragraph = notebook().getNote(noteId).getParagraph(paragraphId);
     broadcast(noteId, msg);
   }
 
@@ -1152,7 +1154,60 @@ public class NotebookServer extends WebSocketServlet implements
             .put("noteId", noteId)
             .put("paragraphId", paragraphId)
             .put("data", output);
-    Paragraph paragraph = notebook().getNote(noteId).getParagraph(paragraphId);
+    broadcast(noteId, msg);
+  }
+
+  /**
+   * When application append output
+   * @param noteId
+   * @param paragraphId
+   * @param appId
+   * @param output
+   */
+  @Override
+  public void onOutputAppend(String noteId, String paragraphId, String appId, String output) {
+    Message msg = new Message(OP.APP_APPEND_OUTPUT)
+        .put("noteId", noteId)
+        .put("paragraphId", paragraphId)
+        .put("appId", appId)
+        .put("data", output);
+    broadcast(noteId, msg);
+  }
+
+  /**
+   * When application update output
+   * @param noteId
+   * @param paragraphId
+   * @param appId
+   * @param output
+   */
+  @Override
+  public void onOutputUpdated(String noteId, String paragraphId, String appId, String output) {
+    Message msg = new Message(OP.APP_UPDATE_OUTPUT)
+        .put("noteId", noteId)
+        .put("paragraphId", paragraphId)
+        .put("appId", appId)
+        .put("data", output);
+    broadcast(noteId, msg);
+  }
+
+  @Override
+  public void onLoad(String noteId, String paragraphId, String appId, HeliumPackage pkg) {
+    Message msg = new Message(OP.APP_LOAD)
+        .put("noteId", noteId)
+        .put("paragraphId", paragraphId)
+        .put("appId", appId)
+        .put("pkg", pkg);
+    broadcast(noteId, msg);
+  }
+
+  @Override
+  public void onStatusChange(String noteId, String paragraphId, String appId, String status) {
+    Message msg = new Message(OP.APP_STATUS_CHANGE)
+        .put("noteId", noteId)
+        .put("paragraphId", paragraphId)
+        .put("appId", appId)
+        .put("status", status);
     broadcast(noteId, msg);
   }
 
@@ -1282,19 +1337,17 @@ public class NotebookServer extends WebSocketServlet implements
 
       List<InterpreterSetting> intpSettings = notebook.getInterpreterFactory()
           .getInterpreterSettings(note.getId());
-      if (intpSettings.isEmpty())
+      if (intpSettings.isEmpty()) {
         continue;
-      for (InterpreterSetting setting : intpSettings) {
-        if (setting.getInterpreterGroup(note.id()).getId().equals(interpreterGroupId)) {
-          broadcast(
-              note.id(),
-              new Message(OP.ANGULAR_OBJECT_UPDATE)
-                  .put("angularObject", object)
-                  .put("interpreterGroupId", interpreterGroupId)
-                  .put("noteId", note.id())
-                  .put("paragraphId", object.getParagraphId()));
-        }
       }
+
+      broadcast(
+          note.id(),
+          new Message(OP.ANGULAR_OBJECT_UPDATE)
+              .put("angularObject", object)
+              .put("interpreterGroupId", interpreterGroupId)
+              .put("noteId", note.id())
+              .put("paragraphId", object.getParagraphId()));
     }
   }
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java
index 81c7190..0ff0135 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/integration/SparkParagraphIT.java
@@ -165,7 +165,7 @@ public class SparkParagraphIT extends AbstractZeppelinIT {
       }
 
       WebElement paragraph1Result = driver.findElement(By.xpath(
-          getParagraphXPath(1) + "//div[@class=\"tableDisplay\"]//table"));
+          getParagraphXPath(1) + "//div[@class=\"tableDisplay\"]/div/div/div/div/div/div[1]"));
       collector.checkThat("Paragraph from SparkParagraphIT of testSqlSpark result: ",
           paragraph1Result.getText().toString(), CoreMatchers.equalTo("age\njob\nmarital\neducation\nbalance\n" +
           "30 unemployed married primary 1,787"));

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
index ada2ef8..f2d4c99 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
@@ -251,7 +251,7 @@ public abstract class AbstractTestRestApi {
       request = httpGet("/");
       isRunning = request.getStatusCode() == 200;
     } catch (IOException e) {
-      LOG.error("Exception in AbstractTestRestApi while checkIfServerIsRunning ", e);
+      LOG.error("AbstractTestRestApi.checkIfServerIsRunning() fails .. ZeppelinServer is not running");
       isRunning = false;
     } finally {
       if (request != null) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java
index d234ffd..3c77b45 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinSparkClusterTest.java
@@ -24,7 +24,12 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.ExecuteWatchdog;
+import org.apache.commons.exec.PumpStreamHandler;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.output.ByteArrayOutputStream;
 import org.apache.zeppelin.interpreter.InterpreterSetting;
 import org.apache.zeppelin.notebook.Note;
 import org.apache.zeppelin.notebook.Paragraph;
@@ -101,6 +106,7 @@ public class ZeppelinSparkClusterTest extends AbstractTestRestApi {
         );
         note.run(p.getId());
         waitForFinish(p);
+        System.err.println("sparkRTest=" + p.getResult().message());
         assertEquals(Status.FINISHED, p.getStatus());
         assertEquals("[1] 3", p.getResult().message());
       }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-web/src/app/notebook/paragraph/paragraph-chart-selector.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-chart-selector.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-chart-selector.html
index 76135b1..507c57f 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph-chart-selector.html
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-chart-selector.html
@@ -12,56 +12,112 @@ See the License for the specific language governing permissions and
 limitations under the License.
 -->
 
-<div ng-if="paragraph.result.type == 'TABLE' && !asIframe && !viewOnly"
+<div id="{{paragraph.id}}_switch"
+     ng-if="(paragraph.result.type == 'TABLE' || apps.length > 0 || suggestion.available && suggestion.available.length > 0) && !asIframe && !viewOnly"
+     class="btn-group"
      style='margin-bottom: 10px;'>
-  <div id="{{paragraph.id}}_switch"
-       class="btn-group">
-    <button type="button" class="btn btn-default btn-sm"
-            ng-class="{'active': isGraphMode('table')}"
-            ng-click="setGraphMode('table', true)" ><i class="fa fa-table"></i>
-    </button>
-    <button type="button" class="btn btn-default btn-sm"
-            ng-class="{'active': isGraphMode('multiBarChart')}"
-            ng-click="setGraphMode('multiBarChart', true)"><i class="fa fa-bar-chart"></i>
-    </button>
-    <button type="button" class="btn btn-default btn-sm"
-            ng-class="{'active': isGraphMode('pieChart')}"
-            ng-click="setGraphMode('pieChart', true)"><i class="fa fa-pie-chart"></i>
-    </button>
-    <button type="button" class="btn btn-default btn-sm"
-            ng-class="{'active': isGraphMode('stackedAreaChart')}"
-            ng-click="setGraphMode('stackedAreaChart', true)"><i class="fa fa-area-chart"></i>
-    </button>
-    <button type="button" class="btn btn-default btn-sm"
-            ng-class="{'active': isGraphMode('lineChart') || isGraphMode('lineWithFocusChart')}"
-            ng-click="paragraph.config.graph.lineWithFocus ? setGraphMode('lineWithFocusChart', true) : setGraphMode('lineChart', true)"><i class="fa fa-line-chart"></i>
-    </button>
-    <button type="button" class="btn btn-default btn-sm"
-            ng-class="{'active': isGraphMode('scatterChart')}"
-            ng-click="setGraphMode('scatterChart', true)"><i class="cf cf-scatter-chart"></i>
-    </button>
-  </div>
-  <span class="btn-group">
-    <button type="button" class="btn btn-default btn-sm"
-            style="margin-left:10px"
-            ng-click="exportToDSV(',')"
-            tooltip="Download Data as CSV" tooltip-placement="bottom">
-      <i class="fa fa-download"></i>
-    </button>
-    <button type="button" class="btn btn-default btn-sm dropdown-toggle caretBtn"
-            data-toggle="dropdown">
-      <span class="caret" style="margin: 0px;"></span>
-      <span class="sr-only">Toggle Dropdown</span>
-    </button>
-    <ul class="dropdown-menu" role="menu" style="min-width: 70px;">
-      <li ng-click="exportToDSV(',')"><a>CSV</a></li>
-      <li ng-click="exportToDSV('\t')"><a>TSV</a></li>
-    </ul>
-  </span>
-  <span ng-if="getGraphMode()!='table'"
-        style="margin-left:5px; cursor:pointer; display: inline-block; vertical-align:top; position: relative; line-height:30px;">
-    <a class="btnText" ng-click="toggleGraphOption()">
-      settings <span ng-class="paragraph.config.graph.optionOpen ? 'fa fa-caret-up' : 'fa fa-caret-down'"></span>
-    </a>
-  </span>
+
+  <button type="button" class="btn btn-default btn-sm"
+          ng-if="paragraph.result.type == 'TABLE'"
+          ng-class="{'active': isGraphMode('table')}"
+          ng-click="setGraphMode('table', true)" ><i class="fa fa-table"></i>
+  </button>
+  <button type="button" class="btn btn-default btn-sm"
+          ng-if="paragraph.result.type == 'TABLE'"
+          ng-class="{'active': isGraphMode('multiBarChart')}"
+          ng-click="setGraphMode('multiBarChart', true)"><i class="fa fa-bar-chart"></i>
+  </button>
+  <button type="button" class="btn btn-default btn-sm"
+          ng-if="paragraph.result.type == 'TABLE'"
+          ng-class="{'active': isGraphMode('pieChart')}"
+          ng-click="setGraphMode('pieChart', true)"><i class="fa fa-pie-chart"></i>
+  </button>
+  <button type="button" class="btn btn-default btn-sm"
+          ng-if="paragraph.result.type == 'TABLE'"
+          ng-class="{'active': isGraphMode('stackedAreaChart')}"
+          ng-click="setGraphMode('stackedAreaChart', true)"><i class="fa fa-area-chart"></i>
+  </button>
+  <button type="button" class="btn btn-default btn-sm"
+          ng-if="paragraph.result.type == 'TABLE'"
+          ng-class="{'active': isGraphMode('lineChart') || isGraphMode('lineWithFocusChart')}"
+          ng-click="paragraph.config.graph.lineWithFocus ? setGraphMode('lineWithFocusChart', true) : setGraphMode('lineChart', true)"><i class="fa fa-line-chart"></i>
+  </button>
+  <button type="button" class="btn btn-default btn-sm"
+          ng-if="paragraph.result.type == 'TABLE'"
+          ng-class="{'active': isGraphMode('scatterChart')}"
+          ng-click="setGraphMode('scatterChart', true)"><i class="cf cf-scatter-chart"></i>
+  </button>
+  
+  <button type="button"
+          ng-if="paragraph.result.type != 'TABLE'"
+          ng-click="switchApp()"
+          ng-class="{'active' : !paragraph.config.helium.activeApp}"
+          class="btn btn-default btn-sm"><i class="fa fa-terminal"></i>
+  </button>
+
+  <button type="button"
+          class="btn btn-default btn-sm"
+          ng-repeat="app in apps"
+          ng-click="switchApp(app.id)"
+          ng-class="{'active' : app.id == paragraph.config.helium.activeApp}"
+          ng-bind-html="app.pkg.icon">
+  </button>
+</div>
+
+<div id="{{paragraph.id}}_helium"
+     ng-if="(suggestion.available && suggestion.available.length > 0) && !asIframe && !viewOnly"
+     class="btn-group"
+     style='margin-bottom: 10px;'>
+  <button type="button"
+          class="btn btn-default btn-sm dropdown-toggle"
+          ng-if="suggestion.available && suggestion.available.length > 0"
+          data-toggle="dropdown"
+          style="font-weight:bold; background-color:#ffdf96; border: 1px solid #FED233">
+    He
+  </button>
+  <ul class="dropdown-menu"
+      style="z-index:1002"
+      ng-if="suggestion.available && suggestion.available.length > 0"
+      role="menu">
+    <li class="appSuggestion">
+      <div ng-repeat="pkgInfo in suggestion.available"
+           style="margin-bottom:5px">
+        <button type="button"
+                class="btn btn-default btn-sm"
+                ng-click="loadApp(pkgInfo.pkg)"
+                ng-bind-html="pkgInfo.pkg.icon">
+        </button>
+        <span class="inline">{{pkgInfo.pkg.name}}</span>
+      </div>
+    </li>
+  </ul>
 </div>
+
+<div class="btn-group"
+     ng-if="paragraph.result.type == 'TABLE' && !asIframe && !viewOnly"
+     style="margin-bottom: 10px;">
+  <button type="button" class="btn btn-default btn-sm"
+          style="margin-left:10px"
+          ng-click="exportToDSV(',')"
+          tooltip="Download Data as CSV" tooltip-placement="bottom">
+    <i class="fa fa-download"></i>
+  </button>
+  <button type="button" class="btn btn-default btn-sm dropdown-toggle caretBtn"
+          data-toggle="dropdown">
+    <span class="caret" style="margin: 0px;"></span>
+    <span class="sr-only">Toggle Dropdown</span>
+  </button>
+  <ul class="dropdown-menu" role="menu" style="min-width: 70px;">
+    <li ng-click="exportToDSV(',')"><a>CSV</a></li>
+    <li ng-click="exportToDSV('\t')"><a>TSV</a></li>
+  </ul>
+</div>
+
+<span
+   ng-if="getResultType()=='TABLE' && !paragraph.config.helium.activeApp && getGraphMode()!='table' && !asIframe && !viewOnly"
+   style="margin-left:10px; cursor:pointer; display: inline-block; vertical-align:top; position: relative; line-height:30px;">
+  <a class="btnText" ng-click="toggleGraphOption()">
+    settings <span ng-class="paragraph.config.graph.optionOpen ? 'fa fa-caret-up' : 'fa fa-caret-down'"></span>
+  </a>
+</span>
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-web/src/app/notebook/paragraph/paragraph-results.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-results.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-results.html
index 612fdbd..fd608c5 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph-results.html
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-results.html
@@ -13,6 +13,7 @@ limitations under the License.
 -->
 <div
   id="p{{paragraph.id}}_resize"
+  ng-if="!paragraph.config.helium.activeApp"
   style='padding-bottom: 5px;'
   resize='{"allowresize": "{{!asIframe && !viewOnly}}", "graphType": "{{getResultType()}}"}'
      resizable on-resize="resizeParagraph(width, height);">
@@ -60,3 +61,11 @@ limitations under the License.
        ng-bind="paragraph.errorMessage">
   </div>
 </div>
+
+<div ng-repeat="app in apps">
+  <div id="p{{app.id}}"
+       ng-show="paragraph.config.helium.activeApp == app.id">
+  </div>
+</div>
+
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
index 398191c..211ab59 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
@@ -16,7 +16,7 @@
 
 angular.module('zeppelinWebApp')
   .controller('ParagraphCtrl', function($scope,$rootScope, $route, $window, $element, $routeParams, $location,
-                                         $timeout, $compile, websocketMsgSrv, ngToast, SaveAsService) {
+                                         $timeout, $compile, $http, websocketMsgSrv, baseUrlSrv, ngToast, SaveAsService) {
   var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_';
   $scope.parentNote = null;
   $scope.paragraph = null;
@@ -116,10 +116,21 @@ angular.module('zeppelinWebApp')
     } else if ($scope.getResultType() === 'TEXT') {
       $scope.renderText();
     }
+
+    getApplicationStates();
+    getSuggestions();
+
+    var activeApp =  _.get($scope.paragraph.config, 'helium.activeApp');
+    if (activeApp) {
+      var app = _.find($scope.apps, {id: activeApp});
+      renderApp(app);
+    }
   };
 
-    $scope.renderHtml = function() {
-      var retryRenderer = function() {
+
+
+  $scope.renderHtml = function() {
+    var retryRenderer = function() {
       if (angular.element('#p' + $scope.paragraph.id + '_html').length) {
         try {
           angular.element('#p' + $scope.paragraph.id + '_html').html($scope.paragraph.result.msg);
@@ -202,8 +213,23 @@ angular.module('zeppelinWebApp')
 
   $scope.$on('angularObjectUpdate', function(event, data) {
     var noteId = $route.current.pathParams.noteId;
-    if (!data.noteId || (data.noteId === noteId && (!data.paragraphId || data.paragraphId === $scope.paragraph.id))) {
-      var scope = paragraphScope;
+    if (!data.noteId || data.noteId === noteId) {
+      var scope;
+      var registry;
+
+      if (!data.paragraphId || data.paragraphId === $scope.paragraph.id) {
+        scope = paragraphScope;
+        registry = angularObjectRegistry;
+      } else {
+        var app = _.find($scope.apps, { id: data.paragraphId});
+        if (app) {
+          scope = getAppScope(app);
+          registry = getAppRegistry(app);
+        } else {
+          // no matching app in this paragraph
+          return;
+        }
+      }
       var varName = data.angularObject.name;
 
       if (angular.equals(data.angularObject.object, scope[varName])) {
@@ -211,32 +237,32 @@ angular.module('zeppelinWebApp')
         return;
       }
 
-      if (!angularObjectRegistry[varName]) {
-        angularObjectRegistry[varName] = {
+      if (!registry[varName]) {
+        registry[varName] = {
           interpreterGroupId : data.interpreterGroupId,
           noteId : data.noteId,
           paragraphId : data.paragraphId
         };
       } else {
-        angularObjectRegistry[varName].noteId = angularObjectRegistry[varName].noteId || data.noteId;
-        angularObjectRegistry[varName].paragraphId = angularObjectRegistry[varName].paragraphId || data.paragraphId;
+        registry[varName].noteId = registry[varName].noteId || data.noteId;
+        registry[varName].paragraphId = registry[varName].paragraphId || data.paragraphId;
       }
 
-      angularObjectRegistry[varName].skipEmit = true;
+      registry[varName].skipEmit = true;
 
-      if (!angularObjectRegistry[varName].clearWatcher) {
-        angularObjectRegistry[varName].clearWatcher = scope.$watch(varName, function(newValue, oldValue) {
-          console.log('angular object (paragraph) updated %o %o', varName, angularObjectRegistry[varName]);
-          if (angularObjectRegistry[varName].skipEmit) {
-            angularObjectRegistry[varName].skipEmit = false;
+      if (!registry[varName].clearWatcher) {
+        registry[varName].clearWatcher = scope.$watch(varName, function(newValue, oldValue) {
+          console.log('angular object (paragraph) updated %o %o', varName, registry[varName]);
+          if (registry[varName].skipEmit) {
+            registry[varName].skipEmit = false;
             return;
           }
           websocketMsgSrv.updateAngularObject(
-            angularObjectRegistry[varName].noteId,
-            angularObjectRegistry[varName].paragraphId,
+            registry[varName].noteId,
+            registry[varName].paragraphId,
             varName,
             newValue,
-            angularObjectRegistry[varName].interpreterGroupId);
+            registry[varName].interpreterGroupId);
         });
       }
       console.log('angular object (paragraph) created %o', varName);
@@ -258,14 +284,30 @@ angular.module('zeppelinWebApp')
 
   $scope.$on('angularObjectRemove', function(event, data) {
     var noteId = $route.current.pathParams.noteId;
-    if (!data.noteId || (data.noteId === noteId && (!data.paragraphId || data.paragraphId === $scope.paragraph.id))) {
-      var scope = paragraphScope;
+    if (!data.noteId || data.noteId === noteId) {
+      var scope;
+      var registry;
+
+      if (!data.paragraphId || data.paragraphId === $scope.paragraph.id) {
+        scope = paragraphScope;
+        registry = angularObjectRegistry;
+      } else {
+        var app = _.find($scope.apps, { id: data.paragraphId});
+        if (app) {
+          scope = getAppScope(app);
+          registry = getAppRegistry(app);
+        } else {
+          // no matching app in this paragraph
+          return;
+        }
+      }
+
       var varName = data.name;
 
       // clear watcher
-      if (angularObjectRegistry[varName]) {
-        angularObjectRegistry[varName].clearWatcher();
-        angularObjectRegistry[varName] = undefined;
+      if (registry[varName]) {
+        registry[varName].clearWatcher();
+        registry[varName] = undefined;
       }
 
       // remove scope variable
@@ -365,12 +407,17 @@ angular.module('zeppelinWebApp')
       var newType = $scope.getResultType(data.paragraph);
       var oldGraphMode = $scope.getGraphMode();
       var newGraphMode = $scope.getGraphMode(data.paragraph);
+      var oldActiveApp = _.get($scope.paragraph.config, 'helium.activeApp');
+      var newActiveApp = _.get(data.paragraph.config, 'helium.activeApp');
+
       var resultRefreshed = (data.paragraph.dateFinished !== $scope.paragraph.dateFinished) ||
         isEmpty(data.paragraph.result) !== isEmpty($scope.paragraph.result) ||
-        data.paragraph.status === 'ERROR';
+        data.paragraph.status === 'ERROR' ||
+        (!newActiveApp && oldActiveApp !== newActiveApp);
 
       var statusChanged = (data.paragraph.status !== $scope.paragraph.status);
 
+
       //console.log("updateParagraph oldData %o, newData %o. type %o -> %o, mode %o -> %o", $scope.paragraph, data, oldType, newType, oldGraphMode, newGraphMode);
 
       if ($scope.paragraph.text !== data.paragraph.text) {
@@ -432,6 +479,14 @@ angular.module('zeppelinWebApp')
         $scope.renderText();
       }
 
+      getApplicationStates();
+      getSuggestions();
+
+      if (newActiveApp && newActiveApp !== oldActiveApp) {
+        var app = _.find($scope.apps, { id : newActiveApp });
+        renderApp(app);
+      }
+
       if (statusChanged || resultRefreshed) {
         // when last paragraph runs, zeppelin automatically appends new paragraph.
         // this broadcast will focus to the newly inserted paragraph
@@ -1214,6 +1269,9 @@ angular.module('zeppelinWebApp')
     // graph options
     newConfig.graph.mode = newMode;
 
+    // see switchApp()
+    _.set(newConfig, 'helium.activeApp', undefined);
+
     commitParagraph($scope.paragraph.title, $scope.paragraph.text, newConfig, newParams);
   };
 
@@ -1432,7 +1490,8 @@ angular.module('zeppelinWebApp')
   };
 
   $scope.isGraphMode = function(graphName) {
-    if ($scope.getResultType() === 'TABLE' && $scope.getGraphMode()===graphName) {
+    var activeAppId = _.get($scope.paragraph.config, 'helium.activeApp');
+    if ($scope.getResultType() === 'TABLE' && $scope.getGraphMode()===graphName && !activeAppId) {
       return true;
     } else {
       return false;
@@ -2180,4 +2239,188 @@ angular.module('zeppelinWebApp')
     }
     SaveAsService.SaveAs(dsv, 'data', extension);
   };
+
+  // Helium ---------------------------------------------
+
+  // app states
+  $scope.apps = [];
+
+  // suggested apps
+  $scope.suggestion = {};
+
+  $scope.switchApp = function(appId) {
+    var app = _.find($scope.apps, { id : appId });
+    var config = $scope.paragraph.config;
+    var settings = $scope.paragraph.settings;
+
+    var newConfig = angular.copy(config);
+    var newParams = angular.copy(settings.params);
+
+    // 'helium.activeApp' can be cleared by setGraphMode()
+    _.set(newConfig, 'helium.activeApp', appId);
+
+    commitConfig(newConfig, newParams);
+  };
+
+  $scope.loadApp = function(heliumPackage) {
+    var noteId = $route.current.pathParams.noteId;
+    $http.post(baseUrlSrv.getRestApiBase() + '/helium/load/' + noteId + '/' + $scope.paragraph.id,
+      heliumPackage)
+      .success(function(data, status, headers, config) {
+        console.log('Load app %o', data);
+      })
+      .error(function(err, status, headers, config) {
+        console.log('Error %o', err);
+      });
+  };
+
+  var commitConfig = function(config, params) {
+    var paragraph = $scope.paragraph;
+    commitParagraph(paragraph.title, paragraph.text, config, params);
+  };
+
+  var getApplicationStates = function() {
+    var appStates = [];
+    var paragraph = $scope.paragraph;
+
+    // Display ApplicationState
+    if (paragraph.apps) {
+      _.forEach(paragraph.apps, function (app) {
+        appStates.push({
+          id: app.id,
+          pkg: app.pkg,
+          status: app.status,
+          output: app.output
+        });
+      });
+    }
+
+    // update or remove app states no longer exists
+    _.forEach($scope.apps, function(currentAppState, idx) {
+      var newAppState = _.find(appStates, { id : currentAppState.id });
+      if (newAppState) {
+        angular.extend($scope.apps[idx], newAppState);
+      } else {
+        $scope.apps.splice(idx, 1);
+      }
+    });
+
+    // add new app states
+    _.forEach(appStates, function(app, idx) {
+      if ($scope.apps.length <= idx || $scope.apps[idx].id !== app.id) {
+        $scope.apps.splice(idx, 0, app);
+      }
+    });
+  };
+
+  var getSuggestions = function() {
+    // Get suggested apps
+    var noteId = $route.current.pathParams.noteId;
+    $http.get(baseUrlSrv.getRestApiBase() + '/helium/suggest/' + noteId + '/' + $scope.paragraph.id)
+      .success(function(data, status, headers, config) {
+        console.log('Suggested apps %o', data);
+        $scope.suggestion = data.body;
+      })
+      .error(function(err, status, headers, config) {
+        console.log('Error %o', err);
+      });
+  };
+
+  var getAppScope = function(appState) {
+    if (!appState.scope) {
+      appState.scope = $rootScope.$new(true, $rootScope);
+    }
+
+    return appState.scope;
+  };
+
+  var getAppRegistry = function(appState) {
+    if (!appState.registry) {
+      appState.registry = {};
+    }
+
+    return appState.registry;
+  };
+
+  var renderApp = function(appState) {
+    var retryRenderer = function() {
+      var targetEl = angular.element(document.getElementById('p' + appState.id));
+      console.log('retry renderApp %o', targetEl);
+      if (targetEl.length) {
+        try {
+          console.log('renderApp %o', appState);
+          targetEl.html(appState.output);
+          $compile(targetEl.contents())(getAppScope(appState));
+        } catch(err) {
+          console.log('App rendering error %o', err);
+        }
+      } else {
+        $timeout(retryRenderer, 1000);
+      }
+    };
+    $timeout(retryRenderer);
+  };
+
+  $scope.$on('appendAppOutput', function(event, data) {
+    if ($scope.paragraph.id === data.paragraphId) {
+      var app = _.find($scope.apps, { id : data.appId });
+      if (app) {
+        app.output += data.data;
+
+        var paragraphAppState = _.find($scope.paragraph.apps, { id : data.appId });
+        paragraphAppState.output = app.output;
+
+        var targetEl = angular.element(document.getElementById('p' + app.id));
+        targetEl.html(app.output);
+        $compile(targetEl.contents())(getAppScope(app));
+        console.log('append app output %o', $scope.apps);
+      }
+    }
+  });
+
+  $scope.$on('updateAppOutput', function(event, data) {
+    if ($scope.paragraph.id === data.paragraphId) {
+      var app = _.find($scope.apps, { id : data.appId });
+      if (app) {
+        app.output = data.data;
+
+        var paragraphAppState = _.find($scope.paragraph.apps, { id : data.appId });
+        paragraphAppState.output = app.output;
+
+        var targetEl = angular.element(document.getElementById('p' + app.id));
+        targetEl.html(app.output);
+        $compile(targetEl.contents())(getAppScope(app));
+        console.log('append app output');
+      }
+    }
+  });
+
+  $scope.$on('appLoad', function(event, data) {
+    if ($scope.paragraph.id === data.paragraphId) {
+      var app = _.find($scope.apps, {id: data.appId});
+      if (!app) {
+        app = {
+          id: data.appId,
+          pkg: data.pkg,
+          status: 'UNLOADED',
+          output: ''
+        };
+
+        $scope.apps.push(app);
+        $scope.paragraph.apps.push(app);
+        $scope.switchApp(app.id);
+      }
+    }
+  });
+
+  $scope.$on('appStatusChange', function(event, data) {
+    if ($scope.paragraph.id === data.paragraphId) {
+      var app = _.find($scope.apps, {id: data.appId});
+      if (app) {
+        app.status = data.status;
+        var paragraphAppState = _.find($scope.paragraph.apps, { id : data.appId });
+        paragraphAppState.status = app.status;
+      }
+    }
+  });
 });

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-web/src/app/notebook/paragraph/paragraph.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.css b/zeppelin-web/src/app/notebook/paragraph/paragraph.css
index cea3ebd..d8b464e 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.css
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.css
@@ -484,6 +484,11 @@ table.table-striped {
   right: 15px;
 }
 
+.appSuggestion {
+  width: 200px;
+  padding: 5px 10px 5px 10px;
+}
+
 /* DSV download toggle button */
 .caretBtn {
   padding-right: 4px !important;

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
index e07fb16..19afdc0 100644
--- a/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
+++ b/zeppelin-web/src/components/websocketEvents/websocketEvents.factory.js
@@ -96,6 +96,14 @@ angular.module('zeppelinWebApp').factory('websocketEvents', function($rootScope,
       $rootScope.$broadcast('angularObjectUpdate', data);
     } else if (op === 'ANGULAR_OBJECT_REMOVE') {
       $rootScope.$broadcast('angularObjectRemove', data);
+    } else if (op === 'APP_APPEND_OUTPUT') {
+      $rootScope.$broadcast('appendAppOutput', data);
+    } else if (op === 'APP_UPDATE_OUTPUT') {
+      $rootScope.$broadcast('updateAppOutput', data);
+    } else if (op === 'APP_LOAD') {
+      $rootScope.$broadcast('appLoad', data);
+    } else if (op === 'APP_STATUS_CHANGE') {
+      $rootScope.$broadcast('appStatusChange', data);
     }
   });
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-web/test/spec/controllers/paragraph.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/test/spec/controllers/paragraph.js b/zeppelin-web/test/spec/controllers/paragraph.js
index 7cdf748..77fc495 100644
--- a/zeppelin-web/test/spec/controllers/paragraph.js
+++ b/zeppelin-web/test/spec/controllers/paragraph.js
@@ -10,6 +10,13 @@ describe('Controller: ParagraphCtrl', function() {
   var paragraphMock = {
     config: {}
   };
+  var route = {
+    current : {
+      pathParams : {
+        noteId : 'noteId'
+      }
+    }
+  };
 
   beforeEach(inject(function($controller, $rootScope) {
     scope = $rootScope.$new();
@@ -18,8 +25,10 @@ describe('Controller: ParagraphCtrl', function() {
     ParagraphCtrl = $controller('ParagraphCtrl', {
       $scope: scope,
       websocketMsgSrv: websocketMsgSrvMock,
-      $element: {}
+      $element: {},
+      $route: route
     });
+
     scope.init(paragraphMock);
   }));
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
index e2673df..6884622 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -362,6 +362,14 @@ public class ZeppelinConfiguration extends XMLConfiguration {
     return getRelativeDir(String.format("%s/interpreter.json", getConfDir()));
   }
 
+  public String getHeliumConfPath() {
+    return getRelativeDir(String.format("%s/helium.json", getConfDir()));
+  }
+
+  public String getHeliumDefaultLocalRegistryPath() {
+    return getRelativeDir(ConfVars.ZEPPELIN_HELIUM_LOCALREGISTRY_DEFAULT);
+  }
+
   public String getNotebookAuthorizationPath() {
     return getRelativeDir(String.format("%s/notebook-authorization.json", getConfDir()));
   }
@@ -540,6 +548,7 @@ public class ZeppelinConfiguration extends XMLConfiguration {
     ZEPPELIN_NOTEBOOK_AUTO_INTERPRETER_BINDING("zeppelin.notebook.autoInterpreterBinding", true),
     ZEPPELIN_CONF_DIR("zeppelin.conf.dir", "conf"),
     ZEPPELIN_DEP_LOCALREPO("zeppelin.dep.localrepo", "local-repo"),
+    ZEPPELIN_HELIUM_LOCALREGISTRY_DEFAULT("zeppelin.helium.localregistry.default", "helium"),
     // Allows a way to specify a ',' separated list of allowed origins for rest and websockets
     // i.e. http://localhost:8080
     ZEPPELIN_ALLOWED_ORIGINS("zeppelin.server.allowed.origins", "*"),

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/9463fb85/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java
new file mode 100644
index 0000000..a07f5f0
--- /dev/null
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/helium/Helium.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.helium;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import org.apache.commons.io.FileUtils;
+import org.apache.zeppelin.interpreter.Interpreter;
+import org.apache.zeppelin.notebook.Paragraph;
+import org.apache.zeppelin.resource.DistributedResourcePool;
+import org.apache.zeppelin.resource.ResourcePool;
+import org.apache.zeppelin.resource.ResourcePoolUtils;
+import org.apache.zeppelin.resource.ResourceSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages helium packages
+ */
+public class Helium {
+  Logger logger = LoggerFactory.getLogger(Helium.class);
+  private List<HeliumRegistry> registry = new LinkedList<HeliumRegistry>();
+
+  private final HeliumConf heliumConf;
+  private final String heliumConfPath;
+  private final String defaultLocalRegistryPath;
+  private final Gson gson;
+
+  public Helium(String heliumConfPath, String defaultLocalRegistryPath) throws IOException {
+    this.heliumConfPath = heliumConfPath;
+    this.defaultLocalRegistryPath = defaultLocalRegistryPath;
+
+    GsonBuilder builder = new GsonBuilder();
+    builder.setPrettyPrinting();
+    builder.registerTypeAdapter(
+        HeliumRegistry.class, new HeliumRegistrySerializer());
+    gson = builder.create();
+
+    heliumConf = loadConf(heliumConfPath);
+  }
+
+  /**
+   * Add HeliumRegistry
+   *
+   * @param registry
+   */
+  public void addRegistry(HeliumRegistry registry) {
+    synchronized (this.registry) {
+      this.registry.add(registry);
+    }
+  }
+
+  public List<HeliumRegistry> getAllRegistry() {
+    synchronized (this.registry) {
+      List list = new LinkedList<HeliumRegistry>();
+      for (HeliumRegistry r : registry) {
+        list.add(r);
+      }
+      return list;
+    }
+  }
+
+  private synchronized HeliumConf loadConf(String path) throws IOException {
+    File heliumConfFile = new File(path);
+    if (!heliumConfFile.isFile()) {
+      logger.warn("{} does not exists", path);
+      HeliumConf conf = new HeliumConf();
+      LinkedList<HeliumRegistry> defaultRegistry = new LinkedList<HeliumRegistry>();
+      defaultRegistry.add(new HeliumLocalRegistry("local", defaultLocalRegistryPath));
+      conf.setRegistry(defaultRegistry);
+      this.registry = conf.getRegistry();
+      return conf;
+    } else {
+      String jsonString = FileUtils.readFileToString(heliumConfFile);
+      HeliumConf conf = gson.fromJson(jsonString, HeliumConf.class);
+      this.registry = conf.getRegistry();
+      return conf;
+    }
+  }
+
+  public synchronized void save() throws IOException {
+    String jsonString;
+    synchronized (registry) {
+      heliumConf.setRegistry(registry);
+      jsonString = gson.toJson(heliumConf);
+    }
+
+    File heliumConfFile = new File(heliumConfPath);
+    if (!heliumConfFile.exists()) {
+      heliumConfFile.createNewFile();
+    }
+
+    FileUtils.writeStringToFile(heliumConfFile, jsonString);
+  }
+
+  public List<HeliumPackageSearchResult> getAllPackageInfo() {
+    List<HeliumPackageSearchResult> list = new LinkedList<HeliumPackageSearchResult>();
+    synchronized (registry) {
+      for (HeliumRegistry r : registry) {
+        try {
+          for (HeliumPackage pkg : r.getAll()) {
+            list.add(new HeliumPackageSearchResult(r.name(), pkg));
+          }
+        } catch (IOException e) {
+          logger.error(e.getMessage(), e);
+        }
+      }
+    }
+    return list;
+  }
+
+  public HeliumPackageSuggestion suggestApp(Paragraph paragraph) {
+    HeliumPackageSuggestion suggestion = new HeliumPackageSuggestion();
+
+    Interpreter intp = paragraph.getCurrentRepl();
+    if (intp == null) {
+      return suggestion;
+    }
+
+    ResourcePool resourcePool = intp.getInterpreterGroup().getResourcePool();
+    ResourceSet allResources;
+
+    if (resourcePool != null) {
+      if (resourcePool instanceof DistributedResourcePool) {
+        allResources = ((DistributedResourcePool) resourcePool).getAll(true);
+      } else {
+        allResources = resourcePool.getAll();
+      }
+    } else {
+      allResources = ResourcePoolUtils.getAllResources();
+    }
+
+    for (HeliumPackageSearchResult pkg : getAllPackageInfo()) {
+      ResourceSet resources = ApplicationLoader.findRequiredResourceSet(
+          pkg.getPkg().getResources(),
+          paragraph.getNote().getId(),
+          paragraph.getId(),
+          allResources);
+      if (resources == null) {
+        continue;
+      } else {
+        suggestion.addAvailablePackage(pkg);
+      }
+    }
+
+    suggestion.sort();
+    return suggestion;
+  }
+}


Mime
View raw message