Class ServiceFactory

java.lang.Object
org.tentackle.common.ServiceFactory

@Service(ServiceFactory.class) public class ServiceFactory extends Object
Factory for services based on ServiceFinder.

A replacement for ServiceLoader providing the following additional features:

  • Not limited to load classes. Can load any kind of configuration.
  • Supports different service types. Each type gets its own directory in META-INF.
  • Each service (type + name) may have its own classloader. Useful for OSGi.
  • Works for modular (jigsaw) and non-modular applications.
  • As opposed to ServiceLoader, ServiceFinders retain the module dependency order for service URLs. The first URL is guaranteed to belong to the topmost relevant module. Unnamed, i.e., classpath and automatic modules, come last (unless the service is annotated with ClasspathFirst, see ModuleSorter).
Application modules must include an implementation of ModuleHook and a provides-clause in module-info. Example:
 provides org.tentackle.common.ModuleHook with com.myapp.somepackage.Hook;

The factory itself is loaded via the standard JDK ServiceLoader and can be replaced via META-INF/services/org.tentackle.common.ServiceFactory on the classpath or, in modular mode, via a provides org.tentackle.common.ServiceFactory with ... clause in module-info (ServiceLoader ignores META-INF/services within named modules). Alternatively, see setFactoryClassname(String). If no such configuration is found, an instance of ServiceFactory is used, i.e., the fallback and default implementation is ServiceFactory itself.

Author:
harald
  • Constructor Details

    • ServiceFactory

      public ServiceFactory()
      Creates the factory.
  • Method Details

    • setFactoryClassname

      public static void setFactoryClassname(String factoryClassname)
      Sets the classname of the service factory.

      If this value is null, the ServiceFactory is loaded via the ServiceLoader. However, if for whatever reason, the loader doesn't find the service configuration or cannot be used, the class defined by factoryClassname can be instantiated. Applications must change this at a very early stage of startup, for example in the main-method.

      Notice that the singleton is created lazily on its first use. Setting the classname afterwards has no effect and is logged as a warning.

      Parameters:
      factoryClassname - the classname
    • getFactoryClassname

      public static String getFactoryClassname()
      Gets the classname of the service factory.
      Returns:
      the classname, default is null
    • setFactoryClassloader

      public static void setFactoryClassloader(ClassLoader factoryClassloader)
      Sets the classloader to load the service factory.

      If this value is null, the ServiceFactory is loaded via a default classloader.

      Notice that the singleton is created lazily on its first use. Setting the classloader afterwards has no effect and is logged as a warning.

      Parameters:
      factoryClassloader - the classloader
    • getFactoryClassloader

      public static ClassLoader getFactoryClassloader()
      Gets the classloader to load the service factory.
      Returns:
      the classloader, default is null
    • getInstance

      public static ServiceFactory getInstance()
      Gets the singleton.

      The instance is created lazily on the first invocation, so that setFactoryClassname(String) and setFactoryClassloader(ClassLoader) are effective if applied before.

      Returns:
      the singleton, never null
    • getServiceFinder

      public static ServiceFinder getServiceFinder(ClassLoader loader, String servicePath)
      Gets a service finder for a given classloader and service path.
      If the finder does not exist yet, it will be created.
      Parameters:
      loader - the classloader, never null
      servicePath - the service path prefix
      Returns:
      the finder, never null
      Throws:
      NullPointerException - if loader or servicePath is null
    • getServiceFinder

      public static ServiceFinder getServiceFinder(ModuleLayer layer, String servicePath)
      Gets a service finder looking up services within a module layer.
      If the finder does not exist yet, it will be created.

      Unlike getServiceFinder(ClassLoader, String), this always creates a DefaultServiceFinder, bypassing the pluggable Service-replaced finder class. A replaced ServiceFinder implementation is never used for module layers.

      Layer-bound finders are cached separately from the classloader-based finders, even if the layer's classloader is the same (see ServiceFinderKey).

      Parameters:
      layer - the module layer
      servicePath - the service path prefix
      Returns:
      the finder, never null
    • getServiceFinder

      public static ServiceFinder getServiceFinder(String servicePath)
      Gets a service finder for a given service path.
      If the finder does not exist yet, it will be created. The classloader to use is determined as follows:
      1. use the fixed classloader set by setFixedClassLoader(java.lang.ClassLoader), if not null
      2. else try Thread.currentThread().getContextClassLoader()
      3. if the context classloader is null, use the classloader that loaded the service factory
      Parameters:
      servicePath - the service path prefix
      Returns:
      the finder, never null
    • getServiceFinder

      public static ServiceFinder getServiceFinder()
      Gets a service finder for the service path "META-INF/services/".
      If the finder does not exist yet, it will be created. The classloader to use is determined as follows:
      1. use the fixed classloader set by setFixedClassLoader(java.lang.ClassLoader), if not null
      2. else try Thread.currentThread().getContextClassLoader()
      3. if the context classloader is null, use the classloader that loaded the service factory
      Returns:
      the finder, never null
    • createServiceClass

      public static <T> Class<T> createServiceClass(Class<T> serviceClass)
      Utility method to determine the provider class of a service.
      Finds the first service implementation in module/classpath order (see ModuleSorter).
      Type Parameters:
      T - the service type
      Parameters:
      serviceClass - the service class
      Returns:
      the provider class of the service
      Throws:
      TentackleRuntimeException - if no provider found or the provider does not implement the service
    • createService

      public static <T> T createService(Class<T> serviceClass)
      Utility method to create a service instance.
      This is the standard way to instantiate singletons. Finds the first service implementation in module/classpath order (see ModuleSorter) and invokes its no-arg constructor.
      Type Parameters:
      T - the service type
      Parameters:
      serviceClass - the service class
      Returns:
      an instance of the service
      Throws:
      TentackleRuntimeException - if no provider found or it could not be instantiated
    • createService

      public static <T> T createService(Class<T> serviceClass, Class<? extends T> defaultClass, boolean logDefault)
      Utility method to create a service instance with a default if not found.
      Type Parameters:
      T - the service type
      Parameters:
      serviceClass - the service class
      defaultClass - the fallback class if no service found or could not be instantiated, null to rethrow the exception
      logDefault - true if log warning with stacktrace if default implementation is used
      Returns:
      an instance of the service
      Throws:
      TentackleRuntimeException - if the service could not be created and the default class is null or could not be instantiated either
    • createService

      public static <T> T createService(Class<T> serviceClass, Class<? extends T> defaultClass)
      Utility method to create a service instance with a default if not found.
      Same as createService(Class, Class, boolean) with a warning logged if the default class is used.
      Type Parameters:
      T - the service type
      Parameters:
      serviceClass - the service class
      defaultClass - the default class if no service found
      Returns:
      an instance of the service
      Throws:
      TentackleRuntimeException - if neither the service nor the default could be created
    • getClassLoader

      public static ClassLoader getClassLoader(String servicePath, String serviceName)
      Gets the classloader for a given service path and name.
      If an explicit classloader is set, the last defined classloader for the given path and name is returned. Otherwise, the default classloader is returned.
      Parameters:
      servicePath - the service path
      serviceName - the service name
      Returns:
      the classloader, never null
    • getExplicitClassLoader

      public static ClassLoader getExplicitClassLoader(String servicePath, String serviceName)
      Gets the explicit classloader for a given service path and name.
      Parameters:
      servicePath - the service path
      serviceName - the service name
      Returns:
      the classloader, null if no explicit classloader set
    • addExplicitClassLoader

      public static void addExplicitClassLoader(String servicePath, String serviceName, ClassLoader classLoader)
      Adds an explicit classloader for a given service path and name.
      Loaders are kept in a list for each path and name combination, the most recently added first. Adding a classloader pushes it onto the list (moving it to the front, if already contained), removing it deletes it from the list, wherever it is located. Thus, removing the most recently added loader activates the previously added one, if any. This allows activation and deactivation as is used in OSGi, for example.
      Parameters:
      servicePath - the service path
      serviceName - the service name
      classLoader - the classloader to add
    • removeExplicitClassLoader

      public static void removeExplicitClassLoader(String servicePath, String serviceName, ClassLoader classLoader)
      Removes an explicit classloader for a given service path and name.
      Loaders are kept in a list for each path and name combination, the most recently added first. Adding a classloader pushes it onto the list (moving it to the front, if already contained), removing it deletes it from the list, wherever it is located. Thus, removing the most recently added loader activates the previously added one, if any. This allows activation and deactivation as is used in OSGi, for example.
      Parameters:
      servicePath - the service path
      serviceName - the service name
      classLoader - the classloader to remove
    • applyResourceIndex

      public static void applyResourceIndex(ClassLoader classLoader, String indexName, boolean set)
      Applies a given resource index.
      The index is usually created via the tentackle-maven-plugin's analyze goal with the index option set to something like "META-INF/RESOURCE-INDEX.LIST". It is a plain list of resource paths, one per line, such as META-INF/services/org.tentackle.common.ServiceFactory. Lines starting with # are ignored. For each entry, the given classloader is added as (or removed from) the explicit classloader for the service path and name (see addExplicitClassLoader(String, String, ClassLoader)). If the classloader finds more than one index resource, only the first is used.

      This is a utility method that can be used during the activation within an OSGi bundle, for example.

      Parameters:
      classLoader - the classloader to load the index resource
      indexName - the name of the index resource
      set - true if set loaders, else remove loaders from the services index
      Throws:
      TentackleRuntimeException - if reading the index failed
    • getFixedClassLoader

      public ClassLoader getFixedClassLoader()
      Gets the fixed classloader.
      Returns:
      the classloader, null if default
    • setFixedClassLoader

      public void setFixedClassLoader(ClassLoader classLoader)
      Sets the fixed classloader.
      If set, it is used instead of the context classloader for all finders that are not created for an explicit classloader or module layer. Finders already created are not affected.
      Parameters:
      classLoader - the classloader, null if default
    • getClassLoader

      protected ClassLoader getClassLoader()
      Gets the default classloader.
      The classloader to use is determined as follows:
      1. use the fixed classloader set by setFixedClassLoader(java.lang.ClassLoader), if not null
      2. else try Thread.currentThread().getContextClassLoader()
      3. if the context classloader is null, use the classloader that loaded the service factory
      Returns:
      the loader, never null
    • getClassLoaderImpl

      protected ClassLoader getClassLoaderImpl(String servicePath, String serviceName)
      Gets the classloader for a given service path and name.
      If an explicit classloader is set, the last defined classloader for the given path and name is returned. Otherwise, the default classloader is returned.
      Parameters:
      servicePath - the service path
      serviceName - the service name
      Returns:
      the classloader, never null
    • getExplicitClassLoaderImpl

      protected ClassLoader getExplicitClassLoaderImpl(String servicePath, String serviceName)
      Gets the explicit classloader for a given service path and name.
      Parameters:
      servicePath - the service path
      serviceName - the service name
      Returns:
      the classloader, null if no explicit classloader set
    • addExplicitClassLoaderImpl

      protected void addExplicitClassLoaderImpl(String servicePath, String serviceName, ClassLoader classLoader)
      Adds an explicit classloader for a given service path and name.
      Loaders are kept in a list for each path and name combination, the most recently added first. Adding a classloader pushes it onto the list (moving it to the front, if already contained), removing it deletes it from the list, wherever it is located. Thus, removing the most recently added loader activates the previously added one, if any. This allows activation and deactivation as is used in OSGi, for example.
      Parameters:
      servicePath - the service path
      serviceName - the service name
      classLoader - the classloader to add
    • removeExplicitClassLoaderImpl

      protected void removeExplicitClassLoaderImpl(String servicePath, String serviceName, ClassLoader classLoader)
      Removes an explicit classloader for a given service path and name.
      Loaders are kept in a list for each path and name combination, the most recently added first. Adding a classloader pushes it onto the list (moving it to the front, if already contained), removing it deletes it from the list, wherever it is located. Thus, removing the most recently added loader activates the previously added one, if any. This allows activation and deactivation as is used in OSGi, for example.
      Parameters:
      servicePath - the service path
      serviceName - the service name
      classLoader - the classloader to remove
    • applyResourceIndexImpl

      protected void applyResourceIndexImpl(ClassLoader classLoader, String indexName, boolean set)
      Applies a given resource index.
      The index is usually created via the tentackle-maven-plugin's analyze goal with the index option set to something like "META-INF/RESOURCE-INDEX.LIST". It is a plain list of resource paths, one per line, such as META-INF/services/org.tentackle.common.ServiceFactory. Lines starting with # are ignored. For each entry, the given classloader is added as (or removed from) the explicit classloader for the service path and name (see addExplicitClassLoader(String, String, ClassLoader)). If the classloader finds more than one index resource, only the first is used.

      This is a utility method that can be used during the activation within an OSGi bundle, for example.

      Parameters:
      classLoader - the classloader to load the index resource
      indexName - the name of the index resource
      set - true if set loaders, else remove loaders from the services index
      Throws:
      TentackleRuntimeException - if reading the index failed
    • getFinderMap

      public Map<ServiceFinderKey, ServiceFinder> getFinderMap()
      Gets the finder map.
      Allows applications to modify or add finders programmatically.
      The map is thread-safe.
      Returns:
      the finder map
    • getServiceFinderClass

      protected Class<? extends ServiceFinder> getServiceFinderClass()
      Gets the class of the service finder.
      Returns:
      the class of the service finder, never null
    • getServiceFinderImpl

      protected ServiceFinder getServiceFinderImpl(ClassLoader loader, String servicePath)
      Gets a service finder for a given classloader and service path.
      If the finder does not exist yet, it will be created.
      Parameters:
      loader - the classloader
      servicePath - the service path prefix
      Returns:
      the finder, never null
    • getServiceFinderImpl

      protected ServiceFinder getServiceFinderImpl(ModuleLayer layer, String servicePath)
      Gets a service finder looking up services within a module layer.
      If the finder does not exist yet, it will be created.
      Parameters:
      layer - the module layer
      servicePath - the service path prefix
      Returns:
      the finder, never null
    • getServiceFinderImpl

      protected ServiceFinder getServiceFinderImpl(String servicePath)
      Gets a service finder for a given service path.
      If the finder does not exist yet, it will be created. The classloader to use is determined as follows:
      1. use the fixed classloader set by setFixedClassLoader(java.lang.ClassLoader), if not null
      2. else try Thread.currentThread().getContextClassLoader()
      3. if the context classloader is null, use the classloader that loaded the service factory
      Parameters:
      servicePath - the service path prefix
      Returns:
      the finder, never null
    • getServiceFinderImpl

      protected ServiceFinder getServiceFinderImpl()
      Gets a service finder for the service path "META-INF/services/".
      If the finder does not exist yet, it will be created. The classloader is determined by getClassLoader().
      Returns:
      the finder, never null