Browse Source

AMBARI-5554 - Ambari Views : Persistence Data Store

tbeerbower 11 years ago
parent
commit
fb57e05a5a
27 changed files with 2178 additions and 60 deletions
  1. 72 50
      ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
  2. 1 1
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewDAO.java
  3. 10 1
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java
  4. 187 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntityEntity.java
  5. 24 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java
  6. 24 0
      ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog160.java
  7. 19 0
      ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java
  8. 30 7
      ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
  9. 43 0
      ambari-server/src/main/java/org/apache/ambari/server/view/configuration/EntityConfig.java
  10. 45 0
      ambari-server/src/main/java/org/apache/ambari/server/view/configuration/PersistenceConfig.java
  11. 15 0
      ambari-server/src/main/java/org/apache/ambari/server/view/configuration/ViewConfig.java
  12. 603 0
      ambari-server/src/main/java/org/apache/ambari/server/view/persistence/DataStoreImpl.java
  13. 118 0
      ambari-server/src/main/java/org/apache/ambari/server/view/persistence/DataStoreModule.java
  14. 36 0
      ambari-server/src/main/java/org/apache/ambari/server/view/persistence/SchemaManagerFactory.java
  15. 3 0
      ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
  16. 3 0
      ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
  17. 5 1
      ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
  18. 11 0
      ambari-server/src/main/resources/META-INF/persistence.xml
  19. 113 0
      ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewEntityEntityTest.java
  20. 10 0
      ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewEntityTest.java
  21. 71 0
      ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog160Test.java
  22. 79 0
      ambari-server/src/test/java/org/apache/ambari/server/view/configuration/EntityConfigTest.java
  23. 83 0
      ambari-server/src/test/java/org/apache/ambari/server/view/configuration/PersistenceConfigTest.java
  24. 447 0
      ambari-server/src/test/java/org/apache/ambari/server/view/persistence/DataStoreImplTest.java
  25. 75 0
      ambari-views/src/main/java/org/apache/ambari/view/DataStore.java
  26. 44 0
      ambari-views/src/main/java/org/apache/ambari/view/PersistenceException.java
  27. 7 0
      ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java

+ 72 - 50
ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java

@@ -102,6 +102,9 @@ public class ControllerModule extends AbstractModule {
   private boolean dbInitNeeded;
   private final Gson prettyGson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
 
+
+  // ----- Constructors ------------------------------------------------------
+
   public ControllerModule() throws Exception {
     configuration = new Configuration();
     hostsMap = new HostsMap(configuration);
@@ -112,6 +115,48 @@ public class ControllerModule extends AbstractModule {
     hostsMap = new HostsMap(configuration);
   }
 
+
+  // ----- ControllerModule --------------------------------------------------
+
+  /**
+   * Get the common persistence related configuration properties.
+   *
+   * @return the configuration properties
+   */
+  public static Properties getPersistenceProperties(Configuration configuration) {
+    Properties properties = new Properties();
+
+    // custom jdbc properties
+    Map<String, String> custom = configuration.getDatabaseCustomProperties();
+
+    if (0 != custom.size()) {
+      for (Entry<String, String> entry : custom.entrySet()) {
+        properties.setProperty("eclipselink.jdbc.property." + entry.getKey(),
+            entry.getValue());
+      }
+    }
+
+    switch (configuration.getPersistenceType()) {
+      case IN_MEMORY:
+        properties.setProperty(JDBC_URL, Configuration.JDBC_IN_MEMORY_URL);
+        properties.setProperty(JDBC_DRIVER, Configuration.JDBC_IN_MEMROY_DRIVER);
+        properties.setProperty(DDL_GENERATION, DROP_AND_CREATE);
+        properties.setProperty(THROW_EXCEPTIONS, "true");
+      case REMOTE:
+        properties.setProperty(JDBC_URL, configuration.getDatabaseUrl());
+        properties.setProperty(JDBC_DRIVER, configuration.getDatabaseDriver());
+        break;
+      case LOCAL:
+        properties.setProperty(JDBC_URL, configuration.getLocalDatabaseUrl());
+        properties.setProperty(JDBC_DRIVER, Configuration.JDBC_LOCAL_DRIVER);
+        break;
+    }
+    return properties;
+  }
+
+
+  // ----- AbstractModule ----------------------------------------------------
+
   @Override
   protected void configure() {
     installFactories();
@@ -154,64 +199,41 @@ public class ControllerModule extends AbstractModule {
     requestStaticInjection(ExecutionCommandWrapper.class);
   }
 
+
+  // ----- helper methods ----------------------------------------------------
+
   private PersistModule buildJpaPersistModule() {
     PersistenceType persistenceType = configuration.getPersistenceType();
     AmbariJpaPersistModule jpaPersistModule = new AmbariJpaPersistModule(Configuration.JDBC_UNIT_NAME);
 
-    Properties properties = new Properties();
-
-    // custom jdbc properties
-    Map<String, String> custom = configuration.getDatabaseCustomProperties();
-
-    if (0 != custom.size()) {
-      for (Entry<String, String> entry : custom.entrySet()) {
-        properties.setProperty("eclipselink.jdbc.property." + entry.getKey(),
-            entry.getValue());
+    Properties persistenceProperties = getPersistenceProperties(configuration);
+
+    if (!persistenceType.equals(PersistenceType.IN_MEMORY)) {
+      persistenceProperties.setProperty(JDBC_USER, configuration.getDatabaseUser());
+      persistenceProperties.setProperty(JDBC_PASSWORD, configuration.getDatabasePassword());
+
+      switch (configuration.getJPATableGenerationStrategy()) {
+        case CREATE:
+          persistenceProperties.setProperty(DDL_GENERATION, CREATE_ONLY);
+          dbInitNeeded = true;
+          break;
+        case DROP_AND_CREATE:
+          persistenceProperties.setProperty(DDL_GENERATION, DROP_AND_CREATE);
+          dbInitNeeded = true;
+          break;
+        case CREATE_OR_EXTEND:
+          persistenceProperties.setProperty(DDL_GENERATION, CREATE_OR_EXTEND);
+          break;
+        default:
+          break;
       }
-    }
-
-    switch (persistenceType) {
-      case IN_MEMORY:
-        properties.setProperty(JDBC_URL, Configuration.JDBC_IN_MEMORY_URL);
-        properties.setProperty(JDBC_DRIVER, Configuration.JDBC_IN_MEMROY_DRIVER);
-        properties.setProperty(DDL_GENERATION, DROP_AND_CREATE);
-        properties.setProperty(THROW_EXCEPTIONS, "true");
-        jpaPersistModule.properties(properties);
-        return jpaPersistModule;
-      case REMOTE:
-        properties.setProperty(JDBC_URL, configuration.getDatabaseUrl());
-        properties.setProperty(JDBC_DRIVER, configuration.getDatabaseDriver());
-        break;
-      case LOCAL:
-        properties.setProperty(JDBC_URL, configuration.getLocalDatabaseUrl());
-        properties.setProperty(JDBC_DRIVER, Configuration.JDBC_LOCAL_DRIVER);
-        break;
-    }
-
-    properties.setProperty(JDBC_USER, configuration.getDatabaseUser());
-    properties.setProperty(JDBC_PASSWORD, configuration.getDatabasePassword());
 
-    switch (configuration.getJPATableGenerationStrategy()) {
-      case CREATE:
-        properties.setProperty(DDL_GENERATION, CREATE_ONLY);
-        dbInitNeeded = true;
-        break;
-      case DROP_AND_CREATE:
-        properties.setProperty(DDL_GENERATION, DROP_AND_CREATE);
-        dbInitNeeded = true;
-        break;
-      case CREATE_OR_EXTEND:
-        properties.setProperty(DDL_GENERATION, CREATE_OR_EXTEND);
-        break;
-      default:
-        break;
+      persistenceProperties.setProperty(DDL_GENERATION_MODE, DDL_BOTH_GENERATION);
+      persistenceProperties.setProperty(CREATE_JDBC_DDL_FILE, "DDL-create.jdbc");
+      persistenceProperties.setProperty(DROP_JDBC_DDL_FILE, "DDL-drop.jdbc");
     }
 
-    properties.setProperty(DDL_GENERATION_MODE, DDL_BOTH_GENERATION);
-    properties.setProperty(CREATE_JDBC_DDL_FILE, "DDL-create.jdbc");
-    properties.setProperty(DROP_JDBC_DDL_FILE, "DDL-drop.jdbc");
-
-    jpaPersistModule.properties(properties);
+    jpaPersistModule.properties(persistenceProperties);
 
     return jpaPersistModule;
   }

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ViewDAO.java

@@ -76,7 +76,7 @@ public class ViewDAO {
   /**
    * Make an instance managed and persistent.
    *
-   * @param ViewEntity  entity to persist
+   * @param ViewEntity  entity to store
    */
   @Transactional
   public void create(ViewEntity ViewEntity) {

+ 10 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java

@@ -89,7 +89,7 @@ public class ViewEntity {
   @OneToMany(cascade = CascadeType.ALL, mappedBy = "view")
   private Collection<ViewResourceEntity> resources = new HashSet<ViewResourceEntity>();
 
-  /**
+   /**
    * The list of view instances.
    */
   @OneToMany(cascade = CascadeType.ALL, mappedBy = "view")
@@ -355,6 +355,15 @@ public class ViewEntity {
     return ambariConfiguration.getProperty(key);
   }
 
+  /**
+   * Get the Ambari configuration.
+   *
+   * @return the Ambari configuration
+   */
+  public Configuration getAmbariConfiguration() {
+    return ambariConfiguration;
+  }
+
   /**
    * Get a resource name qualified by the associated view name.
    *

+ 187 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntityEntity.java

@@ -0,0 +1,187 @@
+/**
+ * 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.entities;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinColumns;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.TableGenerator;
+
+/**
+ * Represents an entity of a View.
+ */
+@Table(name = "viewentity")
+@Entity
+@TableGenerator(name = "viewentity_id_generator",
+    table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "value"
+    , pkColumnValue = "viewentity_id_seq"
+    , initialValue = 1
+    , allocationSize = 50
+)public class ViewEntityEntity {
+
+  @Column(name = "id")
+  @Id
+  @GeneratedValue(strategy = GenerationType.TABLE, generator = "viewentity_id_generator")
+  private Long id;
+
+  /**
+   * The view name.
+   */
+  @Column(name = "view_name", nullable = false, insertable = false, updatable = false)
+  private String viewName;
+
+  @Column(name = "view_instance_name", nullable = false, insertable = false, updatable = false)
+  private String viewInstanceName;
+
+  /**
+   * The entity class name.
+   */
+  @Column(name = "class_name", nullable = false)
+  @Basic
+  private String className;
+
+  /**
+   * The id property of the entity.
+   */
+  @Column(name = "id_property")
+  @Basic
+  private String idProperty;
+
+
+  @ManyToOne
+  @JoinColumns({
+      @JoinColumn(name = "view_name", referencedColumnName = "view_name", nullable = false),
+      @JoinColumn(name = "view_instance_name", referencedColumnName = "name", nullable = false)
+  })
+  private ViewInstanceEntity viewInstance;
+
+
+  // ----- ViewEntityEntity ------------------------------------------------
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  /**
+   * Get the view name.
+   *
+   * @return the view name
+   */
+  public String getViewName() {
+    return viewName;
+  }
+
+  /**
+   * Set the view name
+   *
+   * @param viewName  the view name
+   */
+  public void setViewName(String viewName) {
+    this.viewName = viewName;
+  }
+
+  public String getViewInstanceName() {
+    return viewInstanceName;
+  }
+
+  public void setViewInstanceName(String viewInstanceName) {
+    this.viewInstanceName = viewInstanceName;
+  }
+
+  /**
+   * Get the entity class name.
+   *
+   * @return the entity class name
+   */
+  public String getClassName() {
+    return className;
+  }
+
+  /**
+   * Set the entity class name.
+   *
+   * @param name  the entity class name
+   */
+  public void setClassName(String name) {
+    this.className = name;
+  }
+
+  /**
+   * Get the id property.
+   *
+   * @return the id property
+   */
+  public String getIdProperty() {
+    return idProperty;
+  }
+
+  /**
+   * Set the id property.
+   *
+   * @param idProperty  the id property
+   */
+  public void setIdProperty(String idProperty) {
+    this.idProperty = idProperty;
+  }
+
+  public ViewInstanceEntity getViewInstance() {
+    return viewInstance;
+  }
+
+  public void setViewInstance(ViewInstanceEntity viewInstance) {
+    this.viewInstance = viewInstance;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    ViewEntityEntity that = (ViewEntityEntity) o;
+
+    if (!className.equals(that.className)) return false;
+    if (id != null ? !id.equals(that.id) : that.id != null) return false;
+    if (idProperty != null ? !idProperty.equals(that.idProperty) : that.idProperty != null) return false;
+    if (!viewInstanceName.equals(that.viewInstanceName)) return false;
+    if (!viewName.equals(that.viewName)) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = id != null ? id.hashCode() : 0;
+    result = 31 * result + viewName.hashCode();
+    result = 31 * result + viewInstanceName.hashCode();
+    result = 31 * result + className.hashCode();
+    result = 31 * result + (idProperty != null ? idProperty.hashCode() : 0);
+    return result;
+  }
+}

+ 24 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java

@@ -74,6 +74,12 @@ public class ViewInstanceEntity {
   @OneToMany(cascade = CascadeType.ALL, mappedBy = "viewInstance")
   private Collection<ViewInstanceDataEntity> data = new HashSet<ViewInstanceDataEntity>();
 
+  /**
+   * The list of view entities.
+   */
+  @OneToMany(cascade = CascadeType.ALL, mappedBy = "viewInstance")
+  private Collection<ViewEntityEntity> entities = new HashSet<ViewEntityEntity>();
+
   @ManyToOne
   @JoinColumn(name = "view_name", referencedColumnName = "view_name", nullable = false)
   private ViewEntity view;
@@ -269,6 +275,24 @@ public class ViewInstanceEntity {
     this.data = data;
   }
 
+  /**
+   * Get the view entities.
+   *
+   * @return the view entities
+   */
+  public Collection<ViewEntityEntity> getEntities() {
+    return entities;
+  }
+
+  /**
+   * Set the view entities.
+   *
+   * @param entities  the view entities
+   */
+  public void setEntities(Collection<ViewEntityEntity> entities) {
+    this.entities = entities;
+  }
+
   /**
    * Get the view instance application data.
    *

+ 24 - 0
ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog160.java

@@ -21,6 +21,7 @@ package org.apache.ambari.server.upgrade;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.orm.DBAccessor;
 
 import java.sql.SQLException;
@@ -56,6 +57,16 @@ public class UpgradeCatalog160 extends AbstractUpgradeCatalog {
     dbAccessor.createTable("hostgroup_configuration", columns, "blueprint_name",
         "hostgroup_name", "type_name");
 
+    // View entity
+    columns = new ArrayList<DBAccessor.DBColumnInfo>();
+    columns.add(new DBAccessor.DBColumnInfo("id", Long.class, null, null, false));
+    columns.add(new DBAccessor.DBColumnInfo("view_name", String.class, 255, null, false));
+    columns.add(new DBAccessor.DBColumnInfo("view_instance_name", String.class, 255, null, false));
+    columns.add(new DBAccessor.DBColumnInfo("class_name", String.class, 255, null, false));
+    columns.add(new DBAccessor.DBColumnInfo("id_property", String.class, 255, null, true));
+
+    dbAccessor.createTable("viewentity", columns, "id");
+
     //=========================================================================
     // Add columns
     dbAccessor.addColumn("hostcomponentdesiredstate",
@@ -67,6 +78,9 @@ public class UpgradeCatalog160 extends AbstractUpgradeCatalog {
         "blueprint_name", "hostgroup", "blueprint_name", true);
     dbAccessor.addFKConstraint("hostgroup_configuration", "FK_hg_config_hostgroup_name",
         "hostgroup_name", "hostgroup", "name", true);
+    dbAccessor.addFKConstraint("viewentity", "FK_viewentity_view_name",
+        new String[]{"view_name", "view_instance_name"}, "viewinstance", new String[]{"view_name", "name"}, true);
+
   }
 
 
@@ -74,6 +88,16 @@ public class UpgradeCatalog160 extends AbstractUpgradeCatalog {
 
   @Override
   protected void executeDMLUpdates() throws AmbariException, SQLException {
+    String dbType = getDbType();
+
+    //add new sequences for view entity
+    String valueColumnName = "\"value\"";
+    if (Configuration.ORACLE_DB_NAME.equals(dbType) || Configuration.MYSQL_DB_NAME.equals(dbType)) {
+      valueColumnName = "value";
+    }
+
+    dbAccessor.executeQuery("INSERT INTO ambari_sequences(sequence_name, " + valueColumnName + ") " +
+        "VALUES('viewentity_id_seq', 0)", true);
   }
 
   @Override

+ 19 - 0
ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java

@@ -18,8 +18,13 @@
 
 package org.apache.ambari.server.view;
 
+import com.google.inject.Guice;
+import com.google.inject.Injector;
 import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
 import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
+import org.apache.ambari.server.view.persistence.DataStoreImpl;
+import org.apache.ambari.server.view.persistence.DataStoreModule;
+import org.apache.ambari.view.DataStore;
 import org.apache.ambari.view.ResourceProvider;
 import org.apache.ambari.view.URLStreamProvider;
 import org.apache.ambari.view.ViewContext;
@@ -55,6 +60,11 @@ public class ViewContextImpl implements ViewContext {
    */
   private final URLStreamProvider streamProvider;
 
+  /**
+   * The data store.
+   */
+  private DataStore dataStore = null;
+
 
   // ---- Constructors -------------------------------------------------------
 
@@ -139,6 +149,15 @@ public class ViewContextImpl implements ViewContext {
     return streamProvider;
   }
 
+  @Override
+  public synchronized DataStore getDataStore() {
+    if (dataStore == null) {
+      Injector injector = Guice.createInjector(new DataStoreModule(viewInstanceEntity));
+      dataStore = injector.getInstance(DataStoreImpl.class);
+    }
+    return dataStore;
+  }
+
 
   // ----- Inner class : ViewURLStreamProvider -------------------------------
 

+ 30 - 7
ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java

@@ -32,10 +32,12 @@ 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.ViewEntityEntity;
 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;
+import org.apache.ambari.server.view.configuration.EntityConfig;
 import org.apache.ambari.server.view.configuration.InstanceConfig;
 import org.apache.ambari.server.view.configuration.ParameterConfig;
 import org.apache.ambari.server.view.configuration.PropertyConfig;
@@ -277,7 +279,7 @@ public class ViewRegistry {
                 viewInstanceDefinition.putProperty(propertyConfig.getKey(), propertyConfig.getValue());
               }
 
-              _installViewInstance(viewDefinition, viewInstanceDefinition);
+              installViewInstance(viewDefinition, viewInstanceDefinition);
               instanceDefinitions.add(viewInstanceDefinition);
             }
           } catch (Exception e) {
@@ -313,7 +315,7 @@ public class ViewRegistry {
         }
         instanceDAO.create(instanceEntity);
         try {
-          _installViewInstance(viewEntity, instanceEntity);
+          installViewInstance(viewEntity, instanceEntity);
         } catch (ClassNotFoundException e) {
           LOG.error("Caught exception installing view instance.", e);
         }
@@ -485,8 +487,8 @@ public class ViewRegistry {
   }
 
   // install a view instance definition
-  private void _installViewInstance(ViewEntity viewDefinition,
-                                           ViewInstanceEntity viewInstanceDefinition)
+  private void installViewInstance(ViewEntity viewDefinition,
+                                   ViewInstanceEntity viewInstanceDefinition)
       throws ClassNotFoundException {
 
     ViewContext viewInstanceContext = new ViewContextImpl(viewInstanceDefinition, this);
@@ -518,10 +520,32 @@ public class ViewRegistry {
       }
     }
 
+    setPersistenceEntities(viewInstanceDefinition);
+
     viewDefinition.addInstanceDefinition(viewInstanceDefinition);
     addInstanceDefinition(viewDefinition, viewInstanceDefinition);
   }
 
+  // Set the entities defined in the view persistence element for the given view instance
+  private static void setPersistenceEntities(ViewInstanceEntity viewInstanceDefinition) {
+    ViewEntity viewDefinition = viewInstanceDefinition.getViewEntity();
+    Collection<ViewEntityEntity> entities = new HashSet<ViewEntityEntity>();
+
+    ViewConfig viewConfig = viewDefinition.getConfiguration();
+    for (EntityConfig entityConfiguration : viewConfig.getPersistence().getEntities()) {
+      ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
+
+      viewEntityEntity.setViewName(viewDefinition.getName());
+      viewEntityEntity.setViewInstanceName(viewInstanceDefinition.getName());
+      viewEntityEntity.setClassName(entityConfiguration.getClassName());
+      viewEntityEntity.setIdProperty(entityConfiguration.getIdProperty());
+      viewEntityEntity.setViewInstance(viewInstanceDefinition);
+
+      entities.add(viewEntityEntity);
+    }
+    viewInstanceDefinition.setEntities(entities);
+  }
+
   // get the given service class from the given class loader; inject a handler and context
   private static <T> T getService(Class<T> clazz,
                                   final ViewResourceHandler viewResourceHandler,
@@ -561,8 +585,6 @@ public class ViewRegistry {
     for (ViewEntity viewEntity : viewDAO.findAll()) {
       String name = viewEntity.getName();
       if (!ViewRegistry.getInstance().viewDefinitions.containsKey(name)) {
-
-        System.out.println("removing view " + name);
         viewDAO.remove(viewEntity);
       } else {
         persistedViews.add(name);
@@ -573,12 +595,13 @@ public class ViewRegistry {
           ViewInstanceEntity instanceEntity = viewDefinition.getInstanceDefinition(viewInstanceEntity.getName());
           if (instanceEntity == null) {
             viewInstanceEntity.setViewEntity(viewDefinition);
-            _installViewInstance(viewDefinition, viewInstanceEntity);
+            installViewInstance(viewDefinition, viewInstanceEntity);
             instanceDefinitions.add(viewInstanceEntity);
           } else {
             // apply overrides to the in-memory view instance entities
             instanceEntity.setData(viewInstanceEntity.getData());
             instanceEntity.setProperties(viewInstanceEntity.getProperties());
+            instanceEntity.setEntities(viewInstanceEntity.getEntities());
           }
         }
       }

+ 43 - 0
ambari-server/src/main/java/org/apache/ambari/server/view/configuration/EntityConfig.java

@@ -0,0 +1,43 @@
+/**
+ * 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.view.configuration;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * View persistence entity configuration.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+public class EntityConfig {
+
+  @XmlElement(name="class")
+  private String className;
+
+  @XmlElement(name="id-property")
+  private String idProperty;
+
+  public String getClassName() {
+    return className;
+  }
+
+  public String getIdProperty() {
+    return idProperty;
+  }
+}

+ 45 - 0
ambari-server/src/main/java/org/apache/ambari/server/view/configuration/PersistenceConfig.java

@@ -0,0 +1,45 @@
+/**
+ * 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.view.configuration;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * View persistence configuration.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+public class PersistenceConfig {
+  /**
+   * The persistence entities.
+   */
+  @XmlElement(name="entity")
+  private List<EntityConfig> entities;
+
+  /**
+   * Get the entity configurations.
+   *
+   * @return the entity configurations
+   */
+  public List<EntityConfig> getEntities() {
+    return entities == null ? Collections.<EntityConfig>emptyList() : entities;
+  }
+}

+ 15 - 0
ambari-server/src/main/java/org/apache/ambari/server/view/configuration/ViewConfig.java

@@ -67,6 +67,12 @@ public class ViewConfig {
   @XmlElement(name="instance")
   private List<InstanceConfig> instances;
 
+  /**
+   * The view persistence configuration.
+   */
+  @XmlElement(name="persistence")
+  private PersistenceConfig persistence;
+
   /**
    * Get the unique name.
    *
@@ -120,4 +126,13 @@ public class ViewConfig {
   public List<InstanceConfig> getInstances() {
     return instances == null ? Collections.<InstanceConfig>emptyList() : instances;
   }
+
+  /**
+   * Get the view persistence configuration.
+   *
+   * @return the view persistence configuration
+   */
+  public PersistenceConfig getPersistence() {
+    return persistence;
+  }
 }

+ 603 - 0
ambari-server/src/main/java/org/apache/ambari/server/view/persistence/DataStoreImpl.java

@@ -0,0 +1,603 @@
+/**
+ * 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.view.persistence;
+
+import org.apache.ambari.server.orm.entities.ViewEntityEntity;
+import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
+import org.apache.ambari.view.DataStore;
+import org.apache.ambari.view.PersistenceException;
+import org.eclipse.persistence.dynamic.DynamicClassLoader;
+import org.eclipse.persistence.dynamic.DynamicEntity;
+import org.eclipse.persistence.dynamic.DynamicType;
+import org.eclipse.persistence.jpa.dynamic.JPADynamicHelper;
+import org.eclipse.persistence.jpa.dynamic.JPADynamicTypeBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Query;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+
+/**
+ * A data store implementation that uses dynamic JPA entities to
+ * persist view entities to the Ambari database.
+ */
+public class DataStoreImpl implements DataStore {
+
+  /**
+   * JPA entity manager
+   */
+  @Inject
+  EntityManagerFactory entityManagerFactory;
+
+  /**
+   * The dynamic class loader.
+   */
+  @Inject
+  DynamicClassLoader classLoader;
+
+  /**
+   * The dynamic helper.
+   */
+  @Inject
+  JPADynamicHelper jpaDynamicHelper;
+
+  /**
+   * A factory to get a schema manager.
+   */
+  @Inject
+  SchemaManagerFactory schemaManagerFactory;
+
+  /**
+   * The view instance.
+   */
+  @Inject
+  ViewInstanceEntity viewInstanceEntity;
+
+  /**
+   * Map of dynamic entity names keyed by view entity class.
+   */
+  private final Map<Class, String> entityClassMap = new HashMap<Class, String>();
+
+  /**
+   * Map of entity primary key fields keyed by dynamic entity name.
+   */
+  private final Map<String, ViewEntityEntity> entityMap = new HashMap<String, ViewEntityEntity>();
+
+  /**
+   * Map of dynamic entity type builders keyed by dynamic entity name.
+   */
+  private final Map<String, JPADynamicTypeBuilder> typeBuilderMap = new HashMap<String, JPADynamicTypeBuilder>();
+
+  /**
+   * Indicates whether or not the data store has been initialized.
+   */
+  private volatile boolean initialized = false;
+
+  /**
+   * The logger.
+   */
+  protected final static Logger LOG = LoggerFactory.getLogger(DataStoreImpl.class);
+
+
+  // ----- DataStore ---------------------------------------------------------
+
+  @Override
+  public void store(Object entity) throws PersistenceException {
+    checkInitialize();
+
+    EntityManager em = getEntityManager();
+    try {
+      em.getTransaction().begin();
+      try {
+        persistEntity(entity, em, new HashSet<DynamicEntity>());
+        em.getTransaction().commit();
+      } catch (Exception e) {
+        if (em.getTransaction()!= null) {
+          em.getTransaction().rollback();
+        }
+        throwPersistenceException("Caught exception trying to store view entity " + entity, e);
+      }
+    } finally {
+      em.close();
+    }
+  }
+
+  @Override
+  public void remove(Object entity) throws PersistenceException {
+    checkInitialize();
+
+    EntityManager em = getEntityManager();
+    try {
+      Class       clazz = entity.getClass();
+      String      id    = getIdFieldName(clazz);
+      DynamicType type  = getDynamicEntityType(clazz);
+
+      if (type != null) {
+        try {
+          Map<String, Object> properties    = getEntityProperties(entity);
+          DynamicEntity       dynamicEntity = em.getReference(type.getJavaClass(), properties.get(id));
+
+          if (dynamicEntity != null) {
+            em.getTransaction().begin();
+            try {
+              em.remove(dynamicEntity);
+              em.getTransaction().commit();
+            } catch (Exception e) {
+              if (em.getTransaction()!= null) {
+                em.getTransaction().rollback();
+              }
+              throwPersistenceException("Caught exception trying to remove view entity " + entity, e);
+            }
+          }
+
+        } catch (Exception e) {
+          throwPersistenceException("Caught exception trying to remove view entity " + entity, e);
+        }
+      }
+    } finally {
+      em.close();
+    }
+  }
+
+  @Override
+  public <T> T find(Class<T> clazz, Object primaryKey) throws PersistenceException {
+    checkInitialize();
+
+    EntityManager em = getEntityManager();
+    try {
+      DynamicEntity dynamicEntity = null;
+      DynamicType   type          = getDynamicEntityType(clazz);
+
+      if (type != null) {
+        dynamicEntity = em.find(type.getJavaClass(), primaryKey);
+      }
+      return dynamicEntity == null ? null : toEntity(clazz, type, dynamicEntity);
+    } catch (Exception e) {
+      throwPersistenceException("Caught exception trying to find " +
+          clazz.getName() + " where key=" + primaryKey, e);
+    } finally {
+      em.close();
+    }
+    return null;
+  }
+
+  @Override
+  public <T> Collection<T> findAll(Class<T> clazz, String whereClause) throws PersistenceException {
+    checkInitialize();
+
+    EntityManager em = getEntityManager();
+    try {
+      Collection<T> resources = new HashSet<T>();
+      DynamicType   type      = getDynamicEntityType(clazz);
+
+      if (type != null) {
+        try {
+          Query query = em.createQuery(getSelectStatement(clazz, whereClause));
+
+          List dynamicEntities = query.getResultList();
+
+          for (Object dynamicEntity : dynamicEntities) {
+            resources.add(toEntity(clazz, type, (DynamicEntity) dynamicEntity));
+          }
+        } catch (Exception e) {
+          throwPersistenceException("Caught exception trying to find " +
+              clazz.getName() + " where " + whereClause, e);
+        }
+      }
+      return resources;
+    } finally {
+      em.close();
+    }
+  }
+
+
+  // ----- helper methods ----------------------------------------------------
+
+  // lazy initialize the data store
+  private void checkInitialize() throws PersistenceException {
+    if (!initialized) {
+      synchronized (this) {
+        if (!initialized) {
+          initialized = true;
+          try {
+            for (ViewEntityEntity viewEntityEntity : viewInstanceEntity.getEntities()){
+
+              String className = viewEntityEntity.getClassName();
+              Class  clazz     = classLoader.loadClass(className);
+              String name      = getEntityName(viewEntityEntity);
+
+              entityMap.put(name, viewEntityEntity);
+              entityClassMap.put(clazz, name);
+            }
+
+            configureTypes(jpaDynamicHelper, classLoader);
+
+          } catch (Exception e) {
+            throwPersistenceException("Can't initialize data store for view " +
+                viewInstanceEntity.getViewName() + "." + viewInstanceEntity.getName(), e);
+          }
+        }
+      }
+    }
+  }
+
+  // configure the dynamic types for the entities defined for the associated view
+  private void configureTypes(JPADynamicHelper helper, DynamicClassLoader dcl)
+      throws IntrospectionException, PersistenceException, NoSuchFieldException {
+
+    // create a dynamic type builder for each declared view entity type
+    for (Map.Entry<Class, String> entry: entityClassMap.entrySet()) {
+      String entityName = entry.getValue();
+      Class<?> javaType = dcl.createDynamicClass(entityName);
+      String tableName  = getTableName(entityMap.get(entityName));
+
+      JPADynamicTypeBuilder typeBuilder = new JPADynamicTypeBuilder(javaType, null, tableName);
+      typeBuilderMap.put(entityName, typeBuilder);
+    }
+
+    // add the direct mapped properties to the dynamic type builders
+    for (Map.Entry<Class, String> entry: entityClassMap.entrySet()) {
+
+      Class                 clazz       = entry.getKey();
+      String                entityName  = entry.getValue();
+      JPADynamicTypeBuilder typeBuilder = typeBuilderMap.get(entityName);
+
+      Map<String, PropertyDescriptor> descriptorMap = getDescriptorMap(clazz);
+
+      for (Map.Entry<String, PropertyDescriptor> descriptorEntry : descriptorMap.entrySet()) {
+        String             propertyName = descriptorEntry.getKey();
+        PropertyDescriptor descriptor   = descriptorEntry.getValue();
+
+        if (propertyName.equals(entityMap.get(entityName).getIdProperty())) {
+          typeBuilder.setPrimaryKeyFields(propertyName);
+        }
+
+        Class<?> propertyType = descriptor.getPropertyType();
+
+        if (isDirectMappingType(propertyType)) {
+          typeBuilder.addDirectMapping(propertyName, propertyType, propertyName);
+        }
+      }
+    }
+
+    // add the relationships to the dynamic type builders
+    for (Map.Entry<Class, String> entry: entityClassMap.entrySet()) {
+
+      Class                 clazz       = entry.getKey();
+      String                entityName  = entry.getValue();
+      JPADynamicTypeBuilder typeBuilder = typeBuilderMap.get(entityName);
+
+      Map<String, PropertyDescriptor> descriptorMap = getDescriptorMap(clazz);
+
+      for (Map.Entry<String, PropertyDescriptor> descriptorEntry : descriptorMap.entrySet()) {
+        String propertyName = descriptorEntry.getKey();
+        PropertyDescriptor descriptor = descriptorEntry.getValue();
+        if (propertyName.equals(entityMap.get(entityName).getIdProperty())) {
+          typeBuilder.setPrimaryKeyFields(propertyName);
+        }
+
+        Class<?> propertyType = descriptor.getPropertyType();
+        String refEntityName = entityClassMap.get(propertyType);
+
+        if (refEntityName == null) {
+          if (Collection.class.isAssignableFrom(propertyType)) {
+
+            String tableName = getTableName(entityMap.get(entityName)) + "_" + propertyName;
+
+            Class<?> parameterizedTypeClass = getParameterizedTypeClass(clazz, propertyName);
+
+            refEntityName = entityClassMap.get(parameterizedTypeClass);
+
+            if (refEntityName == null) {
+              typeBuilder.addDirectCollectionMapping(propertyName, tableName, propertyName,
+                  parameterizedTypeClass, entityMap.get(entityName).getIdProperty());
+            } else {
+              DynamicType refType = typeBuilderMap.get(refEntityName).getType();
+              typeBuilder.addManyToManyMapping(propertyName, refType, tableName);
+            }
+          }
+        } else {
+          DynamicType refType = typeBuilderMap.get(refEntityName).getType();
+          typeBuilder.addOneToOneMapping(propertyName, refType, propertyName);
+        }
+      }
+    }
+
+    DynamicType[] types = new DynamicType[ typeBuilderMap.size()];
+    int i = typeBuilderMap.size() - 1;
+    for (JPADynamicTypeBuilder typeBuilder : typeBuilderMap.values()) {
+      types[i--] = typeBuilder.getType();
+    }
+    helper.addTypes(true, true, types);
+
+    // extend the tables if needed (i.e. attribute added to the view entity)
+    schemaManagerFactory.getSchemaManager(helper.getSession()).extendDefaultTables(true);
+  }
+
+  // persist the given view entity to the entity manager and
+  // return the corresponding dynamic entity
+  private DynamicEntity persistEntity(Object entity, EntityManager em, Set<DynamicEntity> persistSet)
+      throws PersistenceException, IntrospectionException, InvocationTargetException,
+      IllegalAccessException, NoSuchFieldException {
+    DynamicEntity dynamicEntity  = null;
+    Class         clazz          = entity.getClass();
+    String        id             = getIdFieldName(clazz);
+
+    Map<String, Object> properties = getEntityProperties(entity);
+
+    DynamicType type = getDynamicEntityType(clazz);
+
+    if (type != null) {
+      dynamicEntity  = em.find(type.getJavaClass(), properties.get(id));
+
+      boolean create = dynamicEntity == null;
+
+      if (create) {
+        dynamicEntity = type.newDynamicEntity();
+      }
+
+      // has this entity already been accounted for?
+      if (persistSet.contains(dynamicEntity)) {
+        return dynamicEntity;
+      }
+
+      persistSet.add(dynamicEntity);
+
+      for (String propertyName : type.getPropertiesNames()) {
+        if (properties.containsKey(propertyName)) {
+          Object value = properties.get(propertyName);
+          if (value != null) {
+            Class<?> valueClass = value.getClass();
+
+            if (Collection.class.isAssignableFrom(valueClass)) {
+
+              Class<?>           typeClass  = getParameterizedTypeClass(clazz, propertyName);
+              Collection<Object> collection = dynamicEntity.get(propertyName);
+
+              collection.clear();
+
+              for (Object collectionValue : (Collection) value) {
+
+                if (getDynamicEntityType(typeClass)!= null ) {
+                  collectionValue = persistEntity(collectionValue, em, persistSet);
+                }
+                if (collectionValue != null) {
+                  collection.add(collectionValue);
+                }
+              }
+            } else {
+              if (getDynamicEntityType(valueClass)!= null ) {
+                value = persistEntity(value, em, persistSet);
+              }
+              if (value != null) {
+                dynamicEntity.set(propertyName, value);
+              }
+            }
+          }
+        }
+      }
+
+      if (create) {
+        em.persist(dynamicEntity);
+      }
+    }
+    return dynamicEntity;
+  }
+
+  // convert the given dynamic entity to a view entity
+  private <T> T toEntity(Class<T> clazz, DynamicType type, DynamicEntity entity)
+      throws IntrospectionException, InvocationTargetException,
+      IllegalAccessException, InstantiationException, NoSuchFieldException {
+    T resource = clazz.newInstance();
+
+    Map<String, Object> properties = new HashMap<String, Object>();
+
+    for (String propertyName : type.getPropertiesNames()) {
+      properties.put(propertyName, entity.get(propertyName));
+    }
+    setEntityProperties(resource, properties);
+
+    return resource;
+  }
+
+  // build a JPA select statement from the given view entity class and where clause
+  private <T> String getSelectStatement(Class<T> clazz, String whereClause)
+      throws IntrospectionException {
+    StringBuilder stringBuilder = new StringBuilder();
+    String        entityName    = entityClassMap.get(clazz);
+
+    stringBuilder.append("SELECT e FROM ").append(entityName).append(" e");
+    if (whereClause != null) {
+      stringBuilder.append(" WHERE");
+
+      Set<String>     propertyNames = getPropertyNames(clazz);
+      StringTokenizer tokenizer     = new StringTokenizer(whereClause, " \t\n\r\f+-*/=><()\"", true);
+      boolean         quoted        = false;
+
+      while (tokenizer.hasMoreElements()) {
+        String token = tokenizer.nextToken();
+
+        quoted = quoted ^ token.equals("\"");
+
+        if (propertyNames.contains(token) && !quoted) {
+          stringBuilder.append(" e.").append(token);
+        } else {
+          stringBuilder.append(token);
+        }
+      }
+    }
+    return stringBuilder.toString();
+  }
+
+  // get a map of properties from the given view entity
+  private Map<String, Object> getEntityProperties(Object entity)
+      throws IntrospectionException, InvocationTargetException, IllegalAccessException {
+    Map<String, Object> properties = new HashMap<String, Object>();
+
+    for (PropertyDescriptor pd : Introspector.getBeanInfo(entity.getClass()).getPropertyDescriptors()) {
+      String name       = pd.getName();
+      Method readMethod = pd.getReadMethod();
+      if (readMethod != null) {
+        properties.put(name, readMethod.invoke(entity));
+      }
+    }
+    return properties;
+  }
+
+  // set the properties on the given view entity from the given map of properties; convert all
+  // DynamicEntity values to their associated view entity types
+  private void setEntityProperties(Object entity, Map<String, Object> properties)
+      throws IntrospectionException, InvocationTargetException, IllegalAccessException,
+      InstantiationException, NoSuchFieldException {
+    for (PropertyDescriptor pd : Introspector.getBeanInfo(entity.getClass()).getPropertyDescriptors()) {
+      String name = pd.getName();
+      if (properties.containsKey(name)) {
+
+        Method writeMethod = pd.getWriteMethod();
+        if (writeMethod != null) {
+
+          Object value = properties.get(name);
+
+          if (value instanceof Collection) {
+            Set<Object> newCollection = new HashSet<Object>();
+
+            for (Object collectionValue: (Collection)value) {
+
+              if (collectionValue instanceof DynamicEntity) {
+
+                Class<?> clazz = entity.getClass();
+                Class<?> parameterizedTypeClass = getParameterizedTypeClass(clazz, pd.getName());
+
+                collectionValue = toEntity(parameterizedTypeClass,
+                    getDynamicEntityType(parameterizedTypeClass), (DynamicEntity) collectionValue);
+              }
+              if ( collectionValue != null) {
+                newCollection.add(collectionValue);
+              }
+            }
+            writeMethod.invoke(entity, newCollection);
+          } else {
+            if (value instanceof DynamicEntity) {
+
+              Class<?> clazz = pd.getPropertyType();
+
+              value = toEntity(clazz, getDynamicEntityType(clazz), (DynamicEntity) value);
+            }
+            if ( value != null) {
+              writeMethod.invoke(entity, value);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // determine whether or not a property of the given type should be a direct mapping in the dynamic entity
+  private boolean isDirectMappingType(Class<?> propertyType) {
+    return !Collection.class.isAssignableFrom(propertyType) && entityClassMap.get(propertyType) == null;
+  }
+
+  // get the dynamic entity type from the given view entity class
+  private DynamicType getDynamicEntityType(Class clazz) {
+    JPADynamicTypeBuilder builder = typeBuilderMap.get(entityClassMap.get(clazz));
+
+    return builder == null ? null : builder.getType();
+  }
+
+  // get the id field name for the given view entity class
+  private String getIdFieldName(Class clazz) throws PersistenceException {
+    if (entityClassMap.containsKey(clazz)){
+      String entityName = entityClassMap.get(clazz);
+      if (entityMap.containsKey(entityName)) {
+        return entityMap.get(entityName).getIdProperty();
+      }
+    }
+    throw new PersistenceException("The class " + clazz.getName() + "is not registered as an entity.");
+  }
+
+  // get a descriptor map for the given bean class
+  private static Map<String, PropertyDescriptor> getDescriptorMap(Class<?> clazz) throws IntrospectionException {
+    Map<String, PropertyDescriptor> descriptorMap = new HashMap<String, PropertyDescriptor>();
+
+    for (PropertyDescriptor pd : Introspector.getBeanInfo(clazz).getPropertyDescriptors()) {
+      String name = pd.getName();
+      if (pd.getReadMethod() != null && !name.equals("class")) {
+        descriptorMap.put(name, pd);
+      }
+    }
+    return descriptorMap;
+  }
+
+  // get the property names for the given view entity class
+  private static Set<String> getPropertyNames(Class clazz) throws IntrospectionException {
+    Set<String> propertyNames = new HashSet<String>();
+    for (PropertyDescriptor pd : Introspector.getBeanInfo(clazz).getPropertyDescriptors()) {
+      propertyNames.add(pd.getName());
+    }
+    return propertyNames;
+  }
+
+  // get the parameterized type class for the given field of the given class
+  private static Class<?> getParameterizedTypeClass(Class clazz, String fieldName) throws NoSuchFieldException {
+    Field field = clazz.getDeclaredField(fieldName);
+    ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
+    return (Class<?>) parameterizedType.getActualTypeArguments()[0];
+  }
+
+  // throw a new persistence exception and log the error
+  private static void throwPersistenceException(String msg, Exception e) throws PersistenceException {
+    LOG.error(msg, e);
+    throw new PersistenceException(msg, e);
+  }
+
+  // get a table name for the given view entity
+  private static String getTableName(ViewEntityEntity entity) {
+    return (getEntityName(entity)).toUpperCase();
+  }
+
+  // get a dynamic entity name for the given view entity
+  private static String getEntityName(ViewEntityEntity entity) {
+    String   className = entity.getClassName();
+    String[] parts     = className.split("\\.");
+
+    return parts[parts.length - 1] + entity.getId();
+  }
+
+  // get an entity manager
+  private EntityManager getEntityManager() {
+    return entityManagerFactory.createEntityManager();
+  }
+}

+ 118 - 0
ambari-server/src/main/java/org/apache/ambari/server/view/persistence/DataStoreModule.java

@@ -0,0 +1,118 @@
+/**
+ * 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.view.persistence;
+
+import com.google.inject.Binder;
+import com.google.inject.Module;
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.controller.ControllerModule;
+import org.apache.ambari.server.orm.PersistenceType;
+import org.apache.ambari.server.orm.entities.ViewEntity;
+import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
+import org.eclipse.persistence.config.PersistenceUnitProperties;
+import org.eclipse.persistence.dynamic.DynamicClassLoader;
+import org.eclipse.persistence.jpa.dynamic.JPADynamicHelper;
+import org.eclipse.persistence.sessions.DatabaseSession;
+import org.eclipse.persistence.tools.schemaframework.SchemaManager;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import java.util.Map;
+
+import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_PASSWORD;
+import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_USER;
+
+/**
+ * Module used for data store creation and injection.
+ */
+public class DataStoreModule implements Module, SchemaManagerFactory {
+
+  /**
+   * The view instance.
+   */
+  private final ViewInstanceEntity viewInstanceEntity;
+
+  /**
+   * The class loader.
+   */
+  private final DynamicClassLoader classLoader;
+
+  /**
+   * The entity manager factory.
+   */
+  private final EntityManagerFactory entityManagerFactory;
+
+  /**
+   * The dynamic JPA helper.
+   */
+  private final JPADynamicHelper jpaDynamicHelper;
+
+  /**
+   * View persistence unit name.
+   */
+  private static final String VIEWS_PERSISTENCE_UNIT_NAME = "ambari-views";
+
+
+  // ----- Constructors ------------------------------------------------------
+
+  public DataStoreModule(ViewInstanceEntity viewInstanceEntity) {
+    ViewEntity view = viewInstanceEntity.getViewEntity();
+
+    this.viewInstanceEntity   = viewInstanceEntity;
+    this.classLoader          = new DynamicClassLoader(view.getClassLoader());
+    this.entityManagerFactory = getEntityManagerFactory(view.getAmbariConfiguration());
+    this.jpaDynamicHelper     = new JPADynamicHelper(entityManagerFactory.createEntityManager());
+  }
+
+
+  // ----- Module ------------------------------------------------------------
+
+  @Override
+  public void configure(Binder binder) {
+    binder.bind(ViewInstanceEntity.class).toInstance(viewInstanceEntity);
+    binder.bind(DynamicClassLoader.class).toInstance(classLoader);
+    binder.bind(EntityManagerFactory.class).toInstance(entityManagerFactory);
+    binder.bind(JPADynamicHelper.class).toInstance(jpaDynamicHelper);
+    binder.bind(SchemaManagerFactory.class).toInstance(this);
+  }
+
+
+  // ----- SchemaManagerFactory ----------------------------------------------
+
+  @Override
+  public SchemaManager getSchemaManager(DatabaseSession session) {
+    return new SchemaManager(session);
+  }
+
+
+  // ----- helper methods ----------------------------------------------------
+
+  // get an entity manager factory for the given class loader and configuration
+  private EntityManagerFactory getEntityManagerFactory(Configuration configuration) {
+    Map<Object, Object> persistenceMap  = ControllerModule.getPersistenceProperties(configuration);
+
+    if (!configuration.getPersistenceType().equals(PersistenceType.IN_MEMORY)) {
+      persistenceMap.put(JDBC_USER, configuration.getDatabaseUser());
+      persistenceMap.put(JDBC_PASSWORD, configuration.getDatabasePassword());
+      persistenceMap.put(PersistenceUnitProperties.CLASSLOADER, classLoader);
+      persistenceMap.put(PersistenceUnitProperties.WEAVING, "static");
+    }
+    return Persistence.createEntityManagerFactory(VIEWS_PERSISTENCE_UNIT_NAME, persistenceMap);
+  }
+}

+ 36 - 0
ambari-server/src/main/java/org/apache/ambari/server/view/persistence/SchemaManagerFactory.java

@@ -0,0 +1,36 @@
+/**
+ * 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.view.persistence;
+
+import org.eclipse.persistence.sessions.DatabaseSession;
+import org.eclipse.persistence.tools.schemaframework.SchemaManager;
+
+/**
+ * Interface to get a SchemaManager
+ */
+public interface SchemaManagerFactory {
+  /**
+   * Get a SchemaManager for the given session.
+   *
+   * @param session  the session
+   *
+   * @return the schema manager
+   */
+  public SchemaManager getSchemaManager(DatabaseSession session);
+}

+ 3 - 0
ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql

@@ -67,6 +67,7 @@ CREATE TABLE viewinstance (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NO
 CREATE TABLE viewinstanceproperty (view_name VARCHAR(255) NOT NULL, view_instance_name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, value VARCHAR(2000) NOT NULL, PRIMARY KEY(view_name, view_instance_name, name));
 CREATE TABLE viewparameter (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255), required CHAR(1), PRIMARY KEY(view_name, name));
 CREATE TABLE viewresource (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, plural_name VARCHAR(255), id_property VARCHAR(255), subResource_names VARCHAR(255), provider VARCHAR(255), service VARCHAR(255), resource VARCHAR(255), PRIMARY KEY(view_name, name));
+CREATE TABLE viewentity (id BIGINT NOT NULL, view_name VARCHAR(255) NOT NULL, view_instance_name VARCHAR(255) NOT NULL, class_name VARCHAR(255) NOT NULL, id_property VARCHAR(255), PRIMARY KEY(id));
 
 ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, ldap_user);
 ALTER TABLE clusterconfig ADD CONSTRAINT FK_clusterconfig_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
@@ -110,6 +111,7 @@ ALTER TABLE viewresource ADD CONSTRAINT FK_viewres_view_name FOREIGN KEY (view_n
 ALTER TABLE viewinstance ADD CONSTRAINT FK_viewinst_view_name FOREIGN KEY (view_name) REFERENCES viewmain(view_name);
 ALTER TABLE viewinstanceproperty ADD CONSTRAINT FK_viewinstprop_view_name FOREIGN KEY (view_name, view_instance_name) REFERENCES viewinstance(view_name, name);
 ALTER TABLE viewinstancedata ADD CONSTRAINT FK_viewinstdata_view_name FOREIGN KEY (view_name, view_instance_name) REFERENCES viewinstance(view_name, name);
+ALTER TABLE viewentity ADD CONSTRAINT FK_viewentity_view_name FOREIGN KEY (view_name, view_instance_name) REFERENCES viewinstance(view_name, name);
 
 
 INSERT INTO ambari_sequences(sequence_name, value) values ('cluster_id_seq', 1);
@@ -118,6 +120,7 @@ INSERT INTO ambari_sequences(sequence_name, value) values ('user_id_seq', 2);
 INSERT INTO ambari_sequences(sequence_name, value) values ('configgroup_id_seq', 1);
 INSERT INTO ambari_sequences(sequence_name, value) values ('requestschedule_id_seq', 1);
 INSERT INTO ambari_sequences(sequence_name, value) values ('resourcefilter_id_seq', 1);
+INSERT INTO ambari_sequences(sequence_name, value) values ('viewentity_id_seq', 0);
 
 insert into roles(role_name)
   select 'admin'

+ 3 - 0
ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql

@@ -57,6 +57,7 @@ CREATE TABLE viewinstance (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NO
 CREATE TABLE viewinstanceproperty (view_name VARCHAR(255) NOT NULL, view_instance_name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, value VARCHAR(2000) NOT NULL, PRIMARY KEY(view_name, view_instance_name, name));
 CREATE TABLE viewparameter (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255), required CHAR(1), PRIMARY KEY(view_name, name));
 CREATE TABLE viewresource (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, plural_name VARCHAR(255), id_property VARCHAR(255), subResource_names VARCHAR(255), provider VARCHAR(255), service VARCHAR(255), "resource" VARCHAR(255), PRIMARY KEY(view_name, name));
+CREATE TABLE viewentity (id NUMBER(19) NOT NULL, view_name VARCHAR(255) NOT NULL, view_instance_name VARCHAR(255) NOT NULL, class_name VARCHAR(255) NOT NULL, id_property VARCHAR(255), PRIMARY KEY(id));
 
 ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, ldap_user);
 ALTER TABLE clusterconfig ADD CONSTRAINT FK_clusterconfig_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
@@ -100,6 +101,7 @@ ALTER TABLE viewresource ADD CONSTRAINT FK_viewres_view_name FOREIGN KEY (view_n
 ALTER TABLE viewinstance ADD CONSTRAINT FK_viewinst_view_name FOREIGN KEY (view_name) REFERENCES viewmain(view_name);
 ALTER TABLE viewinstanceproperty ADD CONSTRAINT FK_viewinstprop_view_name FOREIGN KEY (view_name, view_instance_name) REFERENCES viewinstance(view_name, name);
 ALTER TABLE viewinstancedata ADD CONSTRAINT FK_viewinstdata_view_name FOREIGN KEY (view_name, view_instance_name) REFERENCES viewinstance(view_name, name);
+ALTER TABLE viewentity ADD CONSTRAINT FK_viewentity_view_name FOREIGN KEY (view_name, view_instance_name) REFERENCES viewinstance(view_name, name);
 
 INSERT INTO ambari_sequences(sequence_name, value) values ('host_role_command_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, value) values ('user_id_seq', 1);
@@ -107,6 +109,7 @@ INSERT INTO ambari_sequences(sequence_name, value) values ('cluster_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, value) values ('configgroup_id_seq', 1);
 INSERT INTO ambari_sequences(sequence_name, value) values ('requestschedule_id_seq', 1);
 INSERT INTO ambari_sequences(sequence_name, value) values ('resourcefilter_id_seq', 1);
+INSERT INTO ambari_sequences(sequence_name, value) values ('viewentity_id_seq', 0);
 INSERT INTO metainfo("metainfo_key", "metainfo_value") values ('version', '${ambariVersion}');
 
 insert into Roles(role_name)

+ 5 - 1
ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql

@@ -92,6 +92,7 @@ CREATE TABLE ambari.viewinstance (view_name VARCHAR(255) NOT NULL, name VARCHAR(
 CREATE TABLE ambari.viewinstanceproperty (view_name VARCHAR(255) NOT NULL, view_instance_name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, value VARCHAR(2000) NOT NULL, PRIMARY KEY(view_name, view_instance_name, name));
 CREATE TABLE ambari.viewparameter (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255), required CHAR(1), PRIMARY KEY(view_name, name));
 CREATE TABLE ambari.viewresource (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, plural_name VARCHAR(255), id_property VARCHAR(255), subResource_names VARCHAR(255), provider VARCHAR(255), service VARCHAR(255), resource VARCHAR(255), PRIMARY KEY(view_name, name));
+CREATE TABLE ambari.viewentity (id BIGINT NOT NULL, view_name VARCHAR(255) NOT NULL, view_instance_name VARCHAR(255) NOT NULL, class_name VARCHAR(255) NOT NULL, id_property VARCHAR(255), PRIMARY KEY(id));
 
 --------altering tables by creating foreign keys----------
 ALTER TABLE ambari.clusterconfig ADD CONSTRAINT FK_clusterconfig_cluster_id FOREIGN KEY (cluster_id) REFERENCES ambari.clusters (cluster_id);
@@ -135,6 +136,7 @@ ALTER TABLE ambari.viewresource ADD CONSTRAINT FK_viewres_view_name FOREIGN KEY
 ALTER TABLE ambari.viewinstance ADD CONSTRAINT FK_viewinst_view_name FOREIGN KEY (view_name) REFERENCES viewmain(view_name);
 ALTER TABLE ambari.viewinstanceproperty ADD CONSTRAINT FK_viewinstprop_view_name FOREIGN KEY (view_name, view_instance_name) REFERENCES viewinstance(view_name, name);
 ALTER TABLE ambari.viewinstancedata ADD CONSTRAINT FK_viewinstdata_view_name FOREIGN KEY (view_name, view_instance_name) REFERENCES viewinstance(view_name, name);
+ALTER TABLE ambari.viewentity ADD CONSTRAINT FK_viewentity_view_name FOREIGN KEY (view_name, view_instance_name) REFERENCES viewinstance(view_name, name);
 
 
 ---------inserting some data-----------
@@ -150,7 +152,9 @@ BEGIN;
   union all
   select 'requestschedule_id_seq', 1
   union all
-  select 'resourcefilter_id_seq', 1;
+  select 'resourcefilter_id_seq', 1
+  union all
+  select 'viewentity_id_seq', 0;
 
   INSERT INTO ambari.Roles (role_name)
   SELECT 'admin'

+ 11 - 0
ambari-server/src/main/resources/META-INF/persistence.xml

@@ -52,6 +52,7 @@
     <class>org.apache.ambari.server.orm.entities.ViewInstancePropertyEntity</class>
     <class>org.apache.ambari.server.orm.entities.ViewParameterEntity</class>
     <class>org.apache.ambari.server.orm.entities.ViewResourceEntity</class>
+    <class>org.apache.ambari.server.orm.entities.ViewEntityEntity</class>
 
     <properties>
       <!--<property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost/ambari" />-->
@@ -64,4 +65,14 @@
     </properties>
   </persistence-unit>
 
+  <!-- persistence unit for view persistence : entities are added dynamically -->
+  <persistence-unit name="ambari-views" transaction-type="RESOURCE_LOCAL">
+    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
+    <exclude-unlisted-classes>false</exclude-unlisted-classes>
+    <properties>
+      <property name="eclipselink.cache.size.default" value="10000" />
+      <property name="eclipselink.jdbc.batch-writing" value="JDBC"/>
+      <property name="eclipselink.weaving" value="static" />
+    </properties>
+  </persistence-unit>
 </persistence>

+ 113 - 0
ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewEntityEntityTest.java

@@ -0,0 +1,113 @@
+/**
+ * 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.entities;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * ViewEntityEntity tests.
+ */
+public class ViewEntityEntityTest {
+  @Test
+  public void testSetGetId() throws Exception {
+    ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
+    viewEntityEntity.setId(99L);
+    Assert.assertEquals(99L, (long) viewEntityEntity.getId());
+  }
+
+  @Test
+  public void testSetGetViewName() throws Exception {
+    ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
+    viewEntityEntity.setViewName("foo");
+    Assert.assertEquals("foo", viewEntityEntity.getViewName());
+  }
+
+  @Test
+  public void testSetGetViewInstanceName() throws Exception {
+    ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
+    viewEntityEntity.setViewInstanceName("foo");
+    Assert.assertEquals("foo", viewEntityEntity.getViewInstanceName());
+  }
+
+  @Test
+  public void testSetGetClassName() throws Exception {
+    ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
+    viewEntityEntity.setClassName("TestClass");
+    Assert.assertEquals("TestClass", viewEntityEntity.getClassName());
+  }
+
+
+  @Test
+  public void testSetGetIdProperty() throws Exception {
+    ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
+    viewEntityEntity.setIdProperty("id");
+    Assert.assertEquals("id", viewEntityEntity.getIdProperty());
+  }
+
+  @Test
+  public void testSetGetViewInstance() throws Exception {
+    ViewInstanceEntity viewInstanceEntity = ViewInstanceEntityTest.getViewInstanceEntity();
+    ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
+    viewEntityEntity.setViewInstance(viewInstanceEntity);
+    Assert.assertEquals(viewInstanceEntity, viewEntityEntity.getViewInstance());
+  }
+
+  @Test
+  public void testEquals() throws Exception {
+    ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
+    viewEntityEntity.setId(99L);
+    viewEntityEntity.setClassName("TestClass");
+    viewEntityEntity.setIdProperty("id");
+    viewEntityEntity.setViewName("foo");
+    viewEntityEntity.setViewInstanceName("bar");
+
+    ViewEntityEntity viewEntityEntity2 = new ViewEntityEntity();
+    viewEntityEntity2.setId(99L);
+    viewEntityEntity2.setClassName("TestClass");
+    viewEntityEntity2.setIdProperty("id");
+    viewEntityEntity2.setViewName("foo");
+    viewEntityEntity2.setViewInstanceName("bar");
+
+    Assert.assertTrue(viewEntityEntity.equals(viewEntityEntity2));
+
+    viewEntityEntity2.setId(100L);
+
+    Assert.assertFalse(viewEntityEntity.equals(viewEntityEntity2));
+  }
+
+  @Test
+  public void testHashCode() throws Exception {
+    ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
+    viewEntityEntity.setId(99L);
+    viewEntityEntity.setClassName("TestClass");
+    viewEntityEntity.setIdProperty("id");
+    viewEntityEntity.setViewName("foo");
+    viewEntityEntity.setViewInstanceName("bar");
+
+    ViewEntityEntity viewEntityEntity2 = new ViewEntityEntity();
+    viewEntityEntity2.setId(99L);
+    viewEntityEntity2.setClassName("TestClass");
+    viewEntityEntity2.setIdProperty("id");
+    viewEntityEntity2.setViewName("foo");
+    viewEntityEntity2.setViewInstanceName("bar");
+
+    Assert.assertEquals(viewEntityEntity.hashCode(), viewEntityEntity2.hashCode());
+  }
+}

+ 10 - 0
ambari-server/src/test/java/org/apache/ambari/server/orm/entities/ViewEntityTest.java

@@ -189,4 +189,14 @@ public class ViewEntityTest {
     ViewEntity viewDefinition = getViewEntity();
     Assert.assertEquals("view.jar", viewDefinition.getArchive());
   }
+
+  @Test
+  public void testGetAmbariConfiguration() throws Exception {
+    ViewEntity viewDefinition = getViewEntity();
+    Configuration configuration = viewDefinition.getAmbariConfiguration();
+
+    Assert.assertEquals("v1", configuration.getProperty("p1"));
+    Assert.assertEquals("v2", configuration.getProperty("p2"));
+    Assert.assertEquals("v3", configuration.getProperty("p3"));
+  }
 }

+ 71 - 0
ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog160Test.java

@@ -34,6 +34,7 @@ import java.util.List;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.assertNull;
 import static org.easymock.EasyMock.capture;
 import static org.easymock.EasyMock.createNiceMock;
@@ -53,11 +54,13 @@ public class UpgradeCatalog160Test {
     final DBAccessor dbAccessor = createNiceMock(DBAccessor.class);
     Configuration configuration = createNiceMock(Configuration.class);
     Capture<List<DBAccessor.DBColumnInfo>> hgConfigcolumnCapture = new Capture<List<DBAccessor.DBColumnInfo>>();
+    Capture<List<DBAccessor.DBColumnInfo>> viewEntitycolumnCapture = new Capture<List<DBAccessor.DBColumnInfo>>();
     Capture<DBAccessor.DBColumnInfo> restartRequiredColumnCapture = new Capture<DBAccessor.DBColumnInfo>();
 
     expect(configuration.getDatabaseUrl()).andReturn(Configuration.JDBC_IN_MEMORY_URL).anyTimes();
 
     setBPHostGroupConfigExpectations(dbAccessor, hgConfigcolumnCapture, restartRequiredColumnCapture);
+    setViewEntityConfigExpectations(dbAccessor, viewEntitycolumnCapture);
 
     replay(dbAccessor, configuration);
     AbstractUpgradeCatalog upgradeCatalog = getUpgradeCatalog(dbAccessor);
@@ -70,15 +73,29 @@ public class UpgradeCatalog160Test {
     verify(dbAccessor, configuration);
 
     assertHGConfigColumns(hgConfigcolumnCapture);
+    assertViewEntityColumns(viewEntitycolumnCapture);
     assertRestartRequiredColumn(restartRequiredColumnCapture);
   }
 
   @Test
   public void testExecuteDMLUpdates() throws Exception {
+
+    Configuration configuration = createNiceMock(Configuration.class);
+    expect(configuration.getDatabaseUrl()).andReturn(Configuration.JDBC_IN_MEMORY_URL).anyTimes();
+
     final DBAccessor dbAccessor     = createNiceMock(DBAccessor.class);
     UpgradeCatalog160 upgradeCatalog = (UpgradeCatalog160) getUpgradeCatalog(dbAccessor);
 
+    replay(dbAccessor, configuration);
+
+    Class<?> c = AbstractUpgradeCatalog.class;
+    Field f = c.getDeclaredField("configuration");
+    f.setAccessible(true);
+    f.set(upgradeCatalog, configuration);
+
     upgradeCatalog.executeDMLUpdates();
+
+    verify(dbAccessor, configuration);
   }
 
   @Test
@@ -116,6 +133,12 @@ public class UpgradeCatalog160Test {
         "hostgroup_name", "hostgroup", "name", true);
   }
 
+  private void setViewEntityConfigExpectations(DBAccessor dbAccessor,
+    Capture<List<DBAccessor.DBColumnInfo>> columnCapture) throws SQLException {
+
+    dbAccessor.createTable(eq("viewentity"), capture(columnCapture), eq("id"));
+  }
+
   private void assertHGConfigColumns(Capture<List<DBAccessor.DBColumnInfo>> hgConfigcolumnCapture) {
     List<DBAccessor.DBColumnInfo> columns = hgConfigcolumnCapture.getValue();
     assertEquals(4, columns.size());
@@ -148,6 +171,54 @@ public class UpgradeCatalog160Test {
     assertFalse(column.isNullable());
   }
 
+  private void assertViewEntityColumns(Capture<List<DBAccessor.DBColumnInfo>> hgConfigcolumnCapture) {
+    List<DBAccessor.DBColumnInfo> columns = hgConfigcolumnCapture.getValue();
+    assertEquals(5, columns.size());
+
+
+    columns.add(new DBAccessor.DBColumnInfo("id", Long.class, 255, null, false));
+    columns.add(new DBAccessor.DBColumnInfo("view_name", String.class, 255, null, false));
+    columns.add(new DBAccessor.DBColumnInfo("view_instance_name", String.class, 255, null, false));
+    columns.add(new DBAccessor.DBColumnInfo("class_name", String.class, 255, null, false));
+    columns.add(new DBAccessor.DBColumnInfo("id_property", String.class, 255, null, true));
+
+
+    DBAccessor.DBColumnInfo column = columns.get(0);
+    assertEquals("id", column.getName());
+    assertNull(column.getLength());
+    assertEquals(Long.class, column.getType());
+    assertNull(column.getDefaultValue());
+    assertFalse(column.isNullable());
+
+    column = columns.get(1);
+    assertEquals("view_name", column.getName());
+    assertEquals(255, (int) column.getLength());
+    assertEquals(String.class, column.getType());
+    assertNull(column.getDefaultValue());
+    assertFalse(column.isNullable());
+
+    column = columns.get(2);
+    assertEquals("view_instance_name", column.getName());
+    assertEquals(255, (int) column.getLength());
+    assertEquals(String.class, column.getType());
+    assertNull(column.getDefaultValue());
+    assertFalse(column.isNullable());
+
+    column = columns.get(3);
+    assertEquals("class_name", column.getName());
+    assertEquals(255, (int) column.getLength());
+    assertEquals(String.class, column.getType());
+    assertNull(column.getDefaultValue());
+    assertFalse(column.isNullable());
+
+    column = columns.get(4);
+    assertEquals("id_property", column.getName());
+    assertEquals(255, (int) column.getLength());
+    assertEquals(String.class, column.getType());
+    assertNull(column.getDefaultValue());
+    assertTrue(column.isNullable());
+  }
+
   private void assertRestartRequiredColumn(
     Capture<DBAccessor.DBColumnInfo> restartRequiredColumnCapture) {
     DBAccessor.DBColumnInfo column = restartRequiredColumnCapture.getValue();

+ 79 - 0
ambari-server/src/test/java/org/apache/ambari/server/view/configuration/EntityConfigTest.java

@@ -0,0 +1,79 @@
+/**
+ * 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.view.configuration;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.xml.bind.JAXBException;
+import java.util.List;
+
+/**
+ * EntityConfig tests.
+ */
+public class EntityConfigTest {
+
+  private final static String xml = "<view>\n" +
+      "    <name>MY_VIEW</name>\n" +
+      "    <label>My View!</label>\n" +
+      "    <version>1.0.0</version>\n" +
+      "    <instance>\n" +
+      "        <name>INSTANCE1</name>\n" +
+      "    </instance>\n" +
+      "    <persistence>\n" +
+      "      <entity>\n" +
+      "        <class>org.apache.ambari.server.view.TestEntity1</class>\n" +
+      "        <id-property>id</id-property>\n" +
+      "      </entity>\n" +
+      "      <entity>\n" +
+      "        <class>org.apache.ambari.server.view.TestEntity2</class>\n" +
+      "        <id-property>name</id-property>\n" +
+      "      </entity>\n" +
+      "    </persistence>" +
+      "</view>";
+
+
+  @Test
+  public void testGetClassName() throws Exception {
+    List<EntityConfig> entities = getEntityConfigs();
+
+    Assert.assertEquals(2, entities.size());
+
+    Assert.assertEquals("org.apache.ambari.server.view.TestEntity1", entities.get(0).getClassName());
+    Assert.assertEquals("org.apache.ambari.server.view.TestEntity2", entities.get(1).getClassName());
+  }
+
+  @Test
+  public void testGetIdProperty() throws Exception {
+    List<EntityConfig> entities = getEntityConfigs();
+
+    Assert.assertEquals(2, entities.size());
+
+    Assert.assertEquals("id", entities.get(0).getIdProperty());
+    Assert.assertEquals("name", entities.get(1).getIdProperty());
+  }
+
+  public static List<EntityConfig> getEntityConfigs() throws JAXBException {
+    ViewConfig config = ViewConfigTest.getConfig(xml);
+
+    PersistenceConfig persistenceConfig = config.getPersistence();
+
+    return persistenceConfig.getEntities();
+  }
+}

+ 83 - 0
ambari-server/src/test/java/org/apache/ambari/server/view/configuration/PersistenceConfigTest.java

@@ -0,0 +1,83 @@
+/**
+ * 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.view.configuration;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * PersistenceConfig tests.
+ */
+public class PersistenceConfigTest {
+  private final static String xml = "<view>\n" +
+      "    <name>MY_VIEW</name>\n" +
+      "    <label>My View!</label>\n" +
+      "    <version>1.0.0</version>\n" +
+      "    <instance>\n" +
+      "        <name>INSTANCE1</name>\n" +
+      "    </instance>\n" +
+      "    <persistence>\n" +
+      "      <entity>\n" +
+      "        <class>org.apache.ambari.server.view.TestEntity1</class>\n" +
+      "        <id-property>id</id-property>\n" +
+      "      </entity>\n" +
+      "      <entity>\n" +
+      "        <class>org.apache.ambari.server.view.TestEntity2</class>\n" +
+      "        <id-property>name</id-property>\n" +
+      "      </entity>\n" +
+      "    </persistence>" +
+      "</view>";
+
+  private final static String xml_no_entities = "<view>\n" +
+      "    <name>MY_VIEW</name>\n" +
+      "    <label>My View!</label>\n" +
+      "    <version>1.0.0</version>\n" +
+      "    <instance>\n" +
+      "        <name>INSTANCE1</name>\n" +
+      "    </instance>\n" +
+      "    <persistence>\n" +
+      "    </persistence>" +
+      "</view>";
+
+  private final static String xml_no_persistence = "<view>\n" +
+      "    <name>MY_VIEW</name>\n" +
+      "    <label>My View!</label>\n" +
+      "    <version>1.0.0</version>\n" +
+      "    <instance>\n" +
+      "        <name>INSTANCE1</name>\n" +
+      "    </instance>\n" +
+      "    <persistence>\n" +
+      "    </persistence>" +
+      "</view>";
+
+  @Test
+  public void testGetEntities() throws Exception {
+    ViewConfig config = ViewConfigTest.getConfig(xml);
+    PersistenceConfig persistenceConfig = config.getPersistence();
+    Assert.assertEquals(2, persistenceConfig.getEntities().size());
+
+    config = ViewConfigTest.getConfig(xml_no_entities);
+    persistenceConfig = config.getPersistence();
+    Assert.assertTrue(persistenceConfig.getEntities().isEmpty());
+
+    config = ViewConfigTest.getConfig(xml_no_persistence);
+    persistenceConfig = config.getPersistence();
+    Assert.assertTrue(persistenceConfig.getEntities().isEmpty());
+  }
+}

+ 447 - 0
ambari-server/src/test/java/org/apache/ambari/server/view/persistence/DataStoreImplTest.java

@@ -0,0 +1,447 @@
+/**
+ * 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.view.persistence;
+
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import org.apache.ambari.server.orm.entities.ViewEntity;
+import org.apache.ambari.server.orm.entities.ViewEntityEntity;
+import org.apache.ambari.server.orm.entities.ViewEntityTest;
+import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
+import org.apache.ambari.server.view.configuration.EntityConfig;
+import org.apache.ambari.server.view.configuration.InstanceConfig;
+import org.apache.ambari.server.view.configuration.InstanceConfigTest;
+import org.apache.ambari.server.view.configuration.ViewConfig;
+import org.apache.ambari.server.view.configuration.ViewConfigTest;
+import org.easymock.Capture;
+import org.eclipse.persistence.dynamic.DynamicClassLoader;
+import org.eclipse.persistence.dynamic.DynamicEntity;
+import org.eclipse.persistence.dynamic.DynamicType;
+import org.eclipse.persistence.jpa.dynamic.JPADynamicHelper;
+import org.eclipse.persistence.sessions.DatabaseSession;
+import org.eclipse.persistence.tools.schemaframework.SchemaManager;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.persistence.Query;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+/**
+ * DataStoreImpl tests.
+ */
+public class DataStoreImplTest {
+  private final static String xml = "<view>\n" +
+      "    <name>MY_VIEW</name>\n" +
+      "    <label>My View!</label>\n" +
+      "    <version>1.0.0</version>\n" +
+      "    <instance>\n" +
+      "        <name>INSTANCE1</name>\n" +
+      "    </instance>\n" +
+      "    <persistence>\n" +
+      "      <entity>\n" +
+      "        <class>org.apache.ambari.server.view.persistence.DataStoreImplTest$TestEntity</class>\n" +
+      "        <id-property>id</id-property>\n" +
+      "      </entity>\n" +
+      "    </persistence>" +
+      "</view>";
+
+  @Test
+  public void testStore_create() throws Exception {
+    DynamicClassLoader classLoader = new DynamicClassLoader(DataStoreImplTest.class.getClassLoader());
+
+    // create mocks
+    EntityManagerFactory entityManagerFactory = createMock(EntityManagerFactory.class);
+    EntityManager entityManager = createMock(EntityManager.class);
+    JPADynamicHelper jpaDynamicHelper = createNiceMock(JPADynamicHelper.class);
+    SchemaManager schemaManager = createNiceMock(SchemaManager.class);
+    EntityTransaction transaction = createMock(EntityTransaction.class);
+
+    // set expectations
+    Capture<DynamicType> typeCapture = new Capture<DynamicType>();
+    jpaDynamicHelper.addTypes(eq(true), eq(true), capture(typeCapture));
+
+    expect(entityManagerFactory.createEntityManager()).andReturn(entityManager);
+    expect(entityManager.getTransaction()).andReturn(transaction).anyTimes();
+
+    Capture<Class> entityClassCapture = new Capture<Class>();
+    expect(entityManager.find(capture(entityClassCapture), eq(99))).andReturn(null);
+    Capture<DynamicEntity> entityCapture = new Capture<DynamicEntity>();
+    entityManager.persist(capture(entityCapture));
+    entityManager.close();
+
+    transaction.begin();
+    transaction.commit();
+
+    // replay mocks
+    replay(entityManagerFactory, entityManager, jpaDynamicHelper, transaction, schemaManager);
+
+    DataStoreImpl dataStore = getDataStore(entityManagerFactory, jpaDynamicHelper, classLoader, schemaManager);
+
+    dataStore.store(new TestEntity(99, "foo"));
+
+    Assert.assertEquals(entityClassCapture.getValue(), typeCapture.getValue().getJavaClass());
+    Assert.assertEquals(99, entityCapture.getValue().get("id"));
+    Assert.assertEquals("foo", entityCapture.getValue().get("name"));
+
+    // verify mocks
+    verify(entityManagerFactory, entityManager, jpaDynamicHelper, transaction, schemaManager);
+  }
+
+  @Test
+  public void testStore_update() throws Exception {
+    DynamicClassLoader classLoader = new DynamicClassLoader(DataStoreImplTest.class.getClassLoader());
+
+    // create mocks
+    EntityManagerFactory entityManagerFactory = createMock(EntityManagerFactory.class);
+    EntityManager entityManager = createMock(EntityManager.class);
+    JPADynamicHelper jpaDynamicHelper = createNiceMock(JPADynamicHelper.class);
+    SchemaManager schemaManager = createNiceMock(SchemaManager.class);
+    EntityTransaction transaction = createMock(EntityTransaction.class);
+    DynamicEntity dynamicEntity = createMock(DynamicEntity.class);
+
+    // set expectations
+    Capture<DynamicType> typeCapture = new Capture<DynamicType>();
+    jpaDynamicHelper.addTypes(eq(true), eq(true), capture(typeCapture));
+
+    expect(entityManagerFactory.createEntityManager()).andReturn(entityManager);
+    expect(entityManager.getTransaction()).andReturn(transaction).anyTimes();
+
+    Capture<Class> entityClassCapture = new Capture<Class>();
+    expect(entityManager.find(capture(entityClassCapture), eq(99))).andReturn(dynamicEntity);
+    entityManager.close();
+
+    expect(dynamicEntity.set("id", 99)).andReturn(dynamicEntity);
+    expect(dynamicEntity.set("name", "foo")).andReturn(dynamicEntity);
+
+    transaction.begin();
+    transaction.commit();
+
+    // replay mocks
+    replay(entityManagerFactory, entityManager, jpaDynamicHelper, transaction, schemaManager, dynamicEntity);
+
+    DataStoreImpl dataStore = getDataStore(entityManagerFactory, jpaDynamicHelper, classLoader, schemaManager);
+
+    dataStore.store(new TestEntity(99, "foo"));
+
+    Assert.assertEquals(entityClassCapture.getValue(), typeCapture.getValue().getJavaClass());
+
+    // verify mocks
+    verify(entityManagerFactory, entityManager, jpaDynamicHelper, transaction, schemaManager, dynamicEntity);
+  }
+
+  @Test
+  public void testRemove() throws Exception {
+    DynamicClassLoader classLoader = new DynamicClassLoader(DataStoreImplTest.class.getClassLoader());
+
+    // create mocks
+    EntityManagerFactory entityManagerFactory = createMock(EntityManagerFactory.class);
+    EntityManager entityManager = createMock(EntityManager.class);
+    JPADynamicHelper jpaDynamicHelper = createNiceMock(JPADynamicHelper.class);
+    SchemaManager schemaManager = createNiceMock(SchemaManager.class);
+    EntityTransaction transaction = createMock(EntityTransaction.class);
+    DynamicEntity dynamicEntity = createMock(DynamicEntity.class);
+
+    // set expectations
+    Capture<DynamicType> typeCapture = new Capture<DynamicType>();
+    jpaDynamicHelper.addTypes(eq(true), eq(true), capture(typeCapture));
+
+    expect(entityManagerFactory.createEntityManager()).andReturn(entityManager);
+    expect(entityManager.getTransaction()).andReturn(transaction).anyTimes();
+    Capture<Class> entityClassCapture = new Capture<Class>();
+    expect(entityManager.getReference(capture(entityClassCapture), eq(99))).andReturn(dynamicEntity);
+    entityManager.remove(dynamicEntity);
+    entityManager.close();
+
+    transaction.begin();
+    transaction.commit();
+
+    // replay mocks
+    replay(entityManagerFactory, entityManager, jpaDynamicHelper, transaction, schemaManager, dynamicEntity);
+
+    DataStoreImpl dataStore = getDataStore(entityManagerFactory, jpaDynamicHelper, classLoader, schemaManager);
+
+    dataStore.remove(new TestEntity(99, "foo"));
+
+    Assert.assertEquals(entityClassCapture.getValue(), typeCapture.getValue().getJavaClass());
+
+    // verify mocks
+    verify(entityManagerFactory, entityManager, jpaDynamicHelper, transaction, schemaManager, dynamicEntity);
+  }
+
+  @Test
+  public void testFind() throws Exception {
+    DynamicClassLoader classLoader = new DynamicClassLoader(DataStoreImplTest.class.getClassLoader());
+
+    // create mocks
+    EntityManagerFactory entityManagerFactory = createMock(EntityManagerFactory.class);
+    EntityManager entityManager = createMock(EntityManager.class);
+    JPADynamicHelper jpaDynamicHelper = createNiceMock(JPADynamicHelper.class);
+    SchemaManager schemaManager = createNiceMock(SchemaManager.class);
+    DynamicEntity dynamicEntity = createMock(DynamicEntity.class);
+
+    // set expectations
+    Capture<DynamicType> typeCapture = new Capture<DynamicType>();
+    jpaDynamicHelper.addTypes(eq(true), eq(true), capture(typeCapture));
+
+    expect(entityManagerFactory.createEntityManager()).andReturn(entityManager);
+    Capture<Class> entityClassCapture = new Capture<Class>();
+    expect(entityManager.find(capture(entityClassCapture), eq(99))).andReturn(dynamicEntity);
+    entityManager.close();
+
+    expect(dynamicEntity.get("id")).andReturn(99);
+    expect(dynamicEntity.get("name")).andReturn("foo");
+
+    // replay mocks
+    replay(entityManagerFactory, entityManager, jpaDynamicHelper, dynamicEntity, schemaManager);
+
+    DataStoreImpl dataStore = getDataStore(entityManagerFactory, jpaDynamicHelper, classLoader, schemaManager);
+
+    TestEntity entity = dataStore.find(TestEntity.class, 99);
+
+    Assert.assertEquals(entityClassCapture.getValue(), typeCapture.getValue().getJavaClass());
+    Assert.assertEquals(99, entity.getId());
+    Assert.assertEquals("foo", entity.getName());
+
+    // verify mocks
+    verify(entityManagerFactory, entityManager, jpaDynamicHelper, dynamicEntity, schemaManager);
+  }
+
+  @Test
+  public void testFindAll() throws Exception {
+    DynamicClassLoader classLoader = new DynamicClassLoader(DataStoreImplTest.class.getClassLoader());
+
+    // create mocks
+    EntityManagerFactory entityManagerFactory = createMock(EntityManagerFactory.class);
+    EntityManager entityManager = createMock(EntityManager.class);
+    JPADynamicHelper jpaDynamicHelper = createNiceMock(JPADynamicHelper.class);
+    SchemaManager schemaManager = createNiceMock(SchemaManager.class);
+    DynamicEntity dynamicEntity = createMock(DynamicEntity.class);
+    Query query = createMock(Query.class);
+
+    // set expectations
+    Capture<DynamicType> typeCapture = new Capture<DynamicType>();
+    jpaDynamicHelper.addTypes(eq(true), eq(true), capture(typeCapture));
+
+    expect(entityManagerFactory.createEntityManager()).andReturn(entityManager);
+    expect(entityManager.createQuery(
+        "SELECT e FROM DataStoreImplTest$TestEntity1 e WHERE e.id=99")).andReturn(query);
+    entityManager.close();
+
+    expect(query.getResultList()).andReturn(Collections.singletonList(dynamicEntity));
+
+    expect(dynamicEntity.get("id")).andReturn(99);
+    expect(dynamicEntity.get("name")).andReturn("foo");
+
+    // replay mocks
+    replay(entityManagerFactory, entityManager, jpaDynamicHelper, dynamicEntity, query, schemaManager);
+
+    DataStoreImpl dataStore = getDataStore(entityManagerFactory, jpaDynamicHelper, classLoader, schemaManager);
+
+    Collection<TestEntity> entities = dataStore.findAll(TestEntity.class, "id=99");
+
+    Assert.assertEquals(1, entities.size());
+
+    TestEntity entity = entities.iterator().next();
+
+    Assert.assertEquals(99, entity.getId());
+    Assert.assertEquals("foo", entity.getName());
+
+    // verify mocks
+    verify(entityManagerFactory, entityManager, jpaDynamicHelper, dynamicEntity, query, schemaManager);
+  }
+
+  @Test
+  public void testFindAll_multiple() throws Exception {
+    DynamicClassLoader classLoader = new DynamicClassLoader(DataStoreImplTest.class.getClassLoader());
+
+    // create mocks
+    EntityManagerFactory entityManagerFactory = createMock(EntityManagerFactory.class);
+    EntityManager entityManager = createMock(EntityManager.class);
+    JPADynamicHelper jpaDynamicHelper = createNiceMock(JPADynamicHelper.class);
+    SchemaManager schemaManager = createNiceMock(SchemaManager.class);
+    DynamicEntity dynamicEntity1 = createMock(DynamicEntity.class);
+    DynamicEntity dynamicEntity2 = createMock(DynamicEntity.class);
+    DynamicEntity dynamicEntity3 = createMock(DynamicEntity.class);
+    Query query = createMock(Query.class);
+
+    // set expectations
+    Capture<DynamicType> typeCapture = new Capture<DynamicType>();
+    jpaDynamicHelper.addTypes(eq(true), eq(true), capture(typeCapture));
+
+    expect(entityManagerFactory.createEntityManager()).andReturn(entityManager);
+    expect(entityManager.createQuery(
+        "SELECT e FROM DataStoreImplTest$TestEntity1 e WHERE e.name='foo'")).andReturn(query);
+    entityManager.close();
+
+    List<DynamicEntity> entityList = new LinkedList<DynamicEntity>();
+    entityList.add(dynamicEntity1);
+    entityList.add(dynamicEntity2);
+    entityList.add(dynamicEntity3);
+
+    expect(query.getResultList()).andReturn(entityList);
+
+    expect(dynamicEntity1.get("id")).andReturn(99);
+    expect(dynamicEntity1.get("name")).andReturn("foo");
+
+    expect(dynamicEntity2.get("id")).andReturn(100);
+    expect(dynamicEntity2.get("name")).andReturn("foo");
+
+    expect(dynamicEntity3.get("id")).andReturn(101);
+    expect(dynamicEntity3.get("name")).andReturn("foo");
+
+    // replay mocks
+    replay(entityManagerFactory, entityManager, jpaDynamicHelper,
+        dynamicEntity1, dynamicEntity2, dynamicEntity3, query, schemaManager);
+
+    DataStoreImpl dataStore = getDataStore(entityManagerFactory, jpaDynamicHelper, classLoader, schemaManager);
+
+    Collection<TestEntity> entities = dataStore.findAll(TestEntity.class, "name='foo'");
+
+    Assert.assertEquals(3, entities.size());
+
+    for (TestEntity entity : entities) {
+      Assert.assertEquals("foo", entity.getName());
+    }
+
+    // verify mocks
+    verify(entityManagerFactory, entityManager, jpaDynamicHelper,
+        dynamicEntity1, dynamicEntity2, dynamicEntity3, query, schemaManager);
+  }
+
+  private DataStoreImpl getDataStore(EntityManagerFactory entityManagerFactory,
+                                     JPADynamicHelper jpaDynamicHelper,
+                                     DynamicClassLoader classLoader,
+                                     SchemaManager schemaManager)
+      throws Exception {
+    ViewConfig viewConfig = ViewConfigTest.getConfig(xml);
+    ViewEntity viewDefinition = ViewEntityTest.getViewEntity(viewConfig);
+
+    InstanceConfig instanceConfig = InstanceConfigTest.getInstanceConfigs().get(0);
+    ViewInstanceEntity viewInstanceEntity = new ViewInstanceEntity(viewDefinition, instanceConfig);
+
+    setPersistenceEntities(viewInstanceEntity);
+
+    Injector injector = Guice.createInjector(
+        new TestModule(viewInstanceEntity, entityManagerFactory, jpaDynamicHelper, classLoader, schemaManager));
+    return injector.getInstance(DataStoreImpl.class);
+  }
+
+
+  // TODO : move to ViewEntityEntity test.
+  private static void setPersistenceEntities(ViewInstanceEntity viewInstanceDefinition) {
+    ViewEntity viewDefinition = viewInstanceDefinition.getViewEntity();
+    Collection<ViewEntityEntity> entities = new HashSet<ViewEntityEntity>();
+
+    ViewConfig viewConfig = viewDefinition.getConfiguration();
+    for (EntityConfig entityConfiguration : viewConfig.getPersistence().getEntities()) {
+      ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
+
+      viewEntityEntity.setId(1L);
+      viewEntityEntity.setViewName(viewDefinition.getName());
+      viewEntityEntity.setViewInstanceName(viewInstanceDefinition.getName());
+      viewEntityEntity.setClassName(entityConfiguration.getClassName());
+      viewEntityEntity.setIdProperty(entityConfiguration.getIdProperty());
+      viewEntityEntity.setViewInstance(viewInstanceDefinition);
+
+      entities.add(viewEntityEntity);
+    }
+    viewInstanceDefinition.setEntities(entities);
+  }
+
+
+  public static class TestEntity {
+
+    public TestEntity() {
+    }
+
+    public TestEntity(int id, String name) {
+      this.id = id;
+      this.name = name;
+    }
+
+    int id;
+    String name;
+
+    public int getId() {
+      return id;
+    }
+
+    public void setId(int id) {
+      this.id = id;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(String name) {
+      this.name = name;
+    }
+  }
+
+  private static class TestModule implements Module, SchemaManagerFactory {
+    private final ViewInstanceEntity viewInstanceEntity;
+    private final EntityManagerFactory entityManagerFactory;
+    private final JPADynamicHelper jpaDynamicHelper;
+    private final DynamicClassLoader classLoader;
+    private final SchemaManager schemaManager;
+
+    private TestModule(ViewInstanceEntity viewInstanceEntity, EntityManagerFactory entityManagerFactory,
+                       JPADynamicHelper jpaDynamicHelper, DynamicClassLoader classLoader,
+                       SchemaManager schemaManager) {
+      this.viewInstanceEntity = viewInstanceEntity;
+      this.entityManagerFactory = entityManagerFactory;
+      this.jpaDynamicHelper = jpaDynamicHelper;
+      this.classLoader = classLoader;
+      this.schemaManager = schemaManager;
+    }
+
+    @Override
+    public void configure(Binder binder) {
+      binder.bind(ViewInstanceEntity.class).toInstance(viewInstanceEntity);
+      binder.bind(EntityManagerFactory.class).toInstance(entityManagerFactory);
+      binder.bind(JPADynamicHelper.class).toInstance(jpaDynamicHelper);
+      binder.bind(DynamicClassLoader.class).toInstance(classLoader);
+      binder.bind(SchemaManagerFactory.class).toInstance(this);
+    }
+
+    @Override
+    public SchemaManager getSchemaManager(DatabaseSession session) {
+      return schemaManager;
+    }
+  }
+}

+ 75 - 0
ambari-views/src/main/java/org/apache/ambari/view/DataStore.java

@@ -0,0 +1,75 @@
+/**
+ * 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;
+
+import java.util.Collection;
+
+/**
+ * View data store.
+ */
+public interface DataStore {
+
+  /**
+   * Save the given entity to persistent storage.  The entity must be declared as an
+   * <entity> in the <persistence> element of the view.xml.
+   *
+   * @param entity  the entity to be persisted.
+   *
+   * @throws PersistenceException thrown if the given entity can not be persisted
+   */
+  public void store(Object entity) throws PersistenceException;
+
+  /**
+   * Remove the given entity from persistent storage.
+   *
+   * @param entity  the entity to be removed.
+   *
+   * @throws PersistenceException thrown if the given entity can not be removed
+   */
+  public void remove(Object entity) throws PersistenceException;
+
+  /**
+   * Find the entity of the given class type that is uniquely identified by the
+   * given primary key.
+   *
+   * @param clazz       the entity class
+   * @param primaryKey  the primary key
+   * @param <T>         the entity type
+   *
+   * @return the entity; null if the entity can't be found
+   *
+   * @throws PersistenceException thrown if an error occurs trying to find the entity
+   */
+  public <T> T find(Class<T> clazz, Object primaryKey) throws PersistenceException;
+
+  /**
+   * Find all the entities for the given where clause.  Specifying null for the where
+   * clause should return all entities of the given class type.
+   *
+   * @param clazz        the entity class
+   * @param whereClause  the where clause; may be null
+   * @param <T>          the entity type
+   *
+   * @return all of the entities for the given where clause; empty collection if no
+   *         entities can be found
+   *
+   * @throws PersistenceException
+   */
+  public <T> Collection<T> findAll(Class<T> clazz, String whereClause) throws PersistenceException;
+}

+ 44 - 0
ambari-views/src/main/java/org/apache/ambari/view/PersistenceException.java

@@ -0,0 +1,44 @@
+/**
+ * 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;
+
+/**
+ * View persistence exception.  Indicates that an error occurred while
+ * persisting a view or view data.
+ */
+public class PersistenceException  extends Exception {
+  /**
+   * Constructor.
+   *
+   * @param msg        message
+   */
+  public PersistenceException(String msg) {
+    super(msg);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param msg        message
+   * @param throwable  root exception
+   */
+  public PersistenceException(String msg, Throwable throwable) {
+    super(msg, throwable);
+  }
+}

+ 7 - 0
ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java

@@ -115,4 +115,11 @@ public interface ViewContext {
    * @return a stream provider
    */
   public URLStreamProvider getURLStreamProvider();
+
+  /**
+   * Get a data store for view persistence entities.
+   *
+   * @return a data store
+   */
+  public DataStore getDataStore();
 }