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   */
16  
17  package org.forgerock.api.models;
18  
19  import static org.forgerock.api.util.ValidationUtil.isEmpty;
20  
21  import java.util.Comparator;
22  import java.util.Objects;
23  
24  import org.forgerock.api.ApiValidationException;
25  import org.wrensecurity.guava.common.base.Strings;
26  import org.forgerock.util.i18n.LocalizableString;
27  
28  import com.fasterxml.jackson.annotation.JsonInclude;
29  import com.fasterxml.jackson.annotation.JsonProperty;
30  import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
31  
32  /**
33   * Class that represents the ApiError type in API descriptor.
34   */
35  @JsonInclude(JsonInclude.Include.NON_NULL)
36  @JsonDeserialize(builder = ApiError.Builder.class)
37  public final class ApiError {
38  
39      /**
40       * {@link ApiError} {@link Comparator}, which sorts by code and description.
41       */
42      public static final ErrorComparator ERROR_COMPARATOR = new ErrorComparator();
43  
44      // Must be an Integer, because 0 is not a valid default
45      private final Integer code;
46      private final LocalizableString description;
47      private final Schema schema;
48      @JsonProperty("$ref")
49      private final Reference reference;
50  
51      private ApiError(Builder builder) {
52          this.code = builder.code;
53          this.description = builder.description;
54          this.schema = builder.schema;
55          this.reference = builder.reference;
56  
57          if (reference == null && (code == null || description == null || isEmpty(description.toString()))) {
58              throw new ApiValidationException("code and description are required");
59          }
60          if (reference != null && (code != null || description != null || schema != null)) {
61              throw new ApiValidationException("Cannot set code, description or schema when using a reference");
62          }
63      }
64  
65      /**
66       * Getter of the error code.
67       *
68       * @return Code
69       */
70      public Integer getCode() {
71          return code;
72      }
73  
74      /**
75       * Getter of the error description.
76       *
77       * @return Description
78       */
79      public LocalizableString getDescription() {
80          return description;
81      }
82  
83      /**
84       * Getter of the error schema.
85       *
86       * @return Schema
87       */
88      public Schema getSchema() {
89          return schema;
90      }
91  
92      /**
93       * Getter of the reference.
94       *
95       * @return The reference.
96       */
97      public Reference getReference() {
98          return reference;
99      }
100 
101     @Override
102     public boolean equals(Object o) {
103         if (this == o) {
104             return true;
105         }
106         if (o == null || getClass() != o.getClass()) {
107             return false;
108         }
109         ApiError apiError = (ApiError) o;
110         return Objects.equals(code, apiError.code)
111                 && Objects.equals(description, apiError.description)
112                 && Objects.equals(schema, apiError.schema)
113                 && Objects.equals(reference, apiError.reference);
114     }
115 
116     @Override
117     public int hashCode() {
118         return Objects.hash(code, description, schema, reference);
119     }
120 
121     /**
122      * New apiError builder.
123      *
124      * @return Builder
125      */
126     public static Builder apiError() {
127         return new Builder();
128     }
129 
130     /**
131      * Builds an ApiError object from the data in the annotation. If the {@code ApiError} has an {@code id} defined, the
132      * ApiError will be defined in the top-level {@code descriptor}, and a reference to that definition will be
133      * returned.
134      *
135      * @param apiError The annotation that holds the data
136      * @param descriptor The root descriptor, for adding definitions to.
137      * @param relativeType The type relative to which schema resources should be resolved.
138      * @return ApiError instance
139      */
140     public static ApiError fromAnnotation(org.forgerock.api.annotations.ApiError apiError,
141                                           ApiDescription descriptor, Class<?> relativeType) {
142         ApiError apiErrorDefinition = apiError()
143                 .description(new LocalizableString(apiError.description(), relativeType))
144                 .code(apiError.code())
145                 .schema(Schema.fromAnnotation(apiError.detailSchema(), descriptor, relativeType))
146                 .build();
147         if (!Strings.isNullOrEmpty(apiError.id())) {
148             // we've got an id for this apiApiError, so define it at the top level and return a reference.
149             descriptor.addError(apiError.id(), apiErrorDefinition);
150             return apiError().reference(Reference.reference().value("#/errors/" + apiError.id()).build()).build();
151         } else {
152             return apiErrorDefinition;
153         }
154     }
155 
156     /**
157      * Builder for the ApiError.
158      */
159     public static final class Builder {
160 
161         // Must be an Integer, because 0 is not a valid default.
162         private Integer code;
163         private LocalizableString description;
164         private Schema schema;
165         private Reference reference;
166 
167         private Builder() {
168         }
169 
170         /**
171          * Set the error code.
172          *
173          * @param code The apiError code.
174          * @return This builder.
175          */
176         @JsonProperty("code")
177         public Builder code(Integer code) {
178             this.code = code;
179             return this;
180         }
181 
182         /**
183          * Set the error description.
184          *
185          * @param description ApiError description
186          * @return This builder.
187          */
188         public Builder description(LocalizableString description) {
189             this.description = description;
190             return this;
191         }
192 
193         /**
194          * Set the error description.
195          *
196          * @param description ApiError description
197          * @return This builder.
198          */
199         @JsonProperty("description")
200         public Builder description(String description) {
201             this.description = new LocalizableString(description);
202             return this;
203         }
204 
205         /**
206          * Set the schema.
207          *
208          * @param schema ApiError schema
209          * @return This builder.
210          */
211         @JsonProperty("schema")
212         public Builder schema(Schema schema) {
213             this.schema = schema;
214             return this;
215         }
216 
217         /**
218          * Set the error as a reference to another definition.
219          *
220          * @param reference The reference.
221          * @return This builder.
222          */
223         @JsonProperty("$ref")
224         public Builder reference(Reference reference) {
225             this.reference = reference;
226             return this;
227         }
228 
229         /**
230          * Builds the ApiError.
231          *
232          * @return ApiError instance
233          */
234         public ApiError build() {
235             return new ApiError(this);
236         }
237     }
238 
239     /**
240      * {@link ApiError} {@link Comparator}, which sorts by code and description. This {@code Comparator} does not handle
241      * {@code null} values or duplicates, because those conditions should never occur in practice.
242      * <p>
243      * This class is thread-safe.
244      * </p>
245      */
246     private static class ErrorComparator implements Comparator<ApiError> {
247         @Override
248         public int compare(final ApiError o1, final ApiError o2) {
249             if (o1.getReference() != null) {
250                 return o2.getReference() != null
251                         ? o1.getReference().getValue().compareTo(o2.getReference().getValue())
252                         : 1;
253             }
254             if (o2.getReference() != null) {
255                 return -1;
256             }
257             final int codeCompare = o1.code.compareTo(o2.code);
258             if (codeCompare == 0) {
259                 return o1.description.toString().compareTo(o2.description.toString());
260             }
261             return codeCompare;
262         }
263     }
264 }