You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by tb...@apache.org on 2014/04/09 15:55:05 UTC

git commit: AMBARI-5399 - Ambari Views: Instance data not loaded for pre-defined view instances

Repository: ambari
Updated Branches:
  refs/heads/trunk 3ed1b415b -> 3971bd139


AMBARI-5399 - Ambari Views: Instance data not loaded for pre-defined view instances


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/3971bd13
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/3971bd13
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/3971bd13

Branch: refs/heads/trunk
Commit: 3971bd139f243b934df5626d855c9e03918c5338
Parents: 3ed1b41
Author: tbeerbower <tb...@hortonworks.com>
Authored: Tue Apr 8 18:04:19 2014 -0400
Committer: tbeerbower <tb...@hortonworks.com>
Committed: Wed Apr 9 09:54:28 2014 -0400

----------------------------------------------------------------------
 .../ambari/server/orm/dao/ViewInstanceDAO.java  |  22 ++
 .../ambari/server/view/ViewContextImpl.java     |   3 +-
 .../apache/ambari/server/view/ViewRegistry.java |  23 +-
 .../server/orm/dao/ViewInstanceDAOTest.java     |  82 ++++++
 .../ambari/server/view/ViewRegistryTest.java    |  32 ++
 .../examples/phone-list-view/docs/index.md      | 292 +++++++++++++++++++
 .../examples/phone-list-view/docs/phone_1.png   | Bin 0 -> 45394 bytes
 .../examples/phone-list-view/docs/phone_2.png   | Bin 0 -> 10651 bytes
 .../examples/phone-list-view/docs/phone_3.png   | Bin 0 -> 27134 bytes
 .../examples/phone-list-view/docs/phone_4.png   | Bin 0 -> 15726 bytes
 .../examples/phone-list-view/docs/phone_5.png   | Bin 0 -> 30323 bytes
 ambari-views/examples/phone-list-view/pom.xml   |  90 ++++++
 .../ambari/view/phonelist/PhoneListServlet.java | 144 +++++++++
 .../src/main/resources/WEB-INF/web.xml          |  37 +++
 .../phone-list-view/src/main/resources/view.xml |  27 ++
 ambari-views/examples/pom.xml                   |   1 +
 16 files changed, 750 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewInstanceDAO.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewInstanceDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewInstanceDAO.java
index 34cd732..f4ae891 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewInstanceDAO.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewInstanceDAO.java
@@ -22,6 +22,7 @@ import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
+import org.apache.ambari.server.orm.entities.ViewInstanceDataEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceEntityPK;
 
@@ -110,4 +111,25 @@ public class ViewInstanceDAO {
   public void remove(ViewInstanceEntity ViewInstanceEntity) {
     entityManagerProvider.get().remove(merge(ViewInstanceEntity));
   }
+
+  /**
+   * Merge the state of the given entity data into the current persistence context.
+   *
+   * @param viewInstanceDataEntity  entity to merge
+   * @return the merged entity
+   */
+  @Transactional
+  public ViewInstanceDataEntity mergeData(ViewInstanceDataEntity viewInstanceDataEntity) {
+    return entityManagerProvider.get().merge(viewInstanceDataEntity);
+  }
+
+  /**
+   * Remove the entity instance data.
+   *
+   * @param viewInstanceDataEntity  entity to remove
+   */
+  @Transactional
+  public void removeData(ViewInstanceDataEntity viewInstanceDataEntity) {
+    entityManagerProvider.get().remove(mergeData(viewInstanceDataEntity));
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java
index cc8ae48..e4cc71f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java
@@ -106,8 +106,7 @@ public class ViewContextImpl implements ViewContext {
 
   @Override
   public void removeInstanceData(String key) {
-    viewInstanceEntity.removeInstanceData(key);
-    viewRegistry.updateViewInstance(viewInstanceEntity);
+    viewRegistry.removeInstanceData(viewInstanceEntity, key);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
index b91cb99..a5112e0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
@@ -32,6 +32,7 @@ import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.orm.dao.ViewDAO;
 import org.apache.ambari.server.orm.dao.ViewInstanceDAO;
 import org.apache.ambari.server.orm.entities.ViewEntity;
+import org.apache.ambari.server.orm.entities.ViewInstanceDataEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
 import org.apache.ambari.server.orm.entities.ViewParameterEntity;
 import org.apache.ambari.server.orm.entities.ViewResourceEntity;
@@ -346,6 +347,21 @@ public class ViewRegistry {
   }
 
   /**
+   * Remove the data entry keyed by the given key from the given instance entity.
+   *
+   * @param instanceEntity  the instance entity
+   * @param key             the data key
+   */
+  public void removeInstanceData(ViewInstanceEntity instanceEntity, String key) {
+    ViewInstanceDataEntity dataEntity = instanceEntity.getInstanceData(key);
+    if (dataEntity != null) {
+      instanceDAO.removeData(dataEntity);
+    }
+    instanceEntity.removeInstanceData(key);
+    instanceDAO.merge(instanceEntity);
+  }
+
+  /**
    * Uninstall a view instance for the view with the given view name.
    *
    * @param instanceEntity  the view instance entity
@@ -554,10 +570,15 @@ public class ViewRegistry {
         ViewEntity viewDefinition = ViewRegistry.getInstance().viewDefinitions.get(name);
 
         for (ViewInstanceEntity viewInstanceEntity : viewEntity.getInstances()){
-          if (viewDefinition.getInstanceDefinition(viewInstanceEntity.getName()) == null) {
+          ViewInstanceEntity instanceEntity = viewDefinition.getInstanceDefinition(viewInstanceEntity.getName());
+          if (instanceEntity == null) {
             viewInstanceEntity.setViewEntity(viewDefinition);
             _installViewInstance(viewDefinition, viewInstanceEntity);
             instanceDefinitions.add(viewInstanceEntity);
+          } else {
+            // apply overrides to the in-memory view instance entities
+            instanceEntity.setData(viewInstanceEntity.getData());
+            instanceEntity.setProperties(viewInstanceEntity.getProperties());
           }
         }
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ViewInstanceDAOTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ViewInstanceDAOTest.java b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ViewInstanceDAOTest.java
new file mode 100644
index 0000000..325c953
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ViewInstanceDAOTest.java
@@ -0,0 +1,82 @@
+/**
+ * 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.ambari.server.orm.dao;
+
+import com.google.inject.Provider;
+import org.apache.ambari.server.orm.entities.ViewInstanceDataEntity;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.persistence.EntityManager;
+
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertSame;
+
+/**
+ * ViewInstanceDAO tests.
+ */
+public class ViewInstanceDAOTest {
+  Provider<EntityManager> entityManagerProvider = createStrictMock(Provider.class);
+  EntityManager entityManager = createStrictMock(EntityManager.class);
+
+  @Before
+  public void init() {
+    reset(entityManagerProvider);
+    expect(entityManagerProvider.get()).andReturn(entityManager).atLeastOnce();
+    replay(entityManagerProvider);
+  }
+
+  @Test
+  public void testMergeData() throws Exception {
+    ViewInstanceDataEntity entity = new ViewInstanceDataEntity();
+    ViewInstanceDataEntity entity2 = new ViewInstanceDataEntity();
+
+    // set expectations
+    expect(entityManager.merge(eq(entity))).andReturn(entity2);
+    replay(entityManager);
+
+    ViewInstanceDAO dao = new ViewInstanceDAO();
+    dao.entityManagerProvider = entityManagerProvider;
+    assertSame(entity2, dao.mergeData(entity));
+
+    verify(entityManagerProvider, entityManager);
+  }
+
+  @Test
+  public void testRemoveData() throws Exception {
+    ViewInstanceDataEntity entity = new ViewInstanceDataEntity();
+    ViewInstanceDataEntity entity2 = new ViewInstanceDataEntity();
+
+    // set expectations
+    expect(entityManager.merge(eq(entity))).andReturn(entity2);
+    entityManager.remove(eq(entity2));
+    replay(entityManager);
+
+    ViewInstanceDAO dao = new ViewInstanceDAO();
+    dao.entityManagerProvider = entityManagerProvider;
+    dao.removeData(entity);
+
+    verify(entityManagerProvider, entityManager);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-server/src/test/java/org/apache/ambari/server/view/ViewRegistryTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/view/ViewRegistryTest.java b/ambari-server/src/test/java/org/apache/ambari/server/view/ViewRegistryTest.java
index 14581a7..daedbca 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/view/ViewRegistryTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/view/ViewRegistryTest.java
@@ -21,8 +21,11 @@ package org.apache.ambari.server.view;
 import org.apache.ambari.server.api.resources.SubResourceDefinition;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.orm.dao.ViewDAO;
+import org.apache.ambari.server.orm.dao.ViewInstanceDAO;
 import org.apache.ambari.server.orm.entities.ViewEntity;
 import org.apache.ambari.server.orm.entities.ViewEntityTest;
+import org.apache.ambari.server.orm.entities.ViewInstanceDataEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceEntityTest;
 import org.apache.ambari.server.view.configuration.InstanceConfig;
@@ -38,6 +41,9 @@ import java.util.Collection;
 import java.util.Set;
 
 import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
 
 /**
  * ViewRegistry tests.
@@ -118,6 +124,32 @@ public class ViewRegistryTest {
     Assert.assertEquals(viewInstanceEntity, viewInstanceDefinitions.iterator().next());
   }
 
+  @Test
+  public void testRemoveInstanceData() throws Exception {
+
+    ViewDAO viewDAO = createNiceMock(ViewDAO.class);
+    ViewInstanceDAO viewInstanceDAO = createNiceMock(ViewInstanceDAO.class);
+
+    ViewRegistry.init(viewDAO, viewInstanceDAO);
+
+    ViewRegistry registry = ViewRegistry.getInstance();
+
+    ViewInstanceEntity viewInstanceEntity = ViewInstanceEntityTest.getViewInstanceEntity();
+
+    viewInstanceEntity.putInstanceData("foo", "value");
+
+    ViewInstanceDataEntity dataEntity = viewInstanceEntity.getInstanceData("foo");
+
+    viewInstanceDAO.removeData(dataEntity);
+    expect(viewInstanceDAO.merge(viewInstanceEntity)).andReturn(viewInstanceEntity);
+    replay(viewDAO, viewInstanceDAO);
+
+    registry.removeInstanceData(viewInstanceEntity, "foo");
+
+    Assert.assertNull(viewInstanceEntity.getInstanceData("foo"));
+    verify(viewDAO, viewInstanceDAO);
+  }
+
   @Before
   public void before() throws Exception {
     ViewRegistry.getInstance().clear();

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/phone-list-view/docs/index.md
----------------------------------------------------------------------
diff --git a/ambari-views/examples/phone-list-view/docs/index.md b/ambari-views/examples/phone-list-view/docs/index.md
new file mode 100644
index 0000000..3b46f97
--- /dev/null
+++ b/ambari-views/examples/phone-list-view/docs/index.md
@@ -0,0 +1,292 @@
+<!---
+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](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.
+-->
+
+Phone List View Example
+========
+Description
+-----
+The Phone List view is a simple view example that demonstrates view persistence.  The view displays a list of names and phone numbers. The user may add, modify or delete numbers from the list through the view UI.  The names and phone numbers are stored in the Ambari database for the view instance and will get reloaded when the server is restarted.  This document also describes how to create a new view instance through the Ambari API.
+
+Package
+-----
+
+All views are packaged as a view archive.  The view archive contains the configuration file and various optional components of the view.
+
+#####view.xml
+
+The view.xml file is the only required file for a view archive.  The view.xml is the configuration that describes the view and view instances for Ambari.
+
+      <view>
+        <name>PHONE_LIST</name>
+        <label>The Phone List View</label>
+        <version>1.0.0</version>
+        <instance>
+          <name>LIST_1</name>
+        </instance>
+        <instance>
+          <name>LIST_2</name>
+        </instance>
+      </view>
+
+The configuration in this case defines a view named PHONE_LIST that has a multiple instances.  Each view instance will have its own data persisted to the Ambari database.
+
+
+#####WEB-INF/web.xml
+The web.xml is the deployment descriptor used to deploy the view as a web app.  The Java EE standards apply for the descriptor.  We can see that for this example a single servlet is mapped to the root context path.
+
+        <servlet>
+          <servlet-name>PhoneListServlet</servlet-name>
+          <servlet-class>org.apache.ambari.view.phonelist.PhoneListServlet</servlet-class>
+        </servlet>
+        <servlet-mapping>
+          <servlet-name>PhoneListServlet</servlet-name>
+          <url-pattern>/</url-pattern>
+        </servlet-mapping>
+        
+#####PhoneListServlet.java
+
+The servlet PhoneListServlet will be deployed as part of the view and mapped as described in the web.xml.
+
+Notice that we can access the view context in the servlet by obtaining it as a servlet context attribute in the init() method.
+
+      private ViewContext viewContext;
+
+      @Override
+      public void init(ServletConfig config) throws ServletException {
+        super.init(config);
+
+        ServletContext context = config.getServletContext();
+        viewContext = (ViewContext) context.getAttribute(ViewContext.CONTEXT_ATTRIBUTE);
+      }
+
+
+The view context exposes the methods needed to save an application data map for a view instance.
+
+    public interface ViewContext {
+      …
+      /**
+       * Get the instance data value for the given key.
+       *
+       * @param key  the key
+       *
+       * @return the instance data value
+       */
+      public String getInstanceData(String key);
+
+      /**
+       * Get the instance data values.
+       *
+       * @return the view instance property values
+       */
+      public Map<String, String> getInstanceData();
+
+      /**
+       * Remove the instance data value for the given key.
+       *
+       * @param key  the key
+       */
+      public void removeInstanceData(String key);
+    }
+
+The servlet will make use of the context to persist the instance data (in this case the names and phone numbers).  For example ...
+
+          @Override
+          protected void doPost(HttpServletRequest request, HttpServletResponse response) 
+              throws ServletException, IOException {
+            String name = request.getParameter("name");
+            String phone = request.getParameter("phone");
+        
+            if (name != null && name.length() > 0 && phone != null && phone.length() > 0) {
+              if (request.getParameter("add") != null) {
+                if (viewContext.getInstanceData(name) != null) {
+                  throw new ServletException("A number for " + name + " already exists.");
+                }
+                viewContext.putInstanceData(name, phone);
+              } else if (request.getParameter("update") != null) {
+                viewContext.putInstanceData(name, phone);
+              } else if (request.getParameter("delete") != null) {
+                viewContext.removeInstanceData(name);
+              }
+            }
+            listAll(request, response);
+          }
+
+
+Build
+-----
+
+The view can be built as a maven project.
+
+    cd ambari-views/examples/phone-list-view
+    mvn clean package
+
+The build will produce the view archive.
+
+    ambari-views/examples/phone-list-view/target/phone-list-view-1.0.0.jar
+
+
+Deploy
+-----
+To deploy a view we simply place the view archive in the views folder of the ambari-server machine.  By default the views folder is located at ...
+
+    /var/lib/ambari-server/resources/views
+
+To deploy the Phone List view simply copy the phone-list-view jar to the ambari-server views folder and restart the ambari server.
+
+Use
+-----
+
+After deploying a view you should see it as a view resource in the Ambari REST API.  If we request all views, we should see the PHONE_LIST view.
+
+      http://<server>:8080/api/v1/views
+
+      {
+        "href" : "http://<server>:8080/api/v1/views",
+        "items" : [
+          {
+            "href" : "http://<server>:8080/api/v1/views/HELLO_SERVLET",
+            "ViewInfo" : {
+              "view_name" : "HELLO_SERVLET"
+            }
+          },
+          {
+            "href" : "http://<server>:8080/api/v1/views/HELLO_WORLD",
+            "ViewInfo" : {
+              "view_name" : "HELLO_WORLD"
+            }
+          },
+          {
+            "href" : "http://<server>:8080/api/v1/views/PHONE_LIST",
+            "ViewInfo" : {
+            "view_name" : "PHONE_LIST"
+          }
+        }
+        ]
+      }
+
+
+If we want to see the details about a specific view, we can ask for it by name.  This shows us that the PHONE_LIST view has a two instances.
+
+      {
+        "href" : "http://<server>:8080/api/v1/views/PHONE_LIST/",
+          "ViewInfo" : {
+        "archive" : "/var/lib/ambari-server/resources/views/phone-list-view-1.0.0.jar",
+            "label" : "The Phone List View",
+            "parameters" : [ ],
+            "version" : "1.0.0",
+            "view_name" : "PHONE_LIST"
+      },
+      "instances" : [
+        {
+          "href" : "http://<server>:8080/api/v1/views/PHONE_LIST/instances/LIST_1",
+          "ViewInstanceInfo" : {
+            "instance_name" : "LIST_1",
+            "view_name" : "PHONE_LIST"
+          }
+        },
+        {
+          "href" : "http://<server>:8080/api/v1/views/PHONE_LIST/instances/LIST_2",
+          "ViewInstanceInfo" : {
+            "instance_name" : "LIST_2",
+            "view_name" : "PHONE_LIST"
+          }
+        }
+      ]
+    }
+
+To see a specific instance of a view, we can ask for it by name.  Here we can see the attributes of the view including its name and root context path.  We can also see that this view instance defines a value for the name property.
+
+    http://<server>:8080/api/v1/views/PHONE_LIST/instances/LIST_1
+
+    {
+      "href" : "http://<server>:8080/api/v1/views/PHONE_LIST/instances/LIST_1",
+      "ViewInstanceInfo" : {
+        "context_path" : "/views/PHONE_LIST/LIST_1",
+        "instance_name" : "LIST_1",
+        "view_name" : "PHONE_LIST",
+        "instance_data" : {
+          "Dave" : "999 999 9923",
+          "Fred" : "123 678 1234",
+          "Karl" : "888 888 8788",
+          "Mary" : "888 888 8788"
+        },
+        "properties" : { }
+      },
+      "resources" : [ ]
+    }
+
+If the view contains any web content, we can access it at the view's root context path.  In this case its the PhoneListServlet which displays a list of phone number entries for the view instance.
+      
+    http://<server>:8080/views/PHONE_LIST/LIST_1/
+
+![image](phone_1.png)
+
+Use the view API to add new numbers to the phone list.  Click on any name in the list to edit or delete a number…
+
+![image](phone_2.png)
+
+#####Different Instance
+Each instance of the Phone List view has its own data so you can maintain separate lists.  Access the UI for a different view instance and you will see a different phone list…
+
+
+    http://<server>:8080/views/PHONE_LIST/LIST_2/
+
+![image](phone_3.png)
+
+We can look at the Ambari database and see that the view instance data is being persisted …
+
+![image](phone_4.png)
+
+#####New Instance
+Notice that the two instances above were defined in the view.xml for the Phone List view.  We can also create new instances through the API.  For example, if you POST the following…
+
+
+    POST http://<server>:8080/api/v1/views/PHONE_LIST/instances/LIST_3
+    
+You should then see a new instance named LIST_3 for the Phone List view …
+
+    http://<server>:8080/api/v1/views/PHONE_LIST/instances/  
+    
+    {
+      "href" : "http://<server>:8080/api/v1/views/PHONE_LIST/instances/",
+        "items" : [
+          {
+            "href" : "http://<server>:8080/api/v1/views/PHONE_LIST/instances/LIST_1",
+            "ViewInstanceInfo" : {
+              "instance_name" : "LIST_1",
+              "view_name" : "PHONE_LIST"
+            }
+          },
+          {
+            "href" : "http://<server>:8080/api/v1/views/PHONE_LIST/instances/LIST_2",
+            "ViewInstanceInfo" : {
+              "instance_name" : "LIST_2",
+              "view_name" : "PHONE_LIST"
+            }
+          },
+          {
+            "href" : "http://<server>:8080/api/v1/views/PHONE_LIST/instances/LIST_3",
+            "ViewInstanceInfo" : {
+              "instance_name" : "LIST_3",
+              "view_name" : "PHONE_LIST"
+            }
+          }
+        ]
+      }  
+      
+ You can then access the new instance UI to add, edit and delete numbers for the new list …
+ 
+ ![image](phone_5.png)
+ 

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/phone-list-view/docs/phone_1.png
----------------------------------------------------------------------
diff --git a/ambari-views/examples/phone-list-view/docs/phone_1.png b/ambari-views/examples/phone-list-view/docs/phone_1.png
new file mode 100644
index 0000000..2a39101
Binary files /dev/null and b/ambari-views/examples/phone-list-view/docs/phone_1.png differ

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/phone-list-view/docs/phone_2.png
----------------------------------------------------------------------
diff --git a/ambari-views/examples/phone-list-view/docs/phone_2.png b/ambari-views/examples/phone-list-view/docs/phone_2.png
new file mode 100644
index 0000000..e9efb22
Binary files /dev/null and b/ambari-views/examples/phone-list-view/docs/phone_2.png differ

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/phone-list-view/docs/phone_3.png
----------------------------------------------------------------------
diff --git a/ambari-views/examples/phone-list-view/docs/phone_3.png b/ambari-views/examples/phone-list-view/docs/phone_3.png
new file mode 100644
index 0000000..ba886e6
Binary files /dev/null and b/ambari-views/examples/phone-list-view/docs/phone_3.png differ

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/phone-list-view/docs/phone_4.png
----------------------------------------------------------------------
diff --git a/ambari-views/examples/phone-list-view/docs/phone_4.png b/ambari-views/examples/phone-list-view/docs/phone_4.png
new file mode 100644
index 0000000..e63268f
Binary files /dev/null and b/ambari-views/examples/phone-list-view/docs/phone_4.png differ

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/phone-list-view/docs/phone_5.png
----------------------------------------------------------------------
diff --git a/ambari-views/examples/phone-list-view/docs/phone_5.png b/ambari-views/examples/phone-list-view/docs/phone_5.png
new file mode 100644
index 0000000..53d2d9b
Binary files /dev/null and b/ambari-views/examples/phone-list-view/docs/phone_5.png differ

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/phone-list-view/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-views/examples/phone-list-view/pom.xml b/ambari-views/examples/phone-list-view/pom.xml
new file mode 100644
index 0000000..6662011
--- /dev/null
+++ b/ambari-views/examples/phone-list-view/pom.xml
@@ -0,0 +1,90 @@
+<!--
+   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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <groupId>org.apache.ambari</groupId>
+    <artifactId>ambari-view-examples</artifactId>
+    <version>1.0.0</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>phone-list-view</artifactId>
+  <packaging>jar</packaging>
+  <name>Ambari Phone List View</name>
+  <url>http://maven.apache.org</url>
+  <properties>
+    <ambari.version>1.3.0-SNAPSHOT</ambari.version>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymock</artifactId>
+      <version>3.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.ambari</groupId>
+      <artifactId>ambari-views</artifactId>
+      <version>${ambari.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.sun.jersey</groupId>
+      <artifactId>jersey-server</artifactId>
+      <version>1.8</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>2.5</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>2.12</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rpm-maven-plugin</artifactId>
+        <version>2.0.1</version>
+        <executions>
+          <execution>
+            <phase>none</phase>
+            <goals>
+              <goal>rpm</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/phone-list-view/src/main/java/org/apache/ambari/view/phonelist/PhoneListServlet.java
----------------------------------------------------------------------
diff --git a/ambari-views/examples/phone-list-view/src/main/java/org/apache/ambari/view/phonelist/PhoneListServlet.java b/ambari-views/examples/phone-list-view/src/main/java/org/apache/ambari/view/phonelist/PhoneListServlet.java
new file mode 100644
index 0000000..d0fbbef
--- /dev/null
+++ b/ambari-views/examples/phone-list-view/src/main/java/org/apache/ambari/view/phonelist/PhoneListServlet.java
@@ -0,0 +1,144 @@
+/**
+ * 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.ambari.view.phonelist;
+
+import org.apache.ambari.view.ViewContext;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Servlet for phone list view.
+ */
+public class PhoneListServlet extends HttpServlet {
+
+  /**
+   * The view context.
+   */
+  private ViewContext viewContext;
+
+  @Override
+  public void init(ServletConfig config) throws ServletException {
+    super.init(config);
+
+    ServletContext context = config.getServletContext();
+    viewContext = (ViewContext) context.getAttribute(ViewContext.CONTEXT_ATTRIBUTE);
+  }
+
+  @Override
+  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+    String name = request.getParameter("name");
+    String phone = request.getParameter("phone");
+
+    if (name != null && name.length() > 0 && phone != null && phone.length() > 0) {
+      if (request.getParameter("add") != null) {
+        if (viewContext.getInstanceData(name) != null) {
+          throw new IllegalArgumentException("A number for " + name + " already exists.");
+        }
+        viewContext.putInstanceData(name, phone);
+      } else if (request.getParameter("update") != null) {
+        viewContext.putInstanceData(name, phone);
+      } else if (request.getParameter("delete") != null) {
+        viewContext.removeInstanceData(name);
+      }
+    }
+    listAll(request, response);
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    response.setContentType("text/html");
+    response.setStatus(HttpServletResponse.SC_OK);
+
+    PrintWriter writer = response.getWriter();
+
+
+    String name = request.getParameter("name");
+    String phone = (name == null || name.length() == 0) ? null : viewContext.getInstanceData(name);
+
+    if (phone != null) {
+      editNumber(writer, name, phone);
+    } else {
+      listAll(request, response);
+    }
+  }
+
+  private void enterNumber(PrintWriter writer) {
+    writer.println("<form name=\"input\" method=\"POST\">");
+    writer.println("<table>");
+    writer.println("<tr>");
+    writer.println("<td>Name:</td><td><input type=\"text\" name=\"name\"></td><br/>");
+    writer.println("</tr>");
+    writer.println("<tr>");
+    writer.println("<td>Phone Number:</td><td><input type=\"text\" name=\"phone\"></td><br/><br/>");
+    writer.println("</tr>");
+    writer.println("</table>");
+    writer.println("<input type=\"submit\" value=\"Add\" name=\"add\">");
+    writer.println("</form>");
+  }
+
+  private void editNumber(PrintWriter writer, String name, String phone) {
+    writer.println("<form name=\"input\" method=\"POST\">");
+    writer.println("<table>");
+    writer.println("<tr>");
+    writer.println("<td>Name:</td><td><input type=\"text\" name=\"name\" value=\"" + name + "\" readonly></td><br/>");
+    writer.println("</tr>");
+    writer.println("<tr>");
+    writer.println("<td>Phone Number:</td><td><input type=\"text\" name=\"phone\" value=\"" + phone + "\"></td><br/><br/>");
+    writer.println("</tr>");
+    writer.println("</table>");
+    writer.println("<input type=\"submit\" value=\"Update\" name=\"update\">");
+    writer.println("<input type=\"submit\" value=\"Delete\" name=\"delete\">");
+    writer.println("</form>");
+  }
+
+  private void listAll(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    String name;Map<String, String> data = new LinkedHashMap<String, String>(viewContext.getInstanceData());
+
+    PrintWriter writer = response.getWriter();
+
+    writer.println("<h1>Phone List :" + viewContext.getInstanceName() + "</h1>");
+
+    writer.println("<table border=\"1\" style=\"width:300px\">");
+    writer.println("<tr>");
+    writer.println("<td>Name</td>");
+    writer.println("<td>Phone Number</td>");
+    writer.println("</tr>");
+
+    for (Map.Entry<String,String> entry : data.entrySet()){
+      name = entry.getKey();
+      writer.println("<tr>");
+      writer.println("<td><A href=" + request.getRequestURI() + "?name=" + name + ">" + name + "</A></td>");
+      writer.println("<td>" + entry.getValue() + "</td>");
+      writer.println("</tr>");
+    }
+    writer.println("</table><br/><hr/>");
+
+    enterNumber(writer);
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/phone-list-view/src/main/resources/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/ambari-views/examples/phone-list-view/src/main/resources/WEB-INF/web.xml b/ambari-views/examples/phone-list-view/src/main/resources/WEB-INF/web.xml
new file mode 100644
index 0000000..7f26b85
--- /dev/null
+++ b/ambari-views/examples/phone-list-view/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<!--
+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. Kerberos, LDAP, Custom. Binary/Htt
+-->
+
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+         version="2.4">
+
+  <display-name>Phone List Application</display-name>
+  <description>
+    This is the phone list view application.
+  </description>
+  <servlet>
+    <servlet-name>PhoneListServlet</servlet-name>
+    <servlet-class>org.apache.ambari.view.phonelist.PhoneListServlet</servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>PhoneListServlet</servlet-name>
+    <url-pattern>/</url-pattern>
+  </servlet-mapping>
+</web-app>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/phone-list-view/src/main/resources/view.xml
----------------------------------------------------------------------
diff --git a/ambari-views/examples/phone-list-view/src/main/resources/view.xml b/ambari-views/examples/phone-list-view/src/main/resources/view.xml
new file mode 100644
index 0000000..399b3c9
--- /dev/null
+++ b/ambari-views/examples/phone-list-view/src/main/resources/view.xml
@@ -0,0 +1,27 @@
+<!--
+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. Kerberos, LDAP, Custom. Binary/Htt
+-->
+<view>
+  <name>PHONE_LIST</name>
+  <label>The Phone List View</label>
+  <version>1.0.0</version>
+  <instance>
+    <name>LIST_1</name>
+  </instance>
+  <instance>
+    <name>LIST_2</name>
+  </instance>
+</view>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/3971bd13/ambari-views/examples/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-views/examples/pom.xml b/ambari-views/examples/pom.xml
index 94a9579..7309ef5 100644
--- a/ambari-views/examples/pom.xml
+++ b/ambari-views/examples/pom.xml
@@ -25,6 +25,7 @@
   <modules>
     <module>helloworld-view</module>
     <module>hello-servlet-view</module>
+    <module>phone-list-view</module>
     <module>calculator-view</module>
     <module>weather-view</module>
   </modules>