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

com.anaptecs.jeaf.xfun.api.config.ConfigurationReader Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2004 - 2019 anaptecs GmbH, Burgstr. 96, 72764 Reutlingen, Germany
 *
 * All rights reserved.
 */
package com.anaptecs.jeaf.xfun.api.config;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.anaptecs.jeaf.xfun.api.XFunRuntimeException;
import com.anaptecs.jeaf.xfun.bootstrap.Assert;
import com.anaptecs.jeaf.xfun.bootstrap.Check;
import com.anaptecs.jeaf.xfun.fallback.trace.FallbackTraceProviderImpl;

/**
 * Class implements a so called configuration reader. This classes resolves the name of a single class or of a list of
 * classes from a configuration file. Configuration file loaded using the current class loader. This means that the
 * provided base package must be aware of this as well as the configuration file must be loadable from the classpath.
 * 
 * The class is not intended to be used to read configuration values from a file e.g. a property file.
 * 
 * Configuration classes are expected to consist of one class name per line.
 * 
 * @author JEAF Development Team
 */
public class ConfigurationReader {
  private static final String UTF_8 = StandardCharsets.UTF_8.name();

  /**
   * Method reads all classes from the configuration file with the passed name. This method is intended to be used in
   * cases where a config file should not consists of more than one class name.
   * 
   * @param pConfigurationFileName Name of configuration file. The parameter must not be null. The passed file name will
   * be extended with pBasePackage.
   * @param pBasePackagePath Path under which the file should be found in the classpath. The parameter may be null.
   * @return {@link List} List with all classes that were read from the configuration file. The method never returns
   * null. If the file could not read read from the classpath then the method returns an empty list. The returned list
   * is immutable.
   */
  public List> readClassesFromConfigFile( String pConfigurationFileName, String pBasePackagePath ) {
    return this.readClassesFromConfigFile(pConfigurationFileName, pBasePackagePath, Object.class);
  }

  /**
   * Method reads all classes from the configuration file with the passed name. This method is intended to be used in
   * cases where a config file should not consists of more than one class name.
   * 
   * @param pConfigurationFileName Name of configuration file. The parameter must not be null. The passed file name will
   * be extended with pBasePackage.
   * @param pBasePackagePath Path under which the file should be found in the classpath. The parameter may be null.
   * @param pType Class object representing the expected type of the returned classes.
   * @return {@link List} List with all classes that were read from the configuration file. The method never returns
   * null. If the file could not read read from the classpath then the method returns an empty list. The returned list
   * is immutable. If the configuration file contains a class that is not of the expected type then it will be filtered
   * out.
   */
  public  List> readClassesFromConfigFile( String pConfigurationFileName, String pBasePackagePath,
      Class pType ) {

    // Check parameter
    Check.checkInvalidParameterNull(pConfigurationFileName, "pConfigurationFileName");
    Check.checkInvalidParameterNull(pType, "pType");

    // Build resource name from parameters.
    StringBuilder lBuilder = new StringBuilder();
    if (pBasePackagePath != null) {
      lBuilder.append(pBasePackagePath);
      lBuilder.append('/');
    }
    lBuilder.append(pConfigurationFileName);
    String lResourceName = lBuilder.toString();
    return this.readClassesFromConfigFile(lResourceName, pType);
  }

  /**
   * Method reads all classes from the configuration file with the passed name. This method is intended to be used in
   * cases where a config file should not consists of more than one class name.
   * 
   * @param pConfigurationFilePath Path of configuration file. The parameter must not be null.
   * @param pType Class object representing the expected type of the returned classes.
   * @return {@link List} List with all classes that were read from the configuration file. The method never returns
   * null. If the file could not read read from the classpath then the method returns an empty list. The returned list
   * is immutable. If the configuration file contains a class that is not of the expected type then it will be filtered
   * out.
   */
  public List> readClassesFromConfigFile( String pConfigurationFilePath ) {
    return this.readClassesFromConfigFile(pConfigurationFilePath, Object.class);
  }

  /**
   * Method reads all classes from the configuration file with the passed name. This method is intended to be used in
   * cases where a config file should not consists of more than one class name.
   * 
   * @param pConfigurationFilePath Path of configuration file. The parameter must not be null.
   * @param pType Class object representing the expected type of the returned classes.
   * @return {@link List} List with all classes that were read from the configuration file. The method never returns
   * null. If the file could not read read from the classpath then the method returns an empty list. The returned list
   * is immutable. If the configuration file contains a class that is not of the expected type then it will be filtered
   * out.
   */
  public  List> readClassesFromConfigFile( String pConfigurationFilePath, Class pType ) {
    // Check parameter
    Check.checkInvalidParameterNull(pConfigurationFilePath, "pConfigurationFilePath");
    Check.checkInvalidParameterNull(pType, "pType");

    // Resolve URL of configuration file
    URL lResourceURL = this.resolveResource(pConfigurationFilePath);

    // Configuration file could be located.
    Collection lConfigurationFileEntries;
    if (lResourceURL != null) {
      try (InputStreamReader lInputStream = new InputStreamReader(lResourceURL.openStream(), UTF_8);
          BufferedReader lReader = new BufferedReader(lInputStream);) {

        // Read configuration entries.
        lConfigurationFileEntries = lReader.lines().collect(Collectors.toList());
      }
      catch (IOException e) {
        throw new XFunRuntimeException(
            "Unable to read configuration from resource " + pConfigurationFilePath + ". " + e.getMessage(), e);
      }
    }
    // Configuration file could not be found.
    else {
      FallbackTraceProviderImpl.EMERGENCY_TRACE
          .warn("Configuration file " + pConfigurationFilePath + " could not be found in the application's classpath.");
      lConfigurationFileEntries = Collections.emptyList();
    }

    // Get classes objects for all configured class names
    return this.toClasses(lConfigurationFileEntries, pType, pConfigurationFilePath);
  }

  /**
   * Method reads exactly 1 classes from the configuration file with the passed name. This method is intended to be used
   * in cases where a config file is expected to consist of exactly one class name.
   * 
   * @param pConfigurationFileName Name of configuration file. The parameter must not be null. The passed file name will
   * be extended with pBasePackage.
   * @param pBasePackagePath Path under which the file should be found in the classpath. The parameter may be null.
   * @return Class that was read from the configuration file. The method returns null if the config file does not exists
   * or is empty.
   */
  public Class readClassFromConfigFile( String pConfigurationFileName, String pBasePackagePath ) {
    return this.readClassFromConfigFile(pConfigurationFileName, pBasePackagePath, Object.class);
  }

  /**
   * Method reads exactly 1 classes from the configuration file with the passed name. This method is intended to be used
   * in cases where a config file is expected to consist of exactly one class name.
   * 
   * @param pConfigurationFileName Name of configuration file. The parameter must not be null. The passed file name will
   * be extended with pBasePackage.
   * @param pBasePackagePath Path under which the file should be found in the classpath. The parameter may be null.
   * @param pType Class object representing the expected type of the returned classes.
   * @return Class that was read from the configuration file. The method returns null if the config file does not
   * exists, is empty or the configured class is not of the expected type.
   */
  public  Class readClassFromConfigFile( String pConfigurationFileName, String pBasePackagePath,
      Class pType ) {

    // Check parameter
    Check.checkInvalidParameterNull(pConfigurationFileName, "pConfigurationFileName");
    Check.checkInvalidParameterNull(pType, "pType");

    // Build resource name from parameters.
    StringBuilder lBuilder = new StringBuilder();
    if (pBasePackagePath != null) {
      lBuilder.append(pBasePackagePath);
      lBuilder.append('/');
    }
    lBuilder.append(pConfigurationFileName);
    String lResourceName = lBuilder.toString();
    return this.readClassFromConfigFile(lResourceName, pType);
  }

  /**
   * Method reads exactly 1 classes from the configuration file with the passed name. This method is intended to be used
   * in cases where a config file is expected to consist of exactly one class name.
   * 
   * @param pConfigurationFilePath Path of configuration file. The parameter must not be null.
   * @param pType Class object representing the expected type of the returned classes.
   * @return Class that was read from the configuration file. The method returns null if the config file does not
   * exists, is empty or the configured class is not of the expected type.
   */
  public Class readClassFromConfigFile( String pConfigurationFilePath ) {
    return this.readClassFromConfigFile(pConfigurationFilePath, Object.class);
  }

  /**
   * Method reads exactly 1 classes from the configuration file with the passed name. This method is intended to be used
   * in cases where a config file is expected to consist of exactly one class name.
   * 
   * @param pConfigurationFilePath Path of configuration file. The parameter must not be null.
   * @param pType Class object representing the expected type of the returned classes.
   * @return Class that was read from the configuration file. The method returns null if the config file does not
   * exists, is empty or the configured class is not of the expected type.
   */
  public  Class readClassFromConfigFile( String pConfigurationFilePath, Class pType ) {
    // Check parameter
    Check.checkInvalidParameterNull(pConfigurationFilePath, "pConfigurationFilePath");
    Check.checkInvalidParameterNull(pType, "pType");

    List> lClasses = this.readClassesFromConfigFile(pConfigurationFilePath, pType);
    int lSize = lClasses.size();
    Class lClass;

    // No class is configured.
    if (lSize == 0) {
      lClass = null;
    }
    // Class is configured.
    else if (lSize == 1) {
      lClass = lClasses.get(0);
    }
    // Wrong configuration. We expect not more than one class name.
    else {
      throw new XFunRuntimeException("Configuration file " + pConfigurationFilePath
          + " contains more than one class name. Please correct the file.");
    }
    return lClass;
  }

  /**
   * Method checks if the configuration file with the passed path is available through the application's classpath.
   * 
   * @param pConfigurationFilePath Path of configuration file. The parameter must not be null.
   * @return
   */
  public boolean isConfigurationAvailable( String pConfigurationFilePath ) {
    // Check parameter
    Check.checkInvalidParameterNull(pConfigurationFilePath, "pConfigurationFilePath");

    return this.resolveResource(pConfigurationFilePath) != null;
  }

  /**
   * Method resolved the names of the classes that are defined in the passed configuration files. From all mentioned
   * classes the passed annotation will be read and returned.
   * 
   * @param pConfigurationFileName Name of configuration file. The parameter must not be null. The passed file name will
   * be extended with pBasePackage.
   * @param pBasePackagePath Path under which the file should be found in the classpath. The parameter may be null.
   * @param pAnnotation Annotation type that contains the configurations that should be returned. The parameter must not
   * be null.
   * @return {@link List} List with all annotations of the requested type. The method never returns null.
   */
  public  List readAnnotations( String pConfigurationFileName, String pBasePackagePath,
      Class pAnnotation ) {

    // Check parameter
    Check.checkInvalidParameterNull(pConfigurationFileName, "pConfigurationFileName");
    Check.checkInvalidParameterNull(pAnnotation, "pAnnotation");

    // Build resource name from parameters.
    StringBuilder lBuilder = new StringBuilder();
    if (pBasePackagePath != null) {
      lBuilder.append(pBasePackagePath);
      lBuilder.append('/');
    }
    lBuilder.append(pConfigurationFileName);
    String lResourceName = lBuilder.toString();
    return this.readAnnotations(lResourceName, pAnnotation);
  }

  /**
   * Method resolved the names of the classes that are defined in the passed configuration files. From all mentioned
   * classes the passed annotation will be read and returned.
   * 
   * @param pConfigurationFilePath Configuration file from which the names of annotated classes should be read. The
   * parameter must not be null.
   * @param pAnnotation Annotation type that contains the configurations that should be returned. The parameter must not
   * be null.
   * @return {@link List} List with all annotations of the requested type. The method never returns null.
   */
  public  List readAnnotations( String pConfigurationFilePath, Class pAnnotation ) {
    // Check parameters
    Check.checkInvalidParameterNull(pConfigurationFilePath, "pConfigurationFilePath");
    Check.checkInvalidParameterNull(pAnnotation, "pAnnotation");

    // Read all classes from configuration file and get their requested annotations.
    List> lClasses = this.readClassesFromConfigFile(pConfigurationFilePath);
    return this.getAnnotations(lClasses, pAnnotation);
  }

  /**
   * Method returns all the annotations of the passed type from the passed classes. The annotations will only be
   * detected if they are assigned to the type.
   * 
   * @param 
   * @param pClasses Classes from which the annotations should be read. The parameter must not be null.
   * @param pAnnotation Annotation type that should be returned. The parameter must not be null. all other annotations
   * will be ignored.
   * @return {@link List} List with all annotations of the passed type. The method never returns null.
   */
  private  List getAnnotations( List> pClasses, Class pAnnotation ) {
    List lAnnotations = new ArrayList<>(pClasses.size());
    for (Class lNextClass : pClasses) {
      T lAnnotation = lNextClass.getAnnotation(pAnnotation);
      if (lAnnotation != null) {
        lAnnotations.add(lAnnotation);
      }
    }
    return lAnnotations;
  }

  /**
   * Method returns a map which contains a mapping between the class and its annotation of the passed type. The classes
   * are read from the configuration file with the passed name.
   * 
   * @param pConfigurationFileName Name of configuration file. The parameter must not be null. The passed file name will
   * be extended with pBasePackage.
   * @param pBasePackagePath Path under which the file should be found in the classpath. The parameter may be null.
   * @param pAnnotation Annotation type that should be returned. The parameter must not be null. all other annotations
   * will be ignored.
   * @return {@link Map} Map which contains the mapping between the class and its annotation of the passed type. If a
   * class does not have the requested annotation then it will not be part of the returned map even though its mentioned
   * on the defined configuration file.
   */
  public  Map, T> readAnnotationsMap( String pConfigurationFileName,
      String pBasePackagePath, Class pAnnotation ) {

    // Check parameter
    Check.checkInvalidParameterNull(pConfigurationFileName, "pConfigurationFileName");
    Check.checkInvalidParameterNull(pAnnotation, "pAnnotation");

    // Build resource name from parameters.
    StringBuilder lBuilder = new StringBuilder();
    if (pBasePackagePath != null) {
      lBuilder.append(pBasePackagePath);
      lBuilder.append('/');
    }
    lBuilder.append(pConfigurationFileName);
    String lResourceName = lBuilder.toString();
    return this.readAnnotationsMap(lResourceName, pAnnotation);
  }

  /**
   * Method returns a map which contains a mapping between the class and its annotation of the passed type. The classes
   * are read from the configuration file with the passed name.
   * 
   * @param pConfigurationFilePath Configuration file from which the names of annotated classes should be read. The
   * parameter must not be null.
   * @param pAnnotation Annotation type that should be returned. The parameter must not be null. all other annotations
   * will be ignored.
   * @return {@link Map} Map which contains the mapping between the class and its annotation of the passed type. If a
   * class does not have the requested annotation then it will not be part of the returned map even though its mentioned
   * on the defined configuration file.
   */
  public  Map, T> readAnnotationsMap( String pConfigurationFilePath,
      Class pAnnotation ) {

    // Check parameters
    Check.checkInvalidParameterNull(pConfigurationFilePath, "pConfigurationFilePath");
    Check.checkInvalidParameterNull(pAnnotation, "pAnnotation");

    // Read all classes from configuration file and get their requested annotations.
    List> lClasses = this.readClassesFromConfigFile(pConfigurationFilePath);
    return this.getAnnotationsMap(lClasses, pAnnotation);
  }

  /**
   * 
   * @param 
   * @param pClasses
   * @param pAnnotation
   * @return
   */
  private  Map, T> getAnnotationsMap( List> pClasses, Class pAnnotation ) {
    Map, T> lAnnotationsMap = new HashMap<>();
    for (Class lNextClass : pClasses) {
      T lAnnotation = lNextClass.getAnnotation(pAnnotation);
      if (lAnnotation != null) {
        lAnnotationsMap.put(lNextClass, lAnnotation);
      }
    }
    return lAnnotationsMap;
  }

  /**
   * Method converts a list of class names into a list of class objects.
   * 
   * @param pClassNames List with all class names. The parameter must not be null.
   * @return {@link List} with all class objects to the passed class names. The method never returns null. The returned
   * {@link List} is immutable.
   */
  private  List> toClasses( Collection pClassNames, Class pType,
      String pResourceName ) {
    List> lClasses = new ArrayList<>(pClassNames.size());
    for (String lNextClassName : pClassNames) {
      try {
        @SuppressWarnings("unchecked")
        Class lNextClass = (Class) Class.forName(lNextClassName);

        // As Java generics not really check at runtime that the types match we have to do this manually now.
        if (pType.isAssignableFrom(lNextClass)) {
          lClasses.add(lNextClass);
        }
        else {
          String lMessage = "Configuration error for " + pResourceName + ". Class " + lNextClassName
              + " is not an subclass / implementation of " + pType.getName();
          FallbackTraceProviderImpl.EMERGENCY_TRACE.error(lMessage);
        }
      }
      catch (ExceptionInInitializerError e) {
        Throwable lCause = e.getCause();
        String lCauseMessage;
        if (lCause != null) {
          lCauseMessage = "Root-Cause: " + lCause.getMessage();
        }
        else {
          lCauseMessage = "";
        }
        String lMessage = "Configuration error for " + pResourceName + ". Class " + lNextClassName
            + " could not be loaded. " + lCauseMessage;
        FallbackTraceProviderImpl.EMERGENCY_TRACE.error(lMessage, e);

      }
      catch (Throwable e) {
        String lMessage = "Configuration error for " + pResourceName + ". Class " + lNextClassName
            + " could not be loaded. " + e.getMessage();
        FallbackTraceProviderImpl.EMERGENCY_TRACE.error(lMessage, e);
      }
    }
    return Collections.unmodifiableList(lClasses);
  }

  /**
   * Method resolves the URL of the passed configuration file using the application's classpath.
   * 
   * @param pConfigurationFilePath Path of configuration file. The parameter must not be null.
   * @return {@link URL} URL of the configuration file or null if it could not be accessed through the applications
   * classpath
   */
  private URL resolveResource( String pConfigurationFilePath ) {
    // Check parameter
    Assert.assertNotNull(pConfigurationFilePath, "pConfigurationFilePath");

    // Try to locate resource
    URL lResourceURL = this.getClass().getClassLoader().getResource(pConfigurationFilePath);

    // Try to locate resource as system resource if we could not find it directly.
    if (lResourceURL == null) {
      lResourceURL = ClassLoader.getSystemResource(pConfigurationFilePath);
    }
    // Return URL or null
    return lResourceURL;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy