You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by sr...@apache.org on 2015/03/20 22:55:28 UTC

ambari git commit: AMBARI-10164. Provide /artifacts/theme endpoint for each stack-version service (mpapyrkovskyy via srimanth)

Repository: ambari
Updated Branches:
  refs/heads/trunk 6655d9886 -> e514c9be3


AMBARI-10164. Provide /artifacts/theme endpoint for each stack-version service (mpapyrkovskyy via srimanth)


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

Branch: refs/heads/trunk
Commit: e514c9be38dba024fc098bd12d92d074ce4dedad
Parents: 6655d98
Author: Srimanth Gunturi <sg...@hortonworks.com>
Authored: Fri Mar 20 14:54:31 2015 -0700
Committer: Srimanth Gunturi <sg...@hortonworks.com>
Committed: Fri Mar 20 14:54:31 2015 -0700

----------------------------------------------------------------------
 .../server/api/services/AmbariMetaInfo.java     |   5 +
 .../internal/StackArtifactResourceProvider.java |  65 ++++++++
 .../ambari/server/stack/ServiceDirectory.java   |  18 +++
 .../ambari/server/stack/ServiceModule.java      |  32 +++-
 .../apache/ambari/server/stack/ThemeModule.java | 156 +++++++++++++++++++
 .../apache/ambari/server/stack/Validable.java   |   4 +-
 .../apache/ambari/server/state/ServiceInfo.java |  21 ++-
 .../apache/ambari/server/state/ThemeInfo.java   |  45 ++++++
 .../ambari/server/stack/ThemeModuleTest.java    |  88 +++++++++++
 9 files changed, 429 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/e514c9be/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
index aa239ec..982f10f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
@@ -101,6 +101,11 @@ public class AmbariMetaInfo {
   public static final String KERBEROS_DESCRIPTOR_FILE_NAME = "kerberos.json";
 
   /**
+   * Filename for theme file at service layer
+   */
+  public static final String SERVICE_THEME_FILE_NAME = "theme.json";
+
+  /**
    * This string is used in placeholder in places that are common for
    * all operating systems or in situations where os type is not important.
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/e514c9be/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackArtifactResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackArtifactResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackArtifactResourceProvider.java
index 5b5b46f..8a6e38d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackArtifactResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackArtifactResourceProvider.java
@@ -35,6 +35,7 @@ import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.StackInfo;
+import org.apache.ambari.server.state.ThemeInfo;
 import org.apache.ambari.server.state.kerberos.KerberosDescriptor;
 import org.apache.ambari.server.state.kerberos.KerberosDescriptorFactory;
 import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor;
@@ -46,6 +47,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -111,6 +113,11 @@ public class StackArtifactResourceProvider extends AbstractControllerResourcePro
    */
   public static final String KERBEROS_DESCRIPTOR_NAME = "kerberos_descriptor";
 
+  /**
+   * Name of theme artifact
+   */
+  public static final String THEME_ARTIFACT_NAME = "theme";
+
 
   /**
    * KerberosDescriptorFactory used to create KerberosDescriptor instances
@@ -164,6 +171,7 @@ public class StackArtifactResourceProvider extends AbstractControllerResourcePro
     Set<Resource> resources = new HashSet<Resource>();
 
     resources.addAll(getKerberosDescriptors(request, predicate));
+    resources.addAll(getThemes(request, predicate));
     // add other artifacts types here
 
     if (resources.isEmpty()) {
@@ -263,6 +271,63 @@ public class StackArtifactResourceProvider extends AbstractControllerResourcePro
     return resources;
   }
 
+  private Set<Resource> getThemes(Request request, Predicate predicate) throws NoSuchParentResourceException,
+    NoSuchResourceException, UnsupportedPropertyException, SystemException {
+
+    Set<Resource> resources = new HashSet<Resource>();
+    for (Map<String, Object> properties : getPropertyMaps(predicate)) {
+      String artifactName = (String) properties.get(ARTIFACT_NAME_PROPERTY_ID);
+      if (artifactName == null || artifactName.equals(THEME_ARTIFACT_NAME)) {
+        String stackName = (String) properties.get(STACK_NAME_PROPERTY_ID);
+        String stackVersion = (String) properties.get(STACK_VERSION_PROPERTY_ID);
+        String stackService = (String) properties.get(STACK_SERVICE_NAME_PROPERTY_ID);
+
+        StackInfo stackInfo;
+        try {
+          stackInfo = getManagementController().getAmbariMetaInfo().getStack(stackName, stackVersion);
+        } catch (AmbariException e) {
+          throw new NoSuchParentResourceException(String.format(
+            "Parent stack resource doesn't exist: stackName='%s', stackVersion='%s'", stackName, stackVersion));
+        }
+
+        List<ServiceInfo> serviceInfoList = new ArrayList<ServiceInfo>();
+
+        if (stackService == null) {
+          serviceInfoList.addAll(stackInfo.getServices());
+        } else {
+          ServiceInfo service = stackInfo.getService(stackService);
+          if (service == null) {
+            throw new NoSuchParentResourceException(String.format(
+              "Parent stack/service resource doesn't exist: stackName='%s', stackVersion='%s', serviceName='%s'",
+              stackName, stackVersion, stackService));
+          }
+          serviceInfoList.add(service);
+        }
+
+        for (ServiceInfo serviceInfo : serviceInfoList) {
+          ThemeInfo themeInfo = serviceInfo.getThemeInfo();
+          LOG.info("Theme for stackName={}, stackVersion={}, serviceName={}, themeInfo={}", stackName, stackVersion, stackService, themeInfo);
+          if (themeInfo != null) {
+            Map<String, Object> themeMap = serviceInfo.getThemeInfo().getThemeMap();
+            if (themeMap != null) {
+              Resource resource = new ResourceImpl(Resource.Type.StackArtifact);
+              Set<String> requestedIds = getRequestPropertyIds(request, predicate);
+              setResourceProperty(resource, ARTIFACT_NAME_PROPERTY_ID, THEME_ARTIFACT_NAME, requestedIds);
+              setResourceProperty(resource, ARTIFACT_DATA_PROPERTY_ID, themeMap, requestedIds);
+              setResourceProperty(resource, STACK_NAME_PROPERTY_ID, stackName, requestedIds);
+              setResourceProperty(resource, STACK_VERSION_PROPERTY_ID, stackVersion, requestedIds);
+              setResourceProperty(resource, STACK_SERVICE_NAME_PROPERTY_ID, serviceInfo.getName(), requestedIds);
+
+              resources.add(resource);
+            }
+          }
+
+        }
+      }
+    }
+    return resources;
+  }
+
   /**
    * Get a kerberos descriptor.
    *

http://git-wip-us.apache.org/repos/asf/ambari/blob/e514c9be/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java
index 9b32a97..7f2ff68 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java
@@ -42,6 +42,11 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory {
   private File alertsFile;
 
   /**
+   * theme file
+   */
+  private File themeFile;
+
+  /**
    * kerberos descriptor file
    */
   private File kerberosDescriptorFile;
@@ -103,6 +108,10 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory {
     File kdf = new File(directory.getAbsolutePath()
         + File.separator + AmbariMetaInfo.KERBEROS_DESCRIPTOR_FILE_NAME);
     kerberosDescriptorFile = kdf.exists() ? kdf : null;
+
+    File themeFile = new File(directory.getAbsolutePath() + File.separator + AmbariMetaInfo.SERVICE_THEME_FILE_NAME);
+    this.themeFile = themeFile.exists() ? themeFile : null;
+
   }
 
   /**
@@ -133,6 +142,14 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory {
   }
 
   /**
+   * Obtain theme file
+   * @return theme file
+   */
+  public File getThemeFile() {
+    return themeFile;
+  }
+
+  /**
    * Obtain the Kerberos Descriptor file.
    *
    * @return Kerberos Descriptor file
@@ -178,4 +195,5 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory {
       metaInfoXml.setSchemaVersion(getAbsolutePath().replace(f.getParentFile().getParentFile().getParent()+File.separator, ""));
     }
   }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e514c9be/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
index 8b94101..ab1ede5 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
@@ -61,6 +61,11 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
       new HashMap<String, ComponentModule>();
 
   /**
+   * Map of themes, single value currently
+   */
+  private Map<String, ThemeModule> themeModules = new HashMap<String, ThemeModule>();
+
+  /**
    * Encapsulates IO operations on service directory
    */
   private ServiceDirectory serviceDirectory;
@@ -73,8 +78,8 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
   /**
    * validity flag
    */
-  protected boolean valid = true;  
-  
+  protected boolean valid = true;
+
   /**
    * Constructor.
    *
@@ -109,6 +114,7 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
 
     populateComponentModules();
     populateConfigurationModules();
+    populateThemeModules();
   }
 
   @Override
@@ -168,6 +174,7 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
     mergeConfigDependencies(parent);
     mergeComponents(parentModule, allStacks, commonServices);
     mergeConfigurations(parentModule, allStacks, commonServices);
+    mergeThemes(parentModule, allStacks, commonServices);
     mergeExcludedConfigTypes(parent);
   }
 
@@ -273,6 +280,27 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
     }
   }
 
+  private void populateThemeModules() {
+
+    ThemeModule themeModule = new ThemeModule(serviceDirectory.getThemeFile());
+
+    if (themeModule.isValid()) {
+      serviceInfo.setThemeInfo(themeModule.getModuleInfo());
+      themeModules.put(themeModule.getId(), themeModule);
+    }
+
+    //lets not fail if theme contain errors
+  }
+
+  /**
+   * Merge theme modules.
+   */
+  private void mergeThemes(ServiceModule parent, Map<String, StackModule> allStacks,
+                           Map<String, ServiceModule> commonServices) throws AmbariException {
+    mergeChildModules(allStacks, commonServices, themeModules, parent.themeModules);
+
+  }
+
   /**
    * Merge excluded configs types with parent.  Child values override parent values.
    *

http://git-wip-us.apache.org/repos/asf/ambari/blob/e514c9be/ambari-server/src/main/java/org/apache/ambari/server/stack/ThemeModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ThemeModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ThemeModule.java
new file mode 100644
index 0000000..8430ad4
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ThemeModule.java
@@ -0,0 +1,156 @@
+/**
+ * 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.stack;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.state.ThemeInfo;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class ThemeModule extends BaseModule<ThemeModule, ThemeInfo> implements Validable {
+
+  private static final Logger LOG = LoggerFactory.getLogger(ThemeModule.class);
+  private static final ObjectMapper mapper = new ObjectMapper();
+
+  static {
+  }
+
+
+  private ThemeInfo moduleInfo = new ThemeInfo();
+  private boolean valid = true;
+  private Set<String> errors = new HashSet<String>();
+
+  public ThemeModule(File themeFile) {
+
+    if (themeFile == null) {
+    } else {
+      FileReader reader = null;
+      try {
+        reader = new FileReader(themeFile);
+      } catch (FileNotFoundException e) {
+        LOG.error("Theme file not found");
+      }
+      try {
+        TypeReference<HashMap<String,Object>> typeRef = new TypeReference<HashMap<String,Object>>() {};
+        Map<String, Object> map = mapper.readValue(reader, typeRef);
+        moduleInfo.setThemeMap(map);
+        LOG.info("Loaded theme: {}", moduleInfo);
+      } catch (IOException e) {
+        LOG.error("Unable to parse theme file ", e);
+        setValid(false);
+        setErrors("Unable to parse theme file " + themeFile);
+      }
+    }
+  }
+
+  public ThemeModule(ThemeInfo moduleInfo) {
+    this.moduleInfo = moduleInfo;
+  }
+
+  @Override
+  public void resolve(ThemeModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices) throws AmbariException {
+    if (parent.getModuleInfo() != null) {
+      moduleInfo.setThemeMap(mergedMap(parent.getModuleInfo().getThemeMap(), moduleInfo.getThemeMap()));
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private Map<String, Object> mergedMap(Map<String, Object> parent, Map<String, Object> child) {
+    Map<String, Object> mergedMap = new HashMap<String, Object>();
+    if (parent == null) {
+      mergedMap = child;
+    }else if (child == null) {
+      mergedMap.putAll(parent);
+    } else {
+      mergedMap.putAll(parent);
+      for (Map.Entry<String, Object> entry : child.entrySet()) {
+        String key = entry.getKey();
+        Object childValue = entry.getValue();
+        if (childValue == null) {
+          mergedMap.remove(key);
+        }else if (!mergedMap.containsKey(key) || !(childValue instanceof Map)) {
+          //insert if absent, override if explicitly null, override primitives and arrays
+          mergedMap.put(key, childValue);
+        }else {
+          Object parentValue = mergedMap.get(key);
+          if (!(parentValue instanceof Map)) {
+            //override on type mismatch also
+            mergedMap.put(key, childValue);
+          } else {
+            mergedMap.put(key, mergedMap((Map<String, Object>)parentValue, (Map<String, Object>)childValue));
+          }
+        }
+      }
+
+    }
+
+    return mergedMap;
+  }
+
+  @Override
+  public ThemeInfo getModuleInfo() {
+    return moduleInfo;
+  }
+
+  @Override
+  public boolean isDeleted() {
+    return false;
+  }
+
+  @Override
+  public String getId() {
+    return "theme";
+  }
+
+  @Override
+  public boolean isValid() {
+    return valid;
+  }
+
+  @Override
+  public void setValid(boolean valid) {
+    this.valid = valid;
+  }
+
+  @Override
+  public void setErrors(String error) {
+    errors.add(error);
+  }
+
+  @Override
+  public void setErrors(Collection<String> error) {
+    errors.addAll(error);
+  }
+
+  @Override
+  public Collection<String> getErrors() {
+    return errors;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/e514c9be/ambari-server/src/main/java/org/apache/ambari/server/stack/Validable.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/Validable.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/Validable.java
index e8b5f24..6b75eda 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/Validable.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/Validable.java
@@ -40,9 +40,9 @@ public interface Validable {
   
   public void setErrors(String error);
   
-  public void setErrors(Collection error);
+  public void setErrors(Collection<String> error);
   
-  public Collection getErrors();
+  public Collection<String> getErrors();
   
   
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e514c9be/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
index b1ee121..939a496 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
@@ -99,7 +99,10 @@ public class ServiceInfo implements Validable{
 
   public void setRestartRequiredAfterChange(Boolean restartRequiredAfterChange) {
     this.restartRequiredAfterChange = restartRequiredAfterChange;
-  }  
+  }
+
+  @XmlTransient
+  private ThemeInfo themeInfo = null;
   
   @XmlTransient
   private File metricsFile = null;
@@ -604,6 +607,20 @@ public class ServiceInfo implements Validable{
     return excludedConfigTypes;
   }
 
+  /**
+   * @return theme description map
+   */
+  public ThemeInfo getThemeInfo() {
+    return themeInfo;
+  }
+
+  /**
+   * @param themeInfo map with theme description
+   */
+  public void setThemeInfo(ThemeInfo themeInfo) {
+    this.themeInfo = themeInfo;
+  }
+
   public void setExcludedConfigTypes(Set<String> excludedConfigTypes) {
     this.excludedConfigTypes = excludedConfigTypes;
   }
@@ -627,4 +644,6 @@ public class ServiceInfo implements Validable{
     }
     return result;
   }
+
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e514c9be/ambari-server/src/main/java/org/apache/ambari/server/state/ThemeInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/ThemeInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/state/ThemeInfo.java
new file mode 100644
index 0000000..4f791a9
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/ThemeInfo.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.state;
+
+import java.util.Map;
+
+/**
+ * Wrapper for theme description
+ */
+public class ThemeInfo {
+  private Map<String, Object> themeMap = null;
+
+  public ThemeInfo() {
+  }
+
+  public Map<String, Object> getThemeMap() {
+    return themeMap;
+  }
+
+  public void setThemeMap(Map<String, Object> themeMap) {
+    this.themeMap = themeMap;
+  }
+
+  @Override
+  public String toString() {
+    return "ThemeInfo{" +
+      "themeMap=" + themeMap +
+      '}';
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/e514c9be/ambari-server/src/test/java/org/apache/ambari/server/stack/ThemeModuleTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/ThemeModuleTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/ThemeModuleTest.java
new file mode 100644
index 0000000..500ccb3
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/ThemeModuleTest.java
@@ -0,0 +1,88 @@
+/**
+ * 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.stack;
+
+import org.apache.ambari.server.state.ThemeInfo;
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+public class ThemeModuleTest {
+  private String parentTheme = "{\n" +
+    "  \"Theme\": {\n" +
+    "    \"name\": \"default\",\n" +
+    "    \"description\": \"Default theme for HDFS service\",\n" +
+    "    \"subObject\": {\n" +
+    "      \"primitiveInt\" : 10,\n" +
+    "      \"primitiveStr\" : \"str\",\n" +
+    "      \"array1\" : [1,2,3],\n" +
+    "      \"array2\" : [1,2,3]\n" +
+    "    }\n" +
+    "  }\n" +
+    "}";
+  private String childTheme = "{\n" +
+    "  \"Theme\": {\n" +
+    "    \"description\": \"inherited theme\",\n" +
+    "    \"subObject\": {\n" +
+    "      \"primitiveInt\" : 12,\n" +
+    "      \"primitiveStr\" : \"newStr\",\n" +
+    "      \"array1\" : [1,2,3,4,5],\n" +
+    "      \"array2\" : null,\n" +
+    "      \"subObject2\" : {\"1\":\"1\"}\n" +
+    "    }\n" +
+    "  }\n" +
+    "}";
+
+  private ObjectMapper mapper = new ObjectMapper();
+  TypeReference<HashMap<String,Object>> typeRef = new TypeReference<HashMap<String,Object>>() {};
+
+
+  @Test
+  public void testResolve() throws Exception {
+
+    ThemeInfo parentInfo = new ThemeInfo();
+    parentInfo.setThemeMap(mapper.<Map<String, Object>>readValue(parentTheme, typeRef));
+    ThemeModule parentModule = new ThemeModule(parentInfo);
+
+    ThemeInfo childInfo = new ThemeInfo();
+    childInfo.setThemeMap(mapper.<Map<String, Object>>readValue(childTheme, typeRef));
+    ThemeModule childModule = new ThemeModule(childInfo);
+
+    childModule.resolve(parentModule, null, null);
+
+    Map descriptionMap = ((Map) childInfo.getThemeMap().get("Theme"));
+    Map subObjectMap = (Map) descriptionMap.get("subObject");
+
+    assertTrue(StringUtils.equals((String) descriptionMap.get("description"), "inherited theme"));
+    assertTrue(descriptionMap.containsKey("name"));
+    assertFalse(subObjectMap.containsKey("array2"));
+    assertEquals(subObjectMap.get("primitiveInt"), 12);
+    assertEquals(subObjectMap.get("primitiveStr"), "newStr");
+    assertEquals(((List)subObjectMap.get("array1")).size(), 5);
+
+
+  }
+}
\ No newline at end of file