All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.getperka.flatpack.ext.Property Maven / Gradle / Ivy

There is a newer version: 2.21.0
Show newest version
/*
 * #%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.ext;

import static com.getperka.flatpack.util.FlatPackTypes.UTF8;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Set;
import java.util.UUID;

import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.persistence.Embedded;

import com.getperka.flatpack.BaseHasUuid;
import com.getperka.flatpack.InheritPrincipal;
import com.getperka.flatpack.JsonProperty;
import com.getperka.flatpack.RoleMapper;
import com.getperka.flatpack.SuppressDefaultValue;
import com.getperka.flatpack.util.FlatPackCollections;

/**
 * An immutable view of a property that should be serialized.
 */
public class Property extends BaseHasUuid {

  /**
   * Constructs {@link Property} instances.
   */
  public static class Builder {
    private boolean allowAll;
    private Property prop = new Property();

    public Property build(TypeContext context) {
      Property toReturn = prop;
      prop = null;

      if (allowAll) {
        toReturn.getterRoles = toReturn.setterRoles = allRoles;
      } else {
        toReturn.getterRoleNames = extractRoleNames(toReturn.getter);
        toReturn.setterRoleNames = extractRoleNames(toReturn.setter);
        if (toReturn.setterRoleNames == noRoleNames) {
          toReturn.setterRoleNames = toReturn.getterRoleNames;
        }
        toReturn.getterRoles = extractRoles(toReturn.roleMapper, toReturn.getterRoleNames);
        toReturn.setterRoles = extractRoles(toReturn.roleMapper, toReturn.setterRoleNames);
      }

      // Compute a stable UUID for referring to the property
      Method getter = toReturn.getGetter();
      if (getter != null) {
        Class enclosingType = getter.getDeclaringClass();
        toReturn.enclosingTypeName = context.getPayloadName(enclosingType);

        java.lang.reflect.Type returnType = getter.getGenericReturnType();
        toReturn.codex = context.getCodex(returnType);
        toReturn.embedded = getter.isAnnotationPresent(Embedded.class);
        toReturn.inheritPrincipal = getter.isAnnotationPresent(InheritPrincipal.class);
        toReturn.suppressDefaultValue = getter.isAnnotationPresent(SuppressDefaultValue.class);
        toReturn.type = toReturn.codex.describe(context);
      }

      return toReturn;
    }

    /**
     * Copy initializer.
     */
    public Builder from(Property p) {
      prop.deepTraversalOnly = p.deepTraversalOnly;
      prop.getter = p.getter;
      prop.getterRoles = p.getterRoles;
      prop.implied = p.implied;
      prop.name = p.name;
      prop.setter = p.setter;
      prop.setterRoles = p.setterRoles;
      prop.setUuid(p.getUuid());
      return this;
    }

    /**
     * Returns the Property object under construction.
     */
    public Property peek() {
      return prop;
    }

    public Builder withAllowAllRoles(boolean allowAll) {
      this.allowAll = allowAll;
      return this;
    }

    public Builder withDeepTraversalOnly(boolean only) {
      prop.deepTraversalOnly = only;
      return this;
    }

    public Builder withGetter(Method getter) {
      getter.setAccessible(true);
      prop.getter = getter;
      return this;
    }

    public Builder withImpliedProperty(Property implied) {
      prop.implied = implied;
      return this;
    }

    public Builder withName(String name) {
      prop.name = name;
      return this;
    }

    public Builder withRoleMapper(RoleMapper roleMapper) {
      prop.roleMapper = roleMapper;
      return this;
    }

    public Builder withSetter(Method setter) {
      setter.setAccessible(true);
      prop.setter = setter;
      return this;
    }
  }

  /**
   * Used internally to implement the {@link Builder#withAllowAllRoles(boolean)} method.
   */
  private interface AllRolesView {}

  private interface NoRolesView {}

  /**
   * Sorts Property objects by {@link #getName()}.
   */
  public static final Comparator PROPERTY_NAME_COMPARATOR = new Comparator() {
    @Override
    public int compare(Property o1, Property o2) {
      return o1.getName().compareTo(o2.getName());
    }
  };

  static final Set> allRoles =
      Collections.> singleton(AllRolesView.class);
  static final Set> noRoles =
      Collections.> singleton(NoRolesView.class);
  static final Set allRoleNames = Collections.singleton("*");
  static final Set noRoleNames = Collections.singleton("");

  static boolean checkRoles(RoleMapper roleMapper, Collection> required,
      Collection credentials) {
    // Object comparison intentional
    if (roleMapper == null || required == allRoles) {
      return true;
    }
    if (required == null || required.isEmpty()) {
      return false;
    }
    for (String cred : credentials) {
      Class credView = roleMapper.mapRole(cred);
      if (required.contains(credView)) {
        return true;
      }
      for (Class req : required) {
        if (req.isAssignableFrom(credView)) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Returns the role names associated with a method or class. This method never returns
   * {@code null}, however there are several special return values:
   * 
    *
  • {@link #noRoleNames} indicates that no access information was set *
  • {@link #allRoleNames} indicates that access should be allowed for all roles *
  • An empty set indicates access should be denied for all roles *
*/ static Set extractRoleNames(AnnotatedElement obj) { // Map no method to none-allowed if (obj == null) { return noRoleNames; } if (obj.isAnnotationPresent(DenyAll.class)) { return Collections.emptySet(); } if (obj.isAnnotationPresent(PermitAll.class)) { return allRoleNames; } RolesAllowed view = obj.getAnnotation(RolesAllowed.class); if (view == null) { return noRoleNames; } Set toReturn = FlatPackCollections.setForIteration(); toReturn.addAll(Arrays.asList(view.value())); if (toReturn.isEmpty()) { return Collections.emptySet(); } return Collections.unmodifiableSet(toReturn); } /** * Extract role information from a method or its declaring class. */ static Set extractRoleNames(Method method) { if (method == null) { return noRoleNames; } Set toReturn = extractRoleNames((AnnotatedElement) method); if (noRoleNames == toReturn) { toReturn = extractRoleNames(method.getDeclaringClass()); } return toReturn; } static Set> extractRoles(RoleMapper mapper, Set roleNames) { // Object comparison intentional if (mapper == null || roleNames == null || noRoleNames == roleNames) { return noRoles; } if (allRoleNames == roleNames) { return allRoles; } Set> toReturn = FlatPackCollections.setForIteration(); for (String name : roleNames) { toReturn.add(mapper.mapRole(name)); } return Collections.unmodifiableSet(toReturn); } private Codex codex; private boolean deepTraversalOnly; /** * This property is mutable by external callers. It's kind of a hack to allow the describe * endpoint to lazily add the doc strings. */ private String docString; private String enclosingTypeName; private boolean embedded; private Method getter; private Set> getterRoles; private Set getterRoleNames; private Property implied; private boolean inheritPrincipal; private String name; private RoleMapper roleMapper; private Method setter; private Set> setterRoles; private Set setterRoleNames; private boolean suppressDefaultValue; private Type type; private Property() {} @DenyAll public Codex getCodex() { return codex; } @PermitAll public String getDocString() { return docString; } /** * The payload name of the type that defines the property. */ @PermitAll public String getEnclosingTypeName() { return enclosingTypeName; } /** * Returns the getter method for this Property. The returned method will have a non-{@code void} * return type and no parameters. */ @DenyAll public Method getGetter() { return getter; } /** * Returns the role names that are allowed to get the property. A value containing a single * asterisk means that all roles may access the property. */ @PermitAll public Set getGetterRoleNames() { return getterRoleNames; } /** * When a new value is assigned to the current property in some instance, the implied property of * the new value should also be updated with the current instance. */ @PermitAll public Property getImpliedPropery() { return implied; } /** * Returns the json payload name of the Property, which may differ from the bean name if a * {@link JsonProperty} annotation has been applied to the getter. */ @PermitAll public String getName() { return name; } /** * Returns the optional setter for the property. The returned method will have a single parameter * and a {@code void} return type. */ @DenyAll public Method getSetter() { return setter; } /** * Return the role names that are allowed to set this property. A value containing a single * asterisk means that all roles may set the property. */ @PermitAll public Set getSetterRoleNames() { return setterRoleNames; } /** * A simplified description of the property's type. */ @PermitAll public Type getType() { return type; } /** * Returns {@code true} if the Property should be included only during a deep traversal. */ @PermitAll public boolean isDeepTraversalOnly() { return deepTraversalOnly; } /** * Returns {@code true} if an entity Property's properties should be emitted into the owning * entity's properties. */ @PermitAll public boolean isEmbedded() { return embedded; } /** * Returns {@code true} if the referred entity's owner should also be considered an owner of the * entity that defines the Property. */ @PermitAll public boolean isInheritPrincipal() { return inheritPrincipal; } /** * If {@code true}, non-null properties that contain the property type's default value will not be * serialized. For example, integer properties whose values are {@code 0} will not be serialized. */ @PermitAll public boolean isSuppressDefaultValue() { return suppressDefaultValue; } /** * Returns {@code true} if a user with the given roles may retrieve the property. The roles * provided must intersect or be assignable to the roles defined on the getter via * {@link RolesAllowed}. */ public boolean mayGet(Collection roles) { if (getter == null) { return false; } return checkRoles(roleMapper, getterRoles, roles); } /** * Returns {@code true} if a user with the given roles may set the property. The roles provided * must intersect or be assignable to the roles defined on the setter via {@link RolesAllowed}. If * no roles were specified on the setter method, the roles defined on the getter will be used. */ public boolean maySet(Collection roles) { if (setter == null) { return false; } return checkRoles(roleMapper, setterRoles, roles); } public void setDocString(String docString) { this.docString = docString; } /** * For debugging use only. */ @Override public String toString() { return getEnclosingTypeName() + "." + getName() + " ::= " + getType(); } @Override protected UUID defaultUuid() { return UUID.nameUUIDFromBytes((getEnclosingTypeName() + "." + getName()).getBytes(UTF8)); } Property getImplied() { return implied; } void setDeepTraversalOnly(boolean deepTraversalOnly) { this.deepTraversalOnly = deepTraversalOnly; } void setEmbedded(boolean embedded) { this.embedded = embedded; } void setEnclosingTypeName(String enclosingTypeName) { this.enclosingTypeName = enclosingTypeName; } void setGetterRoleNames(Set names) { this.getterRoleNames = names; } void setImplied(Property implied) { this.implied = implied; } /** * Use for late fixups of implied properties when the OneToMany property is examined after the * ManyToOne relationship. */ void setImpliedProperty(Property implied) { this.implied = implied; } void setInheritPrincipal(boolean inheritPrincipal) { this.inheritPrincipal = inheritPrincipal; } void setName(String name) { this.name = name; } void setSetterRoleNames(Set names) { this.setterRoleNames = names; } void setSuppressDefaultValue(boolean suppressDefaultValue) { this.suppressDefaultValue = suppressDefaultValue; } void setType(Type type) { this.type = type; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy