View Javadoc

1   /*
2    * Copyright 2015 Data Archiving and Networked Services (an institute of
3    * Koninklijke Nederlandse Akademie van Wetenschappen), King's College London,
4    * Georg-August-Universitaet Goettingen Stiftung Oeffentlichen Rechts
5    *
6    * Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
7    * the European Commission - subsequent versions of the EUPL (the "Licence");
8    * You may not use this work except in compliance with the Licence.
9    * You may obtain a copy of the Licence at:
10   *
11   * https://joinup.ec.europa.eu/software/page/eupl
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the Licence is distributed on an "AS IS" basis,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the Licence for the specific language governing
17   * permissions and limitations under the Licence.
18   */
19  
20  package eu.ehri.project.persistence;
21  
22  import com.google.common.base.Joiner;
23  import com.google.common.collect.ArrayListMultimap;
24  import com.google.common.collect.ImmutableListMultimap;
25  import com.google.common.collect.LinkedHashMultiset;
26  import com.google.common.collect.ListMultimap;
27  import com.google.common.collect.Lists;
28  import com.google.common.collect.Maps;
29  import com.google.common.collect.Multimap;
30  import eu.ehri.project.exceptions.SerializationError;
31  
32  import java.util.Collection;
33  import java.util.List;
34  import java.util.Map;
35  
36  import static com.google.common.base.Preconditions.checkNotNull;
37  
38  /**
39   * Class that represents a set of validation errors associated with a bundle.
40   */
41  public final class ErrorSet implements NestableData<ErrorSet> {
42      private final Multimap<String, String> errors;
43      private final Multimap<String, ErrorSet> relations;
44  
45      /**
46       * Serialization constant definitions
47       */
48      public static final String REL_KEY = "relationships";
49      public static final String ERROR_KEY = "errors";
50  
51      /**
52       * Builder class for creating an Error set.
53       */
54      public static class Builder {
55          private final ListMultimap<String, String> errors = ArrayListMultimap.create();
56          private final ListMultimap<String, ErrorSet> relations = ArrayListMultimap.create();
57  
58          Builder addError(String key, String error) {
59              errors.put(key, error);
60              return this;
61          }
62  
63          Builder addRelation(String relation, ErrorSet errorSet) {
64              relations.put(relation, errorSet);
65              return this;
66          }
67  
68          public ErrorSet build() {
69              return new ErrorSet(errors, relations);
70          }
71      }
72  
73      /**
74       * Constructor.
75       */
76      public ErrorSet() {
77          this(ArrayListMultimap.<String, String>create(),
78                  ArrayListMultimap.<String, ErrorSet>create());
79      }
80  
81      /**
82       * Factory constructor.
83       *
84       * @param key   The property key in error
85       * @param error The string description of the property error
86       */
87      public static ErrorSet fromError(String key, String error) {
88          Multimap<String, String> tmp = ArrayListMultimap.create();
89          tmp.put(key, error);
90          return new ErrorSet(tmp);
91      }
92  
93      /**
94       * Constructor.
95       *
96       * @param errors A map of top-level errors
97       */
98      public ErrorSet(Multimap<String, String> errors) {
99          this(errors, ArrayListMultimap.<String, ErrorSet>create());
100     }
101 
102     /**
103      * Constructor.
104      *
105      * @param errors    A map of top-level errors
106      * @param relations A map of relations
107      */
108     public ErrorSet(Multimap<String, String> errors, Multimap<String, ErrorSet> relations) {
109         this.errors = ImmutableListMultimap.copyOf(errors);
110         this.relations = ImmutableListMultimap.copyOf(relations);
111     }
112 
113     /**
114      * Get errors for a key.
115      *
116      * @param key A property key
117      * @return A list of errors associated with a property key
118      */
119     @SuppressWarnings("unchecked")
120     @Override
121     public Collection<String> getDataValue(String key) {
122         checkNotNull(key);
123         return errors.get(key);
124     }
125 
126     /**
127      * Get the top-level errors.
128      *
129      * @return All top-level errors
130      */
131     public Multimap<String, String> getErrors() {
132         return errors;
133     }
134 
135     /**
136      * Get the bundle's relation bundles.
137      *
138      * @return A set of relations
139      */
140     @Override
141     public Multimap<String, ErrorSet> getRelations() {
142         return relations;
143     }
144 
145     /**
146      * Add entire set of relations.
147      *
148      * @param newRelations A map of relations
149      * @return A new error set
150      */
151     @Override
152     public ErrorSet withRelations(Multimap<String, ErrorSet> newRelations) {
153         Multimap<String, ErrorSet> tmp = ArrayListMultimap.create(relations);
154         tmp.putAll(newRelations);
155         return new ErrorSet(errors, tmp);
156     }
157 
158     /**
159      * Get a set of relations.
160      *
161      * @param relation A relation label
162      * @return A new error set
163      */
164     @Override
165     public List<ErrorSet> getRelations(String relation) {
166         return Lists.newArrayList(relations.get(relation));
167     }
168 
169     @Override
170     public boolean hasRelations(String relation) {
171         return relations.containsKey(relation);
172     }
173 
174     @Override
175     public ErrorSet replaceRelations(Multimap<String, ErrorSet> relations) {
176         return new ErrorSet(errors, relations);
177     }
178 
179     /**
180      * Set a value in the bundle's data.
181      *
182      * @param key A property key
183      * @param err A description of the error
184      * @return A new error set
185      */
186     @Override
187     public ErrorSet withDataValue(String key, Object err) {
188         if (err == null) {
189             return this;
190         } else {
191             Multimap<String, String> tmp = ArrayListMultimap.create(errors);
192             tmp.put(key, err.toString());
193             return new ErrorSet(tmp, relations);
194         }
195     }
196 
197     @Override
198     public ErrorSet removeDataValue(String key) {
199         Multimap<String, String> tmp = ArrayListMultimap.create(errors);
200         tmp.removeAll(key);
201         return new ErrorSet(tmp, relations);
202     }
203 
204     /**
205      * Set bundles for a particular relation.
206      *
207      * @param relation A relation label
208      * @param others   A set of relation error sets
209      * @return A new error set
210      */
211     @Override
212     public ErrorSet withRelations(String relation, List<ErrorSet> others) {
213         Multimap<String, ErrorSet> tmp = ArrayListMultimap
214                 .create(relations);
215         tmp.putAll(relation, others);
216         return new ErrorSet(errors, tmp);
217     }
218 
219     /**
220      * Add a bundle for a particular relation.
221      *
222      * @param relation A relation label
223      * @param other    An error set relation
224      */
225     @Override
226     public ErrorSet withRelation(String relation, ErrorSet other) {
227         Multimap<String, ErrorSet> tmp = ArrayListMultimap
228                 .create(relations);
229         tmp.put(relation, other);
230         return new ErrorSet(errors, tmp);
231     }
232 
233     /**
234      * Serialize a error set to raw data.
235      *
236      * @return A map of data
237      */
238     public Map<String, Object> toData() {
239         return DataConverter.errorSetToData(this);
240     }
241 
242     @Override
243     public String toString() {
244         return Joiner.on(", ").join(flatErrors());
245     }
246 
247     private List<String> flatErrors() {
248         List<String> flatErrors = Lists.newArrayList();
249         for (Map.Entry<String, String> e : errors.entries()) {
250             flatErrors.add(String.format("%s: %s", e.getKey(), e.getValue()));
251         }
252         for (Map.Entry<String, ErrorSet> e : relations.entries()) {
253             for (String flatError : e.getValue().flatErrors()) {
254                 flatErrors.add(String.format("%s.%s", e.getKey(), flatError));
255             }
256         }
257         return flatErrors;
258     }
259 
260     /**
261      * Serialize a error set to a JSON string.
262      *
263      * @return json string
264      */
265     public String toJson() {
266         try {
267             return DataConverter.errorSetToJson(this);
268         } catch (SerializationError e) {
269             return "Invalid Errors: " + e.getMessage();
270         }
271     }
272 
273     @Override
274     public boolean equals(Object o) {
275         if (this == o) return true;
276         if (o == null || getClass() != o.getClass()) return false;
277 
278         ErrorSet other = (ErrorSet) o;
279 
280         return errors.equals(other.errors)
281                 && unorderedRelations(relations)
282                 .equals(unorderedRelations(other.relations));
283 
284     }
285 
286     @Override
287     public int hashCode() {
288         int result = errors.hashCode();
289         result = 31 * result + relations.hashCode();
290         return result;
291     }
292 
293     /**
294      * Is this ErrorSet empty? It will be if there are
295      * no errors and none of the relations have errors.
296      *
297      * @return Whether or not the set is empty.
298      */
299     public boolean isEmpty() {
300         if (!errors.isEmpty())
301             return false;
302         for (Map.Entry<String, ErrorSet> rel : relations.entries()) {
303             if (!rel.getValue().isEmpty())
304                 return false;
305         }
306         return true;
307     }
308 
309     /**
310      * Convert the ordered relationship set into an unordered one for comparison.
311      * FIXME: Clean up the code and optimise this function.
312      */
313     private Map<String, LinkedHashMultiset<ErrorSet>> unorderedRelations(Multimap<String, ErrorSet> rels) {
314         Map<String, LinkedHashMultiset<ErrorSet>> map = Maps.newHashMap();
315         for (Map.Entry<String, Collection<ErrorSet>> entry : rels.asMap().entrySet()) {
316             map.put(entry.getKey(), LinkedHashMultiset.create(entry.getValue()));
317         }
318         return map;
319     }
320 }