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 */ 016 017package org.forgerock.api.models; 018 019import static org.forgerock.api.enums.ParameterSource.*; 020import static org.forgerock.api.util.ValidationUtil.*; 021import static org.forgerock.util.Reject.*; 022 023import java.lang.reflect.Method; 024import java.util.Arrays; 025import java.util.Collections; 026import java.util.List; 027import java.util.Objects; 028import java.util.Set; 029import java.util.TreeSet; 030 031import com.fasterxml.jackson.annotation.JsonIgnore; 032import com.fasterxml.jackson.annotation.JsonInclude; 033import com.fasterxml.jackson.annotation.JsonProperty; 034import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 035import org.forgerock.api.ApiValidationException; 036import org.forgerock.api.annotations.Actions; 037import org.forgerock.api.annotations.CollectionProvider; 038import org.forgerock.util.i18n.LocalizableString; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042/** 043 * Class that represents the Items type in API descriptor. 044 */ 045@JsonDeserialize(builder = Items.Builder.class) 046@JsonInclude(JsonInclude.Include.NON_NULL) 047public final class Items { 048 049 private static final Logger LOGGER = LoggerFactory.getLogger(Items.class); 050 051 private final Create create; 052 private final Read read; 053 private final Update update; 054 private final Delete delete; 055 private final Patch patch; 056 private final Action[] actions; 057 private final SubResources subresources; 058 private final Parameter pathParameter; 059 060 private Items(Builder builder) { 061 this.create = builder.create; 062 this.read = builder.read; 063 this.update = builder.update; 064 this.delete = builder.delete; 065 this.patch = builder.patch; 066 this.subresources = builder.subresources; 067 this.pathParameter = builder.pathParameter; 068 this.actions = builder.actions.toArray(new Action[builder.actions.size()]); 069 070 if (create == null && read == null && update == null && delete == null && patch == null && isEmpty(actions)) { 071 throw new ApiValidationException("At least one operation required"); 072 } 073 } 074 075 /** 076 * Getter of Create. 077 * 078 * @return Create 079 */ 080 public Create getCreate() { 081 return create; 082 } 083 084 /** 085 * Getter of Read. 086 * 087 * @return Read 088 */ 089 public Read getRead() { 090 return read; 091 } 092 093 /** 094 * Getter of Update. 095 * 096 * @return Update 097 */ 098 public Update getUpdate() { 099 return update; 100 } 101 102 /** 103 * Getter of Delete. 104 * 105 * @return Delete 106 */ 107 public Delete getDelete() { 108 return delete; 109 } 110 111 /** 112 * Getter of Patch. 113 * 114 * @return Patch 115 */ 116 public Patch getPatch() { 117 return patch; 118 } 119 120 /** 121 * Getter of actions. 122 * 123 * @return Actions 124 */ 125 public Action[] getActions() { 126 return actions.length == 0 ? null : actions; 127 } 128 129 /** 130 * Getter of sub-resources. 131 * 132 * @return Sub-resources 133 */ 134 public SubResources getSubresources() { 135 return subresources; 136 } 137 138 /** 139 * Get the path parameter. 140 * 141 * @return The path parameter. 142 */ 143 public Parameter getPathParameter() { 144 return pathParameter; 145 } 146 147 @Override 148 public boolean equals(Object o) { 149 if (this == o) { 150 return true; 151 } 152 if (o == null || getClass() != o.getClass()) { 153 return false; 154 } 155 Items items = (Items) o; 156 return Objects.equals(create, items.create) 157 && Objects.equals(read, items.read) 158 && Objects.equals(update, items.update) 159 && Objects.equals(delete, items.delete) 160 && Objects.equals(patch, items.patch) 161 && Arrays.equals(actions, items.actions) 162 && Objects.equals(subresources, items.subresources) 163 && Objects.equals(pathParameter, items.pathParameter); 164 } 165 166 @Override 167 public int hashCode() { 168 return Objects.hash(create, read, update, delete, patch, actions, pathParameter, subresources); 169 } 170 171 /** 172 * Builds a {@link Resource} from this {@code Items} instance. 173 * 174 * @param mvccSupported {@code true} when MVCC is supported and {@code false} otherwise 175 * @param resourceSchema Resource-{@link Schema} or {@code null} 176 * @param title The resource title. 177 * @param description The resource description. 178 * @return New {@link Resource} 179 */ 180 @JsonIgnore 181 public Resource asResource(boolean mvccSupported, Schema resourceSchema, LocalizableString title, 182 LocalizableString description) { 183 final List<Action> actions = 184 getActions() == null ? Collections.<Action>emptyList() : Arrays.asList(getActions()); 185 return Resource.resource() 186 .mvccSupported(mvccSupported) 187 .resourceSchema(resourceSchema) 188 .title(title) 189 .description(description) 190 .create(getCreate()) 191 .read(getRead()) 192 .update(getUpdate()) 193 .delete(getDelete()) 194 .patch(getPatch()) 195 .actions(actions) 196 .build(); 197 } 198 199 /** 200 * Create a new Builder for Resoruce. 201 * 202 * @return Builder 203 */ 204 public static Builder items() { 205 return new Builder(); 206 } 207 208 /** 209 * Build an {@code Items} from an annotated request handler. 210 * 211 * @param type The annotated type. 212 * @param descriptor The root descriptor to add definitions to. 213 * @param subResources The sub resources. 214 * @return The built {@code Items} object. 215 */ 216 public static Items fromAnnotatedType(Class<?> type, ApiDescription descriptor, SubResources subResources) { 217 final Builder builder = items(); 218 final CollectionProvider provider = type.getAnnotation(CollectionProvider.class); 219 if (provider == null) { 220 LOGGER.info("Asked for Items for annotated type, but type does not have required RequestHandler" 221 + " annotation. No api descriptor will be available for " + type); 222 return null; 223 } 224 builder.pathParameter(Parameter.fromAnnotation(type, provider.pathParam())); 225 226 for (final Method m : type.getMethods()) { 227 boolean instanceMethod = Arrays.asList(m.getParameterTypes()).indexOf(String.class) > -1; 228 org.forgerock.api.annotations.Action action = m.getAnnotation(org.forgerock.api.annotations.Action.class); 229 if (action != null && instanceMethod) { 230 builder.actions.add(Action.fromAnnotation(action, m, descriptor, type)); 231 } 232 Actions actions = m.getAnnotation(Actions.class); 233 if (actions != null && instanceMethod) { 234 for (org.forgerock.api.annotations.Action a : actions.value()) { 235 builder.actions.add(Action.fromAnnotation(a, null, descriptor, type)); 236 } 237 } 238 org.forgerock.api.annotations.Create create = m.getAnnotation(org.forgerock.api.annotations.Create.class); 239 if (create != null) { 240 builder.create = Create.fromAnnotation(create, true, descriptor, type); 241 } 242 org.forgerock.api.annotations.Read read = m.getAnnotation(org.forgerock.api.annotations.Read.class); 243 if (read != null) { 244 builder.read = Read.fromAnnotation(read, descriptor, type); 245 } 246 org.forgerock.api.annotations.Update update = 247 m.getAnnotation(org.forgerock.api.annotations.Update.class); 248 if (update != null) { 249 builder.update = Update.fromAnnotation(update, descriptor, type); 250 } 251 org.forgerock.api.annotations.Delete delete = 252 m.getAnnotation(org.forgerock.api.annotations.Delete.class); 253 if (delete != null) { 254 builder.delete = Delete.fromAnnotation(delete, descriptor, type); 255 } 256 org.forgerock.api.annotations.Patch patch = m.getAnnotation(org.forgerock.api.annotations.Patch.class); 257 if (patch != null) { 258 builder.patch = Patch.fromAnnotation(patch, descriptor, type); 259 } 260 } 261 262 return builder.subresources(subResources).build(); 263 } 264 265 /** 266 * Builder to help construct the {@code Items}. 267 */ 268 public final static class Builder { 269 private Create create; 270 private Read read; 271 private Update update; 272 private Delete delete; 273 private Patch patch; 274 private SubResources subresources; 275 private Parameter pathParameter = Parameter.parameter().name("id").type("string").source(PATH).required(true) 276 .build(); 277 private final Set<Action> actions; 278 private boolean built = false; 279 280 /** 281 * Private default constructor. 282 */ 283 protected Builder() { 284 actions = new TreeSet<>(); 285 } 286 287 /** 288 * Set create. 289 * 290 * @param create The create operation description, if supported 291 * @return Builder 292 */ 293 @JsonProperty("create") 294 public Builder create(Create create) { 295 checkState(); 296 this.create = create; 297 return this; 298 } 299 300 /** 301 * Set Read. 302 * 303 * @param read The read operation description, if supported 304 * @return Builder 305 */ 306 @JsonProperty("read") 307 public Builder read(Read read) { 308 checkState(); 309 this.read = read; 310 return this; 311 } 312 313 /** 314 * Set Update. 315 * 316 * @param update The update operation description, if supported 317 * @return Builder 318 */ 319 @JsonProperty("update") 320 public Builder update(Update update) { 321 checkState(); 322 this.update = update; 323 return this; 324 } 325 326 /** 327 * Set Delete. 328 * 329 * @param delete The delete operation description, if supported 330 * @return Builder 331 */ 332 @JsonProperty("delete") 333 public Builder delete(Delete delete) { 334 checkState(); 335 this.delete = delete; 336 return this; 337 } 338 339 /** 340 * Set Patch. 341 * 342 * @param patch The patch operation description, if supported 343 * @return Builder 344 */ 345 @JsonProperty("patch") 346 public Builder patch(Patch patch) { 347 checkState(); 348 this.patch = patch; 349 return this; 350 } 351 352 /** 353 * Set Actions. 354 * 355 * @param actions The list of action operation descriptions, if supported 356 * @return Builder 357 */ 358 @JsonProperty("actions") 359 public Builder actions(List<Action> actions) { 360 checkState(); 361 this.actions.addAll(actions); 362 return this; 363 } 364 365 /** 366 * Adds one Action to the list of Actions. 367 * 368 * @param action Action operation description to be added to the list 369 * @return Builder 370 */ 371 public Builder action(Action action) { 372 checkState(); 373 this.actions.add(action); 374 return this; 375 } 376 377 /** 378 * Sets the path parameter for this resource. 379 * 380 * @param pathParameter The path parameter definition. 381 * @return Builder 382 */ 383 @JsonProperty("pathParameter") 384 public Builder pathParameter(Parameter pathParameter) { 385 checkState(); 386 this.pathParameter = pathParameter; 387 return this; 388 } 389 390 /** 391 * Sets the sub-resources for this resource. 392 * 393 * @param subresources The sub-reosurces definition. 394 * @return Builder 395 */ 396 @JsonProperty("subresources") 397 public Builder subresources(SubResources subresources) { 398 checkState(); 399 this.subresources = subresources; 400 return this; 401 } 402 403 /** 404 * Construct a new instance of Resource. 405 * 406 * @return Resource instance 407 */ 408 public Items build() { 409 checkState(); 410 this.built = true; 411 if (create == null && read == null && update == null && delete == null && patch == null 412 && actions.isEmpty() && subresources == null) { 413 return null; 414 } 415 416 return new Items(this); 417 } 418 419 private void checkState() { 420 rejectStateIfTrue(built, "Already built Items"); 421 } 422 423 } 424}