You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by st...@apache.org on 2016/12/23 12:28:15 UTC

ambari git commit: AMBARI-19244. Create profile evaluator. (Balazs Bence Sari via stoader)

Repository: ambari
Updated Branches:
  refs/heads/trunk 564b8f775 -> 83f3b6fb0


AMBARI-19244. Create profile evaluator. (Balazs Bence Sari via stoader)


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

Branch: refs/heads/trunk
Commit: 83f3b6fb0fb98bc99e74b0d1f2c55e5229c697ac
Parents: 564b8f7
Author: Balazs Bence Sari <ba...@hortonworks.com>
Authored: Fri Dec 23 13:27:56 2016 +0100
Committer: Toader, Sebastian <st...@hortonworks.com>
Committed: Fri Dec 23 13:27:56 2016 +0100

----------------------------------------------------------------------
 .../ambari/server/state/quicklinks/Link.java    |  16 +-
 .../quicklinksprofile/AcceptAllFilter.java      |   2 +-
 .../state/quicklinksprofile/Component.java      |   7 +
 .../server/state/quicklinksprofile/Filter.java  |  10 +-
 .../quicklinksprofile/LinkAttributeFilter.java  |  61 ++++++
 .../state/quicklinksprofile/PropertyFilter.java |  60 ------
 .../quicklinksprofile/QuickLinksProfile.java    |   7 +
 .../QuickLinksProfileEvaluator.java             | 202 ++++++++++++++++++
 .../QuickLinksProfileEvaluatorException.java    |  27 +++
 .../QuickLinksProfileParser.java                |  10 +-
 .../server/state/quicklinksprofile/Service.java |   8 +
 .../QuickLinksConfigurationModuleTest.java      |  10 +-
 .../state/quicklinksprofile/EvaluatorTest.java  | 204 +++++++++++++++++++
 .../QuickLinksProfileEvaluatorTest.java         | 167 +++++++++++++++
 .../QuickLinksProfileParserTest.java            |   4 +-
 .../HIVE/0.11.0.2.0.5.0/package/.hash           |   1 -
 .../dummy_stack/HIVE/package/.hash              |   1 -
 .../child_quicklinks_with_attributes.json       |  64 ++++++
 .../child_quicklinks_with_properties.json       |  64 ------
 .../resources/example_quicklinks_profile.json   |   4 +-
 .../inconsistent_quicklinks_profile.json        |   2 +-
 .../parent_quicklinks_with_attributes.json      |  65 ++++++
 .../parent_quicklinks_with_properties.json      |  65 ------
 23 files changed, 841 insertions(+), 220 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinks/Link.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinks/Link.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinks/Link.java
index 72ad764..f589f5d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinks/Link.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinks/Link.java
@@ -50,8 +50,8 @@ public class Link{
   @JsonProperty("protocol")
   private Protocol protocol;
 
-  @JsonProperty("properties")
-  private List<String> properties;
+  @JsonProperty("attributes")
+  private List<String> attributes;
 
   public String getName() {
     return name;
@@ -110,12 +110,12 @@ public class Link{
   }
 
   @Nullable
-  public List<String> getProperties() {
-    return properties;
+  public List<String> getAttributes() {
+    return attributes;
   }
 
-  public void setProperties(List<String> properties) {
-    this.properties = properties;
+  public void setAttributes(List<String> attributes) {
+    this.attributes = attributes;
   }
 
   public boolean isRemoved(){
@@ -148,8 +148,8 @@ public class Link{
       port.mergetWithParent(parentLink.getPort());
     }
 
-    if (null == properties && null != parentLink.properties) {
-      properties = parentLink.properties;
+    if (null == attributes && null != parentLink.attributes) {
+      attributes = parentLink.attributes;
     }
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/AcceptAllFilter.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/AcceptAllFilter.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/AcceptAllFilter.java
index 5124241..d784a22 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/AcceptAllFilter.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/AcceptAllFilter.java
@@ -22,7 +22,7 @@ import org.apache.ambari.server.state.quicklinks.Link;
 
 /**
  * A filter that accepts all links. It is useful to specify a general rule while the more specific
- * ({@link LinkNameFilter} and {@link PropertyFilter}) filters handle more special cases.
+ * ({@link LinkNameFilter} and {@link LinkAttributeFilter}) filters handle more special cases.
  */
 public class AcceptAllFilter extends Filter {
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Component.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Component.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Component.java
index 7ef0259..a1267df 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Component.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Component.java
@@ -36,6 +36,13 @@ public class Component {
   @JsonProperty("filters")
   private List<Filter> filters;
 
+  static Component create(String name, List<Filter> filters) {
+    Component component = new Component();
+    component.setName(name);
+    component.setFilters(filters);
+    return component;
+  }
+
   public String getName() {
     return name;
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Filter.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Filter.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Filter.java
index 1711628..c551830 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Filter.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Filter.java
@@ -69,10 +69,10 @@ public abstract class Filter {
     return linkNameFilter;
   }
 
-  static PropertyFilter propertyFilter(String propertyName, boolean visible) {
-    PropertyFilter propertyFilter = new PropertyFilter();
-    propertyFilter.setPropertyName(propertyName);
-    propertyFilter.setVisible(visible);
-    return propertyFilter;
+  static LinkAttributeFilter linkAttributeFilter(String linkAttribute, boolean visible) {
+    LinkAttributeFilter linkAttributeFilter = new LinkAttributeFilter();
+    linkAttributeFilter.setLinkAttribute(linkAttribute);
+    linkAttributeFilter.setVisible(visible);
+    return linkAttributeFilter;
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/LinkAttributeFilter.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/LinkAttributeFilter.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/LinkAttributeFilter.java
new file mode 100644
index 0000000..b10111d
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/LinkAttributeFilter.java
@@ -0,0 +1,61 @@
+/*
+ * 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.quicklinksprofile;
+
+import java.util.Objects;
+
+import org.apache.ambari.server.state.quicklinks.Link;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * A quicklink filter based on link attribute match (the filter's link_attribute is contained by the links set of
+ * attributes)
+ */
+public class LinkAttributeFilter extends Filter {
+  static final String LINK_ATTRIBUTE = "link_attribute";
+
+  @JsonProperty(LINK_ATTRIBUTE)
+  private String linkAttribute;
+
+  public String getLinkAttribute() {
+    return linkAttribute;
+  }
+
+  public void setLinkAttribute(String linkAttribute) {
+    this.linkAttribute = linkAttribute;
+  }
+
+  @Override
+  public boolean accept(Link link) {
+    return link.getAttributes().contains(linkAttribute);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    LinkAttributeFilter that = (LinkAttributeFilter) o;
+    return isVisible() == that.isVisible() && Objects.equals(linkAttribute, that.linkAttribute);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(isVisible(), linkAttribute);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/PropertyFilter.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/PropertyFilter.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/PropertyFilter.java
deleted file mode 100644
index 7b5eba0..0000000
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/PropertyFilter.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.quicklinksprofile;
-
-import java.util.Objects;
-
-import org.apache.ambari.server.state.quicklinks.Link;
-import org.codehaus.jackson.annotate.JsonProperty;
-
-/**
- * A quicklink filter based on property-match (the filter's property is contained by the links set of properties)
- */
-public class PropertyFilter extends Filter {
-  static final String PROPERTY_NAME = "property_name";
-
-  @JsonProperty(PROPERTY_NAME)
-  private String propertyName;
-
-  public String getPropertyName() {
-    return propertyName;
-  }
-
-  public void setPropertyName(String propertyName) {
-    this.propertyName = propertyName;
-  }
-
-  @Override
-  public boolean accept(Link link) {
-    return link.getProperties().contains(propertyName);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    PropertyFilter that = (PropertyFilter) o;
-    return isVisible() == that.isVisible() && Objects.equals(propertyName, that.propertyName);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(isVisible(), propertyName);
-  }
-}

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfile.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfile.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfile.java
index 1a1488b..e86af38 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfile.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfile.java
@@ -45,6 +45,13 @@ public class QuickLinksProfile {
   @JsonProperty("services")
   private List<Service> services;
 
+  static QuickLinksProfile create(List<Filter> globalFilters, List<Service> services) {
+    QuickLinksProfile profile = new QuickLinksProfile();
+    profile.setFilters(globalFilters);
+    profile.setServices(services);
+    return profile;
+  }
+
   /**
    * @return service-specific quicklink filter definitions
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluator.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluator.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluator.java
new file mode 100644
index 0000000..31335b6
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluator.java
@@ -0,0 +1,202 @@
+/*
+ * 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.quicklinksprofile;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.ambari.server.state.quicklinks.Link;
+
+import com.google.common.base.Optional;
+
+/**
+ * This class can evaluate whether a quicklink has to be shown or hidden based on the received {@link QuickLinksProfile}.
+ */
+public class QuickLinksProfileEvaluator {
+  private final Evaluator globalRules;
+  private final Map<String, Evaluator> serviceRules = new HashMap<>();
+  private final Map<ServiceComponent, Evaluator> componentRules = new HashMap<>();
+
+  public QuickLinksProfileEvaluator(QuickLinksProfile profile) throws QuickLinksProfileEvaluatorException {
+    globalRules = new Evaluator(profile.getFilters());
+    for (Service service: nullToEmptyList(profile.getServices())) {
+      serviceRules.put(service.getName(), new Evaluator(service.getFilters()));
+      for (Component component: nullToEmptyList(service.getComponents())) {
+        componentRules.put(ServiceComponent.of(service.getName(), component.getName()),
+            new Evaluator(component.getFilters()));
+      }
+    }
+  }
+
+  /**
+   * @param service the name of the service
+   * @param quickLink the quicklink
+   * @return a boolean indicating whether the link in the parameter should be visible
+   */
+  public boolean isVisible(@Nonnull String service, @Nonnull Link quickLink) {
+    // First, component rules are evaluated if exist and applicable
+    Optional<Boolean> componentResult = evaluateComponentRules(service, quickLink);
+    if (componentResult.isPresent()) {
+      return componentResult.get();
+    }
+
+    // Secondly, service level rules are applied
+    Optional<Boolean> serviceResult = evaluateServiceRules(service, quickLink);
+    if (serviceResult.isPresent()) {
+      return serviceResult.get();
+    }
+
+    // Global rules are evaluated lastly. If no rules apply to the link, it will be hidden.
+    return globalRules.isVisible(quickLink).or(false);
+  }
+
+  private Optional<Boolean> evaluateComponentRules(@Nonnull String service, @Nonnull Link quickLink) {
+    if (null == quickLink.getComponentName()) {
+      return Optional.absent();
+    }
+    else {
+      Evaluator componentEvaluator = componentRules.get(ServiceComponent.of(service, quickLink.getComponentName()));
+      return componentEvaluator != null ? componentEvaluator.isVisible(quickLink) : Optional.<Boolean>absent();
+    }
+  }
+
+  private Optional<Boolean> evaluateServiceRules(@Nonnull String service, @Nonnull Link quickLink) {
+    return serviceRules.containsKey(service) ?
+        serviceRules.get(service).isVisible(quickLink) : Optional.<Boolean>absent();
+  }
+
+  static <T> List<T> nullToEmptyList(@Nullable List<T> items) {
+    return items != null ? items : Collections.<T>emptyList();
+  }
+}
+
+/**
+ * Groups quicklink filters that are on the same level (e.g. a global evaluator or an evaluator for the "HDFS" service,
+ * etc.). The evaluator pick the most applicable filter for a given quick link. If no applicable filter is found, it
+ * returns {@link Optional#absent()}.
+ * <p>
+ *   Filter evaluation order is the following:
+ *   <ol>
+ *     <li>First, link name filters are evaluated. These match links by name.</li>
+ *     <li>If there is no matching link name filter, link attribute filters are evaluated next. "Hide" type filters
+ *     take precedence to "show" type filters.</li>
+ *     <li>Finally, the match-all filter is evaluated, provided it exists.</li>
+ *   </ol>
+ * </p>
+ */
+class Evaluator {
+  private final Map<String, Boolean> linkNameFilters = new HashMap<>();
+  private final Set<String> showAttributes = new HashSet<>();
+  private final Set<String> hideAttributes = new HashSet<>();
+  private Optional<Boolean> acceptAllFilter = Optional.absent();
+
+  Evaluator(List<Filter> filters) throws QuickLinksProfileEvaluatorException {
+    for (Filter filter: QuickLinksProfileEvaluator.nullToEmptyList(filters)) {
+      if (filter instanceof LinkNameFilter) {
+        String linkName = ((LinkNameFilter)filter).getLinkName();
+        if (linkNameFilters.containsKey(linkName) && linkNameFilters.get(linkName) != filter.isVisible()) {
+          throw new QuickLinksProfileEvaluatorException("Contradicting filters for link name [" + linkName + "]");
+        }
+        linkNameFilters.put(linkName, filter.isVisible());
+      }
+      else if (filter instanceof LinkAttributeFilter) {
+        String linkAttribute = ((LinkAttributeFilter)filter).getLinkAttribute();
+        if (filter.isVisible()) {
+          showAttributes.add(linkAttribute);
+        }
+        else {
+          hideAttributes.add(linkAttribute);
+        }
+        if (showAttributes.contains(linkAttribute) && hideAttributes.contains(linkAttribute)) {
+          throw new QuickLinksProfileEvaluatorException("Contradicting filters for link attribute [" + linkAttribute + "]");
+        }
+      }
+      // If none of the above, it is an accept-all filter. We expect only one for an Evaluator
+      else {
+        if (acceptAllFilter.isPresent() && !acceptAllFilter.get().equals(filter.isVisible())) {
+          throw new QuickLinksProfileEvaluatorException("Contradicting accept-all filters.");
+        }
+        acceptAllFilter = Optional.of(filter.isVisible());
+      }
+    }
+  }
+
+  /**
+   * @param quickLink the link to evaluate
+   * @return Three way evaluation result, which can be one of these:
+   *    show: Optional.of(true), hide: Optional.of(false), don't know: absent optional
+   */
+  Optional<Boolean> isVisible(Link quickLink) {
+    // process first priority filters based on link name
+    if (linkNameFilters.containsKey(quickLink.getName())) {
+      return Optional.of(linkNameFilters.get(quickLink.getName()));
+    }
+
+    // process second priority filters based on link attributes
+    // 'hide' rules take precedence over 'show' rules
+    for (String attribute: QuickLinksProfileEvaluator.nullToEmptyList(quickLink.getAttributes())) {
+      if (hideAttributes.contains(attribute)) return Optional.of(false);
+    }
+    for (String attribute: QuickLinksProfileEvaluator.nullToEmptyList(quickLink.getAttributes())) {
+      if (showAttributes.contains(attribute)) return Optional.of(true);
+    }
+
+    // accept all filter (if exists) is the last priority
+    return acceptAllFilter;
+  }
+}
+
+/**
+ * Simple value class encapsulating a link name an component name.
+ */
+class ServiceComponent {
+  private final String service;
+  private final String component;
+
+  ServiceComponent(String service, String component) {
+    this.service = service;
+    this.component = component;
+  }
+
+  static ServiceComponent of(String service, String component) {
+    return new ServiceComponent(service, component);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ServiceComponent that = (ServiceComponent) o;
+    return Objects.equals(service, that.service) &&
+        Objects.equals(component, that.component);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(service, component);
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluatorException.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluatorException.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluatorException.java
new file mode 100644
index 0000000..c24281a
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluatorException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.state.quicklinksprofile;
+
+public class QuickLinksProfileEvaluatorException extends Exception {
+
+  public QuickLinksProfileEvaluatorException(String message) {
+    super(message);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParser.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParser.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParser.java
index c1f3c86..a3ae677 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParser.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParser.java
@@ -62,7 +62,7 @@ public class QuickLinksProfileParser {
  */
 class QuickLinksFilterDeserializer extends StdDeserializer<Filter> {
   private static final String PARSE_ERROR_MESSAGE =
-      "A filter is not allowed to declare both property_name and link_name at the same time.";
+      "A filter is not allowed to declare both link_name and link_attribute at the same time.";
 
   QuickLinksFilterDeserializer() {
     super(Filter.class);
@@ -71,9 +71,9 @@ class QuickLinksFilterDeserializer extends StdDeserializer<Filter> {
   /**
    * Filter polymorphism is handled here. If a filter object in the JSON document has:
    * <ul>
-   *   <li>a {@code property_name} field, it will parsed as {@link PropertyFilter}</li>
+   *   <li>a {@code link_attribute} field, it will parsed as {@link LinkAttributeFilter}</li>
    *   <li>a {@code link_name} field, it will be parsed as {@link LinkNameFilter}</li>
-   *   <li>both {@code property_name} and {@code link_name}, it will throw a {@link JsonParseException}</li>
+   *   <li>both {@code link_attribute} and {@code link_name}, it will throw a {@link JsonParseException}</li>
    *   <li>neither of the above fields, it will be parsed as {@link AcceptAllFilter}</li>
    * </ul>
    *
@@ -86,11 +86,11 @@ class QuickLinksFilterDeserializer extends StdDeserializer<Filter> {
     Class<? extends Filter> filterClass = null;
     for (String fieldName: ImmutableList.copyOf(root.getFieldNames())) {
       switch(fieldName) {
-        case PropertyFilter.PROPERTY_NAME:
+        case LinkAttributeFilter.LINK_ATTRIBUTE:
           if (null != filterClass) {
             throw new JsonParseException(PARSE_ERROR_MESSAGE, parser.getCurrentLocation());
           }
-          filterClass = PropertyFilter.class;
+          filterClass = LinkAttributeFilter.class;
           break;
         case LinkNameFilter.LINK_NAME:
           if (null != filterClass) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Service.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Service.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Service.java
index 600872f..7724852 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Service.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Service.java
@@ -39,6 +39,14 @@ public class Service {
   @JsonProperty("filters")
   private List<Filter> filters;
 
+  static Service create(String name, List<Filter> filters, List<Component> components) {
+    Service service = new Service();
+    service.setName(name);
+    service.setFilters(filters);
+    service.setComponents(components);
+    return service;
+  }
+
   public String getName() {
     return name;
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/java/org/apache/ambari/server/stack/QuickLinksConfigurationModuleTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/QuickLinksConfigurationModuleTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/QuickLinksConfigurationModuleTest.java
index 190e61b..f6b7dfa 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/stack/QuickLinksConfigurationModuleTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/QuickLinksConfigurationModuleTest.java
@@ -120,8 +120,8 @@ public class QuickLinksConfigurationModuleTest {
 
   @Test
   public void testResolveOverrideProperties() throws Exception{
-    QuickLinks[] results = resolveQuickLinks("parent_quicklinks_with_properties.json",
-        "child_quicklinks_with_properties.json");
+    QuickLinks[] results = resolveQuickLinks("parent_quicklinks_with_attributes.json",
+        "child_quicklinks_with_attributes.json");
     QuickLinks parentQuickLinks = results[0];
     QuickLinks childQuickLinks = results[1];
 
@@ -139,13 +139,13 @@ public class QuickLinksConfigurationModuleTest {
     }
     assertEquals("Links are not properly overridden for foo_ui",
         Lists.newArrayList("authenticated", "sso"),
-        linksByName.get("foo_ui").getProperties());
+        linksByName.get("foo_ui").getAttributes());
     assertEquals("Parent links for foo_jmx are not inherited.",
         Lists.newArrayList("authenticated"),
-        linksByName.get("foo_jmx").getProperties());
+        linksByName.get("foo_jmx").getAttributes());
     assertEquals("Links are not properly overridden for foo_logs",
         new ArrayList<>(),
-        linksByName.get("foo_logs").getProperties());
+        linksByName.get("foo_logs").getAttributes());
   }
 
   private QuickLinks[] resolveQuickLinks(String parentJson, String childJson) throws AmbariException{

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/EvaluatorTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/EvaluatorTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/EvaluatorTest.java
new file mode 100644
index 0000000..bff2e39
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/EvaluatorTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.quicklinksprofile;
+
+import static org.apache.ambari.server.state.quicklinksprofile.Filter.acceptAllFilter;
+import static org.apache.ambari.server.state.quicklinksprofile.Filter.linkNameFilter;
+import static org.apache.ambari.server.state.quicklinksprofile.Filter.linkAttributeFilter;
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+import org.apache.ambari.server.state.quicklinks.Link;
+import org.junit.Test;
+
+import com.google.common.collect.Lists;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+public class EvaluatorTest {
+
+  static final String NAMENODE = "NAMENODE";
+  static final String NAMENODE_UI = "namenode_ui";
+  static final String AUTHENTICATED = "authenticated";
+  static final String NAMENODE_JMX = "namenode_jmx";
+  static final String SSO = "sso";
+  
+  private Link namenodeUi;
+  private Link nameNodeJmx;
+
+  public EvaluatorTest() {
+    namenodeUi = new Link();
+    namenodeUi.setComponentName(NAMENODE);
+    namenodeUi.setName(NAMENODE_UI);
+    namenodeUi.setAttributes(ImmutableList.of(AUTHENTICATED));
+
+    nameNodeJmx = new Link();
+    nameNodeJmx.setComponentName(NAMENODE);
+    nameNodeJmx.setName(NAMENODE_JMX);
+  }
+
+  /**
+   * Evaluators should work when initialized with {@code null} or an empty list of filters.
+   */
+  @Test
+  public void testWithEmptyFilters() throws Exception {
+    Evaluator evaluator = new Evaluator(new ArrayList<Filter>());
+    assertEquals(Optional.absent(), evaluator.isVisible(namenodeUi));
+
+    Evaluator evaluator2 = new Evaluator(null);
+    assertEquals(Optional.absent(), evaluator2.isVisible(namenodeUi));
+  }
+
+  /**
+   * Evaluator should return {@link Optional#absent()} when the link doesn't match any filters
+   */
+  @Test
+  public void testNoMatchingFilter() throws Exception {
+    List<Filter> filters = Lists.newArrayList(
+        linkNameFilter(NAMENODE_JMX, true),
+        linkAttributeFilter(SSO, false));
+    Evaluator evaluator = new Evaluator(filters);
+    assertEquals(Optional.absent(), evaluator.isVisible(namenodeUi));
+  }
+
+  /**
+   * Link name filters should be evaluated first
+   */
+  @Test
+  public void testLinkNameFiltersEvaluatedFirst() throws Exception {
+    List<Filter> filters = Lists.newArrayList(
+      acceptAllFilter(false),
+      linkNameFilter(NAMENODE_UI, true),
+      linkNameFilter(NAMENODE_JMX, false),
+      linkAttributeFilter(AUTHENTICATED, false),
+      linkAttributeFilter(SSO, false));
+    Evaluator evaluator = new Evaluator(filters);
+    assertEquals(Optional.of(true), evaluator.isVisible(namenodeUi));
+  }
+
+  /**
+   * Link attribute filters should be evaluated only if the link does not match any link name filters.
+   */
+  @Test
+  public void testLinkAttributeFiltersEvaluatedSecondly() throws Exception {
+    List<Filter> filters = Lists.newArrayList(
+        acceptAllFilter(false),
+        linkNameFilter(NAMENODE_JMX, false),
+        linkAttributeFilter(AUTHENTICATED, true),
+        linkAttributeFilter(SSO, true));
+    Evaluator evaluator = new Evaluator(filters);
+    assertEquals(Optional.of(true), evaluator.isVisible(namenodeUi));
+  }
+
+  /**
+   * Link attribute filters work with links with null attributes. (No NPE is thrown)
+   */
+  @Test
+  public void testLinkAttributeFiltersWorkWithNullAttributes() throws Exception {
+    List<Filter> filters = Lists.newArrayList(
+        acceptAllFilter(true),
+        linkAttributeFilter(AUTHENTICATED, false),
+        linkAttributeFilter(SSO, false));
+    Evaluator evaluator = new Evaluator(filters);
+    assertEquals(Optional.of(true), evaluator.isVisible(nameNodeJmx));
+  }
+
+
+  /**
+   * If the link matches both a show and hide type link attribute filter, then it will be evaluated as hidden.
+   */
+  @Test
+  public void testHideFilterTakesPrecedence() throws Exception {
+    List<Filter> filters = Lists.<Filter>newArrayList(
+        linkAttributeFilter(AUTHENTICATED, false),
+        linkAttributeFilter(SSO, true));
+    Evaluator evaluator = new Evaluator(filters);
+    namenodeUi.setAttributes(ImmutableList.of(AUTHENTICATED, SSO));
+    assertEquals(Optional.of(false), evaluator.isVisible(namenodeUi));
+  }
+
+  /**
+   * Accept-all filters are only evaluated if the link does not match any link name or link attribute filters.
+   */
+  @Test
+  public void acceptAllFilterEvaluatedLast() throws Exception {
+    List<Filter> filters = Lists.newArrayList(
+        acceptAllFilter(false),
+        linkNameFilter(NAMENODE_JMX, true),
+        linkAttributeFilter(SSO, true));
+    Evaluator evaluator = new Evaluator(filters);
+    assertEquals(Optional.of(false), evaluator.isVisible(namenodeUi));
+  }
+
+  /**
+   * Contradicting link name filters should result in {@link QuickLinksProfileEvaluatorException}.
+   */
+  @Test(expected = QuickLinksProfileEvaluatorException.class)
+  public void contradictingLinkNameFiltersRejected() throws Exception {
+    List<Filter> filters = Lists.newArrayList(
+        linkNameFilter(NAMENODE_JMX, true),
+        linkNameFilter(NAMENODE_JMX, false),
+        linkAttributeFilter(SSO, true));
+    new Evaluator(filters);
+  }
+
+  /**
+   * Contradicting link attribute filters should result in {@link QuickLinksProfileEvaluatorException}.
+   */
+  @Test(expected = QuickLinksProfileEvaluatorException.class)
+  public void contradictingLinkAttributeFiltersRejected() throws Exception {
+    List<Filter> filters = Lists.<Filter>newArrayList(
+        linkAttributeFilter(SSO, true),
+        linkAttributeFilter(SSO, false));
+    new Evaluator(filters);
+  }
+
+  /**
+   * Contradicting accept-all filters should result in {@link QuickLinksProfileEvaluatorException}.
+   */
+  @Test(expected = QuickLinksProfileEvaluatorException.class)
+  public void contradictingAcceptAllFiltersRejected() throws Exception {
+    List<Filter> filters = Lists.newArrayList(
+        linkNameFilter(NAMENODE_JMX, true),
+        linkAttributeFilter(SSO, true),
+        acceptAllFilter(true),
+        acceptAllFilter(false));
+    new Evaluator(filters);
+  }
+
+  /**
+   * Duplicate filter declarations are ok if their visibility rule is the same
+   */
+  @Test
+  public void duplicateFiltersAreOkIfDoNotContradict() throws Exception {
+    List<Filter> filters = Lists.newArrayList(
+        acceptAllFilter(true),
+        acceptAllFilter(true),
+        linkNameFilter(NAMENODE_JMX, false),
+        linkNameFilter(NAMENODE_JMX, false),
+        linkAttributeFilter(SSO, false),
+        linkAttributeFilter(SSO, false));
+    Evaluator evaluator = new Evaluator(filters);
+    assertEquals(Optional.of(true), evaluator.isVisible(namenodeUi));
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluatorTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluatorTest.java
new file mode 100644
index 0000000..a770e0f
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluatorTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.quicklinksprofile;
+
+import static org.junit.Assert.*;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.ambari.server.state.quicklinks.Link;
+import org.junit.Test;
+
+
+public class QuickLinksProfileEvaluatorTest {
+
+  static final String AUTHENTICATED = "authenticated";
+  static final String SSO = "sso";
+  static final String NAMENODE = "NAMENODE";
+  static final String HDFS = "HDFS";
+  static final String NAMENODE_UI = "namenode_ui";
+
+
+  private Link namenodeUi;
+
+  public QuickLinksProfileEvaluatorTest() {
+    namenodeUi = new Link();
+    namenodeUi.setComponentName(NAMENODE);
+    namenodeUi.setName(NAMENODE_UI);
+    namenodeUi.setAttributes(ImmutableList.of(AUTHENTICATED));
+  }
+
+  /**
+   * Test to prove that {@link QuickLinksProfileEvaluator} can accept quicklink profiles with null values.
+   */
+  @Test
+  public void testNullsAreAccepted() throws Exception {
+    QuickLinksProfile profile = new QuickLinksProfile();
+    QuickLinksProfileEvaluator evaluator = new QuickLinksProfileEvaluator(profile);
+    assertFalse("Link should be hidden as there are no applicable filters", evaluator.isVisible(HDFS, namenodeUi));
+
+    Service service = Service.create(HDFS, null, null);
+    profile = QuickLinksProfile.create(null, ImmutableList.of(service));
+    evaluator = new QuickLinksProfileEvaluator(profile);
+    assertFalse("Link should be hidden as there are no applicable filters", evaluator.isVisible(HDFS, namenodeUi));
+  }
+
+  /**
+   * Test to prove that {@link Link}'s with unset {@code componentName} fields are handled properly.
+   */
+  @Test
+  public void testLinkWithNoComponentField() throws Exception {
+    Component component = Component.create(NAMENODE,
+        ImmutableList.<Filter>of(Filter.linkNameFilter(NAMENODE_UI, true)));
+
+    Service service = Service.create(HDFS, ImmutableList.<Filter>of(), ImmutableList.of(component));
+
+    QuickLinksProfile profile = QuickLinksProfile.create(ImmutableList.<Filter>of(), ImmutableList.of(service));
+    QuickLinksProfileEvaluator evaluator = new QuickLinksProfileEvaluator(profile);
+    namenodeUi.setComponentName(null);
+    assertFalse("Link should be hidden as there are no applicable filters", evaluator.isVisible(HDFS, namenodeUi));
+  }
+
+  /**
+   * Test to prove that component level filters are evaluated first.
+   */
+  @Test
+  public void testComponentLevelFiltersEvaluatedFirst() throws Exception {
+    Component component = Component.create(
+        NAMENODE,
+        ImmutableList.<Filter>of(Filter.linkAttributeFilter(AUTHENTICATED, true)));
+
+    Service service = Service.create(
+        HDFS,
+        ImmutableList.<Filter>of(Filter.linkAttributeFilter(AUTHENTICATED, false)),
+        ImmutableList.of(component));
+
+    QuickLinksProfile profile = QuickLinksProfile.create(
+        ImmutableList.<Filter>of(Filter.acceptAllFilter(false)),
+        ImmutableList.of(service));
+
+    QuickLinksProfileEvaluator evaluator = new QuickLinksProfileEvaluator(profile);
+    assertTrue("Component level filter should have been applied.", evaluator.isVisible(HDFS, namenodeUi));
+  }
+
+  /**
+   * Test to prove that service level filters are evaluated secondly.
+   */
+  @Test
+  public void testServiceLevelFiltersEvaluatedSecondly() throws Exception {
+    Component component = Component.create(NAMENODE,
+        ImmutableList.<Filter>of(Filter.linkAttributeFilter(SSO, false)));
+
+    Service service = Service.create(HDFS,
+        ImmutableList.<Filter>of(Filter.linkAttributeFilter(AUTHENTICATED, true)),
+        ImmutableList.of(component));
+
+    QuickLinksProfile profile = QuickLinksProfile.create(
+        ImmutableList.<Filter>of(Filter.acceptAllFilter(false)),
+        ImmutableList.of(service));
+
+    QuickLinksProfileEvaluator evaluator = new QuickLinksProfileEvaluator(profile);
+    assertTrue("Component level filter should have been applied.", evaluator.isVisible(HDFS, namenodeUi));
+  }
+
+  /**
+   * Test to prove that global filters are evaluated last.
+   */
+  @Test
+  public void testGlobalFiltersEvaluatedLast() throws Exception {
+    Component component = Component.create(NAMENODE,
+        ImmutableList.<Filter>of(Filter.linkAttributeFilter(SSO, false)));
+
+    Service service = Service.create(HDFS,
+        ImmutableList.<Filter>of(Filter.linkAttributeFilter(SSO, false)),
+        ImmutableList.of(component));
+
+    QuickLinksProfile profile = QuickLinksProfile.create(
+        ImmutableList.<Filter>of(Filter.acceptAllFilter(true)),
+        ImmutableList.of(service));
+
+    QuickLinksProfileEvaluator evaluator = new QuickLinksProfileEvaluator(profile);
+    assertTrue("Global filter should have been applied.", evaluator.isVisible(HDFS, namenodeUi));
+  }
+
+  /**
+   * Test to prove that the link is hidden if no filters apply.
+   */
+  @Test
+  public void testNoMatchingRule() throws Exception {
+    Component component1 = Component.create(NAMENODE,
+        ImmutableList.<Filter>of(Filter.linkAttributeFilter(SSO, true)));
+
+    Component component2 = Component.create("DATANODE",
+        ImmutableList.<Filter>of(Filter.acceptAllFilter(true)));
+
+    Service service1 = Service.create(HDFS,
+        ImmutableList.<Filter>of(Filter.linkAttributeFilter(SSO, true)),
+        ImmutableList.of(component1, component2));
+
+    Service service2 = Service.create("YARN",
+        ImmutableList.<Filter>of(Filter.acceptAllFilter(true)),
+        ImmutableList.<Component>of());
+
+    QuickLinksProfile profile = QuickLinksProfile.create(
+        ImmutableList.<Filter>of(Filter.linkAttributeFilter(SSO, true)),
+        ImmutableList.of(service1, service2));
+
+    QuickLinksProfileEvaluator evaluator = new QuickLinksProfileEvaluator(profile);
+    assertFalse("No filters should have been applied, so default false should have been returned.",
+        evaluator.isVisible(HDFS, namenodeUi));
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParserTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParserTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParserTest.java
index 5f93475..6f5dd07 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParserTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParserTest.java
@@ -35,7 +35,7 @@ public class QuickLinksProfileParserTest {
     QuickLinksProfile profile = parser.parse(Resources.getResource(profileName));
     assertEquals(1, profile.getFilters().size());
     assertEquals(
-        Filter.propertyFilter("sso", true),
+        Filter.linkAttributeFilter("sso", true),
         profile.getFilters().get(0));
     assertEquals(2, profile.getServices().size());
 
@@ -44,7 +44,7 @@ public class QuickLinksProfileParserTest {
     assertEquals(1, hdfs.getFilters().size());
     assertEquals(1, hdfs.getComponents().size());
     assertEquals(
-        Filter.propertyFilter("authenticated", true),
+        Filter.linkAttributeFilter("authenticated", true),
         hdfs.getFilters().get(0));
 
     Component nameNode = hdfs.getComponents().get(0);

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/resources/TestAmbaryServer.samples/dummy_common_services/HIVE/0.11.0.2.0.5.0/package/.hash
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/TestAmbaryServer.samples/dummy_common_services/HIVE/0.11.0.2.0.5.0/package/.hash b/ambari-server/src/test/resources/TestAmbaryServer.samples/dummy_common_services/HIVE/0.11.0.2.0.5.0/package/.hash
deleted file mode 100644
index 9550dab..0000000
--- a/ambari-server/src/test/resources/TestAmbaryServer.samples/dummy_common_services/HIVE/0.11.0.2.0.5.0/package/.hash
+++ /dev/null
@@ -1 +0,0 @@
-dummy_hash
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/resources/TestAmbaryServer.samples/dummy_stack/HIVE/package/.hash
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/TestAmbaryServer.samples/dummy_stack/HIVE/package/.hash b/ambari-server/src/test/resources/TestAmbaryServer.samples/dummy_stack/HIVE/package/.hash
deleted file mode 100644
index 9550dab..0000000
--- a/ambari-server/src/test/resources/TestAmbaryServer.samples/dummy_stack/HIVE/package/.hash
+++ /dev/null
@@ -1 +0,0 @@
-dummy_hash
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/resources/child_quicklinks_with_attributes.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/child_quicklinks_with_attributes.json b/ambari-server/src/test/resources/child_quicklinks_with_attributes.json
new file mode 100644
index 0000000..cc91ab9
--- /dev/null
+++ b/ambari-server/src/test/resources/child_quicklinks_with_attributes.json
@@ -0,0 +1,64 @@
+{
+  "name": "default",
+  "description": "default quick links configuration",
+  "configuration": {
+    "protocol":
+    {
+      "type":"http",
+      "checks":[
+        {
+          "property":"foo.http.policy",
+          "desired":"HTTP_ONLY",
+          "site":"foo-site"
+        }
+      ]
+    },
+
+    "links": [
+      {
+        "name": "foo_ui",
+        "label": "Foo UI",
+        "requires_user_name": "false",
+        "url": "%@://%@:%@",
+        "attributes": ["authenticated", "sso"],
+        "port":{
+          "http_property": "foo.ui.webapp.address",
+          "http_default_port": "19888",
+          "https_property": "foo.ui.webapp.https.address",
+          "https_default_port": "8090",
+          "regex": "\\w*:(\\d+)",
+          "site": "foo-site"
+        }
+      },
+      {
+        "name":"foo_jmx",
+        "label":"Foo JMX",
+        "requires_user_name":"false",
+        "url":"%@://%@:%@/jmx",
+        "port":{
+          "http_property": "foo.jmx.webapp.address",
+          "http_default_port": "19888",
+          "https_property": "foo.jmx.webapp.https.address",
+          "https_default_port": "8090",
+          "regex": "\\w*:(\\d+)",
+          "site": "foo-site"
+        }
+      },
+      {
+        "name": "foo_logs",
+        "label": "Foo logs",
+        "requires_user_name": "false",
+        "url": "%@://%@:%@/logs",
+        "attributes": [],
+        "port":{
+          "http_property": "foo.logs.webapp.address",
+          "http_default_port": "19888",
+          "https_property": "foo.webapp.https.address",
+          "https_default_port": "8090",
+          "regex": "\\w*:(\\d+)",
+          "site": "foo-site"
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/resources/child_quicklinks_with_properties.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/child_quicklinks_with_properties.json b/ambari-server/src/test/resources/child_quicklinks_with_properties.json
deleted file mode 100644
index 36cd4f3..0000000
--- a/ambari-server/src/test/resources/child_quicklinks_with_properties.json
+++ /dev/null
@@ -1,64 +0,0 @@
-{
-  "name": "default",
-  "description": "default quick links configuration",
-  "configuration": {
-    "protocol":
-    {
-      "type":"http",
-      "checks":[
-        {
-          "property":"foo.http.policy",
-          "desired":"HTTP_ONLY",
-          "site":"foo-site"
-        }
-      ]
-    },
-
-    "links": [
-      {
-        "name": "foo_ui",
-        "label": "Foo UI",
-        "requires_user_name": "false",
-        "url": "%@://%@:%@",
-        "properties": ["authenticated", "sso"],
-        "port":{
-          "http_property": "foo.ui.webapp.address",
-          "http_default_port": "19888",
-          "https_property": "foo.ui.webapp.https.address",
-          "https_default_port": "8090",
-          "regex": "\\w*:(\\d+)",
-          "site": "foo-site"
-        }
-      },
-      {
-        "name":"foo_jmx",
-        "label":"Foo JMX",
-        "requires_user_name":"false",
-        "url":"%@://%@:%@/jmx",
-        "port":{
-          "http_property": "foo.jmx.webapp.address",
-          "http_default_port": "19888",
-          "https_property": "foo.jmx.webapp.https.address",
-          "https_default_port": "8090",
-          "regex": "\\w*:(\\d+)",
-          "site": "foo-site"
-        }
-      },
-      {
-        "name": "foo_logs",
-        "label": "Foo logs",
-        "requires_user_name": "false",
-        "url": "%@://%@:%@/logs",
-        "properties": [],
-        "port":{
-          "http_property": "foo.logs.webapp.address",
-          "http_default_port": "19888",
-          "https_property": "foo.webapp.https.address",
-          "https_default_port": "8090",
-          "regex": "\\w*:(\\d+)",
-          "site": "foo-site"
-        }
-      }
-    ]
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/resources/example_quicklinks_profile.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/example_quicklinks_profile.json b/ambari-server/src/test/resources/example_quicklinks_profile.json
index 028d011..2fa33a4 100644
--- a/ambari-server/src/test/resources/example_quicklinks_profile.json
+++ b/ambari-server/src/test/resources/example_quicklinks_profile.json
@@ -1,7 +1,7 @@
 {
   "filters": [
     {
-      "property_name": "sso",
+      "link_attribute": "sso",
       "visible": true
     }
   ],
@@ -10,7 +10,7 @@
       "name": "HDFS",
       "filters": [
         {
-          "property_name": "authenticated",
+          "link_attribute": "authenticated",
           "visible": true
         }
       ],

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/resources/inconsistent_quicklinks_profile.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/inconsistent_quicklinks_profile.json b/ambari-server/src/test/resources/inconsistent_quicklinks_profile.json
index e5bc310..aa4e5e0 100644
--- a/ambari-server/src/test/resources/inconsistent_quicklinks_profile.json
+++ b/ambari-server/src/test/resources/inconsistent_quicklinks_profile.json
@@ -1,7 +1,7 @@
 {
   "filters": [
     {
-      "property_name": "sso",
+      "link_attribute": "sso",
       "link_name": "namenode_ui",
       "visible": true
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/resources/parent_quicklinks_with_attributes.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/parent_quicklinks_with_attributes.json b/ambari-server/src/test/resources/parent_quicklinks_with_attributes.json
new file mode 100644
index 0000000..525e38e
--- /dev/null
+++ b/ambari-server/src/test/resources/parent_quicklinks_with_attributes.json
@@ -0,0 +1,65 @@
+{
+  "name": "default",
+  "description": "default quick links configuration",
+  "configuration": {
+    "protocol":
+    {
+      "type":"http",
+      "checks":[
+        {
+          "property":"foo.http.policy",
+          "desired":"HTTP_ONLY",
+          "site":"foo-site"
+        }
+      ]
+    },
+
+    "links": [
+      {
+        "name": "foo_ui",
+        "label": "Foo UI",
+        "requires_user_name": "false",
+        "url": "%@://%@:%@",
+        "attributes": ["authenticated"],
+        "port":{
+          "http_property": "foo.ui.webapp.address",
+          "http_default_port": "19888",
+          "https_property": "foo.ui.webapp.https.address",
+          "https_default_port": "8090",
+          "regex": "\\w*:(\\d+)",
+          "site": "foo-site"
+        }
+      },
+      {
+        "name":"foo_jmx",
+        "label":"Foo JMX",
+        "requires_user_name":"false",
+        "url":"%@://%@:%@/jmx",
+        "attributes": ["authenticated"],
+        "port":{
+          "http_property": "foo.jmx.webapp.address",
+          "http_default_port": "19888",
+          "https_property": "foo.jmx.webapp.https.address",
+          "https_default_port": "8090",
+          "regex": "\\w*:(\\d+)",
+          "site": "foo-site"
+        }
+      },
+      {
+        "name": "foo_logs",
+        "label": "Foo logs",
+        "requires_user_name": "false",
+        "url": "%@://%@:%@/logs",
+        "attributes": ["authenticated"],
+        "port":{
+          "http_property": "foo.logs.webapp.address",
+          "http_default_port": "19888",
+          "https_property": "foo.webapp.https.address",
+          "https_default_port": "8090",
+          "regex": "\\w*:(\\d+)",
+          "site": "foo-site"
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/83f3b6fb/ambari-server/src/test/resources/parent_quicklinks_with_properties.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/parent_quicklinks_with_properties.json b/ambari-server/src/test/resources/parent_quicklinks_with_properties.json
deleted file mode 100644
index a315f3f..0000000
--- a/ambari-server/src/test/resources/parent_quicklinks_with_properties.json
+++ /dev/null
@@ -1,65 +0,0 @@
-{
-  "name": "default",
-  "description": "default quick links configuration",
-  "configuration": {
-    "protocol":
-    {
-      "type":"http",
-      "checks":[
-        {
-          "property":"foo.http.policy",
-          "desired":"HTTP_ONLY",
-          "site":"foo-site"
-        }
-      ]
-    },
-
-    "links": [
-      {
-        "name": "foo_ui",
-        "label": "Foo UI",
-        "requires_user_name": "false",
-        "url": "%@://%@:%@",
-        "properties": ["authenticated"],
-        "port":{
-          "http_property": "foo.ui.webapp.address",
-          "http_default_port": "19888",
-          "https_property": "foo.ui.webapp.https.address",
-          "https_default_port": "8090",
-          "regex": "\\w*:(\\d+)",
-          "site": "foo-site"
-        }
-      },
-      {
-        "name":"foo_jmx",
-        "label":"Foo JMX",
-        "requires_user_name":"false",
-        "url":"%@://%@:%@/jmx",
-        "properties": ["authenticated"],
-        "port":{
-          "http_property": "foo.jmx.webapp.address",
-          "http_default_port": "19888",
-          "https_property": "foo.jmx.webapp.https.address",
-          "https_default_port": "8090",
-          "regex": "\\w*:(\\d+)",
-          "site": "foo-site"
-        }
-      },
-      {
-        "name": "foo_logs",
-        "label": "Foo logs",
-        "requires_user_name": "false",
-        "url": "%@://%@:%@/logs",
-        "properties": ["authenticated"],
-        "port":{
-          "http_property": "foo.logs.webapp.address",
-          "http_default_port": "19888",
-          "https_property": "foo.webapp.https.address",
-          "https_default_port": "8090",
-          "regex": "\\w*:(\\d+)",
-          "site": "foo-site"
-        }
-      }
-    ]
-  }
-}
\ No newline at end of file