kikaha.core.cdi.DefaultServiceProvider Maven / Gradle / Ivy
package kikaha.core.cdi;
import java.util.*;
import java.util.concurrent.locks.LockSupport;
import java.util.function.*;
import kikaha.core.cdi.helpers.*;
import kikaha.core.cdi.helpers.filter.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SuppressWarnings( { "rawtypes", "unchecked" } )
public class DefaultServiceProvider implements ServiceProvider {
final ImplementedClassesContext implementedClasses = new ImplementedClassesContext();
final SingletonContext singletonContext = new SingletonContext();
final DependencyMap dependencies;
final ProducerFactoryMap producers;
public DefaultServiceProvider() {
dependencies = new DependencyMap( createDefaultProvidedData() );
singletonContext.setQualifierExtractor( createQualifierExtractor() );
producers = loadAllProducers();
}
private QualifierExtractor createQualifierExtractor() {
final Iterable extractors = loadAll(FieldQualifierExtractor.class);
return new QualifierExtractor( extractors );
}
protected Map, Iterable>> createDefaultProvidedData() {
final Map, Iterable>> injectable = new HashMap<>();
injectable.put( ServiceProvider.class, new SingleObjectIterable<>( this ) );
return injectable;
}
protected ProducerFactoryMap loadAllProducers() {
final Iterable> loadClassesImplementing = loadClassesImplementing( ProducerFactory.class );
return ProducerFactoryMap.from( loadClassesImplementing );
}
public Iterable> loadClassesImplementing( Class targetClass ) {
return implementedClasses.loadClassesImplementing( targetClass );
}
@Override
public T load(final Class serviceClazz, final Condition condition, final ProviderContext context )
throws ServiceProviderException {
while ( true )
try { return fromInjector( i -> i.load( serviceClazz, condition, context ) ); }
catch ( final DependencyMap.TemporarilyLockedException cause ) { LockSupport.parkNanos( 2l ); }
}
@Override
public Iterable loadAll( final Class serviceClazz ) {
while ( true )
try { return fromInjector( i -> i.loadAll( serviceClazz ) ); }
catch ( final DependencyMap.TemporarilyLockedException cause ) { LockSupport.parkNanos( 2l ); }
}
@Override
public void providerFor( final Class serviceClazz, final ProducerFactory provider ) {
producers.memorizeProviderForClazz( provider, serviceClazz );
}
@Override
public void providerFor( final Class serviceClazz, final T object ) {
providerFor( serviceClazz, new SingleObjectIterable<>( object ) );
}
protected void providerFor( final Class serviceClazz, final Iterable iterable ) {
synchronized ( dependencies ) {
dependencies.put( serviceClazz, iterable );
dependencies.unlock( serviceClazz );
}
}
@Override
public void provideOn( final Iterable iterable ) {
withInjector( i->i.loadDependenciesAndInjectInto( iterable ) );
}
@Override
public void provideOn( final Object object ) {
withInjector( i->i.loadDependenciesAndInjectInto( object ) );
}
private void withInjector( Consumer callback ) {
final DependencyInjector injector = new DependencyInjector();
callback.accept( injector );
injector.flush();
}
public ProducerFactory getProducerFor(final Class serviceClazz ) {
return fromInjector( i -> i.getProducerFor( serviceClazz ) );
}
private T fromInjector( Function callback ) {
final DependencyInjector injector = new DependencyInjector();
final T t = callback.apply( injector );
injector.flush();
return t;
}
/**
* A dependency injection context. This class was designed to allow
* reentrant injection to deal with recursive dependencies.
*/
final public class DependencyInjector {
final Queue classesToBeConstructed = new ArrayDeque<>();
final Queue fieldToTryToInjectAgainLater = new ArrayDeque<>();
public T load( final Class serviceClazz, Condition condition, ProviderContext providerContext ) {
final T produced = produceFromFactory( serviceClazz, providerContext );
if ( produced != null )
return produced;
return Filter.first( loadAll( serviceClazz, condition ), condition );
}
private T produceFromFactory( final Class serviceClazz, final ProviderContext context )
{
final ProducerFactory provider = getProducerFor( serviceClazz );
if ( provider != null )
return provider.provide( context );
return null;
}
public ProducerFactory getProducerFor(final Class serviceClazz ) {
if ( producers == null )
return null;
return (ProducerFactory)producers.get( serviceClazz, this );
}
public Iterable loadAll( final Class serviceClazz, Condition condition ) {
return Filter.filter( loadAll( serviceClazz ), condition );
}
public Iterable loadAll( final Class serviceClazz ) {
Iterable> instances = dependencies.get( serviceClazz );
if ( instances == null )
synchronized ( dependencies ) {
instances = dependencies.get( serviceClazz );
if ( instances == null )
instances = loadServicesFor( serviceClazz );
}
return (Iterable)instances;
}
private Iterable loadServicesFor( final Class serviceClazz ) {
final List> iterableInterfaces = implementedClasses.loadClassesImplementing( serviceClazz );
Iterable instances = null;
if ( !iterableInterfaces.isEmpty() ) {
instances = singletonContext.instantiate( iterableInterfaces );
dependencies.put( serviceClazz, instances );
} else {
final T instance = singletonContext.instantiate( serviceClazz );
instances = instance == null ? EmptyIterable.instance() : new SingleObjectIterable<>( instance );
}
loadDependenciesAndInjectInto( instances );
dependencies.unlock( serviceClazz );
return instances;
}
public void loadDependenciesAndInjectInto( Iterable> objs ) {
for ( final Object obj : objs )
loadDependenciesAndInjectInto( obj );
}
public void loadDependenciesAndInjectInto( Object obj ) {
final ProvidableClass> providableClass = singletonContext.retrieveProvidableClass( obj.getClass() );
tryInjectFields( obj, providableClass );
tryPostConstructClass( obj, providableClass );
}
private void tryInjectFields( Object obj, final ProvidableClass> providableClass ) {
for ( final ProvidableField field : providableClass.fields() )
try {
field.provide( obj, this );
} catch ( final Throwable e ) {
log.debug( e.getMessage(), e );
fieldToTryToInjectAgainLater.add( new InjectableField( field, obj ) );
}
}
private void tryPostConstructClass( Object obj, final ProvidableClass> providableClass ) {
final Injectable injectable = new Injectable( providableClass, obj );
try {
injectable.postConstruct();
} catch ( final Throwable cause ) {
classesToBeConstructed.add( injectable );
}
}
public void flush() {
int availableRetries = 5;
while ( availableRetries > 0 && isNotClean() ) {
tryPostConstructClasses();
tryInjectFailedFields();
availableRetries--;
}
injectFailedFields();
postConstructClasses();
}
private boolean isNotClean() {
return !fieldToTryToInjectAgainLater.isEmpty() || !classesToBeConstructed.isEmpty();
}
private void tryPostConstructClasses() {
final List failed = new ArrayList<>();
while ( !classesToBeConstructed.isEmpty() ) {
final Injectable injectable = classesToBeConstructed.poll();
try { injectable.postConstruct(); }
catch ( final Throwable cause ) { failed.add( injectable ); }
}
classesToBeConstructed.addAll( failed );
}
private void postConstructClasses() {
while ( !classesToBeConstructed.isEmpty() ) {
final Injectable injectable = classesToBeConstructed.poll();
try { injectable.postConstruct(); }
catch ( final Throwable cause ) { cause.printStackTrace(); }
}
}
private void tryInjectFailedFields() {
final List failed = new ArrayList<>();
while ( !fieldToTryToInjectAgainLater.isEmpty() ) {
final InjectableField field = fieldToTryToInjectAgainLater.poll();
try { field.structure.provide( field.instance, this ); }
catch ( final Throwable e ) { failed.add( field ); }
}
fieldToTryToInjectAgainLater.addAll( failed );
}
private void injectFailedFields() {
while ( !fieldToTryToInjectAgainLater.isEmpty() ) {
final InjectableField field = fieldToTryToInjectAgainLater.poll();
try { field.structure.provide( field.instance, this ); }
catch ( final Throwable e ) { handleFieldInjectionError( field, e ); }
}
}
private void handleFieldInjectionError( final InjectableField field, final Throwable e ) {
System.err.println( "Failed to provide data on " + field.structure + ":" + e.getMessage() );
e.printStackTrace();
}
}
@RequiredArgsConstructor
class Injectable {
final ProvidableClass> structure;
final Object instance;
public void postConstruct() {
structure.postConstructor().accept( instance );
}
}
}
@RequiredArgsConstructor
class InjectableField {
final ProvidableField structure;
final Object instance;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy