/**
* Copyright (C) 2013 Kametic <epo.jemba@kametic.com>
*
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3, 29 June 2007;
* or any later version
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/lgpl-3.0.txt
*
* 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 org.nuunframework.kernel.internal;
import static org.reflections.ReflectionUtils.withAnnotation;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.nuunframework.kernel.context.Context;
import org.nuunframework.kernel.context.ContextInternal;
import org.nuunframework.kernel.context.InitContextInternal;
import org.nuunframework.kernel.stereotype.Concern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.matcher.Matchers;
import com.google.inject.util.Providers;
/**
* Bootstrap Plugin needed to initialize an application. Propose
*
* @author ejemba
*/
public class InternalKernelGuiceModule extends AbstractModule
{
private Logger logger = LoggerFactory.getLogger(InternalKernelGuiceModule.class);
private final InitContextInternal currentContext;
private boolean overriding = false;
public InternalKernelGuiceModule(InitContextInternal kernelContext)
{
this.currentContext = kernelContext;
}
public InternalKernelGuiceModule overriding()
{
overriding = true;
return this;
}
@Override
protected final void configure()
{
// All bindings will be needed explicitely.
// this simple line makes the framework bullet-proof !
binder().requireExplicitBindings();
// We ContextInternal as implemetation of Context
bind(Context.class).to(ContextInternal.class);
// Bind Types, Subtypes from classpath
// ===================================
bindFromClasspath();
// Start Plugins
}
@SuppressWarnings({
"unchecked", "rawtypes"
})
private void bindFromClasspath()
{
List<Installable> installableList = new ArrayList<Installable>();
Map<Class<?>, Object> classesWithScopes = this.currentContext.classesWithScopes();
if (! overriding )
{
Collection<Class<?>> classes = this.currentContext.classesToBind();
for (Object o : classes)
{
installableList.add(new Installable(o));
}
for (Object o : currentContext.moduleResults())
{
installableList.add(new Installable(o));
}
}
else
{
logger.info("Installing overriding modules");
for (Object o : currentContext.moduleOverridingResults())
{
installableList.add(new Installable(o));
}
}
Collections.sort(installableList , Collections.reverseOrder());
Provider nullProvider = Providers.of(null);
// We install modules and bind class in the right orders
for (Installable installable : installableList)
{
if (Module.class.isAssignableFrom(installable.inner.getClass()))
{ // install module
logger.info("installing module {}", (installable.inner));
install(Module.class.cast(installable.inner));
}
if (installable.inner instanceof Class)
{ // bind object
Class<?> classpathClass = Class.class.cast(installable.inner);
Object scope = classesWithScopes.get(classpathClass);
if (!(classpathClass.isInterface() && withAnnotation(Nullable.class).apply(classpathClass)))
{
if (scope == null)
{
logger.info("binding {} with no scope.", classpathClass.getName());
bind(classpathClass);
}
else
{
logger.info("binding {} in scope {}.", classpathClass.getName() , scope.toString());
bind(classpathClass).in((Scope) scope);
}
}
else
{
bind(classpathClass).toProvider(nullProvider);
}
}
}
}
class Installable implements Comparable<Installable>
{
Object inner;
Installable (Object inner)
{
this.inner = inner;
}
@Override
public int compareTo(Installable anInstallable)
{
Class<?> toCompare;
Class<?> innerClass;
// to compare inner is a class to bind
if (anInstallable.inner instanceof Class)
{
toCompare = (Class<?>) anInstallable.inner;
}
else if (Module.class.isAssignableFrom(anInstallable.inner.getClass()))
// inner is a module annotated
{
toCompare = anInstallable.inner.getClass();
}
else
{
throw new IllegalStateException("Object to compare is not a class nor a Module " + anInstallable);
}
// inner is a class to bind
if (this.inner instanceof Class)
{
innerClass = (Class<?>) this.inner;
}
else if (Module.class.isAssignableFrom(this.inner.getClass()))
// inner is a module annotated
{
innerClass = this.inner.getClass();
}
else
{
throw new IllegalStateException("Object to compare is not a class nor a Module " + this);
}
return computeOrder(innerClass).compareTo( computeOrder(toCompare) ) ;
}
@Override
public String toString()
{
return inner.toString();
}
}
Long computeOrder (Class<?> moduleCläss) {
Long finalOrder = 0l;
boolean reachAtLeastOnce = false;
for(Annotation annotation : moduleCläss.getAnnotations())
{
if ( Matchers.annotatedWith(Concern.class).matches(annotation.annotationType()) )
{
reachAtLeastOnce = true;
Concern concern = annotation.annotationType().getAnnotation(Concern.class);
switch (concern.priority())
{
case HIGHEST:
finalOrder += (3L << 32) + concern.order();
break;
case HIGHER:
finalOrder += (2L << 32) + concern.order();
break;
case HIGH:
finalOrder += (1L << 32) + concern.order();
break;
case NORMAL:
finalOrder = (long)concern.order();
break;
case LOW:
finalOrder -= (1L << 32) + concern.order();
break;
case LOWER:
finalOrder -= (2L << 32) + concern.order();
break;
case LOWEST:
finalOrder -= (3L << 32) + concern.order();
break;
default:
break;
}
break;
}
}
if (! reachAtLeastOnce) finalOrder = (long) 0;
return finalOrder;
}
public static boolean hasAnnotationDeep(Class<?> memberDeclaringClass, Class<? extends Annotation> klass)
{
if (memberDeclaringClass.equals(klass))
{
return true;
}
for (Annotation anno : memberDeclaringClass.getAnnotations())
{
Class<? extends Annotation> annoClass = anno.annotationType();
if (!annoClass.getPackage().getName().startsWith("java.lang") && hasAnnotationDeep(annoClass, klass))
{
return true;
}
}
return false;
}
// private void configureProperties()
// {
//
// // find all properties classes in the classpath
// Collection<String> propertiesFiles = this.currentContext.propertiesFiles();
//
// // add properties from plugins
// CompositeConfiguration configuration = new CompositeConfiguration();
// for (String propertiesFile : propertiesFiles)
// {
// logger.info("adding {} to module", propertiesFile);
// configuration.addConfiguration(configuration(propertiesFile));
// }
// install(new ConfigurationGuiceModule(configuration));
// }
// protected void bindSubTypesOf(Class<?> cläss)
// {
// this.currentContext.parentTypesClasses.add(cläss);
// }
//
// protected void bindAnnotationClass(Class<? extends Annotation> cläss)
// {
// this.currentContext.annotationTypes.add(cläss);
// }
//
// protected void bindAnnotationName(String className)
// {
// this.currentContext.annotationNames.add(className);
// }
}