001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2016 ForgeRock AS. 015 */ 016package org.forgerock.api.models; 017 018import static org.forgerock.api.models.Definitions.definitions; 019import static org.forgerock.api.models.Errors.errors; 020import static org.forgerock.api.models.Paths.paths; 021import static org.forgerock.api.models.Services.services; 022import static org.forgerock.api.util.ValidationUtil.isEmpty; 023 024import java.util.Objects; 025 026import com.fasterxml.jackson.annotation.JsonInclude; 027import com.fasterxml.jackson.annotation.JsonProperty; 028import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 029import org.forgerock.api.ApiValidationException; 030import org.forgerock.util.i18n.LocalizableString; 031 032/** Class that represents the ApiDescription type in API descriptor. */ 033@JsonDeserialize(builder = ApiDescription.Builder.class) 034@JsonInclude(JsonInclude.Include.NON_NULL) 035public final class ApiDescription { 036 037 private final String id; 038 private final String version; 039 private final LocalizableString description; 040 private final Definitions definitions; 041 private final Services services; 042 private final Errors errors; 043 private final Paths paths; 044 045 private ApiDescription(Builder builder) { 046 this.id = builder.id; 047 this.version = builder.version; 048 this.description = builder.description; 049 this.definitions = builder.definitions == null ? definitions().build() : builder.definitions; 050 this.services = builder.services == null ? services().build() : builder.services; 051 this.errors = builder.errors == null ? errors().build() : builder.errors; 052 this.paths = builder.paths == null ? paths().build() : builder.paths; 053 054 if (isEmpty(id) || isEmpty(version)) { 055 throw new ApiValidationException("id and version required"); 056 } 057 } 058 059 /** 060 * Getter of id. 061 * 062 * @return id 063 */ 064 public String getId() { 065 return id; 066 } 067 068 /** 069 * Getter of version. 070 * 071 * @return The version. 072 */ 073 public String getVersion() { 074 return version; 075 } 076 077 /** 078 * Gets description of API Descriptor. 079 * 080 * @return Description of API Descriptor 081 */ 082 public LocalizableString getDescription() { 083 return description; 084 } 085 086 /** 087 * Getter of definitions. 088 * 089 * @return Definitions map 090 */ 091 public Definitions getDefinitions() { 092 return definitions.getDefinitions().isEmpty() ? null : definitions; 093 } 094 095 /** 096 * This allows the models package to mutate the schema defined here. This is used when processing 097 * annotations on resources that may reference schemas using an id, so those schemas need to be defined here 098 * rather than in-line in the resource descriptions. 099 * 100 * @param id The definition id. 101 * @param schema The definition. 102 */ 103 void addDefinition(String id, Schema schema) { 104 if (schema.getReference() != null) { 105 throw new IllegalArgumentException("Cannot define a schema using a reference"); 106 } 107 Schema defined = definitions.get(id); 108 if (defined != null && !defined.equals(schema)) { 109 throw new IllegalArgumentException("Trying to redefine already defined schema, " + id); 110 } 111 definitions.getDefinitions().put(id, schema); 112 } 113 114 /** 115 * Getter of services. 116 * 117 * @return Services map 118 */ 119 public Services getServices() { 120 return services.getServices().isEmpty() ? null : services; 121 } 122 123 /** 124 * This allows the models package to mutate the resources defined here. This is used when processing 125 * annotations on resources that may reference resources using an id, so those services need to be defined here 126 * rather than in-line in the resource descriptions. 127 * 128 * @param id The resource id. 129 * @param resource The resource definition. 130 * @see Resource#fromAnnotatedType(Class, Resource.AnnotatedTypeVariant, ApiDescription) 131 */ 132 void addService(String id, Resource resource) { 133 if (resource.getReference() != null) { 134 throw new IllegalArgumentException("Cannot define a resource using a reference"); 135 } 136 Resource defined = services.get(id); 137 if (defined != null && !defined.equals(resource)) { 138 throw new IllegalArgumentException("Trying to redefine already defined resource, " + id); 139 } 140 services.getServices().put(id, resource); 141 } 142 143 /** 144 * Getter of errors. 145 * 146 * @return Errors map 147 */ 148 public Errors getErrors() { 149 return errors.getErrors().isEmpty() ? null : errors; 150 } 151 152 /** 153 * This allows the models package to mutate the errors defined here. This is used when processing annotations on 154 * resources that may reference errors using an id, so those errors need to be defined here rather than in-line in 155 * the resource descriptions. 156 * 157 * @param id The error id. 158 * @param apiError The error definition. 159 * @see ApiError#fromAnnotation(org.forgerock.api.annotations.ApiError, ApiDescription, Class) 160 */ 161 void addError(String id, ApiError apiError) { 162 if (apiError.getReference() != null) { 163 throw new IllegalArgumentException("Cannot define an apiError using a reference"); 164 } 165 ApiError defined = errors.get(id); 166 if (defined != null && !defined.equals(apiError)) { 167 throw new IllegalArgumentException("Trying to redefine already defined apiError, " + id); 168 } 169 errors.getErrors().put(id, apiError); 170 } 171 172 /** 173 * Getter of paths. 174 * 175 * @return Paths 176 */ 177 // Jackson queries PathsModule.PathsSerializer.isEmpty() to know whether a Paths object is empty 178 @JsonInclude(JsonInclude.Include.NON_EMPTY) 179 public Paths getPaths() { 180 return paths; 181 } 182 183 /** 184 * Create a new Builder for ApiDescription. 185 * 186 * @return Builder 187 */ 188 public static Builder apiDescription() { 189 return new Builder(); 190 } 191 192 @Override 193 public boolean equals(Object o) { 194 if (this == o) { 195 return true; 196 } 197 if (o == null || getClass() != o.getClass()) { 198 return false; 199 } 200 ApiDescription that = (ApiDescription) o; 201 return Objects.equals(id, that.id) 202 && Objects.equals(version, that.version) 203 && Objects.equals(description, that.description) 204 && Objects.equals(definitions, that.definitions) 205 && Objects.equals(services, that.services) 206 && Objects.equals(errors, that.errors) 207 && Objects.equals(paths, that.paths); 208 } 209 210 @Override 211 public int hashCode() { 212 return Objects.hash(id, version, description, definitions, services, errors, paths); 213 } 214 215 /** 216 * Builder for the ApiDescription. 217 */ 218 public static final class Builder { 219 220 private String id; 221 private LocalizableString description; 222 private Definitions definitions; 223 private Errors errors; 224 private Services services; 225 private Paths paths; 226 private String version; 227 228 /** 229 * Private default constructor with the mandatory fields. 230 */ 231 private Builder() { 232 } 233 234 /** 235 * Set the id. 236 * 237 * @param id ApiDescription id 238 * @return Builder 239 */ 240 @JsonProperty("id") 241 public Builder id(String id) { 242 this.id = id; 243 return this; 244 } 245 246 /** 247 * Sets the description. 248 * 249 * @param description Description of API Description 250 * @return Builder 251 */ 252 @JsonProperty("description") 253 public Builder description(String description) { 254 this.description = new LocalizableString(description); 255 return this; 256 } 257 258 /** 259 * Sets the description. 260 * 261 * @param description Description of API Description 262 * @return Builder 263 */ 264 public Builder description(LocalizableString description) { 265 this.description = description; 266 return this; 267 } 268 269 /** 270 * Set the definitions. 271 * 272 * @param definitions Definitions for this API Description 273 * @return Builder 274 */ 275 @JsonProperty("definitions") 276 public Builder definitions(Definitions definitions) { 277 this.definitions = definitions; 278 return this; 279 } 280 281 282 /** 283 * Set the services. 284 * 285 * @param services Services for this API Description 286 * @return Builder 287 */ 288 @JsonProperty("services") 289 public Builder services(Services services) { 290 this.services = services; 291 return this; 292 } 293 294 /** 295 * Set the errors. 296 * 297 * @param errors Errors for this API Description 298 * @return Builder 299 */ 300 @JsonProperty("errors") 301 public Builder errors(Errors errors) { 302 this.errors = errors; 303 return this; 304 } 305 306 /** 307 * Set the paths. 308 * 309 * @param paths Paths 310 * @return Builder 311 */ 312 @JsonProperty("paths") 313 public Builder paths(Paths paths) { 314 this.paths = paths; 315 return this; 316 } 317 318 /** 319 * Set the version of the API. 320 * 321 * @param version The version. 322 * @return This builder. 323 */ 324 @JsonProperty("version") 325 public Builder version(String version) { 326 this.version = version; 327 return this; 328 } 329 330 /** 331 * Builds the ApiDescription instance. 332 * 333 * @return ApiDescription instance 334 */ 335 public ApiDescription build() { 336 return new ApiDescription(this); 337 } 338 } 339} 340