This is an automated email from the ASF dual-hosted git repository. benyoka pushed a commit to branch branch-2.7 in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/branch-2.7 by this push: new a86334b [AMBARI-24938] quick link profiles can override link url (benyoka) (#2646) (#2829) a86334b is described below commit a86334b7047672ec83582e4738bd352d88822f79 Author: benyoka <beny...@users.noreply.github.com> AuthorDate: Fri Feb 22 11:19:53 2019 +0100 [AMBARI-24938] quick link profiles can override link url (benyoka) (#2646) (#2829) * AMBARI-24938 quick link profiles can override link url (benyoka) * AMBARI-24938 new unit test in QuickLinkArtifactResourceProviderTest (benyoka) * AMBARI-24938 review findings + check style issue + unit test fix (benyoka) --- .../QuickLinkArtifactResourceProvider.java | 5 +- .../state/quicklinksprofile/AcceptAllFilter.java | 1 + .../server/state/quicklinksprofile/Component.java | 13 +- .../DefaultQuickLinkVisibilityController.java | 181 ++++++++------------- .../server/state/quicklinksprofile/Filter.java | 11 +- ...ibilityController.java => FilterEvaluator.java} | 115 +------------ .../quicklinksprofile/LinkAttributeFilter.java | 11 +- .../state/quicklinksprofile/LinkNameFilter.java | 49 +++++- .../QuickLinkVisibilityController.java | 9 + .../state/quicklinksprofile/QuickLinksProfile.java | 14 +- .../QuickLinksProfileBuilder.java | 10 +- .../quicklinksprofile/QuickLinksProfileParser.java | 39 ++--- .../server/state/quicklinksprofile/Service.java | 15 +- .../ShowAllLinksVisibilityController.java | 6 + .../StreamUtils.java} | 26 +-- .../internal/ProvisionClusterRequestTest.java | 6 +- .../QuickLinkArtifactResourceProviderTest.java | 35 ++-- .../quicklinksprofile/FilterEvaluatorTest.java | 10 +- .../QuickLinkVisibilityControllerTest.java | 83 +++++++++- .../QuickLinksProfileBuilderTest.java | 28 +++- .../QuickLinksProfileParserTest.java | 17 +- .../test/resources/example_quicklinks_profile.json | 2 + .../inconsistent_quicklinks_profile_4.json | 10 ++ 23 files changed, 375 insertions(+), 321 deletions(-) diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProvider.java index 534c369..6553db3 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProvider.java @@ -181,7 +181,7 @@ public class QuickLinkArtifactResourceProvider extends AbstractControllerResourc } } - setVisibility(serviceInfo.getName(), serviceQuickLinks); + setVisibilityAndOverrides(serviceInfo.getName(), serviceQuickLinks); List<Resource> serviceResources = new ArrayList<>(); for (QuickLinksConfigurationInfo quickLinksConfigurationInfo : serviceQuickLinks) { @@ -208,13 +208,14 @@ public class QuickLinkArtifactResourceProvider extends AbstractControllerResourc * @param serviceName the name of the service * @param serviceQuickLinks the links */ - private void setVisibility(String serviceName, List<QuickLinksConfigurationInfo> serviceQuickLinks) { + private void setVisibilityAndOverrides(String serviceName, List<QuickLinksConfigurationInfo> serviceQuickLinks) { QuickLinkVisibilityController visibilityController = getManagementController().getQuicklinkVisibilityController(); for(QuickLinksConfigurationInfo configurationInfo: serviceQuickLinks) { for (QuickLinks links: configurationInfo.getQuickLinksConfigurationMap().values()) { for(Link link: links.getQuickLinksConfiguration().getLinks()) { link.setVisible(visibilityController.isVisible(serviceName, link)); + visibilityController.getUrlOverride(serviceName, link).ifPresent(link::setUrl); } } } 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 069ae3f..01500bf 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 @@ -48,4 +48,5 @@ public class AcceptAllFilter extends Filter { public String toString() { return getClass().getSimpleName() + " (visible=" + isVisible() + ")"; } + } 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 729e5d4..f07a05a 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 @@ -18,18 +18,19 @@ package org.apache.ambari.server.state.quicklinksprofile; -import java.util.List; +import static java.util.Collections.emptyList; -import org.codehaus.jackson.annotate.JsonIgnoreProperties; -import org.codehaus.jackson.annotate.JsonProperty; -import org.codehaus.jackson.map.annotate.JsonSerialize; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Preconditions; /** * Class to represent component-level filter definitions */ -@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL) +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class Component { @JsonProperty("name") @@ -58,7 +59,7 @@ public class Component { * @return the quicklink filters for this component */ public List<Filter> getFilters() { - return filters; + return null != filters ? filters : emptyList(); } public void setFilters(List<Filter> filters) { diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/DefaultQuickLinkVisibilityController.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/DefaultQuickLinkVisibilityController.java index d0b0442..b8f6f1b 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/DefaultQuickLinkVisibilityController.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/DefaultQuickLinkVisibilityController.java @@ -18,45 +18,97 @@ package org.apache.ambari.server.state.quicklinksprofile; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toMap; + import java.util.Collection; 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.Optional; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.ambari.server.state.quicklinks.Link; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import com.google.common.base.Optional; +import com.google.common.collect.Sets; /** * This class can evaluate whether a quicklink has to be shown or hidden based on the received {@link QuickLinksProfile}. */ public class DefaultQuickLinkVisibilityController implements QuickLinkVisibilityController { + private static final Logger LOG = LoggerFactory.getLogger(DefaultQuickLinkVisibilityController.class); + private final FilterEvaluator globalRules; private final Map<String, FilterEvaluator> serviceRules = new HashMap<>(); - private final Map<ServiceComponent, FilterEvaluator> componentRules = new HashMap<>(); + /** + * Map of (service name, component name) -> filter evaluator + */ + private final Map<Pair<String, String>, FilterEvaluator> componentRules = new HashMap<>(); + /** + * Map of (service name, link name) -> url + */ + private final Map<Pair<String, String>, String> urlOverrides = new HashMap<>(); + public DefaultQuickLinkVisibilityController(QuickLinksProfile profile) throws QuickLinksProfileEvaluationException { int filterCount = size(profile.getFilters()); globalRules = new FilterEvaluator(profile.getFilters()); - for (Service service: nullToEmptyList(profile.getServices())) { + for (Service service: profile.getServices()) { filterCount += size(service.getFilters()); serviceRules.put(service.getName(), new FilterEvaluator(service.getFilters())); - for (Component component: nullToEmptyList(service.getComponents())) { + for (Component component: service.getComponents()) { filterCount += size(component.getFilters()); - componentRules.put(ServiceComponent.of(service.getName(), component.getName()), + componentRules.put(Pair.of(service.getName(), component.getName()), new FilterEvaluator(component.getFilters())); } } if (filterCount == 0) { throw new QuickLinksProfileEvaluationException("At least one filter must be defined."); } + + // compute url overrides + String globalOverrides = LinkNameFilter.getLinkNameFilters(profile.getFilters().stream()) + .filter(f -> f.getLinkUrl() != null) + .map(f -> f.getLinkName() + " -> " + f.getLinkUrl()) + .collect(joining(", ")); + if (!globalOverrides.isEmpty()) { + LOG.warn("Link url overrides only work on service and component levels. The following global overrides will be " + + "ignored: {}", globalOverrides); + } + for (Service service : profile.getServices()) { + urlOverrides.putAll(getUrlOverrides(service.getName(), service.getFilters())); + + for (Component component : service.getComponents()) { + Map<Pair<String, String>, String> componentUrlOverrides = getUrlOverrides(service.getName(), component.getFilters()); + Set<Pair<String, String>> duplicateOverrides = Sets.intersection(urlOverrides.keySet(), componentUrlOverrides.keySet()); + if (!duplicateOverrides.isEmpty()) { + LOG.warn("Duplicate url overrides in quick links profile: {}", duplicateOverrides); + } + urlOverrides.putAll(componentUrlOverrides); + } + } + } + + private Map<Pair<String, String>, String> getUrlOverrides(String serviceName, Collection<Filter> filters) { + return filters.stream() + .filter( f -> f instanceof LinkNameFilter && null != ((LinkNameFilter)f).getLinkUrl() ) + .map( f -> { + LinkNameFilter lnf = (LinkNameFilter)f; + return Pair.of(Pair.of(serviceName, lnf.getLinkName()), lnf.getLinkUrl()); + }) + .collect( toMap(Pair::getKey, Pair::getValue) ); + } + + @Override + public Optional<String> getUrlOverride(@Nonnull String service, @Nonnull Link quickLink) { + return Optional.ofNullable( urlOverrides.get(Pair.of(service, quickLink.getName())) ); } /** @@ -78,7 +130,7 @@ public class DefaultQuickLinkVisibilityController implements QuickLinkVisibility } // Global rules are evaluated lastly. If no rules apply to the link, it will be hidden. - return globalRules.isVisible(quickLink).or(false); + return globalRules.isVisible(quickLink).orElse(false); } private int size(@Nullable Collection<?> collection) { @@ -87,127 +139,22 @@ public class DefaultQuickLinkVisibilityController implements QuickLinkVisibility private Optional<Boolean> evaluateComponentRules(@Nonnull String service, @Nonnull Link quickLink) { if (null == quickLink.getComponentName()) { - return Optional.absent(); + return Optional.empty(); } else { - FilterEvaluator componentEvaluator = componentRules.get(ServiceComponent.of(service, quickLink.getComponentName())); - return componentEvaluator != null ? componentEvaluator.isVisible(quickLink) : Optional.absent(); + FilterEvaluator componentEvaluator = componentRules.get(Pair.of(service, quickLink.getComponentName())); + return componentEvaluator != null ? componentEvaluator.isVisible(quickLink) : Optional.empty(); } } private Optional<Boolean> evaluateServiceRules(@Nonnull String service, @Nonnull Link quickLink) { return serviceRules.containsKey(service) ? - serviceRules.get(service).isVisible(quickLink) : Optional.absent(); + serviceRules.get(service).isVisible(quickLink) : Optional.empty(); } static <T> List<T> nullToEmptyList(@Nullable List<T> items) { return items != null ? items : Collections.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 FilterEvaluator { - 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(); - - FilterEvaluator(List<Filter> filters) throws QuickLinksProfileEvaluationException { - for (Filter filter: DefaultQuickLinkVisibilityController.nullToEmptyList(filters)) { - if (filter instanceof LinkNameFilter) { - String linkName = ((LinkNameFilter)filter).getLinkName(); - if (linkNameFilters.containsKey(linkName) && linkNameFilters.get(linkName) != filter.isVisible()) { - throw new QuickLinksProfileEvaluationException("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 QuickLinksProfileEvaluationException("Contradicting filters for link attribute [" + linkAttribute + "]"); - } - } - // If none of the above, it is an accept-all filter. We expect only one of this type for an Evaluator - else { - if (acceptAllFilter.isPresent() && !acceptAllFilter.get().equals(filter.isVisible())) { - throw new QuickLinksProfileEvaluationException("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: DefaultQuickLinkVisibilityController.nullToEmptyList(quickLink.getAttributes())) { - if (hideAttributes.contains(attribute)) return Optional.of(false); - } - for (String attribute: DefaultQuickLinkVisibilityController.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 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 26410a5..2b54e2f 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 @@ -19,10 +19,10 @@ package org.apache.ambari.server.state.quicklinksprofile; import org.apache.ambari.server.state.quicklinks.Link; -import org.codehaus.jackson.annotate.JsonIgnoreProperties; -import org.codehaus.jackson.annotate.JsonProperty; -import org.codehaus.jackson.map.annotate.JsonSerialize; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.base.Preconditions; /** @@ -66,9 +66,14 @@ public abstract class Filter { } static LinkNameFilter linkNameFilter(String linkName, boolean visible) { + return linkNameFilter(linkName, null, visible); + } + + static LinkNameFilter linkNameFilter(String linkName, String linkUrl, boolean visible) { Preconditions.checkNotNull(linkName, "Link name must not be null"); LinkNameFilter linkNameFilter = new LinkNameFilter(); linkNameFilter.setLinkName(linkName); + linkNameFilter.setLinkUrl(linkUrl); linkNameFilter.setVisible(visible); return linkNameFilter; } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/DefaultQuickLinkVisibilityController.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/FilterEvaluator.java similarity index 51% copy from ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/DefaultQuickLinkVisibilityController.java copy to ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/FilterEvaluator.java index d0b0442..5b6124a 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/DefaultQuickLinkVisibilityController.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/FilterEvaluator.java @@ -18,97 +18,19 @@ package org.apache.ambari.server.state.quicklinksprofile; -import java.util.Collection; -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.Optional; 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 DefaultQuickLinkVisibilityController implements QuickLinkVisibilityController { - private final FilterEvaluator globalRules; - private final Map<String, FilterEvaluator> serviceRules = new HashMap<>(); - private final Map<ServiceComponent, FilterEvaluator> componentRules = new HashMap<>(); - - public DefaultQuickLinkVisibilityController(QuickLinksProfile profile) throws QuickLinksProfileEvaluationException { - int filterCount = size(profile.getFilters()); - globalRules = new FilterEvaluator(profile.getFilters()); - for (Service service: nullToEmptyList(profile.getServices())) { - filterCount += size(service.getFilters()); - serviceRules.put(service.getName(), new FilterEvaluator(service.getFilters())); - for (Component component: nullToEmptyList(service.getComponents())) { - filterCount += size(component.getFilters()); - componentRules.put(ServiceComponent.of(service.getName(), component.getName()), - new FilterEvaluator(component.getFilters())); - } - } - if (filterCount == 0) { - throw new QuickLinksProfileEvaluationException("At least one filter must be defined."); - } - } - - /** - * @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 int size(@Nullable Collection<?> collection) { - return null == collection ? 0 : collection.size(); - } - - private Optional<Boolean> evaluateComponentRules(@Nonnull String service, @Nonnull Link quickLink) { - if (null == quickLink.getComponentName()) { - return Optional.absent(); - } - else { - FilterEvaluator componentEvaluator = componentRules.get(ServiceComponent.of(service, quickLink.getComponentName())); - return componentEvaluator != null ? componentEvaluator.isVisible(quickLink) : Optional.absent(); - } - } - - private Optional<Boolean> evaluateServiceRules(@Nonnull String service, @Nonnull Link quickLink) { - return serviceRules.containsKey(service) ? - serviceRules.get(service).isVisible(quickLink) : Optional.absent(); - } - - static <T> List<T> nullToEmptyList(@Nullable List<T> items) { - return items != null ? items : Collections.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()}. + * returns {@link Optional#empty()}. * <p> * Filter evaluation order is the following: * <ol> @@ -123,7 +45,7 @@ class FilterEvaluator { 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(); + private Optional<Boolean> acceptAllFilter = Optional.empty(); FilterEvaluator(List<Filter> filters) throws QuickLinksProfileEvaluationException { for (Filter filter: DefaultQuickLinkVisibilityController.nullToEmptyList(filters)) { @@ -180,34 +102,3 @@ class FilterEvaluator { 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 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 index b10111d..d13b3d5 100644 --- 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 @@ -21,7 +21,9 @@ 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; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; /** * A quicklink filter based on link attribute match (the filter's link_attribute is contained by the links set of @@ -58,4 +60,11 @@ public class LinkAttributeFilter extends Filter { public int hashCode() { return Objects.hash(isVisible(), linkAttribute); } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("linkAttribute", linkAttribute) + .toString(); + } } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/LinkNameFilter.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/LinkNameFilter.java index b874295..e9d0521 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/LinkNameFilter.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/LinkNameFilter.java @@ -19,28 +19,56 @@ package org.apache.ambari.server.state.quicklinksprofile; import java.util.Objects; +import java.util.stream.Stream; + +import javax.annotation.Nullable; import org.apache.ambari.server.state.quicklinks.Link; -import org.codehaus.jackson.annotate.JsonProperty; +import org.apache.ambari.server.utils.StreamUtils; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; + /** * A filter that accepts quicklinks based on name match. */ +@JsonInclude(JsonInclude.Include.NON_NULL) public class LinkNameFilter extends Filter { static final String LINK_NAME = "link_name"; + static final String LINK_URL = "link_url"; @JsonProperty(LINK_NAME) private String linkName; + /** + * In addition to filtering this filter allows overriding the link url too. + */ + @JsonProperty(LINK_URL) + private String linkUrl; + + @JsonProperty(LINK_NAME) public String getLinkName() { return linkName; } + @JsonProperty(LINK_NAME) public void setLinkName(String linkName) { this.linkName = linkName; } + @JsonProperty(LINK_URL) + public @Nullable String getLinkUrl() { + return linkUrl; + } + + @JsonProperty(LINK_URL) + public void setLinkUrl(@Nullable String linkUrl) { + this.linkUrl = linkUrl; + } + @Override public boolean accept(Link link) { return Objects.equals(link.getName(), linkName); @@ -51,11 +79,26 @@ public class LinkNameFilter extends Filter { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LinkNameFilter that = (LinkNameFilter) o; - return isVisible() == that.isVisible() && Objects.equals(linkName, that.linkName); + return Objects.equals(linkName, that.linkName) && + Objects.equals(linkUrl, that.linkUrl) && + isVisible() == that.isVisible(); } @Override public int hashCode() { - return Objects.hash(isVisible(), linkName); + return Objects.hash(linkName, linkUrl); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("linkName", linkName) + .add("linkUrl", linkUrl) + .add("visible", isVisible()) + .toString(); + } + + static Stream<LinkNameFilter> getLinkNameFilters(Stream<Filter> input) { + return StreamUtils.instancesOf(input, LinkNameFilter.class); } } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityController.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityController.java index caa2e2e..4f137e3 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityController.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityController.java @@ -19,6 +19,8 @@ package org.apache.ambari.server.state.quicklinksprofile; +import java.util.Optional; + import javax.annotation.Nonnull; import org.apache.ambari.server.state.quicklinks.Link; @@ -33,5 +35,12 @@ public interface QuickLinkVisibilityController { */ boolean isVisible(@Nonnull String service, @Nonnull Link quickLink); + /** + * @param service The name of the service the quicklink belongs to + * @param quickLink the link + * @return An optional url override for this link + */ + Optional<String> getUrlOverride(@Nonnull String service, @Nonnull Link quickLink); + } 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 7d480e9..9382dab 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 @@ -18,11 +18,13 @@ package org.apache.ambari.server.state.quicklinksprofile; +import static java.util.Collections.emptyList; + import java.util.List; -import org.codehaus.jackson.annotate.JsonIgnoreProperties; -import org.codehaus.jackson.annotate.JsonProperty; -import org.codehaus.jackson.map.annotate.JsonSerialize; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; /** * A quicklinks profile is essentially a set of quick link filters defined on three levels: @@ -37,7 +39,7 @@ import org.codehaus.jackson.map.annotate.JsonSerialize; * before being returned by {@link org.apache.ambari.server.controller.internal.QuickLinkArtifactResourceProvider}.</p> * <p>When no profile is set, all quick link's visibility flat will be set to {@code true} by the provider</p> */ -@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL) +@JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonIgnoreProperties(ignoreUnknown = true) public class QuickLinksProfile { @@ -67,7 +69,7 @@ public class QuickLinksProfile { * @return service-specific quicklink filter definitions */ public List<Service> getServices() { - return services; + return services != null ? services : emptyList(); } public void setServices(List<Service> services) { @@ -78,7 +80,7 @@ public class QuickLinksProfile { * @return the global quicklink filters */ public List<Filter> getFilters() { - return filters; + return null != filters ? filters : emptyList(); } public void setFilters(List<Filter> filters) { diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilder.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilder.java index 627b1bc..9806caf 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilder.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilder.java @@ -21,6 +21,7 @@ package org.apache.ambari.server.state.quicklinksprofile; import static org.apache.ambari.server.state.quicklinksprofile.Filter.VISIBLE; import static org.apache.ambari.server.state.quicklinksprofile.LinkAttributeFilter.LINK_ATTRIBUTE; import static org.apache.ambari.server.state.quicklinksprofile.LinkNameFilter.LINK_NAME; +import static org.apache.ambari.server.state.quicklinksprofile.LinkNameFilter.LINK_URL; import java.util.ArrayList; import java.util.Collection; @@ -44,7 +45,7 @@ public class QuickLinksProfileBuilder { public static final String COMPONENTS = "components"; public static final String FILTERS = "filters"; public static final Set<String> ALLOWED_FILTER_ATTRIBUTES = - ImmutableSet.of(VISIBLE, LINK_NAME, LINK_ATTRIBUTE); + ImmutableSet.of(VISIBLE, LINK_NAME, LINK_URL, LINK_ATTRIBUTE); /** * @@ -116,6 +117,7 @@ public class QuickLinksProfileBuilder { invalidAttributes); String linkName = filterAsMap.get(LINK_NAME); + String linkUrl = filterAsMap.get(LINK_URL); String attributeName = filterAsMap.get(LINK_ATTRIBUTE); boolean visible = Boolean.parseBoolean(filterAsMap.get(VISIBLE)); @@ -125,8 +127,12 @@ public class QuickLinksProfileBuilder { linkName, attributeName); + Preconditions.checkArgument(null == linkUrl || null != linkName, + "Invalid filter. Link url can only be applied to link name filters. link_url: %s", + linkUrl); + if (null != linkName) { - filters.add(Filter.linkNameFilter(linkName, visible)); + filters.add(Filter.linkNameFilter(linkName, linkUrl, visible)); } else if (null != attributeName) { filters.add(Filter.linkAttributeFilter(attributeName, visible)); 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 1891061..0e0f8b5 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 @@ -23,16 +23,16 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonProcessingException; -import org.codehaus.jackson.Version; -import org.codehaus.jackson.map.DeserializationContext; -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.map.deser.std.StdDeserializer; -import org.codehaus.jackson.map.module.SimpleModule; -import org.codehaus.jackson.node.ObjectNode; - +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableList; import com.google.common.io.Resources; @@ -44,12 +44,12 @@ public class QuickLinksProfileParser { public QuickLinksProfileParser() { SimpleModule module = - new SimpleModule("Quick Links Parser", new Version(1, 0, 0, null)); + new SimpleModule("Quick Links Parser", new Version(1, 0, 0, null, null, null)); module.addDeserializer(Filter.class, new QuickLinksFilterDeserializer()); mapper.registerModule(module); + mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); } - public QuickLinksProfile parse(byte[] input) throws IOException { return mapper.readValue(input, QuickLinksProfile.class); } @@ -91,20 +91,21 @@ class QuickLinksFilterDeserializer extends StdDeserializer<Filter> { @Override public Filter deserialize (JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException { ObjectMapper mapper = (ObjectMapper) parser.getCodec(); - ObjectNode root = (ObjectNode) mapper.readTree(parser); + ObjectNode root = mapper.readTree(parser); Class<? extends Filter> filterClass = null; List<String> invalidAttributes = new ArrayList<>(); - for (String fieldName: ImmutableList.copyOf(root.getFieldNames())) { + for (String fieldName: ImmutableList.copyOf(root.fieldNames())) { switch(fieldName) { case LinkAttributeFilter.LINK_ATTRIBUTE: if (null != filterClass) { - throw new JsonParseException(PARSE_ERROR_MESSAGE_AMBIGUOUS_FILTER, parser.getCurrentLocation()); + throw new JsonParseException(parser, PARSE_ERROR_MESSAGE_AMBIGUOUS_FILTER, parser.getCurrentLocation()); } filterClass = LinkAttributeFilter.class; break; case LinkNameFilter.LINK_NAME: - if (null != filterClass) { - throw new JsonParseException(PARSE_ERROR_MESSAGE_AMBIGUOUS_FILTER, parser.getCurrentLocation()); + case LinkNameFilter.LINK_URL: + if (null != filterClass && !filterClass.equals(LinkNameFilter.class)) { + throw new JsonParseException(parser, PARSE_ERROR_MESSAGE_AMBIGUOUS_FILTER, parser.getCurrentLocation()); } filterClass = LinkNameFilter.class; break; @@ -116,12 +117,12 @@ class QuickLinksFilterDeserializer extends StdDeserializer<Filter> { } } if (!invalidAttributes.isEmpty()) { - throw new JsonParseException(PARSE_ERROR_MESSAGE_INVALID_JSON_TAG + invalidAttributes, + throw new JsonParseException(parser, PARSE_ERROR_MESSAGE_INVALID_JSON_TAG + invalidAttributes, parser.getCurrentLocation()); } if (null == filterClass) { filterClass = AcceptAllFilter.class; } - return mapper.readValue(root, filterClass); + return mapper.readValue(root.traverse(), filterClass); } } \ No newline at end of file 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 07cce29..b3ef612 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 @@ -18,18 +18,19 @@ package org.apache.ambari.server.state.quicklinksprofile; -import java.util.List; +import static java.util.Collections.emptyList; -import org.codehaus.jackson.annotate.JsonIgnoreProperties; -import org.codehaus.jackson.annotate.JsonProperty; -import org.codehaus.jackson.map.annotate.JsonSerialize; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Preconditions; /** * Class to represent component-level filter definitions */ -@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL) +@JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonIgnoreProperties(ignoreUnknown = true) public class Service { @JsonProperty("name") @@ -62,7 +63,7 @@ public class Service { * @return component-specific quicklink filter definitions for components of this service */ public List<Component> getComponents() { - return components; + return null != components ? components : emptyList(); } public void setComponents(List<Component> components) { @@ -73,7 +74,7 @@ public class Service { * @return service-specific filters for this service */ public List<Filter> getFilters() { - return filters; + return null != filters ? filters : emptyList(); } public void setFilters(List<Filter> filters) { diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/ShowAllLinksVisibilityController.java b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/ShowAllLinksVisibilityController.java index 286a10c..250e816 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/ShowAllLinksVisibilityController.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/ShowAllLinksVisibilityController.java @@ -19,6 +19,8 @@ package org.apache.ambari.server.state.quicklinksprofile; +import java.util.Optional; + import javax.annotation.Nonnull; import org.apache.ambari.server.state.quicklinks.Link; @@ -35,4 +37,8 @@ public class ShowAllLinksVisibilityController implements QuickLinkVisibilityCont return true; } + @Override + public Optional<String> getUrlOverride(@Nonnull String service, @Nonnull Link quickLink) { + return Optional.empty(); + } } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityController.java b/ambari-server/src/main/java/org/apache/ambari/server/utils/StreamUtils.java similarity index 59% copy from ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityController.java copy to ambari-server/src/main/java/org/apache/ambari/server/utils/StreamUtils.java index caa2e2e..5a846ed 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityController.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/utils/StreamUtils.java @@ -16,22 +16,24 @@ * limitations under the License. */ -package org.apache.ambari.server.state.quicklinksprofile; +package org.apache.ambari.server.utils; +import java.util.stream.Stream; -import javax.annotation.Nonnull; - -import org.apache.ambari.server.state.quicklinks.Link; - - -public interface QuickLinkVisibilityController { +/** + * Utilities for Streams + */ +public class StreamUtils { /** - * @param service The name of the service the quicklink belongs to - * @param quickLink the link - * @return a boolean indicating if the link should be visible + * Filters a stream for instances of a class and returns a typed stream + * @param stream the stream to filter + * @param clazz stream will be filtered to instances of this class + * @param <T> the type of the class + * @return A stream of containing only instances of {@link T} */ - boolean isVisible(@Nonnull String service, @Nonnull Link quickLink); + public static <T> Stream<T> instancesOf(Stream<?> stream, Class<? extends T> clazz) { + return stream.filter(clazz::isInstance).map(clazz::cast); + } } - diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java index 5ed582f..ecd6a5a 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java @@ -426,7 +426,7 @@ public class ProvisionClusterRequestTest { ProvisionClusterRequest request = new ProvisionClusterRequest(properties, null); assertEquals("Quick links profile doesn't match expected", - "{\"filters\":[{\"visible\":true}],\"services\":[]}", + "{\"filters\":[{\"visible\":true}]}", request.getQuickLinksProfileJson()); } @@ -441,7 +441,7 @@ public class ProvisionClusterRequestTest { ProvisionClusterRequest request = new ProvisionClusterRequest(properties, null); assertEquals("Quick links profile doesn't match expected", - "{\"filters\":[],\"services\":[{\"name\":\"HDFS\",\"components\":[],\"filters\":[{\"visible\":true}]}]}", + "{\"services\":[{\"name\":\"HDFS\",\"filters\":[{\"visible\":true}]}]}", request.getQuickLinksProfileJson()); } @@ -460,7 +460,7 @@ public class ProvisionClusterRequestTest { ProvisionClusterRequest request = new ProvisionClusterRequest(properties, null); System.out.println(request.getQuickLinksProfileJson()); assertEquals("Quick links profile doesn't match expected", - "{\"filters\":[{\"visible\":true}],\"services\":[{\"name\":\"HDFS\",\"components\":[],\"filters\":[{\"visible\":true}]}]}", + "{\"filters\":[{\"visible\":true}],\"services\":[{\"name\":\"HDFS\",\"filters\":[{\"visible\":true}]}]}", request.getQuickLinksProfileJson()); } diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProviderTest.java index 9ce6471..a2cb231 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProviderTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProviderTest.java @@ -21,6 +21,7 @@ import static org.easymock.EasyMock.anyString; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; @@ -41,9 +42,7 @@ import org.apache.ambari.server.state.ServiceInfo; import org.apache.ambari.server.state.StackInfo; import org.apache.ambari.server.state.quicklinks.Link; import org.apache.ambari.server.state.quicklinks.QuickLinks; -import org.apache.ambari.server.state.quicklinksprofile.QuickLinkVisibilityController; import org.apache.ambari.server.state.quicklinksprofile.QuickLinkVisibilityControllerFactory; -import org.easymock.IAnswer; import org.junit.Before; import org.junit.Test; @@ -97,6 +96,28 @@ public class QuickLinkArtifactResourceProviderTest { } /** + * Test the application of link url override in the quick links profile + */ + @Test + public void getResourcesWithUrlOverride() throws Exception { + quicklinkProfile = Resources.toString(Resources.getResource("example_quicklinks_profile.json"), Charsets.UTF_8); + + QuickLinkArtifactResourceProvider provider = createProvider(); + Predicate predicate = new PredicateBuilder().property( + QuickLinkArtifactResourceProvider.STACK_NAME_PROPERTY_ID).equals("HDP"). + and(). + property(QuickLinkArtifactResourceProvider.STACK_VERSION_PROPERTY_ID).equals("2.0.6"). + and(). + property(QuickLinkArtifactResourceProvider.STACK_SERVICE_NAME_PROPERTY_ID).equals("YARN"). + toPredicate(); + Set<Resource> resources = + provider.getResources(PropertyHelper.getReadRequest(Sets.newHashSet()), predicate); + Map<String, Link> linkMap = getLinks(resources); + + assertEquals("http://customlink.org/resourcemanager", linkMap.get("resourcemanager_ui").getUrl()); + } + + /** * Test to prove the all links are visible if no profile is set */ @Test @@ -171,14 +192,8 @@ public class QuickLinkArtifactResourceProviderTest { AmbariManagementController amc = createMock(AmbariManagementController.class); expect(amc.getAmbariMetaInfo()).andReturn(metaInfo).anyTimes(); - expect(amc.getQuicklinkVisibilityController()).andAnswer( - new IAnswer<QuickLinkVisibilityController>() { - @Override - public QuickLinkVisibilityController answer() throws Throwable { - return QuickLinkVisibilityControllerFactory.get(quicklinkProfile); - } - } - ).anyTimes(); + expect(amc.getQuicklinkVisibilityController()) + .andAnswer(() -> QuickLinkVisibilityControllerFactory.get(quicklinkProfile)).anyTimes(); try { expect(metaInfo.getStack(anyString(), anyString())).andReturn(stack).anyTimes(); diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/FilterEvaluatorTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/FilterEvaluatorTest.java index 5821006..d69a167 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/FilterEvaluatorTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/FilterEvaluatorTest.java @@ -25,11 +25,11 @@ import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.apache.ambari.server.state.quicklinks.Link; import org.junit.Test; -import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -62,14 +62,14 @@ public class FilterEvaluatorTest { @Test public void testWithEmptyFilters() throws Exception { FilterEvaluator evaluator = new FilterEvaluator(new ArrayList<>()); - assertEquals(Optional.absent(), evaluator.isVisible(namenodeUi)); + assertEquals(Optional.empty(), evaluator.isVisible(namenodeUi)); FilterEvaluator evaluator2 = new FilterEvaluator(null); - assertEquals(Optional.absent(), evaluator2.isVisible(namenodeUi)); + assertEquals(Optional.empty(), evaluator2.isVisible(namenodeUi)); } /** - * FilterEvaluator should return {@link Optional#absent()} when the link doesn't match any filters + * FilterEvaluator should return {@link Optional.empty()} when the link doesn't match any filters */ @Test public void testNoMatchingFilter() throws Exception { @@ -77,7 +77,7 @@ public class FilterEvaluatorTest { linkNameFilter(NAMENODE_JMX, true), linkAttributeFilter(SSO, false)); FilterEvaluator evaluator = new FilterEvaluator(filters); - assertEquals(Optional.absent(), evaluator.isVisible(namenodeUi)); + assertEquals(Optional.empty(), evaluator.isVisible(namenodeUi)); } /** diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityControllerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityControllerTest.java index 1a36b9d..aa5a2df 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityControllerTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinkVisibilityControllerTest.java @@ -18,9 +18,13 @@ package org.apache.ambari.server.state.quicklinksprofile; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.util.List; +import java.util.Optional; + import org.apache.ambari.server.state.quicklinks.Link; import org.junit.Test; @@ -32,16 +36,26 @@ public class QuickLinkVisibilityControllerTest { static final String SSO = "sso"; static final String NAMENODE = "NAMENODE"; static final String HDFS = "HDFS"; + public static final String YARN = "YARN"; static final String NAMENODE_UI = "namenode_ui"; + static final String NAMENODE_LOGS = "namenode_logs"; + static final String NAMENODE_JMX = "namenode_jmx"; + static final String THREAD_STACKS = "Thread Stacks"; + static final String LINK_URL_1 = "www.overridden.org/1"; + static final String LINK_URL_2 = "www.overridden.org/2"; + static final String LINK_URL_3 = "www.overridden.org/3"; private Link namenodeUi; + private Link namenodeLogs; + private Link namenodeJmx; + private Link threadStacks; public QuickLinkVisibilityControllerTest() { - namenodeUi = new Link(); - namenodeUi.setComponentName(NAMENODE); - namenodeUi.setName(NAMENODE_UI); - namenodeUi.setAttributes(ImmutableList.of(AUTHENTICATED)); + namenodeUi = link(NAMENODE_UI, NAMENODE, ImmutableList.of(AUTHENTICATED)); + namenodeLogs = link(NAMENODE_LOGS, NAMENODE, null); + namenodeJmx = link(NAMENODE_JMX, NAMENODE, null); + threadStacks = link(THREAD_STACKS, NAMENODE, null); } /** @@ -178,4 +192,65 @@ public class QuickLinkVisibilityControllerTest { evaluator.isVisible(HDFS, namenodeUi)); } + @Test + public void testUrlOverride() throws Exception { + Component nameNode = Component.create( + NAMENODE, + ImmutableList.of( + Filter.linkNameFilter(NAMENODE_UI, true), + Filter.linkNameFilter(NAMENODE_LOGS, LINK_URL_1, true))); + Service hdfs = Service.create( + HDFS, + ImmutableList.of(Filter.linkNameFilter(NAMENODE_JMX, LINK_URL_2, true)), + ImmutableList.of(nameNode)); + QuickLinksProfile profile = QuickLinksProfile.create( + ImmutableList.of(Filter.linkNameFilter(THREAD_STACKS, LINK_URL_3, true)), + ImmutableList.of(hdfs)); + + DefaultQuickLinkVisibilityController evaluator = new DefaultQuickLinkVisibilityController(profile); + assertEquals(Optional.empty(), evaluator.getUrlOverride(HDFS, namenodeUi)); + assertEquals(Optional.of(LINK_URL_1), evaluator.getUrlOverride(HDFS, namenodeLogs)); + assertEquals(Optional.of(LINK_URL_2), evaluator.getUrlOverride(HDFS, namenodeJmx)); + // component name doesn't matter + namenodeLogs.setComponentName(null); + assertEquals(Optional.of(LINK_URL_1), evaluator.getUrlOverride(HDFS, namenodeLogs)); + // no override for links not in the profile + assertEquals(Optional.empty(), evaluator.getUrlOverride(YARN, link("resourcemanager_ui", "RESOURCEMANAGER", null))); + // url overrides in global filters are ignored + assertEquals(Optional.empty(), evaluator.getUrlOverride(HDFS, threadStacks)); + } + + @Test + public void testUrlOverride_duplicateDefinitions() throws Exception { + // same link is defined twice for a service + Component nameNode = Component.create( + NAMENODE, + ImmutableList.of( + Filter.linkNameFilter(NAMENODE_UI, LINK_URL_1, true))); // this will override service level setting for the same link + Service hdfs = Service.create( + HDFS, + ImmutableList.of(Filter.linkNameFilter(NAMENODE_UI, LINK_URL_2, true)), // same link on service level with different url + ImmutableList.of(nameNode)); + Service yarn = Service.create( + YARN, + ImmutableList.of(Filter.linkNameFilter(NAMENODE_UI, LINK_URL_3, true)), // this belongs to an other service so doesn't affect outcome + ImmutableList.of(nameNode)); + + QuickLinksProfile profile = QuickLinksProfile.create( + ImmutableList.of(), + ImmutableList.of(hdfs)); + + DefaultQuickLinkVisibilityController evaluator = new DefaultQuickLinkVisibilityController(profile); + assertEquals(Optional.of(LINK_URL_1), evaluator.getUrlOverride(HDFS, namenodeUi)); + } + + + private static final Link link(String name, String componentName, List<String> attributes) { + Link link = new Link(); + link.setName(name); + link.setComponentName(componentName); + link.setAttributes(attributes); + return link; + } + } \ No newline at end of file diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilderTest.java index 49244d4..9dba51d 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilderTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilderTest.java @@ -79,8 +79,13 @@ public class QuickLinksProfileBuilderTest { public void testBuildProfileBothGlobalAndServiceFilters() throws Exception { Set<Map<String, String>> globalFilters = newHashSet( filter(null, null, false) ); - Map<String, Object> nameNode = component("NAMENODE", - newHashSet(filter("namenode_ui", null, false))); + Map<String, Object> nameNode = component( + "NAMENODE", + newHashSet( + filter("namenode_ui", null, false), + filter("namenode_logs", null, "http://customlink.org/namenode_logs", true) + ) + ); Map<String, Object> hdfs = service("HDFS", newHashSet(nameNode), @@ -94,6 +99,9 @@ public class QuickLinksProfileBuilderTest { QuickLinksProfile profile = new QuickLinksProfileParser().parse(profileJson.getBytes()); assertFilterExists(profile, null, null, Filter.acceptAllFilter(false)); assertFilterExists(profile, "HDFS", "NAMENODE", Filter.linkNameFilter("namenode_ui", false)); + assertFilterExists(profile, "HDFS", "NAMENODE", Filter.linkNameFilter("namenode_ui", false)); + assertFilterExists(profile, "HDFS", "NAMENODE", Filter.linkNameFilter("namenode_logs", + "http://customlink.org/namenode_logs", true)); assertFilterExists(profile, "HDFS", null, Filter.linkAttributeFilter("sso", true)); } @@ -208,11 +216,23 @@ public class QuickLinksProfileBuilderTest { throw new AssertionError("Expected service not found: " + serviceName); } - public static Map<String, String> filter(@Nullable String linkName, @Nullable String attributeName, boolean visible) { - Map<String, String> map = new HashMap<>(3); + public static Map<String, String> filter(@Nullable String linkName, + @Nullable String attributeName, + boolean visible) { + return filter(linkName, attributeName, null, visible); + } + + public static Map<String, String> filter(@Nullable String linkName, + @Nullable String attributeName, + @Nullable String linkUrl, + boolean visible) { + Map<String, String> map = new HashMap<>(4); if (null != linkName) { map.put(LinkNameFilter.LINK_NAME, linkName); } + if (null != linkUrl) { + map.put(LinkNameFilter.LINK_URL, linkUrl); + } if (null != attributeName) { map.put(LinkAttributeFilter.LINK_ATTRIBUTE, attributeName); } 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 8b01ca5..abe55bf 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 @@ -20,9 +20,9 @@ package org.apache.ambari.server.state.quicklinksprofile; import static org.junit.Assert.assertEquals; -import org.codehaus.jackson.JsonParseException; import org.junit.Test; +import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.io.Resources; public class QuickLinksProfileParserTest { @@ -50,7 +50,7 @@ public class QuickLinksProfileParserTest { Component nameNode = hdfs.getComponents().get(0); assertEquals(2, nameNode.getFilters().size()); assertEquals( - Filter.linkNameFilter("namenode_ui", false), + Filter.linkNameFilter("namenode_ui", "http://customlink.org/namenode", false), nameNode.getFilters().get(0)); Component historyServer = profile.getServices().get(1).getComponents().get(0); @@ -62,18 +62,25 @@ public class QuickLinksProfileParserTest { Service yarn = profile.getServices().get(2); assertEquals(1, yarn.getFilters().size()); assertEquals( - Filter.linkNameFilter("resourcemanager_ui", true), + Filter.linkNameFilter("resourcemanager_ui", "http://customlink.org/resourcemanager", true), yarn.getFilters().get(0)); } - @Test(expected = JsonParseException.class) + @Test(expected = JsonProcessingException.class) public void testParseInconsistentProfile_ambigousFilterDefinition() throws Exception { String profileName = "inconsistent_quicklinks_profile.json"; QuickLinksProfileParser parser = new QuickLinksProfileParser(); parser.parse(Resources.getResource(profileName)); } - @Test(expected = JsonParseException.class) + @Test(expected = JsonProcessingException.class) + public void testParseInconsistentProfile_invalidLinkUrl() throws Exception { + String profileName = "inconsistent_quicklinks_profile_4.json"; + QuickLinksProfileParser parser = new QuickLinksProfileParser(); + parser.parse(Resources.getResource(profileName)); + } + + @Test(expected = JsonProcessingException.class) public void testParseInconsistentProfile_misspelledFilerDefinition() throws Exception { String profileName = "inconsistent_quicklinks_profile_3.json"; QuickLinksProfileParser parser = new QuickLinksProfileParser(); diff --git a/ambari-server/src/test/resources/example_quicklinks_profile.json b/ambari-server/src/test/resources/example_quicklinks_profile.json index 50ea5e8..227761f 100644 --- a/ambari-server/src/test/resources/example_quicklinks_profile.json +++ b/ambari-server/src/test/resources/example_quicklinks_profile.json @@ -20,6 +20,7 @@ "filters": [ { "link_name": "namenode_ui", + "link_url" : "http://customlink.org/namenode", "visible": false }, { @@ -49,6 +50,7 @@ "filters": [ { "link_name": "resourcemanager_ui", + "link_url" : "http://customlink.org/resourcemanager", "visible": true } ] diff --git a/ambari-server/src/test/resources/inconsistent_quicklinks_profile_4.json b/ambari-server/src/test/resources/inconsistent_quicklinks_profile_4.json new file mode 100644 index 0000000..7bc04ed --- /dev/null +++ b/ambari-server/src/test/resources/inconsistent_quicklinks_profile_4.json @@ -0,0 +1,10 @@ +{ + "filters": [ + { + "link_attributes": "sso", + "link_url": "http://quicklinks.org/link", + "visible": true + } + ], + "services": [] +} \ No newline at end of file