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

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