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

org.mycore.common.config.MCRConfiguration2 Maven / Gradle / Ivy

There is a newer version: 2024.05
Show newest version
/*
 * This file is part of ***  M y C o R e  ***
 * See http://www.mycore.de/ for details.
 *
 * MyCoRe is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MyCoRe is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MyCoRe.  If not, see .
 */

package org.mycore.common.config;

import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.logging.log4j.LogManager;
import org.mycore.common.MCRClassTools;
import org.mycore.common.function.MCRTriConsumer;
import org.mycore.common.inject.MCRInjectorConfig;

import com.google.inject.ConfigurationException;

/**
 * Provides methods to manage and read all configuration properties from the MyCoRe configuration files.
 * The Properties used by this class are used from {@link MCRConfigurationBase}.
 * 

NOTE

*

All {@link Optional} values returned by this class are {@link Optional#empty() empty} if the property * is not set OR the trimmed value {@link String#isEmpty() is empty}. If you want to distinguish between * empty properties and unset properties use {@link MCRConfigurationBase#getString(String)} instead. *

*

* Using this class is very easy, here is an example: *

*
 * // Get a configuration property as a String:
 * String sValue = MCRConfiguration2.getString("MCR.String.Value").orElse(defaultValue);
 *
 * // Get a configuration property as a List of String (values are seperated by ","):
 * List<String> lValue = MCRConfiguration2.getString("MCR.StringList.Value").stream()
 *     .flatMap(MCRConfiguration2::splitValue)
 *     .collect(Collectors.toList());
 *
 * // Get a configuration property as a long array (values are seperated by ","):
 * long[] la = MCRConfiguration2.getString("MCR.LongList.Value").stream()
 *     .flatMap(MCRConfiguration2::splitValue)
 *     .mapToLong(Long::parseLong)
 *     .toArray();
 *
 * // Get a configuration property as an int, use 500 as default if not set:
 * int max = MCRConfiguration2.getInt("MCR.Cache.Size").orElse(500);
 * 
* * There are some helper methods to help you with converting values *
    *
  • {@link #getOrThrow(String, Function)}
  • *
  • {@link #splitValue(String)}
  • *
  • {@link #instantiateClass(String)}
  • *
* * As you see, the class provides methods to get configuration properties as different data types and allows you to * specify defaults. All MyCoRe configuration properties should start with "MCR." * * Using the set methods allows client code to set new configuration properties or * overwrite existing ones with new values. * * @author Thomas Scheffler (yagee) * @since 2018.05 */ public class MCRConfiguration2 { private static ConcurrentHashMap LISTENERS = new ConcurrentHashMap<>(); static ConcurrentHashMap instanceHolder = new ConcurrentHashMap<>(); public static Map getPropertiesMap() { return Collections.unmodifiableMap(MCRConfigurationBase.getResolvedProperties().getAsMap()); } /** * Returns a sub map of properties where key is transformed. * *
    *
  1. if property starts with propertyPrefix, the property is in the result map
  2. *
  3. the key of the target map is the name of the property without propertPrefix
  4. *
* Example for propertyPrefix="MCR.Foo.": *
     *     MCR.Foo.Bar=Baz
     *     MCR.Foo.Hello=World
     *     MCR.Other.Prop=Value
     * 
* will result in *
     *     Bar=Baz
     *     Hello=World
     * 
* @param propertyPrefix prefix of the property name * @return a map of the properties as stated above */ public static Map getSubPropertiesMap(String propertyPrefix) { return MCRConfigurationBase.getResolvedProperties() .getAsMap() .entrySet() .stream() .filter(e -> e.getKey().startsWith(propertyPrefix)) .collect(Collectors.toMap(e -> e.getKey().substring(propertyPrefix.length()), Map.Entry::getValue)); } /** * Returns a new instance of the class specified in the configuration property with the given name. * If you call a method on the returned Optional directly you need to set the type like this: *
     * MCRConfiguration.<MCRMyType> getInstanceOf(name)
     *     .ifPresent(myTypeObj -> myTypeObj.method());
     * 
* * @param name * the non-null and non-empty name of the configuration property * @return the value of the configuration property as a String, or null * @throws MCRConfigurationException * if the class can not be loaded or instantiated */ public static Optional getInstanceOf(String name) throws MCRConfigurationException { return getString(name).map(MCRConfiguration2::instantiateClass); } /** * Returns a instance of the class specified in the configuration property with the given name. If the class was * previously instantiated by this method this instance is returned. * If you call a method on the returned Optional directly you need to set the type like this: *
     * MCRConfiguration.<MCRMyType> getSingleInstanceOf(name)
     *     .ifPresent(myTypeObj -> myTypeObj.method());
     * 
* * @param name * non-null and non-empty name of the configuration property * @return the instance of the class named by the value of the configuration property * @throws MCRConfigurationException * if the class can not be loaded or instantiated */ public static Optional getSingleInstanceOf(String name) { return getString(name) .map(className -> new SingletonKey(name, className)) .map(key -> (T) instanceHolder.computeIfAbsent(key, k -> getInstanceOf(name).orElse(null))); } /** * Returns a instance of the class specified in the configuration property with the given name. If the class was * previously instantiated by this method this instance is returned. * If you call a method on the returned Optional directly you need to set the type like this: *
     * MCRConfiguration.<MCRMyType> getSingleInstanceOf(name, alternative)
     *     .ifPresent(myTypeObj -> myTypeObj.method());
     * 
* * @param name * non-null and non-empty name of the configuration property * @param alternative * alternative class if property is undefined * @return the instance of the class named by the value of the configuration property * @throws MCRConfigurationException * if the class can not be loaded or instantiated */ public static Optional getSingleInstanceOf(String name, Class alternative) { return MCRConfiguration2.getSingleInstanceOf(name) .or(() -> Optional.ofNullable(alternative) .map(className -> new MCRConfiguration2.SingletonKey(name, className.getName())) .map(key -> (T) MCRConfiguration2.instanceHolder.computeIfAbsent(key, k -> instantiateClass(alternative)))); } /** * Loads a Java Class defined in property name. * @param name Name of the property * @param Supertype of class defined in name * @return Optional of Class asignable to <T> * @throws MCRConfigurationException * if the the class can not be loaded or instantiated */ public static Optional> getClass(String name) throws MCRConfigurationException { return getString(name).map(MCRConfiguration2::getClassObject); } /** * Returns the configuration property with the specified name. * If the value of the property is empty after trimming the returned Optional is empty. * @param name * the non-null and non-empty name of the configuration property * @return the value of the configuration property as an {@link Optional Optional<String>} */ public static Optional getString(String name) { return MCRConfigurationBase.getString(name) .map(String::trim) .filter(s -> !s.isEmpty()); } /** * Returns the configuration property with the specified name as String. * * @param name * the non-null and non-empty name of the configuration property * @throws MCRConfigurationException * if property is not set */ public static String getStringOrThrow(String name) { return getString(name).orElseThrow(() -> createConfigurationException(name)); } /** * Returns the configuration property with the specified name. * * @param name * the non-null and non-empty name of the configuration property * @param mapper * maps the String value to the return value * @throws MCRConfigurationException * if property is not set */ public static T getOrThrow(String name, Function mapper) { return getString(name).map(mapper).orElseThrow(() -> createConfigurationException(name)); } public static MCRConfigurationException createConfigurationException(String propertyName) { return new MCRConfigurationException("Configuration property " + propertyName + " is not set."); } /** * Splits a String value in a Stream of trimmed non-empty Strings. * * This method can be used to split a property value delimited by ',' into values. * *

* Example: *

*

* * MCRConfiguration2.getOrThrow("MCR.ListProp", MCRConfiguration2::splitValue)
* .map(Integer::parseInt)
* .collect(Collectors.toList())
*
*

* @param value a property value * @return a Stream of trimmed, non-empty Strings */ public static Stream splitValue(String value) { return MCRConfigurationBase.PROPERTY_SPLITTER.splitAsStream(value) .map(String::trim) .filter(s -> !s.isEmpty()); } /** * Returns the configuration property with the specified name as an * int value. * * @param name * the non-null and non-empty name of the configuration property * @return the value of the configuration property as an int value * @throws NumberFormatException * if the configuration property is not an int value */ public static Optional getInt(String name) throws NumberFormatException { return getString(name).map(Integer::parseInt); } /** * Returns the configuration property with the specified name as a * long value. * * @param name * the non-null and non-empty name of the configuration property * @return the value of the configuration property as a long value * @throws NumberFormatException * if the configuration property is not a long value */ public static Optional getLong(String name) throws NumberFormatException { return getString(name).map(Long::parseLong); } /** * Returns the configuration property with the specified name as a * float value. * * @param name * the non-null and non-empty name of the configuration property * @return the value of the configuration property as a float value * @throws NumberFormatException * if the configuration property is not a float value */ public static Optional getFloat(String name) throws NumberFormatException { return getString(name).map(Float::parseFloat); } /** * Returns the configuration property with the specified name as a * double value. * * @param name * the non-null and non-empty name of the configuration property * @return the value of the configuration property as a double * value * @throws NumberFormatException * if the configuration property is not a double value */ public static Optional getDouble(String name) throws NumberFormatException { return getString(name).map(Double::parseDouble); } /** * Returns the configuration property with the specified name as a * boolean value. * * @param name * the non-null and non-empty name of the configuration property * @return true, if and only if the specified property has the value true */ public static Optional getBoolean(String name) { return getString(name).map(Boolean::parseBoolean); } /** * Sets the configuration property with the specified name to a new * String value. If the parameter value is * null, the property will be deleted. * * @param name * the non-null and non-empty name of the configuration property * @param value * the new value of the configuration property, possibly * null */ public static void set(final String name, String value) { Optional oldValue = MCRConfigurationBase.getStringUnchecked(name); MCRConfigurationBase.set(name, value); LISTENERS .values() .stream() .filter(el -> el.keyPredicate.test(name)) .forEach(el -> el.listener.accept(name, oldValue, Optional.ofNullable(value))); } public static void set(String name, Supplier value) { set(name, value.get()); } public static void set(String name, T value, Function mapper) { set(name, mapper.apply(value)); } /** * Adds a listener that is called after a new value is set. * * @param keyPredicate * a filter upon the property name that if matches executes the listener * @param listener * a {@link MCRTriConsumer} with property name as first argument and than old and new value as Optional. * @return a UUID to {@link #removePropertyChangeEventListener(UUID) remove the listener} later */ public static UUID addPropertyChangeEventLister(Predicate keyPredicate, MCRTriConsumer, Optional> listener) { EventListener eventListener = new EventListener(keyPredicate, listener); LISTENERS.put(eventListener.uuid, eventListener); return eventListener.uuid; } public static boolean removePropertyChangeEventListener(UUID uuid) { return LISTENERS.remove(uuid) != null; } public static T instantiateClass(String classname) { LogManager.getLogger().debug("Loading Class: {}", classname); Class cl = getClassObject(classname); return instantiateClass(cl); } private static T instantiateClass(Class cl) { try { return MCRInjectorConfig.injector().getInstance(cl); } catch (ConfigurationException e) { // no default or injectable constructor, check for singleton factory method try { return (T) Stream.of(cl.getMethods()) .filter(m -> m.getReturnType().isAssignableFrom(cl)) .filter(m -> Modifier.isStatic(m.getModifiers())) .filter(m -> Modifier.isPublic(m.getModifiers())) .filter(m -> m.getName().toLowerCase(Locale.ROOT).contains("instance")) .findAny() .orElseThrow(() -> new MCRConfigurationException("Could not instantiate class " + cl.getName(), e)) .invoke(cl, (Object[]) null); } catch (ReflectiveOperationException r) { throw new MCRConfigurationException("Could not instantiate class " + cl.getName(), r); } } } private static Class getClassObject(String classname) { try { return MCRClassTools.forName(classname.trim()); } catch (ClassNotFoundException ex) { throw new MCRConfigurationException("Could not load class.", ex); } } private static class EventListener { private Predicate keyPredicate; private MCRTriConsumer, Optional> listener; private UUID uuid; EventListener(Predicate keyPredicate, MCRTriConsumer, Optional> listener) { this.keyPredicate = keyPredicate; this.listener = listener; this.uuid = UUID.randomUUID(); } } static class SingletonKey { private String property, className; SingletonKey(String property, String className) { super(); this.property = property; this.className = className; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((className == null) ? 0 : className.hashCode()); result = prime * result + ((property == null) ? 0 : property.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } SingletonKey other = (SingletonKey) obj; if (className == null) { if (other.className != null) { return false; } } else if (!className.equals(other.className)) { return false; } if (property == null) { return other.property == null; } else { return property.equals(other.property); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy