/* * #%L * FlatPack serialization code * %% * Copyright (C) 2012 Perka Inc. * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package com.getperka.flatpack; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.security.Principal; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.inject.Inject; import javax.validation.ConstraintViolation; import org.joda.time.DateTime; import com.getperka.flatpack.util.FlatPackCollections; import com.getperka.flatpack.util.FlatPackTypes; import com.google.inject.TypeLiteral; /** * Encapsulates a return value, the role(s) to encode the entity for, and an optional collection of * extra entities to include in the payload. * * @param <T> the type of value being returned */ public class FlatPackEntity<T> extends TypeReference<T> { /** * A convenience method to create a FlatPackEntity for a List of entities. * * @param <T> the type of entity contained in the list. * @param clazz the entity type */ public static <T extends HasUuid> FlatPackEntity<Collection<? extends T>> collectionOf( final Class<T> clazz) { return new FlatPackEntity<Collection<? extends T>>(new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return new Type[] { clazz }; } @Override public Type getOwnerType() { return null; } @Override public Type getRawType() { return Collection.class; } }); } /** * A factory method to create a fully-specified FlatPackEntity. */ public static FlatPackEntity<?> create(Type returnType, Object toReturn, Principal principal) { // Convert primitive types to boxed Class<?> erased = FlatPackTypes.erase(returnType); if (erased.isPrimitive()) { returnType = FlatPackTypes.box(erased); } return new FlatPackEntity<Object>(returnType).withPrincipal(principal).withValue(toReturn); } /** * A convenience method to create a FlatPackEntity that embeds a single entity as the return * value. This method is null-safe. */ public static <T extends HasUuid> FlatPackEntity<T> entity(T toReturn) { return new FlatPackEntity<T>(toReturn == null ? HasUuid.class : toReturn.getClass()) .withValue(toReturn); } /** * A convenience method to create a FlatPackEntity for a Map of entities to entities. * * @param <K> the type of entity for the map keys * @param <V> the type of entity for the map values * @param key the key entity type * @param value the value entity type */ public static <K extends HasUuid, V extends HasUuid> FlatPackEntity<Map<? extends K, ? extends V>> mapOf( final Class<K> key, final Class<V> value) { return new FlatPackEntity<Map<? extends K, ? extends V>>(new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return new Type[] { key, value }; } @Override public Type getOwnerType() { return null; } @Override public Type getRawType() { return Map.class; } }); } /** * Convenience method to return a FlatPackEntity that represents a null value. It is permissible * to call the extra decoration methods on the value returned from this method. */ public static FlatPackEntity<Void> nullResponse() { return new FlatPackEntity<Void>(Void.class); } /** * A convenience method to create a FlatPackEntity for a Map of strings to entities. * * @param <V> the type of entity for the map values * @param value the value entity type */ public static <V extends HasUuid> FlatPackEntity<Map<String, ? extends V>> stringMapOf( final Class<V> value) { return new FlatPackEntity<Map<String, ? extends V>>(new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return new Type[] { String.class, value }; } @Override public Type getOwnerType() { return null; } @Override public Type getRawType() { return Map.class; } }); } private DateTime lastModifiedTime; private Map<String, String> extraData; private Set<HasUuid> extraEntities; private Map<String, String> extraErrors; private Map<String, String> extraWarnings; private List<EntityMetadata> metadata; private Principal principal; private TraversalMode traversalMode = TraversalMode.SIMPLE; private T value; private Set<ConstraintViolation<?>> violations; /** * Provide type information from implicit parameterization. */ protected FlatPackEntity() {} /** * Allow construction via injection. */ @Inject FlatPackEntity(TypeLiteral<T> type) { super(type.getType()); } /** * Only used by create methods. */ private FlatPackEntity(Type type) { super(type); } /** * Populate the {@code errors} segment of the payload from {@link ConstraintViolation}. */ public FlatPackEntity<T> addConstraintViolations(Set<? extends ConstraintViolation<?>> violations) { if (this.violations == null) { this.violations = FlatPackCollections.setForIteration(); } this.violations.addAll(violations); return this; } /** * Add a key-value pair to the {@code errors} segment of the payload. */ public FlatPackEntity<T> addError(String key, String message) { if (extraErrors == null) { extraErrors = FlatPackCollections.mapForIteration(); } extraErrors.put(key, message); return this; } /** * Add entities not normally reachable from {@link #getValue()} into the payload. */ public FlatPackEntity<T> addExtraEntities(Collection<? extends HasUuid> entities) { if (extraEntities == null) { extraEntities = FlatPackCollections.setForIteration(); } extraEntities.addAll(entities); return this; } /** * Add an entity not normally reachable from {@link #getValue()} into the payload. */ public FlatPackEntity<T> addExtraEntity(HasUuid entity) { if (extraEntities == null) { extraEntities = FlatPackCollections.setForIteration(); } extraEntities.add(entity); return this; } /** * Add a key-value pair to the {@code warnings} segment of the payload. */ public FlatPackEntity<T> addWarning(String key, String message) { if (extraWarnings == null) { extraWarnings = FlatPackCollections.mapForIteration(); } extraWarnings.put(key, message); return this; } /** * Returns an immutable view of the ConstraintViolations that have been added to the * FlatPackEntity. */ public Set<ConstraintViolation<?>> getConstraintViolations() { return violations == null ? Collections.<ConstraintViolation<?>> emptySet() : Collections.unmodifiableSet(violations); } /** * Returns an immutable view of the extra side-channel entries to include at the root of the * payload. */ public Map<String, String> getExtraData() { return extraData == null ? Collections.<String, String> emptyMap() : Collections.unmodifiableMap(extraData); } /** * Returns an immutable view of the extra entities to include in the returned payload. */ public Set<HasUuid> getExtraEntities() { return extraEntities == null ? Collections.<HasUuid> emptySet() : Collections.unmodifiableSet(extraEntities); } /** * Returns an immutable view of the extra errors to include in the returned payload. */ public Map<String, String> getExtraErrors() { return extraErrors == null ? Collections.<String, String> emptyMap() : Collections.unmodifiableMap(extraErrors); } /** * Returns an immutable view of the extra warnings to include in the returned payload. */ public Map<String, String> getExtraWarnings() { return extraWarnings == null ? Collections.<String, String> emptyMap() : Collections.unmodifiableMap(extraWarnings); } /** * Clients with some amount of temporary storage may request that entities that were created or * last modified before a particular instant in time be excluded from the data section. */ public DateTime getLastModifiedTime() { return lastModifiedTime; } public Principal getPrincipal() { return principal; } public TraversalMode getTraversalMode() { return traversalMode; } /** * Returns the data previous passed to {@link #withValue(Object)}. */ public T getValue() { return value; } /** * Allows extra string data to be returned at the top level of the payload. */ public void putExtraData(String key, String value) { if (extraData == null) { extraData = new TreeMap<String, String>(); } extraData.put(key, value); } public FlatPackEntity<T> withLastModifiedTime(DateTime lastModified) { this.lastModifiedTime = lastModified; return this; } public FlatPackEntity<T> withPrincipal(Principal principal) { this.principal = principal; return this; } public FlatPackEntity<T> withTraversalMode(TraversalMode traversalMode) { this.traversalMode = traversalMode; return this; } public FlatPackEntity<T> withValue(T value) { // Sanity-check type assignments if (value != null && !FlatPackTypes.erase(getType()).isInstance(value)) { throw new IllegalArgumentException(value.getClass().getName() + " is not assignable to " + getType()); } this.value = value; return this; } FlatPackEntity<T> addMetadata(EntityMetadata meta) { if (metadata == null) { metadata = FlatPackCollections.listForAny(); } metadata.add(meta); return this; } List<EntityMetadata> getMetadata() { return metadata == null ? Collections.<EntityMetadata> emptyList() : Collections.unmodifiableList(metadata); } void setExtraEntities(Set<HasUuid> extraEntities) { this.extraEntities = extraEntities; } }