Items.java
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2016 ForgeRock AS.
*/
package org.forgerock.api.models;
import static org.forgerock.api.enums.ParameterSource.*;
import static org.forgerock.api.util.ValidationUtil.*;
import static org.forgerock.util.Reject.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.forgerock.api.ApiValidationException;
import org.forgerock.api.annotations.Actions;
import org.forgerock.api.annotations.CollectionProvider;
import org.forgerock.util.i18n.LocalizableString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class that represents the Items type in API descriptor.
*/
@JsonDeserialize(builder = Items.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
public final class Items {
private static final Logger LOGGER = LoggerFactory.getLogger(Items.class);
private final Create create;
private final Read read;
private final Update update;
private final Delete delete;
private final Patch patch;
private final Action[] actions;
private final SubResources subresources;
private final Parameter pathParameter;
private Items(Builder builder) {
this.create = builder.create;
this.read = builder.read;
this.update = builder.update;
this.delete = builder.delete;
this.patch = builder.patch;
this.subresources = builder.subresources;
this.pathParameter = builder.pathParameter;
this.actions = builder.actions.toArray(new Action[builder.actions.size()]);
if (create == null && read == null && update == null && delete == null && patch == null && isEmpty(actions)) {
throw new ApiValidationException("At least one operation required");
}
}
/**
* Getter of Create.
*
* @return Create
*/
public Create getCreate() {
return create;
}
/**
* Getter of Read.
*
* @return Read
*/
public Read getRead() {
return read;
}
/**
* Getter of Update.
*
* @return Update
*/
public Update getUpdate() {
return update;
}
/**
* Getter of Delete.
*
* @return Delete
*/
public Delete getDelete() {
return delete;
}
/**
* Getter of Patch.
*
* @return Patch
*/
public Patch getPatch() {
return patch;
}
/**
* Getter of actions.
*
* @return Actions
*/
public Action[] getActions() {
return actions.length == 0 ? null : actions;
}
/**
* Getter of sub-resources.
*
* @return Sub-resources
*/
public SubResources getSubresources() {
return subresources;
}
/**
* Get the path parameter.
*
* @return The path parameter.
*/
public Parameter getPathParameter() {
return pathParameter;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Items items = (Items) o;
return Objects.equals(create, items.create)
&& Objects.equals(read, items.read)
&& Objects.equals(update, items.update)
&& Objects.equals(delete, items.delete)
&& Objects.equals(patch, items.patch)
&& Arrays.equals(actions, items.actions)
&& Objects.equals(subresources, items.subresources)
&& Objects.equals(pathParameter, items.pathParameter);
}
@Override
public int hashCode() {
return Objects.hash(create, read, update, delete, patch, actions, pathParameter, subresources);
}
/**
* Builds a {@link Resource} from this {@code Items} instance.
*
* @param mvccSupported {@code true} when MVCC is supported and {@code false} otherwise
* @param resourceSchema Resource-{@link Schema} or {@code null}
* @param title The resource title.
* @param description The resource description.
* @return New {@link Resource}
*/
@JsonIgnore
public Resource asResource(boolean mvccSupported, Schema resourceSchema, LocalizableString title,
LocalizableString description) {
final List<Action> actions =
getActions() == null ? Collections.<Action>emptyList() : Arrays.asList(getActions());
return Resource.resource()
.mvccSupported(mvccSupported)
.resourceSchema(resourceSchema)
.title(title)
.description(description)
.create(getCreate())
.read(getRead())
.update(getUpdate())
.delete(getDelete())
.patch(getPatch())
.actions(actions)
.build();
}
/**
* Create a new Builder for Resoruce.
*
* @return Builder
*/
public static Builder items() {
return new Builder();
}
/**
* Build an {@code Items} from an annotated request handler.
*
* @param type The annotated type.
* @param descriptor The root descriptor to add definitions to.
* @param subResources The sub resources.
* @return The built {@code Items} object.
*/
public static Items fromAnnotatedType(Class<?> type, ApiDescription descriptor, SubResources subResources) {
final Builder builder = items();
final CollectionProvider provider = type.getAnnotation(CollectionProvider.class);
if (provider == null) {
LOGGER.info("Asked for Items for annotated type, but type does not have required RequestHandler"
+ " annotation. No api descriptor will be available for " + type);
return null;
}
builder.pathParameter(Parameter.fromAnnotation(type, provider.pathParam()));
for (final Method m : type.getMethods()) {
boolean instanceMethod = Arrays.asList(m.getParameterTypes()).indexOf(String.class) > -1;
org.forgerock.api.annotations.Action action = m.getAnnotation(org.forgerock.api.annotations.Action.class);
if (action != null && instanceMethod) {
builder.actions.add(Action.fromAnnotation(action, m, descriptor, type));
}
Actions actions = m.getAnnotation(Actions.class);
if (actions != null && instanceMethod) {
for (org.forgerock.api.annotations.Action a : actions.value()) {
builder.actions.add(Action.fromAnnotation(a, null, descriptor, type));
}
}
org.forgerock.api.annotations.Create create = m.getAnnotation(org.forgerock.api.annotations.Create.class);
if (create != null) {
builder.create = Create.fromAnnotation(create, true, descriptor, type);
}
org.forgerock.api.annotations.Read read = m.getAnnotation(org.forgerock.api.annotations.Read.class);
if (read != null) {
builder.read = Read.fromAnnotation(read, descriptor, type);
}
org.forgerock.api.annotations.Update update =
m.getAnnotation(org.forgerock.api.annotations.Update.class);
if (update != null) {
builder.update = Update.fromAnnotation(update, descriptor, type);
}
org.forgerock.api.annotations.Delete delete =
m.getAnnotation(org.forgerock.api.annotations.Delete.class);
if (delete != null) {
builder.delete = Delete.fromAnnotation(delete, descriptor, type);
}
org.forgerock.api.annotations.Patch patch = m.getAnnotation(org.forgerock.api.annotations.Patch.class);
if (patch != null) {
builder.patch = Patch.fromAnnotation(patch, descriptor, type);
}
}
return builder.subresources(subResources).build();
}
/**
* Builder to help construct the {@code Items}.
*/
public final static class Builder {
private Create create;
private Read read;
private Update update;
private Delete delete;
private Patch patch;
private SubResources subresources;
private Parameter pathParameter = Parameter.parameter().name("id").type("string").source(PATH).required(true)
.build();
private final Set<Action> actions;
private boolean built = false;
/**
* Private default constructor.
*/
protected Builder() {
actions = new TreeSet<>();
}
/**
* Set create.
*
* @param create The create operation description, if supported
* @return Builder
*/
@JsonProperty("create")
public Builder create(Create create) {
checkState();
this.create = create;
return this;
}
/**
* Set Read.
*
* @param read The read operation description, if supported
* @return Builder
*/
@JsonProperty("read")
public Builder read(Read read) {
checkState();
this.read = read;
return this;
}
/**
* Set Update.
*
* @param update The update operation description, if supported
* @return Builder
*/
@JsonProperty("update")
public Builder update(Update update) {
checkState();
this.update = update;
return this;
}
/**
* Set Delete.
*
* @param delete The delete operation description, if supported
* @return Builder
*/
@JsonProperty("delete")
public Builder delete(Delete delete) {
checkState();
this.delete = delete;
return this;
}
/**
* Set Patch.
*
* @param patch The patch operation description, if supported
* @return Builder
*/
@JsonProperty("patch")
public Builder patch(Patch patch) {
checkState();
this.patch = patch;
return this;
}
/**
* Set Actions.
*
* @param actions The list of action operation descriptions, if supported
* @return Builder
*/
@JsonProperty("actions")
public Builder actions(List<Action> actions) {
checkState();
this.actions.addAll(actions);
return this;
}
/**
* Adds one Action to the list of Actions.
*
* @param action Action operation description to be added to the list
* @return Builder
*/
public Builder action(Action action) {
checkState();
this.actions.add(action);
return this;
}
/**
* Sets the path parameter for this resource.
*
* @param pathParameter The path parameter definition.
* @return Builder
*/
@JsonProperty("pathParameter")
public Builder pathParameter(Parameter pathParameter) {
checkState();
this.pathParameter = pathParameter;
return this;
}
/**
* Sets the sub-resources for this resource.
*
* @param subresources The sub-reosurces definition.
* @return Builder
*/
@JsonProperty("subresources")
public Builder subresources(SubResources subresources) {
checkState();
this.subresources = subresources;
return this;
}
/**
* Construct a new instance of Resource.
*
* @return Resource instance
*/
public Items build() {
checkState();
this.built = true;
if (create == null && read == null && update == null && delete == null && patch == null
&& actions.isEmpty() && subresources == null) {
return null;
}
return new Items(this);
}
private void checkState() {
rejectStateIfTrue(built, "Already built Items");
}
}
}