LinkTemplateProcessor.java
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2018 wcm.io
* %%
* Licensed 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.
* #L%
*/
package io.wcm.caravan.hal.comparison.impl.links.steps;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import com.damnhandy.uri.template.UriTemplate;
import com.google.common.collect.Sets;
import io.wcm.caravan.hal.comparison.HalComparisonContext;
import io.wcm.caravan.hal.comparison.HalComparisonStrategy;
import io.wcm.caravan.hal.comparison.HalDifference;
import io.wcm.caravan.hal.comparison.impl.context.HalComparisonContextImpl;
import io.wcm.caravan.hal.comparison.impl.difference.HalDifferenceListBuilder;
import io.wcm.caravan.hal.comparison.impl.links.LinkProcessingStep;
import io.wcm.caravan.hal.resource.Link;
/**
* Detects if the available in a pair of link templates is different, and filteres all link-templates so they are not
* being followed in the recursive comparison.
*/
public class LinkTemplateProcessor implements LinkProcessingStep {
private final HalComparisonStrategy strategy;
/**
* @param strategy that defines which relations should be ignored
*/
public LinkTemplateProcessor(HalComparisonStrategy strategy) {
this.strategy = strategy;
}
@Override
public List<HalDifference> apply(HalComparisonContextImpl context, List<Link> expected, List<Link> actual) {
HalDifferenceListBuilder diffs = new HalDifferenceListBuilder(context);
List<Link> expectedExpandedTemplates = new ArrayList<>();
List<Link> actualExpandedTemplates = new ArrayList<>();
Iterator<Link> expectedIt = expected.iterator();
Iterator<Link> actualIt = actual.iterator();
while (expectedIt.hasNext() && actualIt.hasNext()) {
Link expectedLink = expectedIt.next();
Link actualLink = actualIt.next();
// whenever one of the links is templated...
if (expectedLink.isTemplated() || actualLink.isTemplated()) {
// return a result when there is a difference in the template parameters
findTemplateDifferences(context, expectedLink, actualLink, diffs);
// expand the templates if the strategy provides a map of variables for them
List<Map<String, Object>> variables = strategy.getVariablesToExpandLinkTemplate(context, expectedLink, actualLink);
if (variables != null) {
for (Map<String, Object> map : variables) {
expectedExpandedTemplates.add(createExpandedLink(expectedLink, map));
actualExpandedTemplates.add(createExpandedLink(actualLink, map));
}
}
// and finally remove the links from the list of links to compare
expectedIt.remove();
actualIt.remove();
}
}
// there could be more expected than actual links (or vice versa)
filterTemplatesFromRemaining(expectedIt);
filterTemplatesFromRemaining(actualIt);
expected.addAll(expectedExpandedTemplates);
actual.addAll(actualExpandedTemplates);
return diffs.build();
}
private static Link createExpandedLink(Link linkTemplate, Map<String, Object> variables) {
UriTemplate template = UriTemplate.fromTemplate(linkTemplate.getHref());
String expandedUrl = template.expand(variables);
Link expandedLink = new Link(expandedUrl);
expandedLink.setName(StringUtils.trimToEmpty(linkTemplate.getName()) + variables.toString());
return expandedLink;
}
private static void filterTemplatesFromRemaining(Iterator<Link> remainingIt) {
while (remainingIt.hasNext()) {
Link link = remainingIt.next();
if (link.isTemplated()) {
remainingIt.remove();
}
}
}
private static String formatNames(Collection<String> items) {
return items.stream()
.collect(Collectors.joining(", ", "[", "]"));
}
private static String formatTemplateVariables(Link link) {
Set<String> variables = Sets.newHashSet(UriTemplate.fromTemplate(link.getHref()).getVariables());
return formatNames(variables);
}
private static void findTemplateDifferences(HalComparisonContext context, Link expected, Link actual, HalDifferenceListBuilder diffs) {
if (expected.isTemplated() != actual.isTemplated()) {
String msg = expected.isTemplated()
? "Expected templated link with variables " + formatTemplateVariables(expected) + ", but found resolved link"
: "Expected resolved link, but found template with variables " + formatTemplateVariables(actual);
diffs.reportModifiedLink(msg, expected, actual);
return;
}
UriTemplate expectedTemplate = UriTemplate.fromTemplate(expected.getHref());
UriTemplate actualTemplate = UriTemplate.fromTemplate(actual.getHref());
Set<String> expectedVariables = Sets.newHashSet(expectedTemplate.getVariables());
Set<String> actualVariables = Sets.newHashSet(actualTemplate.getVariables());
Set<String> missingVariables = Sets.difference(expectedVariables, actualVariables);
Set<String> additionalVariables = Sets.difference(actualVariables, expectedVariables);
if (!missingVariables.isEmpty() || !additionalVariables.isEmpty()) {
String msg = "Expected template parameters to be " + formatNames(expectedVariables) + ","
+ " but found " + formatNames(actualVariables);
diffs.reportModifiedLink(msg, expected, actual);
}
}
}