diff --git a/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceLocator.java b/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceLocator.java index 3e00c9d35..fd834495f 100644 --- a/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceLocator.java +++ b/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceLocator.java @@ -27,8 +27,8 @@ public interface ServiceLocator { /** - * Instantiate the given class and inject dependencies. The object created in - * this way will not be managed by the DI. + * Instantiate the given class and inject dependencies. The object created + * in this way will not be managed by the DI. * * @param serviceClass * @return @@ -36,7 +36,9 @@ public interface ServiceLocator { T create(Class serviceClass); /** - * Get a Service instance + * Get the service of the given type. This method can return external + * services and does return a object of the type registered for the service + * type. * * @param serviceType * @return diff --git a/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/annotation/Context.java b/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/annotation/Context.java new file mode 100644 index 000000000..a9424a034 --- /dev/null +++ b/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/annotation/Context.java @@ -0,0 +1,24 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declare the use of context information + * + * @author Leon Kiefer + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target(java.lang.annotation.ElementType.FIELD) +public @interface Context { + /** + * The class of the context provider to use for getting the context + * information + * + * @return the ContextProvider class + */ + Class value(); +} diff --git a/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/annotation/Reference.java b/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/annotation/Reference.java index db8152258..6cb39046d 100644 --- a/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/annotation/Reference.java +++ b/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/annotation/Reference.java @@ -33,10 +33,5 @@ @Documented @Target(java.lang.annotation.ElementType.FIELD) public @interface Reference { - /** - * The {@link Scope scope} of this reference. - * - * @return the scope. - */ - Scope value() default Scope.GLOBAL; -} + +} \ No newline at end of file diff --git a/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/annotation/Scope.java b/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/annotation/Scope.java deleted file mode 100644 index 2f1804c2f..000000000 --- a/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/annotation/Scope.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This source file is part of the Amy open source project. - * For more information see github.com/AmyAssist - * - * Copyright (c) 2018 the Amy project authors. - * - * 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. - */ -package de.unistuttgart.iaas.amyassist.amy.core.di.annotation; - -/** - * This enum contains the possible scopes for the dependency injection. - * - * @author Tim Neumann - */ -public enum Scope { - /** Inject the global instance of this class */ - GLOBAL, - /** Inject the instance bound to the plugin */ - PLUGIN, - /** Inject a instance bound to this class */ - CLASS, - /** Inject a new instance, which is never used again after that. */ - ONCE -} diff --git a/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/context/provider/StaticProvider.java b/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/context/provider/StaticProvider.java new file mode 100644 index 000000000..1013e4b26 --- /dev/null +++ b/di-api/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/context/provider/StaticProvider.java @@ -0,0 +1,17 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.context.provider; + +/** + * A Static Context Provider which provides context information from then + * consumer class. + * + * @author Leon Kiefer + */ +public interface StaticProvider { + /** + * + * @param consumer + * the class of the consumer + * @return the context information + */ + T getContext(Class consumer); +} diff --git a/di/pom.xml b/di/pom.xml index 65aa9c747..bcc5894e5 100644 --- a/di/pom.xml +++ b/di/pom.xml @@ -1,4 +1,5 @@ - 4.0.0 amy-di @@ -50,5 +51,9 @@ 1.2.0 test + + com.google.code.findbugs + jsr305 + diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjection.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjection.java index c1e3ab41d..5a65cead0 100644 --- a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjection.java +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjection.java @@ -20,24 +20,26 @@ package de.unistuttgart.iaas.amyassist.amy.core.di; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import org.apache.commons.lang3.reflect.FieldUtils; -import org.apache.commons.lang3.reflect.MethodUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import de.unistuttgart.iaas.amyassist.amy.core.IPlugin; -import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.PostConstruct; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Reference; -import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Scope; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Service; +import de.unistuttgart.iaas.amyassist.amy.core.di.consumer.ServiceConsumer; +import de.unistuttgart.iaas.amyassist.amy.core.di.consumer.ServiceFunction; +import de.unistuttgart.iaas.amyassist.amy.core.di.context.provider.ClassProvider; +import de.unistuttgart.iaas.amyassist.amy.core.di.context.provider.StaticProvider; +import de.unistuttgart.iaas.amyassist.amy.core.di.provider.ClassServiceProvider; +import de.unistuttgart.iaas.amyassist.amy.core.di.provider.ServiceProvider; +import de.unistuttgart.iaas.amyassist.amy.core.di.provider.SingeltonServiceProvider; +import de.unistuttgart.iaas.amyassist.amy.core.di.util.Util; /** * Dependency Injection Used to manage dependencies and Service instantiation at @@ -61,61 +63,21 @@ public class DependencyInjection implements ServiceLocator { /** * A register which maps a class to it's service provider. */ - protected Map, ServiceProvider> register; + protected Map, ServiceFunction> register; - /** - * A register which maps a class to it's dependencies, for each dependency - * also noting the scope. - */ - protected Map, Map, Scope>> dependencyRegister; - - /** - * The map of global instances for each service provider - */ - protected Map, Object> globalInstances; - - /** - * The map of class instances for a class - */ - protected Map, Map, Object>> classInstances; - - /** - * The map of class instances for a plugin - */ - protected Map, Object>> pluginInstances; - - /** - * The map of external services - */ - protected Map, Object> externalServices; - - private List plugins; + protected Map, StaticProvider> staticProvider; /** * Creates a new Dependency Injection */ public DependencyInjection() { this.register = new HashMap<>(); - this.dependencyRegister = new HashMap<>(); - this.globalInstances = new HashMap<>(); - this.externalServices = new HashMap<>(); - this.classInstances = new HashMap<>(); - this.pluginInstances = new HashMap<>(); + this.staticProvider = new HashMap<>(); + this.registerContextProvider(ClassProvider.class, new ClassProvider()); this.addExternalService(ServiceLocator.class, this); } - /** - * Set's the internal list of plugins, which the DI needs to be able to - * understand the scope plugin. - * - * @param p_plugins - * The list of plugins - */ - public void setPlugins(List p_plugins) { - this.plugins = p_plugins; - } - /** * Registers a service * @@ -134,37 +96,24 @@ public synchronized void register(Class cls) { serviceTypes = new Class[1]; serviceTypes[0] = cls; } - // TODO check if serviceType matches cls + for (Class serviceType : serviceTypes) { - if (this.hasServiceOfType(serviceType)) + if (this.hasServiceOfType(serviceType)) { throw new DuplicateServiceException(); - } - - if (!this.constructorCheck(cls)) - throw new RuntimeException("There is no default public constructor on class " + cls.getName()); - - Field[] dependencyFields = FieldUtils.getFieldsWithAnnotation(cls, Reference.class); - Map, Scope> dependencies = new HashMap<>(); - for (Field field : dependencyFields) { - Class dependency = field.getType(); - if (dependencies.containsKey(dependency)) { - this.logger.warn("The Service {} have a duplicate dependeny on {}", cls.getName(), - dependency.getName()); - } else { - Reference[] annotations = field.getAnnotationsByType(Reference.class); - if (annotations.length > 1) { - this.logger.warn("In the service {} the field {} has the annotation @Reference multiple times." - + "Only respecting the first.", cls.getName(), field.getName()); - } - dependencies.put(dependency, annotations[0].value()); + } + if (!serviceType.isAssignableFrom(cls)) { + throw new IllegalArgumentException(); } } for (Class serviceType : serviceTypes) { - ServiceProvider p = new ServiceProvider<>(cls); + ServiceFunction p = new ClassServiceProvider<>(cls); this.register.put(serviceType, p); } - this.dependencyRegister.put(cls, dependencies); + } + + public synchronized void registerContextProvider(Class key, StaticProvider staticProvider) { + this.staticProvider.put(key, staticProvider); } /** @@ -180,176 +129,98 @@ public synchronized void register(Class cls) { public synchronized void addExternalService(Class serviceType, Object externalService) { if (this.hasServiceOfType(serviceType)) throw new DuplicateServiceException(); - this.externalServices.put(serviceType, externalService); + this.register.put(serviceType, new SingeltonServiceProvider<>(externalService)); } private boolean hasServiceOfType(Class serviceType) { - if (this.register.containsKey(serviceType)) - return true; - if (this.externalServices.containsKey(serviceType)) - return true; - return false; - } - - /** - * check if default constructor exists and is accessible - * - * @param cls - * The class to check - * @return Whether the default constructor is present. - */ - @SuppressWarnings("static-method") - private boolean constructorCheck(Class cls) { - try { - cls.getConstructor(); - return true; - } catch (NoSuchMethodException | SecurityException e) { - return false; - } + return this.register.containsKey(serviceType); } @Override public T getService(Class serviceType) { - return this.getService(serviceType, new ArrayDeque<>(), new ScopeInformation(Scope.GLOBAL)); + ServiceFunction provider = this.getServiceProvider(serviceType); + ServiceFactory factory = this.resolve(provider); + return factory.build(); } /** - * Get the service of the given type, considering the stack of classes, for - * which this service is needed. This method can return external services - * and does return a object of the type registered for the service type. + * Resolve the dependencies of the ServiceProvider and creates a factory for + * the Service. * * @param - * The type of the service. - * @param serviceType - * The class of the service type. - * @param stack - * The stack of classes, for which this service is needed. - * @return The instance of the service. + * The type of the service + * @param serviceProvider + * The Service Provider. + * @return The factory for a Service of the ServiceProvider. */ - private T getService(Class serviceType, Deque> stack, ScopeInformation scope) { - if (this.externalServices.containsKey(serviceType)) { - if (scope.getScope() == Scope.GLOBAL) - return (T) this.externalServices.get(serviceType); - } - ServiceProvider provider = this.getProvider(serviceType); - return (T) this.resolve(provider, stack, scope); - } - - private Map, Object> getResolved(ScopeInformation scope) { - if (scope.getScope() == Scope.GLOBAL) { - return this.globalInstances; - } - - if (scope.getScope() == Scope.PLUGIN) { - if (!this.pluginInstances.containsKey(scope.getPlugin())) { - this.pluginInstances.put(scope.getPlugin(), new HashMap<>()); - } - return this.pluginInstances.get(scope.getPlugin()); - } - - if (scope.getScope() == Scope.CLASS) { - if (!this.classInstances.containsKey(scope.getCls())) { - this.classInstances.put(scope.getCls(), new HashMap<>()); - } - return this.classInstances.get(scope.getCls()); - } - return new HashMap<>(); + private ServiceFactory resolve(ServiceFunction serviceProvider) { + return this.resolve(serviceProvider, new ArrayDeque<>(), null); } /** - * Resolve a class considering the stack of classes, for which this class is - * needed. This method does not return external services and can only return - * a instance of the exact type + * Resolve the dependencies of the ServiceProvider and creates a factory for + * the Service. considering the stack of dependencies, for which this + * ServiceProvider is needed. * * @param - * The type of the class + * The type of the service * @param serviceProvider * The Service Provider. * @param stack * The stack of classes, for which this class is needed. - * @return The instance of the class. + * @return The factory for a Service of the ServiceProvider. */ - private T resolve(ServiceProvider serviceProvider, Deque> stack, ScopeInformation scope) { - Map, Object> resolved = this.getResolved(scope); - - if (resolved.containsKey(serviceProvider)) - return (T) resolved.get(serviceProvider); - - if (stack.contains(serviceProvider)) - throw new RuntimeException("circular dependencies"); - - stack.push(serviceProvider); - - try { - T instance = serviceProvider.newInstance(); - this.inject(instance, stack); - this.postConstruct(instance); + private ServiceFactory resolve(ServiceFunction serviceProvider, Deque> stack, + ServiceConsumer consumer) { + if (stack.contains(serviceProvider)) { + throw new IllegalStateException("circular dependencies"); + } else { + stack.push(serviceProvider); + } + ServiceProviderServiceFactory serviceProviderServiceFactory = new ServiceProviderServiceFactory<>( + serviceProvider); + for (Class dependency : serviceProvider.getDependencies()) { + ServiceFunction provider = this.getServiceProvider(dependency); + ServiceFactory dependencyFactory = this.resolve(provider, stack, serviceProvider); + serviceProviderServiceFactory.resolved(dependency, dependencyFactory); + } - resolved.put(serviceProvider, instance); - stack.pop(); - return instance; - } catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); + for (Class requiredContextProviderType : serviceProvider.getRequiredContextProviderTypes()) { + StaticProvider contextProvider = this.getContextProvider(requiredContextProviderType); + serviceProviderServiceFactory.setContextProvider(requiredContextProviderType, contextProvider); } + serviceProviderServiceFactory.setConsumer(consumer); + stack.pop(); + return serviceProviderServiceFactory; + } @Override public void inject(Object instance) { - this.inject(instance, new ArrayDeque<>()); - } - - private void inject(Object instance, Deque> stack) { Field[] dependencyFields = FieldUtils.getFieldsWithAnnotation(instance.getClass(), Reference.class); for (Field field : dependencyFields) { Class dependency = field.getType(); - Reference[] annotations = field.getAnnotationsByType(Reference.class); - if (annotations.length > 1) { - this.logger.warn("In the class {} the field {} has the annotation @Reference multiple times." - + "Only respecting the first.", instance.getClass().getName(), field.getName()); - } - Scope s = annotations[0].value(); - ScopeInformation si = null; - switch (s) { - case CLASS: - si = new ScopeInformation(instance.getClass()); - break; - case PLUGIN: - IPlugin p = this.getPluginFromClass(instance.getClass()); - if (p == null) { - si = new ScopeInformation(instance.getClass()); - } else { - si = new ScopeInformation(p); - } - break; - default: - si = new ScopeInformation(s); - break; - } - Object object = this.getService(dependency, stack, si); - try { - FieldUtils.writeField(field, instance, object, true); - } catch (IllegalAccessException e) { - this.logger.error("tryed to inject the dependency {} into {} but failed", object, instance, e); - } + Object object = this.getService(dependency); + Util.inject(instance, object, field); } } @Override public void postConstruct(Object instance) { - Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(instance.getClass(), PostConstruct.class); - for (Method m : methodsWithAnnotation) { - try { - m.invoke(instance); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - this.logger.error("tryed to invoke method {} but got an error", m, e); - } - } + Util.postConstruct(instance); } - private ServiceProvider getProvider(Class serviceType) { + @SuppressWarnings("unchecked") + private ServiceFunction getServiceProvider(Class serviceType) { if (!this.register.containsKey(serviceType)) throw new ServiceNotFoundException(serviceType); - return this.register.get(serviceType); + return (ServiceFunction) this.register.get(serviceType); + } + + private StaticProvider getContextProvider(Class contextProviderType) { + if (!this.staticProvider.containsKey(contextProviderType)) + throw new NoSuchElementException(contextProviderType.getName()); + return this.staticProvider.get(contextProviderType); } /** @@ -358,27 +229,9 @@ private ServiceProvider getProvider(Class serviceType) { @Override public T create(Class serviceClass) { Deque> stack = new ArrayDeque<>(); - ServiceProvider provider = new ServiceProvider<>(serviceClass); - stack.push(provider); - try { - T instance = provider.newInstance(); - this.inject(instance, stack); - this.postConstruct(instance); + ServiceFunction provider = new ClassServiceProvider<>(serviceClass); + ServiceFactory serviceFactory = this.resolve(provider, stack, null); - stack.pop(); - return instance; - } catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - private IPlugin getPluginFromClass(Class cls) { - for (IPlugin p : this.plugins) { - if (p.getClasses().contains(cls)) - return p; - } - this.logger.error("The class {} does not seem to belong to any plugin. Falling back to class scope.", - cls.getName()); - return null; + return serviceFactory.build(); } } diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ScopeInformation.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ScopeInformation.java deleted file mode 100644 index 0dfb93b05..000000000 --- a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ScopeInformation.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This source file is part of the Amy open source project. - * For more information see github.com/AmyAssist - * - * Copyright (c) 2018 the Amy project authors. - * - * 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. - */ -package de.unistuttgart.iaas.amyassist.amy.core.di; - -import de.unistuttgart.iaas.amyassist.amy.core.IPlugin; -import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Scope; - -/** - * This class represents information about a scope - * - * @author Tim Neumann - */ -public class ScopeInformation { - private Scope scope; - private Class cls; - private IPlugin plugin; - - /** - * Creates a new scope information with the given scope - * - * Don't use this for the scopes class or plugin, because additional - * information is required - * - * @param p_scope - * The scope for this information - */ - public ScopeInformation(Scope p_scope) { - this.scope = p_scope; - } - - /** - * Creates a new scope information for the scope class - * - * @param p_cls - * The class for the scope. - */ - public ScopeInformation(Class p_cls) { - this.scope = Scope.CLASS; - this.cls = p_cls; - } - - /** - * Creates a new scope information for the scope plugin - * - * @param p_plugin - * The plugin for the scope - */ - public ScopeInformation(IPlugin p_plugin) { - this.scope = Scope.PLUGIN; - this.plugin = p_plugin; - } - - /** - * Get's the scope - * - * @return scope - */ - public Scope getScope() { - return this.scope; - } - - /** - * Get's the class of the scope if the scope is class - * - * @return the class or null if the scope is not class - */ - public Class getCls() { - return this.cls; - } - - /** - * Get's the plugin or the scope of the scope is plugin - * - * @return the plugin or null if the scope is not plugin - */ - public IPlugin getPlugin() { - return this.plugin; - } -} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceFactory.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceFactory.java new file mode 100644 index 000000000..602e83f35 --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceFactory.java @@ -0,0 +1,18 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di; + +/** + * The interface of all ServiceFactories + * + * @author Leon Kiefer + */ +public interface ServiceFactory { + /** + * Build the Service after all configuration is done. This doesn't mean a + * new instance is created. Multiple calls of this method must return the + * same instance. So after calling this method the configuration can't be + * changed. + * + * @return the instance of the Service + */ + T build(); +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceNotFoundException.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceNotFoundException.java index b1904d8a0..c92d6226a 100644 --- a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceNotFoundException.java +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceNotFoundException.java @@ -30,7 +30,7 @@ public class ServiceNotFoundException extends RuntimeException { * */ private static final long serialVersionUID = 2441944380474159637L; - private Class serviceType; + private final Class serviceType; /** * @param serviceType diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceProvider.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceProvider.java deleted file mode 100644 index 0fde99407..000000000 --- a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.unistuttgart.iaas.amyassist.amy.core.di; - -class ServiceProvider { - private Class cls; - - ServiceProvider(Class cls) { - this.cls = cls; - } - - T newInstance() throws InstantiationException, IllegalAccessException { - return cls.newInstance(); - } -} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceProviderServiceFactory.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceProviderServiceFactory.java new file mode 100644 index 000000000..e77beeac4 --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceProviderServiceFactory.java @@ -0,0 +1,52 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +import de.unistuttgart.iaas.amyassist.amy.core.di.consumer.ServiceConsumer; +import de.unistuttgart.iaas.amyassist.amy.core.di.context.provider.StaticProvider; +import de.unistuttgart.iaas.amyassist.amy.core.di.provider.ServiceProvider; + +/** + * A ServiceFactory for building Services for ServiceProviders + * + * @author Leon Kiefer + */ +public class ServiceProviderServiceFactory implements ServiceFactory { + + private ServiceProvider serviceProvider; + + private Map, ServiceFactory> resolvedDependencies = new HashMap<>(); + private Map, StaticProvider> contextProviders = new HashMap<>(); + @Nullable + private ServiceConsumer consumerClass; + + private T buildedInstance; + + public ServiceProviderServiceFactory(ServiceProvider serviceProvider) { + this.serviceProvider = serviceProvider; + } + + @Override + public T build() { + if (this.buildedInstance == null) { + this.buildedInstance = this.serviceProvider.getService(this.resolvedDependencies, this.contextProviders, + this.consumerClass); + } + return this.buildedInstance; + } + + public void resolved(Class dependency, ServiceFactory dependencyFactory) { + this.resolvedDependencies.put(dependency, dependencyFactory); + } + + public void setContextProvider(Class requiredContextProviderType, StaticProvider contextProvider) { + this.contextProviders.put(requiredContextProviderType, contextProvider); + } + + public void setConsumer(@Nullable ServiceConsumer consumer) { + this.consumerClass = consumer; + } +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/consumer/ServiceConsumer.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/consumer/ServiceConsumer.java new file mode 100644 index 000000000..6104cf24d --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/consumer/ServiceConsumer.java @@ -0,0 +1,12 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.consumer; + +/** + * A Service Consumer + * + * @author Leon Kiefer + */ +public interface ServiceConsumer { + default Class getConsumerClass() { + return null; + } +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/consumer/ServiceFunction.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/consumer/ServiceFunction.java new file mode 100644 index 000000000..237dbdd51 --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/consumer/ServiceFunction.java @@ -0,0 +1,12 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.consumer; + +import de.unistuttgart.iaas.amyassist.amy.core.di.provider.ServiceProvider; + +/** + * A combination of ServiceConsumer and ServiceProvider + * + * @author Leon Kiefer + */ +public interface ServiceFunction extends ServiceConsumer, ServiceProvider { + +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/context/provider/ClassProvider.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/context/provider/ClassProvider.java new file mode 100644 index 000000000..bc302661c --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/context/provider/ClassProvider.java @@ -0,0 +1,15 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.context.provider; + +/** + * A ContextProvider which provides the class of the consumer + * + * @author Leon Kiefer + */ +public class ClassProvider implements StaticProvider> { + + @Override + public Class getContext(Class consumer) { + return consumer; + } + +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/context/provider/PluginProvider.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/context/provider/PluginProvider.java new file mode 100644 index 000000000..5b9227b4e --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/context/provider/PluginProvider.java @@ -0,0 +1,36 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.context.provider; + +import java.util.Collection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.unistuttgart.iaas.amyassist.amy.core.IPlugin; + +/** + * A ContextProvider for Plugin informations + * + * @author Leon Kiefer + */ +public class PluginProvider implements StaticProvider { + + private final Logger logger = LoggerFactory.getLogger(PluginProvider.class); + + private Collection plugins; + + public PluginProvider(Collection plugins) { + this.plugins = plugins; + } + + @Override + public IPlugin getContext(Class consumer) { + for (IPlugin p : this.plugins) { + if (p.getClasses().contains(consumer)) { + return p; + } + } + this.logger.error("The class {} does not seem to belong to any plugin.", consumer.getName()); + return null; + } + +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/ClassServiceProvider.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/ClassServiceProvider.java new file mode 100644 index 000000000..666dfbe08 --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/ClassServiceProvider.java @@ -0,0 +1,147 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.provider; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.unistuttgart.iaas.amyassist.amy.core.di.ServiceFactory; +import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Context; +import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Reference; +import de.unistuttgart.iaas.amyassist.amy.core.di.consumer.ServiceConsumer; +import de.unistuttgart.iaas.amyassist.amy.core.di.consumer.ServiceFunction; +import de.unistuttgart.iaas.amyassist.amy.core.di.context.provider.StaticProvider; +import de.unistuttgart.iaas.amyassist.amy.core.di.util.NTuple; +import de.unistuttgart.iaas.amyassist.amy.core.di.util.Util; +/** + * A ClassServiceProvider which provides service instances for a class + * + * @author Leon Kiefer + */ +public class ClassServiceProvider implements ServiceFunction { + private final Logger logger = LoggerFactory.getLogger(ServiceProvider.class); + + private final Class cls; + + /** + * A register which contains all dependencies + */ + private Collection> dependencies = new HashSet<>(); + private Collection injetionPoints = new HashSet<>(); + private Collection> requiredContextProviderTypes = new HashSet<>(); + private final NTuple> contextType; + private final NTuple contextInjectionPoints; + private Map, T> serviceInstances = new HashMap<>(); + + @Override + public Collection> getRequiredContextProviderTypes() { + return Collections.unmodifiableCollection(this.requiredContextProviderTypes); + } + + @Override + public Collection> getDependencies() { + return Collections.unmodifiableCollection(this.dependencies); + } + + public ClassServiceProvider(@Nonnull Class cls) { + if (!Util.classCheck(cls)) + throw new IllegalArgumentException( + "There is a problem with the class " + cls.getName() + ". It can't be used as a Service"); + + this.cls = cls; + Field[] dependencyFields = FieldUtils.getFieldsWithAnnotation(cls, Reference.class); + for (Field field : dependencyFields) { + if (field.isAnnotationPresent(Context.class)) { + throw new IllegalArgumentException(); + } + + InjetionPoint injetionPoint = new InjetionPoint(field); + this.injetionPoints.add(injetionPoint); + Class dependency = injetionPoint.getType(); + if (dependencies.contains(dependency)) { + this.logger.warn("The Service {} have a duplicate dependeny on {}", cls.getName(), + dependency.getName()); + } else { + this.dependencies.add(dependency); + } + } + + Field[] contextFields = FieldUtils.getFieldsWithAnnotation(cls, Context.class); + this.contextType = new NTuple<>(contextFields.length); + this.contextInjectionPoints = new NTuple<>(contextFields.length); + int i = 0; + for (Field field : contextFields) { + ContextInjectionPoint injetionPoint = new ContextInjectionPoint(field); + this.requiredContextProviderTypes.add(injetionPoint.getContextProviderType()); + this.contextType.set(i, injetionPoint.getContextProviderType()); + this.contextInjectionPoints.set(i, injetionPoint); + i++; + } + } + + private NTuple getContextTuple(Map, StaticProvider> contextProviders, + @Nullable ServiceConsumer consumer) { + if (consumer == null) { + return new NTuple<>(this.contextType.n); + } + + Class consumerClass = consumer.getConsumerClass(); + return this.contextType.map(type -> contextProviders.get(type).getContext(consumerClass)); + } + + @Override + public T getService(Map, ServiceFactory> resolvedDependencies, + Map, StaticProvider> contextProviders, @Nullable ServiceConsumer consumer) { + NTuple contextTuple = this.getContextTuple(contextProviders, consumer); + if (this.serviceInstances.containsKey(contextTuple)) { + return this.serviceInstances.get(contextTuple); + } + + T createdService = this.createService(resolvedDependencies, contextTuple); + this.serviceInstances.put(contextTuple, createdService); + return createdService; + } + + /** + * Create a new Service instance with the given resolved dependencies and + * context + * + * @param resolvedDependencies + * @param contextTuple + * the context + * @return + */ + private T createService(Map, ServiceFactory> resolvedDependencies, NTuple contextTuple) { + try { + T serviceInstance = this.cls.newInstance(); + for (InjetionPoint injetionPoint : injetionPoints) { + ServiceFactory serviceFactory = resolvedDependencies.get(injetionPoint.getType()); + injetionPoint.inject(serviceInstance, serviceFactory.build()); + } + for (int i = 0; i < this.contextInjectionPoints.n; i++) { + ContextInjectionPoint contextInjectionPoint = this.contextInjectionPoints.get(i); + contextInjectionPoint.inject(serviceInstance, contextTuple.get(i)); + } + + Util.postConstruct(serviceInstance); + return serviceInstance; + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException("The constructor of " + this.cls.getName() + " should have been checked", + e); + } + } + + @Override + public Class getConsumerClass() { + return this.cls; + } +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/ContextInjectionPoint.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/ContextInjectionPoint.java new file mode 100644 index 000000000..d55934085 --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/ContextInjectionPoint.java @@ -0,0 +1,34 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.provider; + +import java.lang.reflect.Field; + +import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Context; + +/** + * A ContextInjectionPoint is an InjectionPoint where the context is injected. + * + * @author Leon Kiefer + */ +public class ContextInjectionPoint extends InjetionPoint { + + private Class contextProviderType; + + /** + * @return the contextProviderType + */ + public Class getContextProviderType() { + return this.contextProviderType; + } + + public ContextInjectionPoint(Field field) { + super(field); + Context[] annotations = field.getAnnotationsByType(Context.class); + if (annotations.length > 1) { + throw new IllegalArgumentException("In the service " + field.getDeclaringClass().getName() + " the field " + + field.getName() + " has the annotation @Context multiple times."); + } + Context context = annotations[0]; + this.contextProviderType = context.value(); + } + +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/InjetionPoint.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/InjetionPoint.java new file mode 100644 index 000000000..1112c013a --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/InjetionPoint.java @@ -0,0 +1,34 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.provider; + +import java.lang.reflect.Field; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import de.unistuttgart.iaas.amyassist.amy.core.di.util.Util; + +/** + * A InjectionPoint is an abstraction of where an object is injected into an + * instance. + * + * @author Leon Kiefer + */ +class InjetionPoint { + private Field field; + + public InjetionPoint(Field field) { + this.field = field; + } + + /** + * + * @return + */ + public Class getType() { + return this.field.getType(); + } + + public void inject(@Nonnull Object instance, @Nullable Object object) { + Util.inject(instance, object, this.field); + } +} \ No newline at end of file diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/ServiceProvider.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/ServiceProvider.java new file mode 100644 index 000000000..3c13d4119 --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/ServiceProvider.java @@ -0,0 +1,43 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.provider; + +import java.util.Collection; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import de.unistuttgart.iaas.amyassist.amy.core.di.ServiceFactory; +import de.unistuttgart.iaas.amyassist.amy.core.di.consumer.ServiceConsumer; +import de.unistuttgart.iaas.amyassist.amy.core.di.context.provider.StaticProvider; + +/** + * + * @author Leon Kiefer + * + * @param + * service type + */ +public interface ServiceProvider { + @Nonnull + default T getService(Map, ServiceFactory> resolvedDependencies) { + return this.getService(resolvedDependencies, null, null); + } + + @Nonnull + T getService(Map, ServiceFactory> resolvedDependencies, + @Nullable Map, StaticProvider> contextProviders, @Nullable ServiceConsumer consumer); + + /** + * + * @return the dependencies + */ + @Nonnull + Collection> getDependencies(); + + /** + * @return the requiredContextProviderTypes + */ + @Nonnull + Collection> getRequiredContextProviderTypes(); + +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/SingeltonServiceProvider.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/SingeltonServiceProvider.java new file mode 100644 index 000000000..b7ffe0f6a --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/provider/SingeltonServiceProvider.java @@ -0,0 +1,44 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.provider; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import de.unistuttgart.iaas.amyassist.amy.core.di.ServiceFactory; +import de.unistuttgart.iaas.amyassist.amy.core.di.consumer.ServiceConsumer; +import de.unistuttgart.iaas.amyassist.amy.core.di.consumer.ServiceFunction; +import de.unistuttgart.iaas.amyassist.amy.core.di.context.provider.StaticProvider; + +/** + * A ServiceProvider which provides only a single existing instance + * + * @author Leon Kiefer + */ +public class SingeltonServiceProvider implements ServiceFunction { + + private final T instance; + + public SingeltonServiceProvider(@Nonnull T instance) { + this.instance = instance; + } + + @Override + public T getService(Map, ServiceFactory> resolvedDependencies, + @Nullable Map, StaticProvider> contextProviders, @Nullable ServiceConsumer consumer) { + return this.instance; + } + + @Override + public Collection> getDependencies() { + return Collections.emptySet(); + } + + @Override + public Collection> getRequiredContextProviderTypes() { + return Collections.emptySet(); + } + +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/util/NTuple.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/util/NTuple.java new file mode 100644 index 000000000..3b7a39c58 --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/util/NTuple.java @@ -0,0 +1,66 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.util; + +import java.util.function.Function; + +/** + * A simple n Tuple implementation + * + * @author Leon Kiefer + */ +public class NTuple { + private T[] vector; + + public final int n; + + @SuppressWarnings("unchecked") + public NTuple(int size) { + this.n = size; + this.vector = (T[]) new Object[size]; + } + + public T get(int i) { + return this.vector[i]; + } + + public void set(int i, T value) { + this.vector[i] = value; + } + + public NTuple map(Function function) { + NTuple nTuple = new NTuple<>(this.n); + for (int i = 0; i < this.n; i++) { + nTuple.set(i, function.apply(this.get(i))); + } + return nTuple; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof NTuple)) { + return false; + } + NTuple comapareTuple = (NTuple) obj; + if (this.n != comapareTuple.n) { + return false; + } + + for (int i = 0; i < this.n; i++) { + if (!this.get(i).equals(comapareTuple.get(i))) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int hashCode = 0; + for (T value : this.vector) { + hashCode += value.hashCode(); + } + return hashCode; + } +} diff --git a/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/util/Util.java b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/util/Util.java new file mode 100644 index 000000000..1329cf565 --- /dev/null +++ b/di/src/main/java/de/unistuttgart/iaas/amyassist/amy/core/di/util/Util.java @@ -0,0 +1,82 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.util; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.commons.lang3.reflect.MethodUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.PostConstruct; + +/** + * Util for checks and java reflection + * + * @author Leon Kiefer + */ +public class Util { + private Util() { + // hide constructor + } + + private static final Logger logger = LoggerFactory.getLogger(Util.class); + + /** + * Checks if the given class can be used as a Service. There for it must be + * a not abstract class with a default constructor. + * + * @param cls + * @return + */ + public static boolean classCheck(@Nonnull Class cls) { + return constructorCheck(cls) && !cls.isArray() && !cls.isInterface() + && !Modifier.isAbstract(cls.getModifiers()); + } + + /** + * check if default constructor exists and is accessible + * + * @param cls + * The class to check + * @return Whether the default constructor is present. + */ + public static boolean constructorCheck(@Nonnull Class cls) { + try { + cls.getConstructor(); + return true; + } catch (NoSuchMethodException | SecurityException e) { + return false; + } + } + + public static void postConstruct(@Nonnull Object instance) { + Method[] methodsWithAnnotation = MethodUtils.getMethodsWithAnnotation(instance.getClass(), PostConstruct.class); + for (Method m : methodsWithAnnotation) { + try { + m.invoke(instance); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + logger.error("tryed to invoke method {} but got an error", m, e); + } + } + } + + public static void inject(@Nonnull Object instance, @Nullable Object object, @Nonnull Field field) { + if (object != null && !field.getType().isAssignableFrom(object.getClass())) { + throw new IllegalArgumentException( + "the object doesn't have the correct type to be assigable to the given field. The object is of type " + + object.getClass() + " and the field of " + field.getType()); + } + + try { + FieldUtils.writeField(field, instance, object, true); + } catch (IllegalAccessException e) { + logger.error("tryed to inject the dependency {} into {} but failed", object, instance, e); + } + } +} diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/AbstractService.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/AbstractService.java new file mode 100644 index 000000000..21d2e62b3 --- /dev/null +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/AbstractService.java @@ -0,0 +1,15 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di; + +import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Service; + +/** + * Test Service for DI + * + * @author Leon Kiefer + */ +@Service +public abstract class AbstractService { + public AbstractService() { + + } +} diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjectionScopeTest.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjectionScopeTest.java index 071c25748..b42f783ff 100644 --- a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjectionScopeTest.java +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjectionScopeTest.java @@ -9,16 +9,19 @@ */ package de.unistuttgart.iaas.amyassist.amy.core.di; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; import java.util.ArrayList; +import java.util.NoSuchElementException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import de.unistuttgart.iaas.amyassist.amy.core.IPlugin; +import de.unistuttgart.iaas.amyassist.amy.core.di.context.provider.PluginProvider; import uk.org.lidalia.slf4jtest.TestLoggerFactory; /** @@ -35,12 +38,6 @@ public class DependencyInjectionScopeTest { @BeforeEach public void setup() { this.dependencyInjection = new DependencyInjection(); - this.dependencyInjection.register(Service11.class); - this.dependencyInjection.register(Service12.class); - this.dependencyInjection.register(Service13.class); - this.dependencyInjection.register(Service14.class); - this.dependencyInjection.register(Service15.class); - this.dependencyInjection.register(Service16.class); } // Scope GLOBAL and once are already being tested by the other test. @@ -50,12 +47,16 @@ public void setup() { */ @Test public void testScopeClass() { + this.dependencyInjection.register(Service11.class); + this.dependencyInjection.register(Service12.class); + this.dependencyInjection.register(Service13.class); + Service11 s1 = this.dependencyInjection.getService(Service11.class); s1.s.id = 4; Service13 s2 = this.dependencyInjection.getService(Service13.class); assertThat(s1.s, not(theInstance(s2.s1))); - + assertThat(s1, theInstance(s2.s2)); assertThat(s1.s, theInstance(s2.s2.s)); } @@ -64,21 +65,26 @@ public void testScopeClass() { */ @Test public void testScopePlugin() { + this.dependencyInjection.register(Service14.class); + this.dependencyInjection.register(Service15.class); + this.dependencyInjection.register(Service16.class); + this.dependencyInjection.register(ServiceForPlugins.class); + ArrayList plugins = new ArrayList<>(); - this.dependencyInjection.setPlugins(plugins); + this.dependencyInjection.registerContextProvider(PluginProvider.class, new PluginProvider(plugins)); Class cls1 = Service14.class; Class cls2 = Service15.class; Class cls3 = Service16.class; ArrayList> classes1 = new ArrayList<>(); - plugins.add(new TestPlugin(classes1)); classes1.add(cls1); classes1.add(cls2); + plugins.add(new TestPlugin(classes1)); ArrayList> classes2 = new ArrayList<>(); - plugins.add(new TestPlugin(classes2)); classes2.add(cls3); + plugins.add(new TestPlugin(classes2)); Service14 s1 = this.dependencyInjection.getService(Service14.class); Service15 s2 = this.dependencyInjection.getService(Service15.class); @@ -87,7 +93,27 @@ public void testScopePlugin() { assertThat(s1.s, theInstance(s2.s)); assertThat(s1.s, not(theInstance(s3.s))); + } + + @Test + public void testContextValue() { + this.dependencyInjection.register(Service11.class); + this.dependencyInjection.register(Service12.class); + this.dependencyInjection.register(Service13.class); + + Service11 s1 = this.dependencyInjection.getService(Service11.class); + assertThat(s1.s.getConsumerClass(), notNullValue()); + assertThat(s1.s.getConsumerClass(), equalTo(Service11.class)); + } + + @Test + public void testNoContextProvider() { + this.dependencyInjection.register(Service16.class); + this.dependencyInjection.register(ServiceForPlugins.class); + String message = assertThrows(NoSuchElementException.class, + () -> this.dependencyInjection.getService(Service16.class)).getMessage(); + assertThat(message, equalTo(PluginProvider.class.getName())); } /** diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjectionTest.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjectionTest.java index ad19098b3..684558d08 100644 --- a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjectionTest.java +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/DependencyInjectionTest.java @@ -19,15 +19,16 @@ package de.unistuttgart.iaas.amyassist.amy.core.di; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static uk.org.lidalia.slf4jtest.LoggingEvent.warn; +import static org.junit.jupiter.api.Assertions.*; +import static uk.org.lidalia.slf4jtest.LoggingEvent.*; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import de.unistuttgart.iaas.amyassist.amy.core.di.provider.ServiceProvider; import uk.org.lidalia.slf4jtest.TestLogger; import uk.org.lidalia.slf4jtest.TestLoggerFactory; @@ -60,6 +61,7 @@ void testDependencyInjection() { assertThat(service2.checkServices(), is(true)); } + @Test void testServiceRegistry() { Service1 s1 = this.dependencyInjection.getService(Service1.class); Service1 s2 = this.dependencyInjection.getService(Service1.class); @@ -107,16 +109,16 @@ void testDuplicateServiceException2() { @Test() void testConstructorCheck() { - String message = assertThrows(RuntimeException.class, + String message = assertThrows(IllegalArgumentException.class, () -> this.dependencyInjection.register(ServiceWithConstructor.class)).getMessage(); - assertThat(message, - equalTo("There is no default public constructor on class " + ServiceWithConstructor.class.getName())); + assertThat(message, equalTo("There is a problem with the class " + ServiceWithConstructor.class.getName() + + ". It can't be used as a Service")); } @Test() void testServiceWithDuplicateDependency() { - TestLogger logger = TestLoggerFactory.getTestLogger(DependencyInjection.class); + TestLogger logger = TestLoggerFactory.getTestLogger(ServiceProvider.class); this.dependencyInjection.register(ServiceWithDuplicateDependency.class); @@ -153,10 +155,23 @@ void testCreateNotAService() { @Test() void testCreateIllegalAccessException() { - Throwable cause = assertThrows(RuntimeException.class, () -> this.dependencyInjection.create(Service8.class)) - .getCause(); + String message = assertThrows(IllegalArgumentException.class, + () -> this.dependencyInjection.create(Service8.class)).getMessage(); + + assertThat(message, equalTo( + "There is a problem with the class " + Service8.class.getName() + ". It can't be used as a Service")); + } - assertThat(cause.getClass(), equalTo(InstantiationException.class)); + @Test() + void testExternalService() { + Service7 service7 = new Service7(); + this.dependencyInjection.addExternalService(Service7API.class, service7); + assertThat(this.dependencyInjection.getService(Service7API.class), is(theInstance(service7))); + } + + @Test() + void testAbstractService() { + assertThrows(IllegalArgumentException.class, () -> this.dependencyInjection.register(AbstractService.class)); } @AfterEach diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service11.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service11.java index a4fb764bd..92f4e9463 100644 --- a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service11.java +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service11.java @@ -10,7 +10,6 @@ package de.unistuttgart.iaas.amyassist.amy.core.di; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Reference; -import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Scope; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Service; /** @@ -22,6 +21,6 @@ public class Service11 { public int id; - @Reference(Scope.CLASS) + @Reference public Service12 s; } diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service12.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service12.java index dfe1a32ab..64afedb4e 100644 --- a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service12.java +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service12.java @@ -9,14 +9,27 @@ */ package de.unistuttgart.iaas.amyassist.amy.core.di; +import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Context; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Service; +import de.unistuttgart.iaas.amyassist.amy.core.di.context.provider.ClassProvider; /** * A test service for DependencyInjectionScopeTest * - * @author Tim Neumann + * @author Tim Neumann, Leon Kiefer */ @Service public class Service12 { + + @Context(ClassProvider.class) + private Class consumerClass; + + /** + * @return the consumerClass + */ + public Class getConsumerClass() { + return consumerClass; + } + public int id; } diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service13.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service13.java index b3571fdf4..c4c45a451 100644 --- a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service13.java +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service13.java @@ -10,7 +10,6 @@ package de.unistuttgart.iaas.amyassist.amy.core.di; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Reference; -import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Scope; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Service; /** @@ -22,7 +21,7 @@ public class Service13 { public int id; - @Reference(Scope.CLASS) + @Reference public Service12 s1; @Reference diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service14.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service14.java index a46c73fe7..f31b6254c 100644 --- a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service14.java +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service14.java @@ -10,7 +10,6 @@ package de.unistuttgart.iaas.amyassist.amy.core.di; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Reference; -import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Scope; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Service; /** @@ -20,6 +19,6 @@ */ @Service public class Service14 { - @Reference(Scope.PLUGIN) - Service12 s; + @Reference + ServiceForPlugins s; } diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service15.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service15.java index 979d2b5da..84e552b13 100644 --- a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service15.java +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service15.java @@ -10,7 +10,6 @@ package de.unistuttgart.iaas.amyassist.amy.core.di; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Reference; -import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Scope; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Service; /** @@ -20,6 +19,6 @@ */ @Service public class Service15 { - @Reference(Scope.PLUGIN) - Service12 s; + @Reference + ServiceForPlugins s; } diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service16.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service16.java index 46c6ebe03..b8e8ddf37 100644 --- a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service16.java +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service16.java @@ -10,7 +10,6 @@ package de.unistuttgart.iaas.amyassist.amy.core.di; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Reference; -import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Scope; import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Service; /** @@ -20,6 +19,6 @@ */ @Service public class Service16 { - @Reference(Scope.PLUGIN) - Service12 s; + @Reference + ServiceForPlugins s; } diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service9.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service9.java new file mode 100644 index 000000000..2c44178cf --- /dev/null +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/Service9.java @@ -0,0 +1,16 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di; + +import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Context; +import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Service; +import de.unistuttgart.iaas.amyassist.amy.core.di.context.provider.ClassProvider; + +/** + * Test Service for DI + * + * @author Leon Kiefer + */ +@Service +public class Service9 { + @Context(ClassProvider.class) + private Class consumerClass; +} diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceForPlugins.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceForPlugins.java new file mode 100644 index 000000000..0bd704f40 --- /dev/null +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/ServiceForPlugins.java @@ -0,0 +1,20 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di; + +import de.unistuttgart.iaas.amyassist.amy.core.IPlugin; +import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Context; +import de.unistuttgart.iaas.amyassist.amy.core.di.annotation.Service; +import de.unistuttgart.iaas.amyassist.amy.core.di.context.provider.PluginProvider; + +/** + * A test service for DependencyInjectionScopeTest + * + * @author Leon Kiefer + */ +@Service +public class ServiceForPlugins { + + @Context(PluginProvider.class) + private IPlugin plugin; + + public int id; +} diff --git a/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/util/UtilTest.java b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/util/UtilTest.java new file mode 100644 index 000000000..7ac82b83f --- /dev/null +++ b/di/src/test/java/de/unistuttgart/iaas/amyassist/amy/core/di/util/UtilTest.java @@ -0,0 +1,24 @@ +package de.unistuttgart.iaas.amyassist.amy.core.di.util; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +import org.junit.jupiter.api.Test; + +import de.unistuttgart.iaas.amyassist.amy.core.di.Service1; +import de.unistuttgart.iaas.amyassist.amy.core.di.ServiceWithConstructor; + +/** + * Tests for the DI Util + * + * @author Leon Kiefer + */ +class UtilTest { + + @Test() + void testConstructorCheck() { + assertThat(Util.constructorCheck(ServiceWithConstructor.class), is(false)); + assertThat(Util.constructorCheck(Service1.class), is(true)); + } + +} diff --git a/pom.xml b/pom.xml index b75212b97..c22ff4d48 100644 --- a/pom.xml +++ b/pom.xml @@ -186,6 +186,11 @@ ${mockito.version} test + + com.google.code.findbugs + jsr305 + 3.0.2 +