View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016 ForgeRock AS.
15   * Portions Copyright 2018 Wren Security
16   */
17  
18  package org.forgerock.api.models;
19  
20  import static org.forgerock.api.util.ValidationUtil.*;
21  import static org.forgerock.util.Reject.*;
22  
23  import java.util.Comparator;
24  import java.util.HashMap;
25  import java.util.Map;
26  import java.util.Objects;
27  import java.util.Set;
28  
29  import com.fasterxml.jackson.annotation.JsonAnySetter;
30  import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
31  
32  import com.fasterxml.jackson.annotation.JsonIgnore;
33  import com.fasterxml.jackson.annotation.JsonValue;
34  
35  /**
36   * Class that represents API descriptor {@link ApiError} errors.
37   */
38  @JsonDeserialize(builder = Errors.Builder.class)
39  public final class Errors {
40  
41      /**
42       * {@link ApiError} {@code Map}-entry {@link Comparator}, which sorts by code and description.
43       */
44      public static final ErrorEntryComparator ERROR_ENTRY_COMPARATOR = new ErrorEntryComparator();
45  
46      private final Map<String, ApiError> errors;
47  
48      private Errors(Builder builder) {
49          this.errors = builder.errors;
50      }
51  
52      /**
53       * Gets a {@code Map} of error-names to {@link ApiError}s.
54       *
55       * @return {@code Map} of error-names to {@link ApiError}s.
56       */
57      @JsonValue
58      public Map<String, ApiError> getErrors() {
59          return errors;
60      }
61  
62      /**
63       * Gets the {@link ApiError} for a given ApiError-name.
64       *
65       * @param name ApiError name
66       * @return {@link ApiError} or {@code null} if does-not-exist.
67       */
68      @JsonIgnore
69      public ApiError get(String name) {
70          return errors.get(name);
71      }
72  
73      /**
74       * Returns all {@link ApiError} names.
75       *
76       * @return All {@link ApiError} names.
77       */
78      @JsonIgnore
79      public Set<String> getNames() {
80          return errors.keySet();
81      }
82  
83      @Override
84      public boolean equals(Object o) {
85          if (this == o) {
86              return true;
87          }
88          if (o == null || getClass() != o.getClass()) {
89              return false;
90          }
91          Errors errors1 = (Errors) o;
92          return Objects.equals(errors, errors1.errors);
93      }
94  
95      @Override
96      public int hashCode() {
97          return Objects.hash(errors);
98      }
99  
100     /**
101      * Create a new Builder for Errors.
102      *
103      * @return Builder
104      */
105     public static Builder errors() {
106         return new Builder();
107     }
108 
109     /**
110      * Builder to help construct the Errors.
111      */
112     public static final class Builder {
113 
114         private final Map<String, ApiError> errors = new HashMap<>();
115 
116         /**
117          * Private default constructor.
118          */
119         private Builder() {
120         }
121 
122         /**
123          * Adds a {@link ApiError}.
124          *
125          * @param name Error name
126          * @param apiError {@link ApiError}
127          * @return Builder
128          */
129         @JsonAnySetter
130         public Builder put(String name, ApiError apiError) {
131             if (isEmpty(name) || containsWhitespace(name)) {
132                 throw new IllegalArgumentException(
133                     "Error name is required, must not be blank, and must not contain " +
134                     "whitespace; given: '" + name + "'");
135             }
136             if (errors.containsKey(name) && !errors.get(name).equals(apiError)) {
137                 throw new IllegalStateException(
138                     "Error name already exists but Error objects are not equal; " +
139                     "given: '" + name + "'");
140             }
141             errors.put(name, checkNotNull(apiError));
142             return this;
143         }
144 
145         /**
146          * Builds the Errors instance.
147          *
148          * @return Errors instance
149          */
150         public Errors build() {
151             return new Errors(this);
152         }
153     }
154 
155     /**
156      * {@link ApiError} {@code Map}-entry {@link Comparator}, which sorts by code and description.
157      * This {@code Comparator} does not handle {@code null} values or duplicates,
158      * because those conditions should never occur in practice.
159      * <p>
160      * This class is thread-safe.
161      * </p>
162      */
163     private static class ErrorEntryComparator implements Comparator<Map.Entry<String, ApiError>> {
164         @Override
165         public int compare(final Map.Entry<String, ApiError> o1, final Map.Entry<String, ApiError> o2) {
166             final int codeCompare = Integer.compare(o1.getValue().getCode(), o2.getValue().getCode());
167             if (codeCompare == 0) {
168                 return o1.getValue().getDescription().toString()
169                         .compareTo(o2.getValue().getDescription().toString());
170             }
171             return codeCompare;
172         }
173     }
174 
175 }