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

jodd.petite.PetiteContainer Maven / Gradle / Ivy

Go to download

Jodd Petite is slick and lightweight DI container that uses annotations and supports sufficient most of features offered by other containers.

There is a newer version: 6.0.2
Show newest version
// Copyright (c) 2003-2014, Jodd Team (jodd.org). All Rights Reserved.

package jodd.petite;

import jodd.JoddPetite;
import jodd.bean.BeanUtil;
import jodd.petite.meta.InitMethodInvocationStrategy;
import jodd.petite.scope.Scope;
import jodd.petite.scope.SingletonScope;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import jodd.typeconverter.Convert;
import jodd.log.Logger;
import jodd.log.LoggerFactory;

/**
 * Petite IOC container.
 */
public class PetiteContainer extends PetiteBeans {

	private static final Logger log = LoggerFactory.getLogger(PetiteContainer.class);

	/**
	 * Petite container reference name.
	 * Used when container itself is added as its bean.
	 * @see #addSelf()
	 * @see #addSelf(String)
	 */
	public static final String PETITE_CONTAINER_REF_NAME = "petiteContainer";

	protected final ScopedProxyManager scopedProxyManager;

	/**
	 * Creates new Petite container using {@link PetiteConfig default configuration}.
	 */
	public PetiteContainer() {
		this(new PetiteConfig());
	}

	/**
	 * Creates new Petite container using {@link PetiteContainer provided configuration}.
	 */
	public PetiteContainer(PetiteConfig config) {
		super(config);

		if (JoddPetite.useProxetta) {
			scopedProxyManager = new ScopedProxyManager();
		} else {
			scopedProxyManager = null;
		}

		if (log.isDebugEnabled()) {
			log.debug("Petite container created.");

			if (JoddPetite.useProxetta) {
				log.debug("Petite proxy features enabled.");
			} else {
				log.debug("Petite proxy features not available.");
			}
		}
	}

	// ---------------------------------------------------------------- core

	/**
	 * Creates new bean instance and performs constructor injection.
	 */
	protected Object newBeanInstance(BeanDefinition def, Map acquiredBeans) {
		if (def.ctor == null) {
			def.ctor = petiteResolvers.resolveCtorInjectionPoint(def.type);
		}

		// other ctors
		if (def.name != null) {
			acquiredBeans.put(def.name, Void.TYPE);     // puts a dummy marker for cyclic dependency check
		}

		int paramNo = def.ctor.references.length;
		Object[] args = new Object[paramNo];

		// wiring
		if (def.wiringMode != WiringMode.NONE) {
			for (int i = 0; i < paramNo; i++) {
				args[i] = getBean(def.ctor.references[i], acquiredBeans);
				if (args[i] == null) {
					if ((def.wiringMode == WiringMode.STRICT)) {
						throw new PetiteException(
								"Wiring constructor failed. References '" + Convert.toString(def.ctor.references[i]) +
								"' not found for constructor: " + def.ctor.constructor);
					}
				}
			}
		}

		// create instance
		Object bean;
		try {
			bean = def.ctor.constructor.newInstance(args);
		} catch (Exception ex) {
			throw new PetiteException("Failed to create new bean instance '" + def.type.getName() + "' using constructor: " + def.ctor.constructor, ex);
		}

		if (def.name != null) {
			acquiredBeans.put(def.name, bean);
		}
		return bean;
	}

	/**
	 * Wires beans.
	 * @param bean target bean
	 * @param def bean definition
	 * @param acquiredBeans set of acquired beans
	 */
	protected void wireBean(Object bean, BeanDefinition def, Map acquiredBeans) {
		if (def.wiringMode == WiringMode.NONE) {
			return;
		}
		wireFields(bean, def, acquiredBeans);
		wireMethods(bean, def, acquiredBeans);
	}

	/**
	 * Wires fields.
	 */
	protected void wireFields(Object bean, BeanDefinition def, Map acquiredBeans) {
		if (def.properties == null) {
			def.properties = petiteResolvers.resolvePropertyInjectionPoint(def.type, def.wiringMode == WiringMode.AUTOWIRE);
		}

		boolean mixing = petiteConfig.wireScopedProxy || petiteConfig.detectMixedScopes;

		for (PropertyInjectionPoint pip : def.properties) {
			String[] refNames = pip.references;

			Object value = null;

			if (mixing) {
				BeanDefinition refBeanDefinition = lookupBeanDefinitions(refNames);

				if (refBeanDefinition != null) {
					value = scopedProxyManager.lookupValue(this, def, refBeanDefinition);
				}
			}

			if (value == null) {
				value = getBean(refNames, acquiredBeans);
			}

			if (value == null) {
				if ((def.wiringMode == WiringMode.STRICT)) {
					throw new PetiteException("Wiring failed. Beans references: '" +
							Convert.toString(refNames) + "' not found for property: "+ def.type.getName() + '#' + pip.field.getName());
				}
				continue;
			}
			BeanUtil.setDeclaredProperty(bean, pip.field.getName(), value);
		}

		// sets
		if (def.sets == null) {
			def.sets = petiteResolvers.resolveSetInjectionPoint(def.type, def.wiringMode == WiringMode.AUTOWIRE);
		}
		for (SetInjectionPoint sip : def.sets) {

			String[] beanNames = resolveBeanNamesForType(sip.targetClass);

			Collection beans = sip.createSet(beanNames.length);

			for (String beanName : beanNames) {
				if (beanName.equals(def.name) == false) {
					Object value = getBean(beanName, acquiredBeans);
					beans.add(value);
				}
			}

			BeanUtil.setDeclaredProperty(bean, sip.field.getName(), beans);
		}
	}

	/**
	 * Wires methods.
	 */
	protected void wireMethods(Object bean, BeanDefinition def, Map acquiredBeans) {
		if (def.methods == null) {
			def.methods = petiteResolvers.resolveMethodInjectionPoint(def.type);
		}
		for (MethodInjectionPoint methodRef : def.methods) {
			String[][] refNames = methodRef.references;
			Object[] args = new Object[refNames.length];
			for (int i = 0; i < refNames.length; i++) {
				String[] refName = refNames[i];
				Object value = null;

				boolean mixing = petiteConfig.wireScopedProxy || petiteConfig.detectMixedScopes;

				if (mixing) {
					BeanDefinition refBeanDefinition = lookupBeanDefinitions(refName);

					if (refBeanDefinition != null) {
						value = scopedProxyManager.lookupValue(this, def, refBeanDefinition);
					}
				}

				if (value == null) {
					value = getBean(refName, acquiredBeans);
				}

				args[i] = value;
				if (value == null) {
					if ((def.wiringMode == WiringMode.STRICT)) {
						throw new PetiteException("Wiring failed. Beans references: '" +
								Convert.toString(refName) + "' not found for method: " + def.type.getName() + '#' + methodRef.method.getName());
					}
				}
			}

			try {
				methodRef.method.invoke(bean, args);
			} catch (Exception ex) {
				throw new PetiteException(ex);
			}

		}
	}

	/**
	 * Invokes all init methods, if they exist. Also resolves destroy methods.
	 */
	protected void invokeInitMethods(Object bean, BeanDefinition def, InitMethodInvocationStrategy invocationStrategy) {
		if (def.initMethods == null) {
			def.initMethods = petiteResolvers.resolveInitMethodPoint(bean);
		}
		if (def.destroyMethods == null) {
			def.destroyMethods = petiteResolvers.resolveDestroyMethodPoint(bean);
		}
		for (InitMethodPoint initMethod : def.initMethods) {
			if (invocationStrategy != initMethod.invocationStrategy) {
				continue;
			}
			try {
				initMethod.method.invoke(bean);
			} catch (Exception ex) {
				throw new PetiteException("Invalid init method: " + initMethod, ex);
			}
		}
	}

	/**
	 * Injects all parameters.
	 */
	protected void injectParams(Object bean, BeanDefinition def) {
		if (def.name == null) {
			return;
		}

		if (def.params == null) {
			def.params = resolveBeanParams(def.name, petiteConfig.getResolveReferenceParameters());
		}
		int len = def.name.length() + 1;
		for (String param : def.params) {
			Object value = getParameter(param);
			String destination = param.substring(len);
			try {
				BeanUtil.setDeclaredProperty(bean, destination, value);
			} catch (Exception ex) {
				throw new PetiteException("Unable to set parameter: '" + param + "' to bean: " + def.name, ex);
			}
		}
	}


	// ---------------------------------------------------------------- get beans

	/**
	 * Returns Petite bean instance. Bean name will be resolved from provided type.
	 */
	@SuppressWarnings({"unchecked"})
	public  T getBean(Class type) {
		String name = resolveBeanName(type);
		return (T) getBean(name);
	}

	/**
	 * Returns Petite bean instance.
	 * Petite container will find the bean in corresponding scope and all its dependencies,
	 * either by constructor or property injection. When using constructor injection, cyclic dependencies
	 * can not be prevented, but at least they are detected.
	 *
	 * @see PetiteContainer#createBean(Class)
	 */
	public Object getBean(String name) {
		return getBean(name, new HashMap());
	}

	/**
	 * Returns Petite bean instance named as one of the provided names.
	 */
	protected Object getBean(String[] names, Map acquiredBeans) {
		for (String name : names) {
			if (name == null) {
				continue;
			}
			Object bean = getBean(name, acquiredBeans);
			if (bean != null) {
				return bean;
			}
		}
		return null;
	}

	/**
	 * Returns Petite bean instance.
	 * @see PetiteContainer#createBean(Class)
	 */
	protected Object getBean(String name, Map acquiredBeans) {

		// First check if bean is already acquired within this call.
		// This prevents cyclic dependencies problem. It is expected than single
		// object tree path contains less elements, therefore, this search is faster
		// then the next one.
		Object bean = acquiredBeans.get(name);
		if (bean != null) {
			if (bean == Void.TYPE) {
				throw new PetiteException("Cycle dependencies on constructor injection detected!");
			}
			return bean;
		}

		// Lookup for registered bean definition.
		BeanDefinition def = lookupBeanDefinition(name);
		if (def == null) {

			// try provider
			ProviderDefinition providerDefinition = providers.get(name);

			if (providerDefinition != null) {
				return invokeProvider(providerDefinition);
			}
			return null;
		}

		// Find the bean in its scope
		bean = def.scopeLookup();
		if (bean == null) {
			// Create new bean in the scope
			bean = newBeanInstance(def, acquiredBeans);
			wireBeanInjectParamsAndInvokeInitMethods(def, bean, acquiredBeans);
			def.scopeRegister(bean);
		}
		return bean;
	}

	/**
	 * Wires bean, injects parameters and invokes init methods.
	 */
	protected void wireBeanInjectParamsAndInvokeInitMethods(BeanDefinition def, Object bean, Map acquiredBeans) {
		invokeInitMethods(bean, def, InitMethodInvocationStrategy.POST_CONSTRUCT);
		wireBean(bean, def, acquiredBeans);
		invokeInitMethods(bean, def, InitMethodInvocationStrategy.POST_DEFINE);
		injectParams(bean, def);
		invokeInitMethods(bean, def, InitMethodInvocationStrategy.POST_INITIALIZE);
	}

	// ---------------------------------------------------------------- wire

	/**
	 * Wires provided bean with the container using default wiring mode.
	 * Bean is not registered.
	 */
	public void wire(Object bean) {
		wire(bean, null);
	}

	/**
	 * Wires provided bean with the container and optionally invokes init methods.
	 * Bean is not registered.
	 */
	public void wire(Object bean, WiringMode wiringMode) {
		wiringMode = petiteConfig.resolveWiringMode(wiringMode);
		BeanDefinition def = new BeanDefinition(null, bean.getClass(), null, wiringMode);
		Map acquiredBeans = new HashMap();
		wireBeanInjectParamsAndInvokeInitMethods(def, bean, acquiredBeans);
	}

	// ---------------------------------------------------------------- create

	/**
	 * Creates and wires a bean within the container using default wiring mode and default init methods flag.
	 * Bean is not registered.
	 */
	public  E createBean(Class type) {
		return createBean(type, null);
	}

	/**
	 * Creates and wires a bean within the container and optionally invokes init methods. However, bean is
	 * not registered.
	 */
	@SuppressWarnings({"unchecked"})
	public  E createBean(Class type, WiringMode wiringMode) {
		wiringMode = petiteConfig.resolveWiringMode(wiringMode);
		BeanDefinition def = new BeanDefinition(null, type, null, wiringMode);
		Map acquiredBeans = new HashMap();
		Object bean = newBeanInstance(def, acquiredBeans);
		wireBeanInjectParamsAndInvokeInitMethods(def, bean, acquiredBeans);
		return (E) bean;
	}

	// ---------------------------------------------------------------- providers

	/**
	 * Invokes provider to get a bean.
	 */
	protected Object invokeProvider(ProviderDefinition provider) {
		if (provider.method != null) {

			Object bean;
			if (provider.beanName != null) {
				// instance factory method
				bean = getBean(provider.beanName);
			} else {
				// static factory method
				bean = null;
			}
			try {
				return provider.method.invoke(bean);
			} catch (Exception ex) {
				throw new PetiteException("Invalid provider method: " + provider.method.getName(), ex);
			}
		}

		throw new PetiteException("Invalid provider");
	}



	// ---------------------------------------------------------------- add

	/**
	 * Adds object instance to the container as singleton bean using default
	 * wiring mode and default init method flag.
	 */
	public void addBean(String name, Object bean) {
		addBean(name, bean, null);
	}

	/**
	 * Adds object instance to the container as singleton bean.
	 */
	public void addBean(String name, Object bean, WiringMode wiringMode) {
		wiringMode = petiteConfig.resolveWiringMode(wiringMode);
		registerPetiteBean(bean.getClass(), name, SingletonScope.class, wiringMode, false);
		BeanDefinition def = lookupExistingBeanDefinition(name);
		Map acquiredBeans = new HashMap();
		acquiredBeans.put(name, bean);
		wireBeanInjectParamsAndInvokeInitMethods(def, bean, acquiredBeans);
		def.scopeRegister(bean);
	}

	/**
	 * Adds self instance to the container so internal beans may fetch
	 * container for further usage. No wiring is used and no init methods are invoked.
	 */
	public void addSelf(String name) {
		addBean(name, this, WiringMode.NONE);
	}

	/**
	 * Adds self instance to the container so internal beans may fetch
	 * container for further usage. No wiring is used and no init methods are invoked.
	 */
	public void addSelf() {
		addBean(PETITE_CONTAINER_REF_NAME, this, WiringMode.NONE);
	}

	// ---------------------------------------------------------------- property

	/**
	 * Sets petite bean property.
	 */
	public void setBeanProperty(String name, Object value) {
		Object bean = null;
		int ndx = name.length();

		while (true) {
			ndx = name.lastIndexOf('.', ndx);
			if (ndx == -1) {
				break;
			}

			String beanName = name.substring(0, ndx);
			bean = getBean(beanName);
			if (bean != null) {
				break;
			}
			ndx--;
		}

		if (bean == null) {
			throw new PetiteException("Invalid bean property: " + name);
		}

		try {
			BeanUtil.setDeclaredProperty(bean, name.substring(ndx + 1), value);
		} catch (Exception ex) {
			throw new PetiteException("Invalid bean property: " + name, ex);
		}
	}

	/**
	 * Returns petite bean property value.
	 */
	public Object getBeanProperty(String name) {
		int ndx = name.indexOf('.');
		if (ndx == -1) {
			throw new PetiteException("Only bean name is specified, missing property name: " + name);
		}
		String beanName = name.substring(0, ndx);
		Object bean = getBean(beanName);
		if (bean == null) {
			throw new PetiteException("Bean doesn't exist: " + name);
		}
		try {
			return BeanUtil.getDeclaredProperty(bean, name.substring(ndx + 1));
		} catch (Exception ex) {
			throw new PetiteException("Invalid bean property: " + name, ex);
		}
	}

	// ---------------------------------------------------------------- shutdown

	/**
	 * Shutdowns container.
	 */
	public void shutdown() {
		for (Scope scope : scopes.values()) {
			scope.shutdown();
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy