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

net.officefloor.spring.SpringSupplierSource Maven / Gradle / Ivy

There is a newer version: 3.40.0
Show newest version
/*
 * OfficeFloor - http://www.officefloor.net
 * Copyright (C) 2005-2018 Daniel Sagenschneider
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package net.officefloor.spring;

import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.springframework.beans.FatalBeanException;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

import net.officefloor.compile.impl.structure.SupplierThreadLocalNodeImpl;
import net.officefloor.compile.spi.office.OfficeArchitect;
import net.officefloor.compile.spi.office.OfficeSupplier;
import net.officefloor.compile.spi.supplier.source.SupplierSource;
import net.officefloor.compile.spi.supplier.source.SupplierSourceContext;
import net.officefloor.compile.spi.supplier.source.SupplierThreadLocal;
import net.officefloor.compile.spi.supplier.source.impl.AbstractSupplierSource;
import net.officefloor.frame.api.manage.Office;
import net.officefloor.frame.api.manage.OfficeFloor;
import net.officefloor.frame.api.managedobject.ManagedObject;
import net.officefloor.frame.api.thread.ThreadSynchroniserFactory;
import net.officefloor.plugin.section.clazz.Property;
import net.officefloor.spring.extension.SpringBeanDecoratorContext;
import net.officefloor.spring.extension.SpringSupplierExtension;
import net.officefloor.spring.extension.SpringSupplierExtensionContext;
import net.officefloor.spring.extension.SpringSupplierExtensionServiceFactory;

/**
 * Spring {@link SupplierSource}.
 * 
 * @author Daniel Sagenschneider
 */
public class SpringSupplierSource extends AbstractSupplierSource {

	/**
	 * 

* Obtains the bean from {@link OfficeFloor}. *

* This should be used as follows: * *

	 * @Configuration
	 * public class SomeConfigurationOnScanPath {
	 * 
	 * 	@Bean
	 * 	public DependencyType officeFloorDependency() {
	 * 		return SpringSupplierSource.getManagedObject("qualifier", DependencyType.class);
	 * 	}
	 * }
	 * 
* * @param Object type. * @param qualifier Qualifier. May be null. * @param objectType Type of object required. * @return Object sourced from an {@link OfficeFloor} {@link ManagedObject}. */ @SuppressWarnings("unchecked") public static O getManagedObject(String qualifier, Class objectType) { // Obtain the dependency factory SpringDependencyFactory factory = springDependencyFactory.get(); if (factory == null) { throw new IllegalStateException("Attempting to create " + OfficeFloor.class.getSimpleName() + " dependency for Spring outside setup. Ensure dependency in Spring is configured to be used as singleton."); } // Create and return the dependency try { return (O) factory.createDependency(qualifier, objectType); } catch (Throwable ex) { // Propagate as fatal error throw new FatalBeanException("Failed to obtain " + OfficeFloor.class.getSimpleName() + " dependency " + SupplierThreadLocalNodeImpl.getSupplierThreadLocalName(qualifier, objectType.getName())); } } /** * Access to {@link SpringDependencyFactory} to create the {@link OfficeFloor} * dependencies for Spring. */ private static final ThreadLocal springDependencyFactory = new ThreadLocal<>(); /** * Provides the loading of Spring. * * @param Spring loaded object to return. * @param Possible failure in loading. */ public static interface SpringLoader { /** * Loads the Spring item. * * @return Spring item. * @throws E Possible failure in loading. */ S load() throws E; } /** * Factory for the creation of the Spring dependencies. */ public static interface SpringDependencyFactory { /** * Creates the dependency. * * @param qualifier Qualifier. May be null. * @param objectType Object type required. * @return Dependency object. * @throws Exception Possible {@link Exception} in creating the dependency. */ Object createDependency(String qualifier, Class objectType) throws Exception; } /** * Runs the {@link Runnable} in context for the {@link SpringDependencyFactory} * to create additional beans for Spring. * * @param Loaded context. * @param Possible {@link Throwable} from loading. * @param loader {@link SpringLoader}. * @param factory {@link SpringDependencyFactory} to create the additional * beans. * @return Loaded context. * @throws E If fails to load. */ public static S runInContext(SpringLoader loader, SpringDependencyFactory factory) throws E { // Provide factory for creating OfficeFloor dependencies springDependencyFactory.set(factory); try { // Undertake the load return loader.load(); } finally { // Ensure clear factory (no longer loading) springDependencyFactory.set(null); } } /** * Convenience method for configuring Spring programmatically into an * {@link Office}. * * @param architect {@link OfficeArchitect}. * @param configurationClass Spring Boot configuration * {@link Class}. * @param additionalPropertyNameValuePairs Additional {@link Property} * name/value pairs. * @return {@link OfficeSupplier} for the {@link SpringSupplierSource}. */ public static OfficeSupplier configure(OfficeArchitect architect, Class configurationClass, String... additionalPropertyNameValuePairs) { OfficeSupplier supplier = architect.addSupplier("Spring", SpringSupplierSource.class.getName()); supplier.addProperty(SpringSupplierSource.CONFIGURATION_CLASS_NAME, configurationClass.getName()); for (int i = 0; i < additionalPropertyNameValuePairs.length; i += 2) { String name = additionalPropertyNameValuePairs[i]; String value = additionalPropertyNameValuePairs[i + 1]; supplier.addProperty(name, value); } return supplier; } /** * Name of {@link Property} for the Spring Boot configuration {@link Class}. */ public static final String CONFIGURATION_CLASS_NAME = "configuration.class"; /** * {@link ConfigurableApplicationContext}. */ private ConfigurableApplicationContext springContext; /** * Decorates the Spring bean. * * @param beanName Name of Spring Bean. * @param beanType Type of the Spring Bean. * @param extensions {@link SpringSupplierExtension} instances. * @param springDependencies {@link SpringDependency} instances by their name. * @return {@link SpringDependency} instances for the Spring Bean. * @throws Exception If fails to decorate the Spring Bean. */ private SpringDependency[] decorateBean(String beanName, Class beanType, List extensions, Map springDependencies) throws Exception { // Decorate the beans via the extensions SpringBeanDecoratorContextImpl decoratorContext = new SpringBeanDecoratorContextImpl(beanName, beanType, springDependencies); for (SpringSupplierExtension extension : extensions) { extension.decorateSpringBean(decoratorContext); } // Return the spring dependencies return decoratorContext.dependencies.values().stream().toArray(SpringDependency[]::new); } /* * ================== SupplierSource ============================ */ @Override protected void loadSpecification(SpecificationContext context) { context.addProperty(CONFIGURATION_CLASS_NAME, "Configuration Class"); } @Override public void supply(SupplierSourceContext context) throws Exception { // Load the extensions List extensions = new LinkedList<>(); for (SpringSupplierExtension extension : context .loadOptionalServices(SpringSupplierExtensionServiceFactory.class)) { extensions.add(extension); } // Create the dependency factory Map existingSpringDependencies = new HashMap<>(); Map springDependencies = new HashMap<>(); SpringDependencyFactory dependencyFactory = (qualifier, objectType) -> { // Obtain the dependency name String dependencyName = SupplierThreadLocalNodeImpl.getSupplierThreadLocalName(qualifier, objectType.getName()); // Determine if already created Object dependency = existingSpringDependencies.get(dependencyName); if (dependency != null) { return dependency; } // Obtain the supplier thread local SupplierThreadLocal threadLocal = context.addSupplierThreadLocal(qualifier, objectType); // Register the Spring dependency springDependencies.put(dependencyName, new SpringDependency(qualifier, objectType)); // Create the dependency to supplier thread local dependency = Proxy.newProxyInstance(context.getClassLoader(), new Class[] { objectType }, (proxy, method, args) -> { // Ensure obtain the object Object object = threadLocal.get(); if (object == null) { throw new IllegalStateException(OfficeFloor.class.getSimpleName() + " supplied bean for " + dependencyName + " is not available"); } // Invoke the method on the object return object.getClass().getMethod(method.getName(), method.getParameterTypes()).invoke(object, args); }); // Register the dependency and return it existingSpringDependencies.put(dependencyName, dependency); return dependency; }; // Create the extension context SpringSupplierExtensionContext extensionContext = new SpringSupplierExtensionContext() { @Override @SuppressWarnings("unchecked") public B getManagedObject(String qualifier, Class objectType) throws Exception { return (B) dependencyFactory.createDependency(qualifier, objectType); } @Override public void addThreadSynchroniser(ThreadSynchroniserFactory threadSynchroniserFactory) { context.addThreadSynchroniser(threadSynchroniserFactory); } }; // Load Spring with access to hook in OfficeFloor managed objects this.springContext = runInContext(() -> { // Run before spring load for (SpringSupplierExtension extension : extensions) { extension.beforeSpringLoad(extensionContext); } // Load the configurable application context String configurationClassName = context.getProperty(CONFIGURATION_CLASS_NAME); Class configurationClass = context.loadClass(configurationClassName); ConfigurableApplicationContext applicationContext = SpringApplication.run(configurationClass); // Run after spring load for (SpringSupplierExtension extension : extensions) { extension.afterSpringLoad(extensionContext); } // Return the application context return applicationContext; }, dependencyFactory); // Load listing of all the beans (mapped by their type) Map, List> beanNamesByType = new HashMap<>(); NEXT_BEAN: for (String name : this.springContext.getBeanDefinitionNames()) { // Load the bean type Class beanType = this.springContext.getBean(name).getClass(); // Filter out Spring beans being loaded from OfficeFloor for (SpringDependency dependency : springDependencies.values()) { if (dependency.getObjectType().isAssignableFrom(beanType)) { continue NEXT_BEAN; // OfficeFloor providing } } // Add the bean List beanNames = beanNamesByType.get(beanType); if (beanNames == null) { beanNames = new LinkedList<>(); beanNamesByType.put(beanType, beanNames); } beanNames.add(name); } // Load the supplied managed object sources for (Class beanType : beanNamesByType.keySet()) { List beanNames = beanNamesByType.get(beanType); SpringDependency[] springDependenciesList; switch (beanNames.size()) { case 1: // Only the one type (so no qualifier) String singleBeanName = beanNames.get(0); // Decorate the bean springDependenciesList = this.decorateBean(singleBeanName, beanType, extensions, springDependencies); // Load the single (unqualified) bean context.addManagedObjectSource(null, beanType, new SpringBeanManagedObjectSource(singleBeanName, beanType, this.springContext, springDependenciesList)); break; default: // Multiple, so provide qualifier for (String beanName : beanNames) { // Decorate the bean springDependenciesList = this.decorateBean(beanName, beanType, extensions, springDependencies); // Load the qualified bean context.addManagedObjectSource(beanName, beanType, new SpringBeanManagedObjectSource(beanName, beanType, this.springContext, springDependenciesList)); } break; } } } @Override public void terminate() { // Close the spring context this.springContext.close(); } /** * {@link SpringBeanDecoratorContext} implementation. */ private static class SpringBeanDecoratorContextImpl implements SpringBeanDecoratorContext { /** * Bean name. */ private final String name; /** * Bean type. */ private final Class type; /** * {@link SpringDependency} instances by name. */ private final Map dependencies; /** * Instantiate. * * @param name Bean name. * @param type Bean type. * @param dependencies {@link SpringDependency} instances by name. */ public SpringBeanDecoratorContextImpl(String name, Class type, Map dependencies) { this.name = name; this.type = type; this.dependencies = new HashMap<>(dependencies); } /* * ====================== SpringBeanDecoratorContext ======================== */ @Override public String getBeanName() { return this.name; } @Override public Class getBeanType() { return this.type; } @Override public void addDependency(String qualifier, Class type) { // Obtain the dependency name String dependencyName = SupplierThreadLocalNodeImpl.getSupplierThreadLocalName(qualifier, type.getName()); if (this.dependencies.containsKey(dependencyName)) { return; // already have dependency } // Add the dependency this.dependencies.put(dependencyName, new SpringDependency(qualifier, type)); } } }