/*
* Copyright 2011 cruxframework.org.
*
* 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 org.cruxframework.crux.core.ioc;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cruxframework.crux.core.client.Legacy;
import org.cruxframework.crux.core.client.controller.Controller;
import org.cruxframework.crux.core.client.datasource.annotation.DataSource;
import org.cruxframework.crux.core.client.ioc.Inject;
import org.cruxframework.crux.core.client.ioc.IoCResource;
import org.cruxframework.crux.core.client.ioc.IoCResource.NoClass;
import org.cruxframework.crux.core.client.ioc.IoCResource.NoProvider;
import org.cruxframework.crux.core.client.screen.DeviceAdaptive.Device;
import org.cruxframework.crux.core.rebind.GeneratorProperties;
import org.cruxframework.crux.core.rebind.context.RebindContext;
import org.cruxframework.crux.core.rebind.context.scanner.ResourceNotFoundException;
import org.cruxframework.crux.core.rebind.screen.View;
import org.cruxframework.crux.core.utils.ClassUtils;
import com.google.gwt.core.ext.typeinfo.JClassType;
/**
* @author Thiago da Rosa de Bustamante
*
*/
public class IocContainerManager
{
private static final Log logger = LogFactory.getLog(IocContainerManager.class);
private RebindContext context;
public IocContainerManager(RebindContext context)
{
this.context = context;
initialize();
}
/**
*
* @param view
* @return
*/
public Map<String, IocConfig<?>> getConfigurationsForView(View view, Device device)
{
Map<String, IocConfig<?>> globalConfigurations = IocContainerConfigurations.getConfigurations();
Map<String, IocConfig<?>> viewConfigurations = new HashMap<String, IocConfig<?>>();
viewConfigurations.putAll(globalConfigurations);
if (view != null)
{
bindImplicityInjectcionsForControllers(view, viewConfigurations, device);
bindImplicityInjectcionsForDatasources(view, viewConfigurations, device);
}
return viewConfigurations;
}
/**
*
* @param type
* @param added
* @param path
*/
private void bindImplicityInjectcions(Class<?> type, Set<String> added, Set<String> path, boolean iocRootType, Map<String, IocConfig<?>> configurations)
{
if (isBindable(type, iocRootType))
{
bindImplicityInjectionsForFields(type, added, path, configurations);
bindImplicityInjectionsForMethods(type, added, path, configurations);
if (type.getSuperclass() != null)
{
bindImplicityInjectcions(type.getSuperclass(), added, path, iocRootType, configurations);
}
}
}
/**
* @param viewConfigurations
* @param view
*
*/
private void bindImplicityInjectcionsForControllers(View view, Map<String, IocConfig<?>> viewConfigurations, Device device)
{
Iterator<String> controllers = view.iterateControllers();
while (controllers.hasNext())
{
String controller = controllers.next();
Class<?> controllerClass;
try
{
controllerClass = context.getControllers().getControllerClass(controller, device);
}
catch (ResourceNotFoundException e)
{
throw new IoCException("Can not bind controllers.", e);
}
bindImplicityInjectcions(controllerClass, new HashSet<String>(), new HashSet<String>(), true, viewConfigurations);
}
}
/**
* @param viewConfigurations
* @param view
*
*/
@Deprecated
@Legacy
private void bindImplicityInjectcionsForDatasources(View view, Map<String, IocConfig<?>> viewConfigurations, Device device)
{
Iterator<String> datasources = view.iterateDataSources();
try
{
while (datasources.hasNext())
{
Class<?> datasourceClass = context.getDataSources().getDataSourceClass(datasources.next(), device);
bindImplicityInjectcions(datasourceClass, new HashSet<String>(), new HashSet<String>(), true, viewConfigurations);
}
}
catch (ResourceNotFoundException e)
{
throw new IoCException("Can not bind dataSources.", e);
}
}
/**
*
* @param type
* @param added
* @param path
* @param configurations
*/
private void bindImplicityInjectionsForFields(Class<?> type, Set<String> added, Set<String> path, Map<String, IocConfig<?>> configurations)
{
for (Field field : type.getDeclaredFields())
{
String fieldName = field.getName();
if (!added.contains(fieldName))
{
added.add(fieldName);
Class<?> fieldType = field.getType();
if (isBindable(fieldType, false))
{
Inject inject = field.getAnnotation(Inject.class);
if (inject != null)
{
if (path.contains(fieldType.getCanonicalName()))
{
throw new IoCException("IoC Create Looping Error between classes ["+type.getCanonicalName()+"] and ["+fieldType.getCanonicalName()+"].");
}
Set<String> fieldPath = new HashSet<String>();
fieldPath.addAll(path);
fieldPath.add(fieldType.getCanonicalName());
bindTypeImplicitly(fieldType, configurations);
bindImplicityInjectcions(fieldType, new HashSet<String>(), fieldPath, false, configurations);
}
}
}
}
}
/**
*
* @param type
* @param added
* @param path
* @param configurations
*/
private void bindImplicityInjectionsForMethods(Class<?> type, Set<String> added, Set<String> path, Map<String, IocConfig<?>> configurations)
{
for (Method method : type.getDeclaredMethods())
{
Inject inject = method.getAnnotation(Inject.class);
Class<?>[] parameterTypes = method.getParameterTypes();
if (inject != null && !Modifier.isAbstract(method.getModifiers()) && parameterTypes != null && parameterTypes.length > 0)
{
if (!added.contains(method.toString()))
{
added.add(method.toString());
for (int i=0; i< parameterTypes.length; i++)
{
Class<?> parameterType = parameterTypes[i];
if (isBindable(parameterType, false))
{
if (path.contains(parameterType.getCanonicalName()))
{
throw new IoCException("IoC Create Looping Error between classes ["+type.getCanonicalName()+"] and ["+parameterType.getCanonicalName()+"].");
}
Set<String> methodPath = new HashSet<String>();
methodPath.addAll(path);
methodPath.add(parameterType.getCanonicalName());
bindTypeImplicitly(parameterType, configurations);
bindImplicityInjectcions(parameterType, new HashSet<String>(), methodPath, false, configurations);
}
}
}
}
}
}
/**
*
* @param <T>
* @param clazz
* @param configurations
*/
private <T> void bindTypeImplicitly(Class<T> clazz, Map<String, IocConfig<?>> configurations)
{
String className = clazz.getCanonicalName();
if (!configurations.containsKey(className))
{
IocConfig<T> iocConfig = new IocConfigImpl<T>(clazz);
configurations.put(className, iocConfig);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void configureAnnotatedClasses() throws ClassNotFoundException
{
Map<String, IocConfig<?>> globalConfigurations = IocContainerConfigurations.getConfigurations();
JClassType[] configurations = context.getClassScanner().searchClassesByAnnotation(IoCResource.class);
if (configurations != null)
{
for (JClassType resourceClassType : configurations)
{
String resourceClassName = resourceClassType.getQualifiedSourceName();
if (!globalConfigurations.containsKey(resourceClassName))
{
IoCResource ioCResource = resourceClassType.getAnnotation(IoCResource.class);
Class<?> resourceClass = Class.forName(resourceClassName);
bindTypeImplicitly(resourceClass, globalConfigurations);
IocConfig<?> iocConfig = globalConfigurations.get(resourceClassName);
if (!ioCResource.bindClass().equals(NoClass.class))
{
iocConfig.toClass((Class) ioCResource.bindClass());
}
if (!ioCResource.provider().equals(NoProvider.class))
{
iocConfig.toProvider((Class)ioCResource.provider());
}
iocConfig.runtimeAccessible(ioCResource.runtimeAccessible());
iocConfig.scope(ioCResource.scope());
}
}
}
}
private void initialize()
{
try
{
List<String> iocConfigClasses = GeneratorProperties.readConfigurationPropertyValues(context, GeneratorProperties.IOC_CONFIG_CLASS);
for (String configurationClassName : iocConfigClasses)
{
Class<?> configurationClass = Class.forName(configurationClassName);
if (!Modifier.isAbstract(configurationClass.getModifiers()) && IocContainerConfigurations.class.isAssignableFrom(configurationClass))
{
IocContainerConfigurations configuration = (IocContainerConfigurations)configurationClass.newInstance();
if (configuration.isEnabled())
{
if (logger.isInfoEnabled())
{
logger.info("Configuring new ioc module ["+configurationClassName+"]...");
}
configuration.configure();
}
}
}
configureAnnotatedClasses();
}
catch (Exception e)
{
logger.error("Error initializing ioc container.", e);
}
}
/**
*
* @param type
* @return
*/
private static boolean isBindable(Class<?> type, boolean iocRootType)
{
boolean bindable = !ClassUtils.isSimpleType(type);
if (bindable && !iocRootType)
{
if (type.getAnnotation(Controller.class) != null || type.getAnnotation(DataSource.class) != null)
{
bindable = false;
}
}
return bindable;
}
}