/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.runtime.config.spring.dsl.spring; import static org.springframework.cglib.proxy.Enhancer.registerStaticCallbacks; import org.mule.runtime.api.exception.MuleRuntimeException; import org.mule.runtime.dsl.api.component.ComponentBuildingDefinition; import org.mule.runtime.dsl.api.component.ObjectFactory; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import java.util.function.Supplier; import org.springframework.beans.factory.SmartFactoryBean; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; /** * Repository for storing the dynamic class generated to mimic {@link org.springframework.beans.factory.FactoryBean} from an * {@link ObjectFactory}. This is done because we need dependency injection and instrospection done over the {@link ObjectFactory} * without the user knowing about it. * <p> * The created {@link org.springframework.beans.factory.FactoryBean} is the one that receives the injection of fields declared by * the {@link ObjectFactory}. It also provides information about the instance type that is creating since it's used to know the * order in which beans must be initialised based on the dependencies between them. * <p> * The repository has a cache of the created classes based on the configuration of the {@link ComponentBuildingDefinition} and the * component configuration. * * @since 4.0 */ public class ObjectFactoryClassRepository { private Cache<ComponentBuildingDefinition, Class<ObjectFactory>> objectFactoryClassCache = CacheBuilder.newBuilder().build(); private List<Class> createdClasses = new LinkedList<>(); /** * Retrieves a {@link Class} for the {@link ObjectFactory} defined by the {@code objectFactoryType} parameter. Once acquired the * {@code Class} instance should not be reused for another {@link ComponentBuildingDefinition}. * * @param componentBuildingDefinition the definition on how to build the component * @param objectFactoryType the {@link ObjectFactory} of the component * @param createdObjectType the type of object created by the {@code ObjectFactory} * @param isLazyInitFunction function that defines if the object created by the component can be created lazily * @param instancePostCreationFunctionOptional function to do custom processing of the created instance by the * {@code ObjectFactory}. When there's no need for post processing this value must be {@link Optional#empty()} * @return the {@code FactoryBean} class to be used by spring for the provided configuration. */ public Class<ObjectFactory> getObjectFactoryClass(ComponentBuildingDefinition componentBuildingDefinition, Class objectFactoryType, Class createdObjectType, Supplier<Boolean> isLazyInitFunction, Optional<Consumer<Object>> instancePostCreationFunctionOptional) { try { if (instancePostCreationFunctionOptional.isPresent()) { return objectFactoryClassCache .get(componentBuildingDefinition, () -> getObjectFactoryDynamicClass(componentBuildingDefinition, objectFactoryType, createdObjectType, isLazyInitFunction, instancePostCreationFunctionOptional.get())); } else { // instancePostCreationFunctionOptional is used within the intercepted method so we can't use a cache. return getObjectFactoryDynamicClass(componentBuildingDefinition, objectFactoryType, createdObjectType, isLazyInitFunction, (object) -> { }); } } catch (ExecutionException e) { throw new MuleRuntimeException(e); } } private Class<ObjectFactory> getObjectFactoryDynamicClass(final ComponentBuildingDefinition componentBuildingDefinition, Class objectFactoryType, final Class createdObjectType, final Supplier<Boolean> isLazyInitFunction, final Consumer<Object> instancePostCreationFunction) { /* * We need this to allow spring create the object using a FactoryBean but using the object factory setters and getters so we * create as FactoryBean a dynamic class that will have the same attributes and methods as the ObjectFactory that the user * defined. This way our API does not expose spring specific classes. */ Enhancer enhancer = new Enhancer(); // Use SmartFactoryBean since it's the only way to force spring to pre-instantiate FactoryBean for singletons enhancer.setInterfaces(new Class[] {SmartFactoryBean.class}); enhancer.setSuperclass(objectFactoryType); enhancer.setCallbackType(MethodInterceptor.class); enhancer.setUseCache(false); Class factoryBeanClass = enhancer.createClass(); createdClasses.add(factoryBeanClass); registerStaticCallbacks(factoryBeanClass, new Callback[] { (MethodInterceptor) (obj, method, args, proxy) -> { if (method.getName().equals("isSingleton")) { return !componentBuildingDefinition.isPrototype(); } if (method.getName().equals("getObjectType")) { return createdObjectType; } if (method.getName().equals("getObject")) { Object createdInstance = proxy.invokeSuper(obj, args); instancePostCreationFunction.accept(createdInstance); return createdInstance; } if (method.getName().equals("isPrototype")) { return componentBuildingDefinition.isPrototype(); } if (method.getName().equals("isEagerInit")) { return !isLazyInitFunction.get(); } return proxy.invokeSuper(obj, args); } }); return factoryBeanClass; } /** * Removes all registered callbacks create for each created {@code FactoryBean} class. This is a must since it prevents a memory * leak in CGLIB */ public void destroy() { createdClasses.stream().forEach(clazz -> registerStaticCallbacks(clazz, null)); } }