Package hudson.util.spring

Source Code of hudson.util.spring.BeanBuilder$DeferredProperty

/*
* Copyright 2004-2005 the original author or authors.
*
* 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 hudson.util.spring;

import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GString;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.GroovyShell;
import groovy.lang.MetaClass;
import groovy.lang.MissingMethodException;
import org.apache.commons.lang.ArrayUtils;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.web.context.WebApplicationContext;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;

/**
* <p>Runtime bean configuration wrapper. Like a Groovy builder, but more of a DSL for
* Spring configuration. Allows syntax like:</p>
*
* <pre>
* import org.hibernate.SessionFactory
* import org.apache.commons.dbcp.BasicDataSource
*
* BeanBuilder builder = new BeanBuilder()
* builder.beans {
*   dataSource(BasicDataSource) {                  // <--- invokeMethod
*      driverClassName = "org.hsqldb.jdbcDriver"
*      url = "jdbc:hsqldb:mem:grailsDB"
*      username = "sa"                            // <-- setProperty
*      password = ""
*      settings = [mynew:"setting"]
*  }
*  sessionFactory(SessionFactory) {
*       dataSource = dataSource                 // <-- getProperty for retrieving refs
*  }
*  myService(MyService) {
*      nestedBean = { AnotherBean bean->          // <-- setProperty with closure for nested bean
*          dataSource = dataSource
*      }
*  }
* }
* </pre>
* <p>
*   You can also use the Spring IO API to load resources containing beans defined as a Groovy
*   script using either the constructors or the loadBeans(Resource[] resources) method
* </p>
*
* @author Graeme Rocher
* @since 0.4
*
*/
public class BeanBuilder extends GroovyObjectSupport {
    private static final String ANONYMOUS_BEAN = "bean";
    private RuntimeSpringConfiguration springConfig = new DefaultRuntimeSpringConfiguration();
    private BeanConfiguration currentBeanConfig;
    private Map<String,DeferredProperty> deferredProperties = new HashMap<String,DeferredProperty>();
    private ApplicationContext parentCtx;
    private Map binding = new HashMap();
    private ClassLoader classLoader = null;


    public BeanBuilder() {
    super();
  }

    public BeanBuilder(ClassLoader classLoader) {
    super();
    this.classLoader = classLoader;
  }

  public BeanBuilder(ApplicationContext parent) {
    super();
    this.parentCtx = parent;
    this.springConfig = new DefaultRuntimeSpringConfiguration(parent);
  }

  public BeanBuilder(ApplicationContext parent,ClassLoader classLoader) {
    super();
    this.parentCtx = parent;
    this.springConfig = new DefaultRuntimeSpringConfiguration(parent);
    this.classLoader = classLoader;
  }

    /**
     * Parses the bean definition groovy script.
     */
    public void parse(InputStream script) {
        parse(script,new Binding());
    }

    /**
     * Parses the bean definition groovy script by first exporting the given {@link Binding}.
     */
    public void parse(InputStream script, Binding binding) {
        setBinding(binding);
        CompilerConfiguration cc = new CompilerConfiguration();
        cc.setScriptBaseClass(ClosureScript.class.getName());
        GroovyShell shell = new GroovyShell(classLoader,binding,cc);

        ClosureScript s = (ClosureScript)shell.parse(script);
        s.setDelegate(this);
        s.run();
    }

    /**
   * Retrieves the parent ApplicationContext
   * @return The parent ApplicationContext
   */
  public ApplicationContext getParentCtx() {
    return parentCtx;
  }

  /**
   * Retrieves the RuntimeSpringConfiguration instance used the the BeanBuilder
   * @return The RuntimeSpringConfiguration instance
   */
  public RuntimeSpringConfiguration getSpringConfig() {
    return springConfig;
  }

  /**
   * Retrieves a BeanDefinition for the given name
   * @param name The bean definition
   * @return The BeanDefinition instance
   */
  public BeanDefinition getBeanDefinition(String name) {
    if(!getSpringConfig().containsBean(name))
      return null;
    return getSpringConfig().getBeanConfig(name).getBeanDefinition();
  }

    /**
     * Retrieves all BeanDefinitions for this BeanBuilder
     *
     * @return A map of BeanDefinition instances with the bean id as the key
     */
    public Map<String,BeanDefinition> getBeanDefinitions() {

        Map<String,BeanDefinition> beanDefinitions = new HashMap<String,BeanDefinition>();
        for (String beanName : getSpringConfig().getBeanNames()) {
            BeanDefinition bd = getSpringConfig()
                    .getBeanConfig(beanName)
                    .getBeanDefinition();
            beanDefinitions.put(beanName, bd);
        }
        return beanDefinitions;
    }

    /**
   * Sets the runtime Spring configuration instance to use. This is not necessary to set
   * and is configured to default value if not, but is useful for integrating with other
   * spring configuration mechanisms @see org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator
   *
   * @param springConfig The spring config
   */
  public void setSpringConfig(RuntimeSpringConfiguration springConfig) {
    this.springConfig = springConfig;
  }



  /**
   * This class is used to defer the adding of a property to a bean definition until later
   * This is for a case where you assign a property to a list that may not contain bean references at
   * that point of asignment, but may later hence it would need to be managed
   *
   * @author Graeme Rocher
   */
  private static class DeferredProperty {
    private BeanConfiguration config;
    private String name;
    private Object value;

    DeferredProperty(BeanConfiguration config, String name, Object value) {
      this.config = config;
      this.name = name;
      this.value = value;
    }

    public void setInBeanConfig() {
      this.config.addProperty(name, value);
    }
  }

  /**
   * A RuntimeBeanReference that takes care of adding new properties to runtime references
   *
   * @author Graeme Rocher
   * @since 0.4
   *
   */
  private class ConfigurableRuntimeBeanReference extends RuntimeBeanReference implements GroovyObject {

    private MetaClass metaClass;
    private BeanConfiguration beanConfig;

    public ConfigurableRuntimeBeanReference(String beanName, BeanConfiguration beanConfig) {
      this(beanName, beanConfig, false);
    }

    public ConfigurableRuntimeBeanReference(String beanName, BeanConfiguration beanConfig, boolean toParent) {
      super(beanName, toParent);
      this.beanConfig = beanConfig;
      if(beanConfig == null)
        throw new IllegalArgumentException("Argument [beanConfig] cannot be null");
      this.metaClass = InvokerHelper.getMetaClass(this);
    }

    public MetaClass getMetaClass() {
      return this.metaClass;
    }

    public Object getProperty(String property) {
      if(property.equals("beanName"))
        return getBeanName();
      else if(property.equals("source"))
        return getSource();
      else if(this.beanConfig != null) {
        return new WrappedPropertyValue(property,beanConfig.getPropertyValue(property));
      }
      else
        return this.metaClass.getProperty(this, property);
    }



        /**
     * Wraps a BeanConfiguration property an ensures that any RuntimeReference additions to it are
     * deferred for resolution later
     *
     * @author Graeme Rocher
     * @since 0.4
     *
     */
    private class WrappedPropertyValue extends GroovyObjectSupport {
      private Object propertyValue;
      private String propertyName;
      public WrappedPropertyValue(String propertyName, Object propertyValue) {
        this.propertyValue = propertyValue;
        this.propertyName = propertyName;
      }

      public void leftShift(Object value) {
        InvokerHelper.invokeMethod(propertyValue, "leftShift", value);
        if(value instanceof RuntimeBeanReference) {
          deferredProperties.put(beanConfig.getName(), new DeferredProperty(beanConfig, propertyName, propertyValue));
        }
      }
    }
    public Object invokeMethod(String name, Object args) {
      return this.metaClass.invokeMethod(this, name, args);
    }

    public void setMetaClass(MetaClass metaClass) {
      this.metaClass = metaClass;
    }

    public void setProperty(String property, Object newValue) {
            if(!addToDeferred(beanConfig,property, newValue)) {
                beanConfig.setPropertyValue(property, newValue);
            }
    }
  }
  /**
   * Takes a resource pattern as (@see org.springframework.core.io.support.PathMatchingResourcePatternResolver)
   * This allows you load multiple bean resources in this single builder
   *
   * eg loadBeans("classpath:*Beans.groovy")
   *
   * @param resourcePattern
   * @throws IOException When the path cannot be matched
   */
  public void loadBeans(String resourcePattern) throws IOException {
    loadBeans(new PathMatchingResourcePatternResolver().getResources(resourcePattern));
  }

  /**
   * Loads a single Resource into the bean builder
   *
   * @param resource The resource to load
   * @throws IOException When an error occurs
   */
  public void loadBeans(Resource resource) throws IOException {
    loadBeans(new Resource[]{resource});
  }

  /**
   * Loads a set of given beans
   * @param resources The resources to load
   * @throws IOException
   */
  public void loadBeans(Resource[] resources) throws IOException {
    Closure beans = new Closure(this){
      @Override
      public Object call(Object[] args) {
        return beans((Closure)args[0]);
      }
    };
    Binding b = new Binding();
    b.setVariable("beans", beans);

    GroovyShell shell = classLoader != null ? new GroovyShell(classLoader,b) : new GroovyShell(b);
        for (Resource resource : resources) {
            shell.evaluate(resource.getInputStream());
        }
  }

    public void registerBeans(StaticApplicationContext ctx) {
        finalizeDeferredProperties();
        springConfig.registerBeansWithContext(ctx);
    }

    public RuntimeBeanReference ref(String refName) {
        return ref(refName,false);
    }

    public RuntimeBeanReference parentRef(String refName) {
        return ref(refName,true);
    }

    public RuntimeBeanReference ref(String refName, boolean parentRef) {
        return new RuntimeBeanReference(refName, parentRef);
    }

    /**
   * This method is invoked by Groovy when a method that's not defined in Java is invoked.
     * We use that as a syntax for bean definition.
   */
  public Object methodMissing(String name, Object arg) {
        Object[] args = (Object[])arg;

        if(args.length == 0)
      throw new MissingMethodException(name,getClass(),args);

    if(args[0] instanceof Closure) {
            // abstract bean definition
            return invokeBeanDefiningMethod(name, args);
    }
    else if(args[0] instanceof Class || args[0] instanceof RuntimeBeanReference || args[0] instanceof Map) {
      return invokeBeanDefiningMethod(name, args);
    }
    else if (args.length > 1 && args[args.length -1] instanceof Closure) {
      return invokeBeanDefiningMethod(name, args);
    }
        WebApplicationContext ctx = springConfig.getUnrefreshedApplicationContext();
        MetaClass mc = DefaultGroovyMethods.getMetaClass(ctx);
        if(!mc.respondsTo(ctx, name, args).isEmpty()){
            return mc.invokeMethod(ctx,name, args);
        }
        return this;
  }

    public WebApplicationContext createApplicationContext() {
        finalizeDeferredProperties();
        return springConfig.getApplicationContext();
    }

    private void finalizeDeferredProperties() {
        for (DeferredProperty dp : deferredProperties.values()) {
            if (dp.value instanceof List) {
                dp.value = manageListIfNecessary((List)dp.value);
            } else if (dp.value instanceof Map) {
                dp.value = manageMapIfNecessary((Map)dp.value);
            }
            dp.setInBeanConfig();
        }
    deferredProperties.clear();
  }

  private boolean addToDeferred(BeanConfiguration beanConfig,String property, Object newValue) {
    if(newValue instanceof List) {
      deferredProperties.put(currentBeanConfig.getName()+property,new DeferredProperty(currentBeanConfig, property, newValue));
      return true;
    }
    else if(newValue instanceof Map) {
      deferredProperties.put(currentBeanConfig.getName()+property,new DeferredProperty(currentBeanConfig, property, newValue));
      return true;
    }
    return false;
  }
  /**
   * This method is called when a bean definition node is called
   *
   * @param name The name of the bean to define
   * @param args The arguments to the bean. The first argument is the class name, the last argument is sometimes a closure. All
   * the arguments in between are constructor arguments
   * @return The bean configuration instance
   */
  private BeanConfiguration invokeBeanDefiningMethod(String name, Object[] args) {
        BeanConfiguration old = currentBeanConfig;
        try {
            if(args[0] instanceof Class) {
                Class beanClass = args[0] instanceof Class ? (Class)args[0] : args[0].getClass();

                if(args.length >= 1) {
                    if(args[args.length-1] instanceof Closure) {
                        if(args.length-1 != 1) {
                            Object[] constructorArgs = ArrayUtils.subarray(args, 1, args.length-1);
                            filterGStringReferences(constructorArgs);
                            if(name.equals(ANONYMOUS_BEAN))
                                currentBeanConfig = springConfig.createSingletonBean(beanClass,Arrays.asList(constructorArgs));
                            else
                                currentBeanConfig = springConfig.addSingletonBean(name, beanClass, Arrays.asList(constructorArgs));
                        }
                        else {
                            if(name.equals(ANONYMOUS_BEAN))
                                currentBeanConfig = springConfig.createSingletonBean(beanClass);
                            else
                                currentBeanConfig = springConfig.addSingletonBean(name, beanClass);
                        }
                    }
                    else  {
                        Object[] constructorArgs = ArrayUtils.subarray(args, 1, args.length);
                        filterGStringReferences(constructorArgs);
                        if(name.equals(ANONYMOUS_BEAN))
                            currentBeanConfig = springConfig.createSingletonBean(beanClass,Arrays.asList(constructorArgs));
                        else
                            currentBeanConfig = springConfig.addSingletonBean(name, beanClass, Arrays.asList(constructorArgs));
                    }

                }
            }
            else if(args[0] instanceof RuntimeBeanReference) {
                currentBeanConfig = springConfig.addSingletonBean(name);
                currentBeanConfig.setFactoryBean(((RuntimeBeanReference)args[0]).getBeanName());
            }
            else if(args[0] instanceof Map) {
                currentBeanConfig = springConfig.addSingletonBean(name);
                Map.Entry factoryBeanEntry = (Map.Entry)((Map)args[0]).entrySet().iterator().next();
                currentBeanConfig.setFactoryBean(factoryBeanEntry.getKey().toString());
                currentBeanConfig.setFactoryMethod(factoryBeanEntry.getValue().toString());
            }
            else if(args[0] instanceof Closure) {
                currentBeanConfig = springConfig.addAbstractBean(name);
            }
            else {
                Object[] constructorArgs;
                if(args[args.length-1] instanceof Closure) {
                    constructorArgs= ArrayUtils.subarray(args, 0, args.length-1);
                }
                else {
                    constructorArgs= ArrayUtils.subarray(args, 0, args.length);
                }
                filterGStringReferences(constructorArgs);
                currentBeanConfig = new DefaultBeanConfiguration(name, null, Arrays.asList(constructorArgs));
                springConfig.addBeanConfiguration(name,currentBeanConfig);
            }
            if(args[args.length-1] instanceof Closure) {
                Closure callable = (Closure)args[args.length-1];
                callable.setDelegate(this);
                callable.setResolveStrategy(Closure.DELEGATE_FIRST);
                callable.call(new Object[]{currentBeanConfig});

            }

            return currentBeanConfig;
        } finally {
            currentBeanConfig = old;
        }
    }

    private void filterGStringReferences(Object[] constructorArgs) {
        for (int i = 0; i < constructorArgs.length; i++) {
            Object constructorArg = constructorArgs[i];
            if(constructorArg instanceof GString) constructorArgs[i] = constructorArg.toString();
        }
    }

    /**
   * When an methods argument is only a closure it is a set of bean definitions
   *
   * @param callable The closure argument
   */
  public BeanBuilder beans(Closure callable) {
    callable.setDelegate(this);
  //      callable.setResolveStrategy(Closure.DELEGATE_FIRST);
        callable.call();
    finalizeDeferredProperties();

        return this;
    }

    /**
   * This method overrides property setting in the scope of the BeanBuilder to set
   * properties on the current BeanConfiguration
   */
  @Override
  public void setProperty(String name, Object value) {
    if(currentBeanConfig != null) {
      if(value instanceof GString)value = value.toString();
      if(addToDeferred(currentBeanConfig, name, value)) {
        return;
      }
      else if(value instanceof Closure) {
        BeanConfiguration current = currentBeanConfig;
        try {
          Closure callable = (Closure)value;

          Class parameterType = callable.getParameterTypes()[0];
          if(parameterType.equals(Object.class)) {
            currentBeanConfig = springConfig.createSingletonBean("");
            callable.call(new Object[]{currentBeanConfig});
          }
          else {
            currentBeanConfig = springConfig.createSingletonBean(parameterType);
            callable.call(null);
          }

          value = currentBeanConfig.getBeanDefinition();
        }
        finally {
          currentBeanConfig = current;
        }
      }
            currentBeanConfig.addProperty(name, value);
    } else {
            binding.put(name,value);
        }
  }

  /**
   * Checks whether there are any runtime refs inside a Map and converts
   * it to a ManagedMap if necessary
   *
   * @param value The current map
   * @return A ManagedMap or a normal map
   */
  private Object manageMapIfNecessary(Map<Object, Object> value) {
    boolean containsRuntimeRefs = false;
        for (Entry<Object, Object> e : value.entrySet()) {
            Object v = e.getValue();
            if (v instanceof RuntimeBeanReference) {
                containsRuntimeRefs = true;
            }
            if (v instanceof BeanConfiguration) {
                BeanConfiguration c = (BeanConfiguration) v;
                e.setValue(c.getBeanDefinition());
                containsRuntimeRefs = true;
            }
        }
    if(containsRuntimeRefs) {
//      return new ManagedMap(map);
            ManagedMap m = new ManagedMap();
            m.putAll(value);
            return m;
        }
    return value;
  }

  /**
   * Checks whether there are any runtime refs inside the list and
   * converts it to a ManagedList if necessary
   *
   * @param value The object that represents the list
   * @return Either a new list or a managed one
   */
  private Object manageListIfNecessary(List<Object> value) {
    boolean containsRuntimeRefs = false;
    for (ListIterator<Object> i = value.listIterator(); i.hasNext();) {
      Object e = i.next();
      if(e instanceof RuntimeBeanReference) {
        containsRuntimeRefs = true;
      }
            if (e instanceof BeanConfiguration) {
                BeanConfiguration c = (BeanConfiguration) e;
                i.set(c.getBeanDefinition());
                containsRuntimeRefs = true;
            }
        }
    if(containsRuntimeRefs) {
      List tmp = new ManagedList();
      tmp.addAll(value);
      value = tmp;
    }
    return value;
  }

  /**
   * This method overrides property retrieval in the scope of the BeanBuilder to either:
   *
   * a) Retrieve a variable from the bean builder's binding if it exits
   * b) Retrieve a RuntimeBeanReference for a specific bean if it exists
   * c) Otherwise just delegate to super.getProperty which will resolve properties from the BeanBuilder itself
   */
  @Override
  public Object getProperty(String name) {
    if(binding.containsKey(name)) {
      return binding.get(name);
    }
    else {
      if(springConfig.containsBean(name)) {
        BeanConfiguration beanConfig = springConfig.getBeanConfig(name);
        if(beanConfig != null) {
          return new ConfigurableRuntimeBeanReference(name, springConfig.getBeanConfig(name) ,false);
        }
        else
          return new RuntimeBeanReference(name,false);
      }
      // this is to deal with the case where the property setter is the last
      // statement in a closure (hence the return value)
      else if(currentBeanConfig != null) {
        if(currentBeanConfig.hasProperty(name))
          return currentBeanConfig.getPropertyValue(name);
        else {
          DeferredProperty dp = deferredProperties.get(currentBeanConfig.getName()+name);
          if(dp!=null) {
            return dp.value;
          }
          else {
            return super.getProperty(name);
          }
        }
      }
      else {
        return super.getProperty(name);
      }
    }
  }

  /**
   * Sets the binding (the variables available in the scope of the BeanBuilder)
   * @param b The Binding instance
   */
  public void setBinding(Binding b) {
    this.binding = b.getVariables();
  }

}
TOP

Related Classes of hudson.util.spring.BeanBuilder$DeferredProperty

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.