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

org.rapidoid.wire.Wire Maven / Gradle / Ivy

The newest version!
package org.rapidoid.wire;

/*
 * #%L
 * rapidoid-wire
 * %%
 * Copyright (C) 2014 - 2015 Nikolche Mihajlovski and contributors
 * %%
 * 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.
 * #L%
 */

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Autocreate;
import org.rapidoid.annotation.Init;
import org.rapidoid.annotation.Inject;
import org.rapidoid.annotation.Local;
import org.rapidoid.annotation.Session;
import org.rapidoid.annotation.Since;
import org.rapidoid.beany.Beany;
import org.rapidoid.beany.Builder;
import org.rapidoid.cls.Cls;
import org.rapidoid.cls.Proxies;
import org.rapidoid.config.Conf;
import org.rapidoid.lambda.F3;
import org.rapidoid.lambda.Lmbd;
import org.rapidoid.lambda.Mapper;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;

@Authors("Nikolche Mihajlovski")
@Since("4.0.0")
public class Wire {

	private static final Map, Object> SINGLETONS = U.map();
	private static final Set> MANAGED_CLASSES = U.set();
	private static final Set MANAGED_INSTANCES = U.set();
	private static final Map IOC_INSTANCES = U.map();

	private static final Map, List> INJECTABLE_FIELDS = U
			.autoExpandingMap(new Mapper, List>() {
				@Override
				public List map(Class clazz) throws Exception {
					List fields = Cls.getFieldsAnnotated(clazz, Inject.class);
					Log.debug("Retrieved @Inject fields", "class", clazz, "fields", fields);
					return fields;
				}
			});

	private static final Map, List> SESSION_FIELDS = U
			.autoExpandingMap(new Mapper, List>() {
				@Override
				public List map(Class clazz) throws Exception {
					List fields = Cls.getFieldsAnnotated(clazz, Session.class);
					Log.debug("Retrieved @Session fields", "class", clazz, "fields", fields);
					return fields;
				}
			});

	private static final Map, List> LOCAL_FIELDS = U
			.autoExpandingMap(new Mapper, List>() {
				@Override
				public List map(Class clazz) throws Exception {
					List fields = Cls.getFieldsAnnotated(clazz, Local.class);
					Log.debug("Retrieved @Local fields", "class", clazz, "fields", fields);
					return fields;
				}
			});

	private static final Map, Set> INJECTION_PROVIDERS = U.map();
	private static final Map, List>> INTERCEPTORS = U.map();

	public static synchronized void reset() {
		Log.info("Reseting IoC state");

		Conf.args();
		Beany.reset();

		SINGLETONS.clear();
		MANAGED_CLASSES.clear();
		MANAGED_INSTANCES.clear();
		IOC_INSTANCES.clear();
		INJECTABLE_FIELDS.clear();
		SESSION_FIELDS.clear();
		LOCAL_FIELDS.clear();
		INJECTION_PROVIDERS.clear();
		INTERCEPTORS.clear();
	}

	public static  Map autoExpandingInjectingMap(final Class clazz) {
		return U.autoExpandingMap(new Mapper() {
			@Override
			public V map(K src) throws Exception {
				return inject(Cls.newInstance(clazz));
			}
		});
	}

	public static synchronized void manage(Object... classesOrInstances) {
		List> autocreate = new ArrayList>();

		for (Object classOrInstance : classesOrInstances) {

			boolean isClass = isClass(classOrInstance);
			Class clazz = isClass ? (Class) classOrInstance : classOrInstance.getClass();

			for (Class interfacee : Cls.getImplementedInterfaces(clazz)) {
				addInjectionProvider(interfacee, classOrInstance);
			}

			if (isClass) {
				Log.debug("configuring managed class", "class", classOrInstance);
				MANAGED_CLASSES.add(clazz);

				if (!clazz.isInterface() && !clazz.isEnum() && !clazz.isAnnotation()) {
					// if the class is annotated, auto-create an instance
					if (clazz.getAnnotation(Autocreate.class) != null) {
						autocreate.add(clazz);
					}
				}
			} else {
				Log.debug("configuring managed instance", "instance", classOrInstance);
				addInjectionProvider(clazz, classOrInstance);
				MANAGED_INSTANCES.add(classOrInstance);
			}
		}

		for (Class clazz : autocreate) {
			singleton(clazz);
		}
	}

	private static void addInjectionProvider(Class type, Object provider) {
		Set providers = INJECTION_PROVIDERS.get(type);

		if (providers == null) {
			providers = U.set();
			INJECTION_PROVIDERS.put(type, providers);
		}

		providers.add(provider);
	}

	public static synchronized  T singleton(Class type) {
		Log.debug("Inject", "type", type);
		return provideIoCInstanceOf(null, type, null, null, false);
	}

	public static synchronized  T autowire(T target) {
		Log.debug("Autowire", "target", target);
		autowire(target, null, null, null);
		return target;
	}

	public static synchronized  T autowire(T target, Mapper session, Mapper bindings) {
		Log.debug("Autowire", "target", target);
		autowire(target, null, session, bindings);
		return target;
	}

	public static synchronized  T inject(T target) {
		Log.debug("Inject", "target", target);
		return ioc(target, null);
	}

	public static synchronized  T inject(T target, Map properties) {
		Log.debug("Inject", "target", target, "properties", properties);
		return ioc(target, properties);
	}

	private static  T provideSessionValue(Object target, Class type, String name, Mapper session) {
		U.notNull(session, "session");
		Object value = Lmbd.eval(session, name);
		return value != null ? Cls.convert(value, type) : null;
	}

	private static  T provideBindValue(Object target, Class type, String name, Mapper bindings) {
		U.notNull(bindings, "bindings");
		Object value = Lmbd.eval(bindings, name);
		return value != null ? Cls.convert(value, type) : null;
	}

	private static  T provideIoCInstanceOf(Object target, Class type, String name,
			Map properties, boolean optional) {
		T instance = null;

		if (name != null) {
			instance = provideInstanceByName(target, type, name, properties);
		}

		if (instance == null) {
			instance = provideIoCInstanceByType(type, properties);
		}

		if (instance == null && canInjectNew(type)) {
			instance = provideNewIoCInstanceOf(type, properties);
		}

		if (!optional) {
			if (instance == null) {
				if (name != null) {
					throw U.rte("Didn't find a value for type '%s' and name '%s'!", type, name);
				} else {
					throw U.rte("Didn't find a value for type '%s'!", type);
				}
			}
		}

		return instance != null ? ioc(instance, properties) : null;
	}

	private static boolean canInjectNew(Class type) {
		return !type.isAnnotation() && !type.isEnum() && !type.isInterface() && !type.isPrimitive()
				&& !type.equals(String.class) && !type.equals(Object.class) && !type.equals(Boolean.class)
				&& !Number.class.isAssignableFrom(type);
	}

	@SuppressWarnings("unchecked")
	private static  T provideNewIoCInstanceOf(Class type, Map properties) {
		// instantiation if it's real class
		if (!type.isInterface() && !type.isEnum() && !type.isAnnotation()) {
			T instance = (T) SINGLETONS.get(type);

			if (instance == null) {
				instance = ioc(Cls.newInstance(type, properties), properties);
			}

			return instance;
		} else {
			return null;
		}
	}

	private static  T provideIoCInstanceByType(Class type, Map properties) {
		Set providers = INJECTION_PROVIDERS.get(type);

		if (providers != null && !providers.isEmpty()) {

			Object provider = null;

			for (Object pr : providers) {
				if (provider == null) {
					provider = pr;
				} else {
					if (isClass(provider) && !isClass(pr)) {
						provider = pr;
					} else if (isClass(provider) || !isClass(pr)) {
						throw U.rte("Found more than 1 injection candidates for type '%s': %s", type, providers);
					}
				}
			}

			if (provider != null) {
				return provideFrom(provider, properties);
			}
		}

		return null;
	}

	@SuppressWarnings("unchecked")
	private static  T provideFrom(Object classOrInstance, Map properties) {
		T instance;
		if (isClass(classOrInstance)) {
			instance = provideNewIoCInstanceOf((Class) classOrInstance, properties);
		} else {
			instance = (T) classOrInstance;
		}
		return instance;
	}

	private static boolean isClass(Object obj) {
		return obj instanceof Class;
	}

	private static  T provideInstanceByName(Object target, Class type, String name, Map properties) {
		T instance = getInjectableByName(type, name, properties, false);

		if (target != null) {
			instance = getInjectableByName(type, propVarName(target, name), properties, true);
		}

		if (instance == null) {
			instance = getInjectableByName(type, name, properties, true);
		}

		return (T) instance;
	}

	@SuppressWarnings("unchecked")
	private static  T getInjectableByName(Class type, String name, Map properties,
			boolean useConfig) {
		Object instance = properties != null ? properties.get(name) : null;

		if (instance == null && useConfig) {
			if (type.equals(Boolean.class) || type.equals(boolean.class)) {
				instance = Conf.is(name);
			} else {
				String opt = Conf.option(name, (String) null);
				if (opt != null) {
					instance = Cls.convert(opt, type);
				}
			}
		}

		return (T) instance;
	}

	private static void autowire(Object target, Map properties, Mapper session,
			Mapper locals) {

		Log.debug("Autowiring", "target", target, "session", session, "bindings", locals);

		for (Field field : INJECTABLE_FIELDS.get(target.getClass())) {

			boolean optional = isInjectOptional(field);
			Object value = provideIoCInstanceOf(target, field.getType(), field.getName(), properties, optional);

			Log.debug("Injecting field value", "target", target, "field", field.getName(), "value", value);

			if (!optional || value != null) {
				Cls.setFieldValue(target, field.getName(), value);
			}
		}

		for (Field field : SESSION_FIELDS.get(target.getClass())) {

			Object value = provideSessionValue(target, field.getType(), field.getName(), session);

			if (value != null) {
				Log.debug("Injecting session field value", "target", target, "field", field.getName(), "value", value);
				Cls.setFieldValue(target, field.getName(), value);
			}
		}

		for (Field field : LOCAL_FIELDS.get(target.getClass())) {

			Object value = provideBindValue(target, field.getType(), field.getName(), locals);

			if (value != null) {
				Log.debug("Injecting bind field value", "target", target, "field", field.getName(), "value", value);
				Cls.setFieldValue(target, field.getName(), value);
			}
		}
	}

	private static boolean isInjectOptional(Field field) {
		Inject inject = field.getAnnotation(Inject.class);
		return inject != null && inject.optional();
	}

	private static  void invokePostConstruct(T target) {
		List methods = Cls.getMethodsAnnotated(target.getClass(), Init.class);

		for (Method method : methods) {
			Cls.invoke(method, target);
		}
	}

	private static  T ioc(T target, Map properties) {
		if (!isIocProcessed(target)) {
			IOC_INSTANCES.put(target, null);

			manage(target);

			autowire(target, properties, null, null);

			invokePostConstruct(target);

			T proxy = proxyWrap(target);

			IOC_INSTANCES.put(target, proxy);

			manage(proxy);

			target = proxy;
		}

		return target;
	}

	private static boolean isIocProcessed(Object target) {
		for (Entry e : IOC_INSTANCES.entrySet()) {
			if (e.getKey() == target || e.getValue() == target) {
				return true;
			}
		}

		return false;
	}

	private static  T proxyWrap(T instance) {
		Set> done = U.set();

		for (Class interf : Cls.getImplementedInterfaces(instance.getClass())) {
			final List> interceptors = INTERCEPTORS.get(interf);

			if (interceptors != null) {
				for (final F3 interceptor : interceptors) {
					if (interceptor != null && !done.contains(interceptor)) {
						Log.debug("Creating proxy", "target", instance, "interface", interf, "interceptor", interceptor);

						final T target = instance;
						InvocationHandler handler = new InvocationHandler() {
							@Override
							public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
								return interceptor.execute(target, method, args);
							}
						};

						instance = Proxies.implement(instance, handler, interf);

						done.add(interceptor);
					}
				}
			}
		}

		return instance;
	}

	public static > B builder(final Class builderInterface, final Class builtInterface,
			final Class implClass) {

		final Map properties = U.map();

		InvocationHandler handler = new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				if (method.getDeclaringClass().equals(Builder.class)) {
					T instance = inject(Cls.newInstance(implClass, properties), properties);
					Beany.update(instance, properties, false, true);
					return instance;
				} else {
					U.must(args.length == 1, "expected 1 argument!");
					properties.put(method.getName(), args[0]);
					return proxy;
				}
			}
		};

		B builder = Proxies.implement(handler, builderInterface);
		return builder;
	}

	public static synchronized List getSessionFields(Object target) {
		return SESSION_FIELDS.get(target.getClass());
	}

	public static synchronized List getLocalFields(Object target) {
		return LOCAL_FIELDS.get(target.getClass());
	}

	public static String propVarName(Object target, String name) {
		return name;

		// TODO consider complex names (e.g. Person.name in future
		// if (Cls.isBean(target)) {
		// return target.getClass().getSimpleName() + "." + name;
		// } else {
		// return name;
		// }
	}

}