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-2017 ForgeRock AS. 015 */ 016 017package org.forgerock.api.models; 018 019import static org.forgerock.api.models.ApiError.apiError; 020import static org.forgerock.api.models.Reference.reference; 021import static org.forgerock.api.util.ValidationUtil.isEmpty; 022 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.List; 026import java.util.Objects; 027 028import com.fasterxml.jackson.annotation.JsonInclude; 029import com.fasterxml.jackson.annotation.JsonProperty; 030import org.forgerock.api.enums.Stability; 031import org.forgerock.util.i18n.LocalizableString; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035/** 036 * Class that represents the Operation type in API descriptor. 037 */ 038@JsonInclude(JsonInclude.Include.NON_NULL) 039public abstract class Operation { 040 041 private static final Logger logger = LoggerFactory.getLogger(Operation.class); 042 043 private final LocalizableString description; 044 private final String[] supportedLocales; 045 @JsonProperty("errors") 046 private final ApiError[] apiErrors; 047 private final Parameter[] parameters; 048 private final Stability stability; 049 050 /** 051 * Protected constructor of the Operation. 052 * 053 * @param builder Operation Builder 054 */ 055 protected Operation(Builder builder) { 056 this.description = builder.description; 057 this.supportedLocales = builder.supportedLocales; 058 this.stability = builder.stability; 059 060 final List<ApiError> apiErrors = builder.apiErrors; 061 this.apiErrors = apiErrors.toArray(new ApiError[apiErrors.size()]); 062 063 final List<Parameter> parameters = builder.parameters; 064 this.parameters = parameters.toArray(new Parameter[parameters.size()]); 065 } 066 067 /** 068 * Getter of the description. 069 * 070 * @return Description 071 */ 072 public LocalizableString getDescription() { 073 return description; 074 } 075 076 /** 077 * Getter of the supported locales array. 078 * 079 * @return Supported locales 080 */ 081 public String[] getSupportedLocales() { 082 return supportedLocales; 083 } 084 085 /** 086 * Getter of the error array. 087 * 088 * @return ApiError array 089 */ 090 public ApiError[] getApiErrors() { 091 return apiErrors.length == 0 ? null : apiErrors; 092 } 093 094 /** 095 * Getter of the parameters array. 096 * 097 * @return Parameters 098 */ 099 public Parameter[] getParameters() { 100 return parameters.length == 0 ? null : parameters; 101 } 102 103 /** 104 * Getter of Operation stability. 105 * 106 * @return Stability or {@code null} which suggests {@link Stability#STABLE} (default). 107 */ 108 public Stability getStability() { 109 return stability; 110 } 111 112 @Override 113 public boolean equals(Object o) { 114 if (this == o) { 115 return true; 116 } 117 if (o == null || getClass() != o.getClass()) { 118 return false; 119 } 120 Operation operation = (Operation) o; 121 return Objects.equals(description, operation.description) 122 && Arrays.equals(supportedLocales, operation.supportedLocales) 123 && Arrays.equals(apiErrors, operation.apiErrors) 124 && Arrays.equals(parameters, operation.parameters) 125 && stability == operation.stability; 126 } 127 128 @Override 129 public int hashCode() { 130 return Objects.hash(description, supportedLocales, apiErrors, parameters, stability); 131 } 132 133 /** 134 * Allocates the operation by operation type to the given Resource Builder 135 * by calling the corresonding method by type. 136 * 137 * @param resourceBuilder - Resource Builder to add the operation 138 */ 139 protected abstract void allocateToResource(Resource.Builder resourceBuilder); 140 141 /** 142 * Builder to help construct the Operation. 143 */ 144 public abstract static class Builder<T extends Builder<T>> { 145 146 private LocalizableString description; 147 private String[] supportedLocales; 148 private final List<ApiError> apiErrors; 149 private final List<Parameter> parameters; 150 private Stability stability; 151 152 /** 153 * Creates a new Builder. 154 */ 155 protected Builder() { 156 apiErrors = new ArrayList<>(); 157 parameters = new ArrayList<>(); 158 } 159 160 /** 161 * Abstract method that returns the instantiated Builder itself. 162 * 163 * @return Builder 164 */ 165 protected abstract T self(); 166 167 /** 168 * Set the description. 169 * 170 * @param description A description of the endpoint 171 * @return Builder 172 */ 173 public T description(LocalizableString description) { 174 this.description = description; 175 return self(); 176 } 177 178 /** 179 * Set the description. 180 * 181 * @param description A description of the endpoint 182 * @return Builder 183 */ 184 @JsonProperty("description") 185 public T description(String description) { 186 this.description = new LocalizableString(description); 187 return self(); 188 } 189 190 /** 191 * Set the supported locale. 192 * 193 * @param supportedlocales Locales codes supported by the operation 194 * @return Builder 195 */ 196 @JsonProperty("supportedLocales") 197 public T supportedLocales(String... supportedlocales) { 198 this.supportedLocales = supportedlocales; 199 return self(); 200 } 201 202 /** 203 * Set multiple supported errors. 204 * 205 * @param apiErrors What errors may be returned by this operation 206 * @return Builder 207 */ 208 @JsonProperty("errors") 209 public T errors(List<ApiError> apiErrors) { 210 this.apiErrors.addAll(apiErrors); 211 return self(); 212 } 213 214 /** 215 * Sets a single supported error. 216 * 217 * @param apiError An error that may be returned by this operation 218 * @return Builder 219 */ 220 public T error(ApiError apiError) { 221 this.apiErrors.add(apiError); 222 return self(); 223 } 224 225 /** 226 * Set multiple supported parameters. 227 * 228 * @param parameters Extra parameters supported by the operation 229 * @return Builder 230 */ 231 @JsonProperty("parameters") 232 public T parameters(List<Parameter> parameters) { 233 this.parameters.addAll(parameters); 234 return self(); 235 } 236 237 /** 238 * Sets a single supported parameters. 239 * 240 * @param parameter Extra parameter supported by the operation 241 * @return Builder 242 */ 243 public T parameter(Parameter parameter) { 244 this.parameters.add(parameter); 245 return self(); 246 } 247 248 /** 249 * Sets stability of Operation. 250 * 251 * @param stability Stability 252 * @return Builder 253 */ 254 @JsonProperty("stability") 255 public T stability(Stability stability) { 256 this.stability = stability; 257 return self(); 258 } 259 260 /** 261 * Set all properties in the Builder using the data in the annotation. 262 * @param operation The annotation that holds the data 263 * @param descriptor The root descriptor 264 * @param relativeType The type relative to which schema resources should be resolved. 265 * @return Builder 266 */ 267 public T detailsFromAnnotation(org.forgerock.api.annotations.Operation operation, 268 ApiDescription descriptor, Class<?> relativeType) { 269 for (String ref : operation.errorRefs()) { 270 if (isEmpty(ref)) { 271 logger.debug("Empty errorRefs array-element ignored on: " + relativeType.getCanonicalName()); 272 } else { 273 error(apiError().reference(reference().value(ref).build()).build()); 274 } 275 } 276 for (org.forgerock.api.annotations.ApiError apiApiError : operation.errors()) { 277 error(ApiError.fromAnnotation(apiApiError, descriptor, relativeType)); 278 } 279 for (org.forgerock.api.annotations.Parameter parameter : operation.parameters()) { 280 parameter(Parameter.fromAnnotation(relativeType, parameter)); 281 } 282 return description(new LocalizableString(operation.description(), relativeType)) 283 .supportedLocales(operation.locales()) 284 .stability(operation.stability()); 285 } 286 } 287 288}