package de.joshavg.simpledic; import de.joshavg.simpledic.exception.ClassNotRegistered; import de.joshavg.simpledic.exception.ContainerInitException; import de.joshavg.simpledic.exception.SdicInstantiationException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The main container that loads a specific properties file * and creates instances of defined services *

* Services are given arbitrary names and can be defined as singletons: *


 * service.servicename: tld.vendor.project.ServiceClass
 * service.servicename.singleton: true
 * 
* Service names must match the regular expression ^service\.[^.]+$ *

* Services declare their dependencies via their constructor. Only one * constructor per class is allowed. * All dependencies must be declared in the same container as the declaring * service. *

* After loading the properties file, an {@link IntegrityCheck} will be performed. */ public class SdiContainer implements SdiContainerInterface { private static final Logger LOG = LoggerFactory.getLogger(SdiContainer.class); private final List definitions; private final Map singletons; private SdiContainer(List definitions) { this.definitions = definitions; this.singletons = new HashMap<>(); } /** * Creates a Container using the file sdic.properties from the classpath. * * @return the loaded and integrity checked container */ @SuppressWarnings("unused") public static SdiContainer load() { return load("sdic.properties"); } /** * Creates a Container using the properties file at the designated location * * @param filename the filename that shall be loaded * @return the loaded and integrity checked container */ @SuppressWarnings("WeakerAccess") public static SdiContainer load(String filename) { Properties props = new Properties(); InputStream inputStream = SdiContainer.class.getClassLoader().getResourceAsStream(filename); if (inputStream == null) { throw new ContainerInitException("config file " + filename + " not found", null); } try { props.load(inputStream); } catch (IOException e) { throw new ContainerInitException("failed loading properties", e); } IntegrityCheck integrityCheck = new IntegrityCheck(props); integrityCheck.check(); return new SdiContainer(integrityCheck.getDefinitions()); } private ServiceDefinition getDefinition(Class clz) { return definitions.stream() .filter(d -> d.getClz() == clz) .findFirst() .orElse(null); } /** * Creates and returns a service instance of the given class *

* Dependency services are automatically created. Services marked * as singletons will be only created once, either as transient * or direct dependency. * * @param clz the type which shall be created * @return the created type with declared dependencies fulfilled */ @Override public T getInstance(Class clz) { LOG.trace("instance ordered: ", clz); ServiceDefinition definition = getDefinition(clz); if (definition == null) { throw new ClassNotRegistered(clz); } LOG.debug("service name is {}", definition.getName()); if (isStoredAsSingleton(definition)) { return clz.cast(singletons.get(definition)); } try { T instance = new Instantiator<>(clz, this).createInstance(); handleSingleton(definition, instance); return instance; } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { throw new SdicInstantiationException(e); } } /** * returns all registered services that implement or extend the given class * * @param clz parent class or interface * @param type of the requested service * @return the list with instances, or an empty list if nothing is found */ @SuppressWarnings("WeakerAccess") public List getInstancesThatImplement(Class clz) { LOG.trace("instances of interface {} ordered", clz); return definitions.stream() .filter(d -> clz.isAssignableFrom(d.getClz())) .map(ServiceDefinition::getClz) .map(c -> clz.cast(getInstance(c))) .collect(Collectors.toList()); } private void handleSingleton(ServiceDefinition definition, T instance) { if (definition.isSingleton()) { singletons.put(definition, instance); } } private boolean isStoredAsSingleton(ServiceDefinition def) { return singletons.containsKey(def); } }