View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2018 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.caravan.hal.comparison.impl.context;
21  
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  
28  import com.fasterxml.jackson.databind.node.ObjectNode;
29  import com.google.common.collect.ImmutableMap;
30  
31  import io.wcm.caravan.hal.comparison.HalComparisonContext;
32  import io.wcm.caravan.hal.comparison.impl.PairWithRelation;
33  import io.wcm.caravan.hal.resource.HalResource;
34  import io.wcm.caravan.hal.resource.Link;
35  
36  /**
37   * An immutable object that specifies in which part of the tree the comparison is currently being executed.
38   */
39  public class HalComparisonContextImpl implements HalComparisonContext {
40  
41    private final HalPathImpl halPath;
42  
43    private final String expectedUrl;
44    private final String actualUrl;
45  
46    private final Map<HalPathImpl, HalResource> parentResources;
47  
48    /**
49     * This constructor shouldn't be used except for creating the initial context for the entry point. From then on, use
50     * the #withXyz methods to build a new context
51     * @param expectedUrl
52     * @param actualUrl
53     */
54    public HalComparisonContextImpl(String expectedUrl, String actualUrl) {
55      this.halPath = new HalPathImpl();
56      this.expectedUrl = expectedUrl;
57      this.actualUrl = actualUrl;
58      this.parentResources = Collections.emptyMap();
59    }
60  
61    private HalComparisonContextImpl(HalPathImpl halPath, String expectedUrl, String actualUrl, Map<HalPathImpl, HalResource> parentResources) {
62      this.halPath = halPath;
63      this.expectedUrl = expectedUrl;
64      this.actualUrl = actualUrl;
65      this.parentResources = ImmutableMap.copyOf(parentResources);
66    }
67  
68    @Override
69    public String getExpectedUrl() {
70      return this.expectedUrl;
71    }
72  
73    @Override
74    public String getActualUrl() {
75      return this.actualUrl;
76    }
77  
78    /**
79     * @param relation of the linked or embedded resource that is about to be processed
80     * @return a new instance with an updated {@link HalPathImpl}
81     */
82    public HalComparisonContextImpl withAppendedHalPath(String relation, HalResource contextResource) {
83  
84      HalPathImpl newHalPath = halPath.append(relation, null, null);
85      return new HalComparisonContextImpl(newHalPath, expectedUrl, actualUrl, createNewParentResourceMap(contextResource));
86    }
87  
88    private Map<HalPathImpl, HalResource> createNewParentResourceMap(HalResource contextResource) {
89      Map<HalPathImpl, HalResource> newParents = new HashMap<>(parentResources);
90      newParents.put(halPath, contextResource);
91      return newParents;
92    }
93  
94    /**
95     * @param pair of resources that is about to be compared
96     * @param contextResource the resource from the expected tree that embeds the resource to be compared
97     * @return a new instance with an updated {@link HalPathImpl} that adds array indices if required
98     */
99    public HalComparisonContextImpl withHalPathOfEmbeddedResource(PairWithRelation<HalResource> pair, HalResource contextResource) {
100 
101     String relation = pair.getRelation();
102     List<HalResource> originalEmbedded = contextResource.getEmbedded(relation);
103 
104     Integer originalIndex = null;
105     if (originalEmbedded.size() > 1) {
106       originalIndex = findOriginalIndex(pair, originalEmbedded);
107     }
108 
109     HalPathImpl newHalPath = halPath.append(relation, originalIndex, null);
110     return new HalComparisonContextImpl(newHalPath, expectedUrl, actualUrl, createNewParentResourceMap(contextResource));
111   }
112 
113 
114   private Integer findOriginalIndex(PairWithRelation<HalResource> pair, List<HalResource> originalEmbedded) {
115     for (int i = 0; i < originalEmbedded.size(); i++) {
116       ObjectNode originalModel = originalEmbedded.get(i).getModel();
117       if (originalModel == pair.getExpected().getModel()) {
118         return i;
119       }
120     }
121     throw new IllegalArgumentException("The resource from the given pair is not actually embedded in the context resource");
122   }
123 
124   /**
125    * @param pair of links that are about to be followed and compared
126    * @param contextResource the resource from the expected tree that contains the links
127    * @return a new instance with an updated {@link HalPathImpl} that adds array indices if required
128    */
129   public HalComparisonContextImpl withHalPathOfLinkedResource(PairWithRelation<Link> pair, HalResource contextResource) {
130 
131     String relation = pair.getRelation();
132     List<Link> originalLinks = contextResource.getLinks(relation);
133 
134     Integer originalIndex = null;
135     String name = pair.getExpected().getName();
136     if (originalLinks.size() > 1) {
137       originalIndex = originalLinks.indexOf(pair.getExpected());
138     }
139 
140     HalPathImpl newHalPath = halPath.append(relation, originalIndex, name);
141     Map<HalPathImpl, HalResource> newParents = new HashMap<>(parentResources);
142     newParents.put(halPath, contextResource);
143     return new HalComparisonContextImpl(newHalPath, expectedUrl, actualUrl, createNewParentResourceMap(contextResource));
144   }
145 
146   /**
147    * @param indexInArray the index of the next array entry to be compared
148    * @return a new instance with an updated {@link HalPathImpl} that contains array indices
149    */
150   public HalComparisonContextImpl withHalPathIndex(int indexInArray) {
151     HalPathImpl newHalPath = halPath.replaceHalPathIndex(indexInArray);
152     return new HalComparisonContextImpl(newHalPath, expectedUrl, actualUrl, parentResources);
153   }
154 
155   /**
156    * @param fieldName the name of the JSON property to be compared
157    * @return a new instance with an updated {@link HalPathImpl}
158    */
159   public HalComparisonContextImpl withAppendedJsonPath(String fieldName) {
160     HalPathImpl newHalPath = halPath.appendJsonPath(fieldName);
161     return new HalComparisonContextImpl(newHalPath, expectedUrl, actualUrl, parentResources);
162   }
163 
164   /**
165    * @param indexInArray the index of the next array entry to be compared
166    * @return a new instance with an updated {@link HalPathImpl} that contains array indices
167    */
168   public HalComparisonContextImpl withJsonPathIndex(int indexInArray) {
169     HalPathImpl newHalPath = halPath.replaceJsonPathIndex(indexInArray);
170     return new HalComparisonContextImpl(newHalPath, expectedUrl, actualUrl, parentResources);
171   }
172 
173   /**
174    * @param newUrl of the expected resource that is about to be loaded
175    * @return a new instance with an updated {@link #getExpectedUrl()} value
176    */
177   public HalComparisonContextImpl withNewExpectedUrl(String newUrl) {
178     return new HalComparisonContextImpl(halPath, newUrl, actualUrl, parentResources);
179   }
180 
181   /**
182    * @param newUrl of the actual resource that is about to be loaded
183    * @return a new instance with an updated {@link #getActualUrl()} value
184    */
185   public HalComparisonContextImpl withNewActualUrl(String newUrl) {
186     return new HalComparisonContextImpl(halPath, expectedUrl, newUrl, parentResources);
187   }
188 
189   @Override
190   public String getLastRelation() {
191     return halPath.getLastRelation();
192   }
193 
194   @Override
195   public List<String> getAllRelations() {
196     return halPath.getAllRelations();
197   }
198 
199   @Override
200   public String getLastProperyName() {
201     return halPath.getLastProperyName();
202   }
203 
204   @Override
205   public List<String> getAllPropertyNames() {
206     return halPath.getAllPropertyNames();
207   }
208 
209   @Override
210   public HalResource getParentResourceWithRelation(String relation) {
211     return parentResources.entrySet().stream()
212         .filter(entry -> entry.getKey().getLastRelation().equals(relation))
213         .sorted(HalComparisonContextImpl::longestHalPathFirstComparator)
214         .map(Entry::getValue)
215         .findFirst()
216         .orElse(null);
217   }
218 
219   private static int longestHalPathFirstComparator(Entry<HalPathImpl, HalResource> entry1, Entry<HalPathImpl, HalResource> entry2) {
220     String firstHalPath = entry1.getKey().toString();
221     String secondHalPath = entry2.getKey().toString();
222 
223     return -Integer.compare(firstHalPath.length(), secondHalPath.length());
224   }
225 
226   @Override
227   public String toString() {
228     return halPath.toString();
229   }
230 }