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

de.fraunhofer.iese.ind2uce.reactive.RxPEPFactory Maven / Gradle / Ivy

/*-
 * =================================LICENSE_START=================================
 * IND2UCE
 * %%
 * Copyright (C) 2016 Fraunhofer IESE (www.iese.fraunhofer.de)
 * %%
 * 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.
 * =================================LICENSE_END=================================
 */

package de.fraunhofer.iese.ind2uce.reactive;

import de.fraunhofer.iese.ind2uce.api.component.description.InputParameterDescription;
import de.fraunhofer.iese.ind2uce.api.component.description.ModifierInterfaceDescription;
import de.fraunhofer.iese.ind2uce.api.component.description.PepInterfaceDescription;
import de.fraunhofer.iese.ind2uce.api.component.identifier.ComponentId;
import de.fraunhofer.iese.ind2uce.api.policy.AuthorizationDecision;
import de.fraunhofer.iese.ind2uce.api.policy.Event;
import de.fraunhofer.iese.ind2uce.api.policy.identifier.ActionId;
import de.fraunhofer.iese.ind2uce.connectors.OAuthCredentials;
import de.fraunhofer.iese.ind2uce.json.schema.JsonSchemaGenerator;
import de.fraunhofer.iese.ind2uce.logger.LoggerFactory;
import de.fraunhofer.iese.ind2uce.pep.PolicyEnforcementPoint;
import de.fraunhofer.iese.ind2uce.pep.common.DecisionEnforcer;
import de.fraunhofer.iese.ind2uce.pep.common.ModifierMethod;
import de.fraunhofer.iese.ind2uce.pep.enforce.JsonPathDecisionEnforcer;
import de.fraunhofer.iese.ind2uce.reactive.common.EventParameter;
import de.fraunhofer.iese.ind2uce.reactive.common.EventSpecification;
import de.fraunhofer.iese.ind2uce.reactive.common.IncorrectPEPDescriptionError;
import de.fraunhofer.iese.ind2uce.reactive.common.PEPServiceDescription;
import de.fraunhofer.iese.ind2uce.reactive.common.PEPType;
import de.fraunhofer.iese.ind2uce.reactive.common.ProvidedModifiers;
import de.fraunhofer.iese.ind2uce.registry.ActionDescription;
import de.fraunhofer.iese.ind2uce.registry.ActionParameterDescription;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import rx.Observable;

/**
 * Entry Point to define reactive Policy Enforcement Points.
 * This Factory has the capability to create Reactive PEP.
 */
public class RxPEPFactory {

  protected static Logger LOG = LoggerFactory.getLogger(PolicyEnforcementPoint.class);

  /**
   * Creates a new ReactivePEP object.
   *
   * @param  the generic type
   * @param pepDocumentationApi the pep documentation api
   * @param pmpUri the pmp uri
   * @param decisionEnforcer the decision enforcer
   * @param aliveCheckUri the alive check uri
   * @param credentials the credentials
   * @return the rx PEP of type T
   */
  public static  ReactivePEP createRxPEP(final Class pepDocumentationApi, URI pmpUri, DecisionEnforcer decisionEnforcer, URI aliveCheckUri, OAuthCredentials credentials) {
    RxPEPFactory.validateDocumentationApi(pepDocumentationApi);
    RxPEPFactory.validateMethodReturnType(pepDocumentationApi);
    final Collection allModifierMethods = getAllModifierActor(pepDocumentationApi);
    try {
      allModifierMethods.forEach((modifierActor -> decisionEnforcer.addModificationMethod(modifierActor)));
    } catch (final Exception e) {
      LOG.error("Error while adding modifiers");
      throw new IncorrectPEPDescriptionError("Unknown PEP creation error happened!" + e.getMessage());
    }
    try {
      final PolicyEnforcementPoint policyEnforcementPoint = new PolicyEnforcementPoint(decisionEnforcer, getComponentId(pepDocumentationApi), pmpUri, discoverPEPDocumentationApi(pepDocumentationApi),
          discoverModifierInterfaceDescription(pepDocumentationApi), aliveCheckUri, false, credentials);
      return new ReactivePEP<>(pepDocumentationApi, policyEnforcementPoint);
    } catch (final IOException io) {
      LOG.error("IOException must not happen!");
    } catch (final ClassNotFoundException e) {
      LOG.error("Reactive PEP is not created: documentation API not found");
      throw new IncorrectPEPDescriptionError("PEP not created because of some classloading issue", e);
    }
    //
    throw new IncorrectPEPDescriptionError("Unknown PEP creation error happened!");
  }

  /**
   * Creates a new ReactivePEP object.
   *
   * @param  the generic type
   * @param pepDocumentationApi the pep documentation api
   * @param pmpUri the pmp uri
   * @param aliveCheckUri the alive check uri
   * @param oauthClientCredentials the oauth client credentials
   * @return the rx PEP of type T
   */
  public static  ReactivePEP createRxPEP(final Class pepDocumentationApi, URI pmpUri, URI aliveCheckUri, OAuthCredentials oauthClientCredentials) {
    final JsonPathDecisionEnforcer jvmNativeDecisionEnforcer = new JsonPathDecisionEnforcer();
    return createRxPEP(pepDocumentationApi, pmpUri, jvmNativeDecisionEnforcer, aliveCheckUri, oauthClientCredentials);
  }

  /**
   * Creates an input parameter description.
   *
   * @param parameterType the parameter type
   * @param annotations the annotations
   * @return the input parameter description
   */
  private static InputParameterDescription createInputParameterDescription(Class parameterType, Annotation[] annotations) {
    final ActionParameterDescription annotation = getParameterDescriptionAnnotation(annotations);

    if (annotation != null) {
      final String parameterName = annotation.name();
      final String description = annotation.description();
      parameterType = annotation.type().equals(Void.class) ? parameterType : annotation.type();
      return new InputParameterDescription(parameterName, description, annotation.pattern(), annotation.mandatory(), parameterType);
    }
    // TODO don't check validity of the parameter annotation. there is no
    // constraints regarding it.
    return null;
  }

  /**
   * Discover modifier interface description.
   *
   * @param  the generic type
   * @param tClass the t class
   * @return the list
   */
  private static final  List discoverModifierInterfaceDescription(final Class tClass) {
    final List modifierInterfaceDescriptions = new LinkedList<>();
    final Annotation requiredModifier[] = tClass.getAnnotationsByType(ProvidedModifiers.class);
    if (requiredModifier.length > 0) {
      // get all modifier required for the event
      final Class modifierClasses[] = ((ProvidedModifiers)requiredModifier[0]).className();
      for (final Class modifierClass : modifierClasses) {
        final Method modifierMethods[] = modifierClass.getDeclaredMethods();
        for (final Method modifierMethod : modifierMethods) {
          // if method is annotated with ActionDescription then read action
          // name and description
          if (modifierMethod.isAnnotationPresent(ActionDescription.class)) {
            // get the ActionDescription of the method
            final ActionDescription actionDescription = modifierMethod.getAnnotation(ActionDescription.class);
            // take the method name is name is not specified with
            // ActionDescription
            final String modifierName = readName(modifierMethod, actionDescription);
            // read modifier successfully so there is no problem
            final List modifierInputParameter = readModifierParameter(modifierMethod);
            // get the return type of the method
            final Class returnType = actionDescription.pepSupportedType();
            // read the description
            final String modifierDescription = actionDescription.description();
            // add the PepInterfaceDescription to interfaceDescriptions
            modifierInterfaceDescriptions.add(new ModifierInterfaceDescription(modifierName, returnType, modifierDescription, returnType.getTypeName(), modifierInputParameter));
          }
        }
      }
    }
    return modifierInterfaceDescriptions;
  }

  /**
   * Discovers PEP interface description.
   *
   * @param  the generic type
   * @param pepDocumentationApi the pep documentation API
   * @return the list of interface description
   */
  private static final  List discoverPEPDocumentationApi(final Class pepDocumentationApi) throws ClassNotFoundException {
    // get all the method of the API documentation interface
    final Method methods[] = pepDocumentationApi.getDeclaredMethods();
    // list of PEP interface description
    final List interfaceDescriptions = new LinkedList<>();
    // iterate over all methods
    for (final Method method : methods) {
      // if method is
      final Annotation eventDescriptionAnnotations[] = method.getAnnotationsByType(EventSpecification.class);
      if (eventDescriptionAnnotations.length == 0) {
        continue;
      }
      final EventSpecification eventDescriptionForMethod = (EventSpecification)eventDescriptionAnnotations[0];
      // create the action id for the event
      final ActionId actionId = new ActionId(eventDescriptionForMethod.scope(), eventDescriptionForMethod.action());
      // get all the modifier required for event
      final PepInterfaceDescription pepInterfaceDescription = new PepInterfaceDescription(actionId, true, eventDescriptionForMethod.description());
      pepInterfaceDescription.setEventParameterDescription(readEventParameterDetails(method));
      interfaceDescriptions.add(pepInterfaceDescription);
    }
    return interfaceDescriptions;
  }

  /**
   * Filters annotations of type given annotation class.
   *
   * @param  the generic type
   * @param annotations the annotations
   * @param annotationClass the annotation class
   * @return the annotation of type given annotation class or it returns NULL if
   *         there is non.
   */
  private static  T filter(Annotation[] annotations, Class annotationClass) {
    for (final Annotation annotation : annotations) {
      if (annotationClass.isInstance(annotation)) {
        return (T)annotation;
      }
    }
    return null;
  }

  /**
   * This method determines whether the documentation API is Valid or not
   *
   * @param pepDocumentationApi interface of documentation API
   * @param  the generic type
   * @return PEPType
   */
  public static  PEPType findAPIDocumentationType(final Class pepDocumentationApi) {
    if (isValidDocumentation(pepDocumentationApi).getKey()) {
      return PEPType.REACTIVE;
    }
    return PEPType.INVALID;
  }

  /**
   * Gets all modifier actors.
   *
   * @param pepDocumentationApi the PEP documentation API
   * @param  the generic type
   * @return a collection of modifier actors
   */
  private static final  Collection getAllModifierActor(final Class pepDocumentationApi) {
    // TODO test it
    final Map toReturn = readModifierNewInstanceFromApiDocumentation(pepDocumentationApi);
    final Annotation requiredModifier[] = pepDocumentationApi.getAnnotationsByType(ProvidedModifiers.class);
    if (requiredModifier.length > 0) {
      final Class modifierActors[] = ((ProvidedModifiers)requiredModifier[0]).className();
      for (final Class modifierActor : modifierActors) {
        if (!toReturn.containsKey(modifierActor.getCanonicalName())) {
          try {
            toReturn.put(modifierActor.getCanonicalName(), modifierActor.newInstance());
          } catch (InstantiationException | IllegalAccessException e) {
            throw new IncorrectPEPDescriptionError(modifierActor.getCanonicalName() + "does not have default contractor nor supplied with static method in PEP API documentation interface");
          }
        }
      }
    }
    return toReturn.values();
  }

  /**
   * Gets the component ID from the PEPServiceDescription annotation included in
   * the given PEP documentation API.
   *
   * @param  the generic type
   * @param pepDocumentationApi the PEP documentation API
   * @return the component ID
   */
  private static final  ComponentId getComponentId(final Class pepDocumentationApi) {
    final Annotation classAnnotations[] = pepDocumentationApi.getAnnotationsByType(PEPServiceDescription.class);
    final String componentIdStr = ((PEPServiceDescription)classAnnotations[0]).componentId();

    final ComponentId componentId = new ComponentId(componentIdStr);
    return componentId;
  }

  /**
   * Gets the parameter description annotation.
   *
   * @param annotations the annotations
   * @return the parameter description annotation
   */
  private static ActionParameterDescription getParameterDescriptionAnnotation(Annotation[] annotations) {
    for (final Annotation annotation : annotations) {
      if (annotation instanceof ActionParameterDescription) {
        return (ActionParameterDescription)annotation;
      }
    }
    return null;
  }

  /***
   * Checks whether documentation API is valid.
   *
   * @param  the generic type
   * @param pepDocumentationApi documentation API interface
   * @return if correct or incorrect and reason as exception
   */
  public static  Pair isValidDocumentation(final Class pepDocumentationApi) {
    final Method pepDocumentationApiMethods[] = pepDocumentationApi.getDeclaredMethods();
    for (final Method pepDocumentationApiMethod : pepDocumentationApiMethods) {
      if (Modifier.isStatic(pepDocumentationApiMethod.getModifiers())) {
        continue;
      }
      final Type returnType = pepDocumentationApiMethod.getGenericReturnType();
      if (pepDocumentationApiMethod.getReturnType() == Observable.class && returnType instanceof ParameterizedType && pepDocumentationApiMethod.isAnnotationPresent(EventSpecification.class)) {
        final Type observableType[] = ((ParameterizedType)returnType).getActualTypeArguments();
        if (observableType[0] == Event.class) {
          continue;
        } else if (observableType[0] instanceof ParameterizedType && ((ParameterizedType)observableType[0]).getRawType() == Pair.class
            && ((ParameterizedType)observableType[0]).getActualTypeArguments().length == 2 && ((ParameterizedType)observableType[0]).getActualTypeArguments()[0] == Event.class
            && ((ParameterizedType)observableType[0]).getActualTypeArguments()[1] == AuthorizationDecision.class) {
          continue;
        } else {
          return new ImmutablePair(false, new IncorrectPEPDescriptionError("Observable only can have Event or Pair"));
        }
      } else {
        return new ImmutablePair(false, new IncorrectPEPDescriptionError("PEP can't have void/anything else method, All methods should return ModifierMethod or Observable"));
      }
    }
    return new ImmutablePair(true, null);
  }

  /**
   * Filters parameters of type PEPParamKey from the given method and creates
   * the input parameter descriptions.
   *
   * @param method the modifier
   * @return the list of input parameter descriptions
   */
  private static List readEventParameterDetails(Method method) throws ClassNotFoundException {
    final List inputParameterDescriptions = new LinkedList<>();
    final Parameter parameters[] = method.getParameters();
    final Annotation annotation[][] = method.getParameterAnnotations();
    for (int i = 0; i < annotation.length; i++) {
      final EventParameter pepParamKey = filter(annotation[i], EventParameter.class);
      if (pepParamKey != null) {
        inputParameterDescriptions.add(new InputParameterDescription(pepParamKey.name(), pepParamKey.description(), null, true, parameters[i].getParameterizedType(),
            JsonSchemaGenerator.getJsonType(parameters[i].getType()), JsonSchemaGenerator.createTypeDescription(parameters[i].getParameterizedType(), parameters[i].getType())));

      }
    }
    return inputParameterDescriptions;
  }

  /**
   * Gets modifier actors with their corresponding names from documentation API.
   *
   * @param  the generic type
   * @param pepDocumentationApi the PEP documentation API
   * @return a map of modifier actors and their corresponding names
   */
  private static final  Map readModifierNewInstanceFromApiDocumentation(final Class pepDocumentationApi) {
    final Method allMethod[] = pepDocumentationApi.getDeclaredMethods();
    final Map toReturn = new HashMap<>();
    for (final Method method : allMethod) {
      if (Modifier.isStatic(method.getModifiers()) && ModifierMethod.class.isAssignableFrom(method.getReturnType())) {
        try {
          final ModifierMethod modifierActor = (ModifierMethod)method.invoke(null);
          toReturn.put(method.getReturnType().getCanonicalName(), modifierActor);
        } catch (IllegalAccessException | InvocationTargetException e) {
          throw new IncorrectPEPDescriptionError("method to supply ModifierActor should not have any arguments.");
        }
      }
    }
    return toReturn;
  }

  /**
   * Reads method parameters.
   *
   * @param method the modifier actor
   * @return the list of input parameter description
   */
  private static List readModifierParameter(Method method) {
    final Class[] parameters = method.getParameterTypes();
    final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
    final List toReturn = new ArrayList<>(parameters.length);
    for (int i = 0; i < parameters.length; i++) {
      final InputParameterDescription inputParameterDescription = createInputParameterDescription(parameters[i], parameterAnnotations[i]);
      if (inputParameterDescription != null) {
        toReturn.add(inputParameterDescription);
      }
    }
    return toReturn;
  }

  /**
   * Returns method name.
   *
   * @param method the modifier actor
   * @param actionDescription the action description
   * @return method name
   */
  private static String readName(Method method, ActionDescription actionDescription) {
    return StringUtils.isNotBlank(actionDescription.methodName()) ? actionDescription.methodName() : method.getName();
  }

  /**
   * Validates that the PEP documentation API is an interface and does not
   * extend other interfaces.
   *
   * @param  the generic type
   * @param pepDocumentationApi the PEP documentation API
   */
  private static  void validateDocumentationApi(final Class pepDocumentationApi) {
    if (!pepDocumentationApi.isInterface()) {
      throw new IllegalArgumentException("Reactive PEP API documentation must be interfaces.");
    }
    if (pepDocumentationApi.getInterfaces().length > 0) {
      throw new IllegalArgumentException("Reactive PEP API documentation interfaces must not extend other interfaces.");
    }
  }

  /**
   * Validates method return type for reactive PEP.
   * 

* Enforcement method must have observable event or observable pair of event * and authorization decision to get the response from PDP to use ReactivePEP * * @param the generic type * @param pepDocumentationApi the pep documentation api */ private static void validateMethodReturnType(final Class pepDocumentationApi) { final Pair booleanRuntimeExceptionPair = isValidDocumentation(pepDocumentationApi); if (!booleanRuntimeExceptionPair.getKey()) { throw booleanRuntimeExceptionPair.getValue(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy