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

kz.greetgo.conf.hot.AbstractConfigFactory Maven / Gradle / Ivy

There is a newer version: 2.0.5
Show newest version
package kz.greetgo.conf.hot;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;

import static java.util.Collections.unmodifiableMap;
import static kz.greetgo.conf.hot.ConfigDataLoader.loadConfigDataTo;

/**
 * Factory for hot config implementations
 */
public abstract class AbstractConfigFactory {
  /**
   * Gets config storage
   *
   * @return config storage
   */
  protected abstract ConfigStorage getConfigStorage();

  /**
   * Calculates config location for config interface
   *
   * @param configInterface config interface
   * @return config location
   */
  protected abstract  String configLocationFor(Class configInterface);

  /**
   * Marks all configs to reread from storage
   */
  public void resetAll() {
    resetIf(config -> true);
  }

  /**
   * Marks specified configs to reread from storage
   */
  public void resetIf(Predicate predicate) {
    for (HotConfigImpl mediator : workingConfigs.values()) {
      if (predicate.test(mediator)) {
        mediator.reset();
      }
    }
  }

  private static final Object ABSENT_ENV = new Object();

  /**
   * Defines auto reset timeout. It is a time interval in milliseconds to check last config modification date and time.
   * And if the date and time was changed, then it calls method `reset` for this config.
   *
   * @return auto reset timeout. Zero - auto resetting is off
   */
  protected long autoResetTimeout() {
    return 500;
  }

  private final Map workingConfigs = new ConcurrentHashMap<>();

  /**
   * Creates config storage with default values if it is absent
   */
  public void sync() {
    workingConfigs.values().forEach(HotConfigImpl::getData);
  }

  private class HotConfigImpl implements HotConfig {
    private final AtomicReference> data = new AtomicReference<>(null);
    private final HotConfigDefinition configDefinition;

    public HotConfigImpl(HotConfigDefinition configDefinition) {
      this.configDefinition = configDefinition;
    }

    void reset() {
      data.set(null);
    }

    private final AtomicReference lastModificationTime = new AtomicReference<>(null);
    private final AtomicLong lastChecked = new AtomicLong(System.currentTimeMillis());

    private void preReset() {
      try {

        long autoResetTimeout = autoResetTimeout();

        if (autoResetTimeout == 0) {
          return;
        }

        long delay = System.currentTimeMillis() - lastChecked.get();

        if (delay < autoResetTimeout) {
          return;
        }

        Date lastModificationTime = this.lastModificationTime.get();
        if (lastModificationTime == null) {
          Date lastChangedAt = getConfigStorage().getLastChangedAt(configDefinition.location());
          this.lastModificationTime.set(lastChangedAt);
          lastChecked.set(System.currentTimeMillis());
          return;
        }

        Date lastChangedAt = getConfigStorage().getLastChangedAt(configDefinition.location());
        lastChecked.set(System.currentTimeMillis());
        if (lastChangedAt == null) {
          return;
        }

        if (lastChangedAt.equals(lastModificationTime)) {
          return;
        }

        this.lastModificationTime.set(lastChangedAt);

        reset();

      } catch (RuntimeException e) {
        throw e;
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    Map getData() {
      preReset();

      {
        Map x = data.get();
        if (x != null) {
          return x;
        }
      }

      synchronized (workingConfigs) {
        {
          Map x = data.get();
          if (x != null) {
            return x;
          }
        }
        {
          Map newData = new HashMap<>();
          loadConfigDataTo(newData, configDefinition, getConfigStorage(), new Date());
          newData = unmodifiableMap(newData);
          data.set(newData);
          return newData;
        }
      }
    }


    private final ConcurrentHashMap environmentValues = new ConcurrentHashMap<>();

    @Override
    @SuppressWarnings("unchecked")
    public Object getElementValue(String elementName) {

      Object envValue = environmentValues.get(elementName);

      if (envValue == null) {
        for (ElementDefinition definition : configDefinition.elementList()) {
          if (definition.name.equals(elementName)) {
            if (definition.firstReadEnv == null) {
              envValue = ABSENT_ENV;
              break;
            }

            String strEnvValue = System.getenv(definition.firstReadEnv.value());
            if (strEnvValue != null && strEnvValue.trim().length() > 0) {
              envValue = definition.typeManager.fromStr(strEnvValue);
            } else {
              envValue = ABSENT_ENV;
            }

            break;
          }
        }
        if (envValue == null) {
          envValue = ABSENT_ENV;
        }
        environmentValues.put(elementName, envValue);
      }

      if (envValue == ABSENT_ENV) {
        return getData().get(elementName);
      }

      return envValue;
    }

    @Override
    public boolean isElementExists(String elementName) {
      return getData().containsKey(elementName);
    }

    @Override
    public String location() {
      return configDefinition.location();
    }

    @Override
    public Class configInterface() {
      return configDefinition.configInterface();
    }
  }

  /**
   * Creates and returns instance of hot config
   *
   * @param configDefinition definition of config
   * @return instance of hot config
   */
  public HotConfig getOrCreateConfig(HotConfigDefinition configDefinition) {
    {
      HotConfigImpl config = workingConfigs.get(configDefinition.location());
      if (config != null) {
        return config;
      }
    }
    synchronized (this) {
      {
        HotConfigImpl config = workingConfigs.get(configDefinition.location());
        if (config != null) {
          return config;
        }
      }

      HotConfigImpl config = new HotConfigImpl(configDefinition);
      workingConfigs.put(configDefinition.location(), config);

      return config;
    }
  }

  /**
   * Creates and returns instance of config
   *
   * @param configInterface config interface
   * @return instance of config
   */
  @SuppressWarnings("unchecked")
  public  T createConfig(Class configInterface) {
    return (T) Proxy.newProxyInstance(
      getClass().getClassLoader(), new Class[]{configInterface}, createInvocationHandler(configInterface)
    );
  }


  /**
   * Replace parameters in value with what you want and return it
   *
   * @param value value containing parameters
   * @return value without parameters - all parameters was replaced with it's values
   */
  protected String replaceParametersInDefaultStrValue(String value) {
    value = value.replaceAll("\\{user\\.name}", System.getProperty("user.name"));
    value = value.replaceAll("\\{user\\.home}", System.getProperty("user.home"));
    return value;
  }

  private  InvocationHandler createInvocationHandler(Class configInterface) {
    String configLocation = configLocationFor(configInterface);

    {
      HotConfig hotConfig = workingConfigs.get(configLocation);
      if (hotConfig != null) {
        return createInvocationHandlerOnHotConfig(hotConfig, configInterface);
      }
    }

    return createInvocationHandlerOnHotConfig(
      getOrCreateConfig(

        DefinitionCreator.createDefinition(
          configLocation, configInterface, this::replaceParametersInDefaultStrValue
        )

      ), configInterface
    );
  }

  private InvocationHandler createInvocationHandlerOnHotConfig(final HotConfig hotConfig,
                                                               final Class configInterface) {
    final Object identityObject = new Object();
    return (proxy, method, args) -> {

      if (method.getParameterTypes().length > 0) return null;

      if ("toString".equals(method.getName())) {
        return "[Hot config for <" + configInterface.getName() + ">@" + identityObject.hashCode() + "]";
      }

      if ("hashCode".equals(method.getName()) && method.getParameterCount() == 0) {
        return identityObject.hashCode();
      }
      if ("equals".equals(method.getName()) && method.getParameterCount() == 1) {
        return identityObject.equals(args[0]);
      }

      return hotConfig.getElementValue(method.getName());
    };
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy