PooledProxyBeanDefinitionDecorator.java
001 /*
002  *  PooledProxyBeanDefinitionDecorator.java
003  *
004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  Ian Roberts, 10/Apr/2010
013  *
014  *  $Id: PooledProxyBeanDefinitionDecorator.java 18817 2015-07-08 10:18:34Z ian_roberts $
015  */
016 package gate.util.spring.xml;
017 
018 import javax.xml.XMLConstants;
019 
020 import org.springframework.beans.factory.config.BeanDefinition;
021 import org.springframework.beans.factory.config.BeanDefinitionHolder;
022 import org.springframework.beans.factory.config.RuntimeBeanReference;
023 import org.springframework.beans.factory.support.AbstractBeanDefinition;
024 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
025 import org.springframework.beans.factory.support.RootBeanDefinition;
026 import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
027 import org.springframework.beans.factory.xml.ParserContext;
028 import org.springframework.core.Conventions;
029 import org.w3c.dom.Attr;
030 import org.w3c.dom.Element;
031 import org.w3c.dom.NamedNodeMap;
032 import org.w3c.dom.Node;
033 
034 /**
035  * Bean decorator to easily create a pool of target beans.
036  
037  <pre>
038  * &lt;bean id=&quot;myBean&quot; class=&quot;some.pkg.MyBean&quot;&gt;
039  *   &lt;property name=&quot;gateApplication&quot; ref=&quot;app&quot; /&gt;
040  *   &lt;gate:pooled-proxy max-size=&quot;3&quot; /&gt;
041  * &lt;/bean&gt;
042  </pre>
043  
044  <p>
045  * This replaces the <code>myBean</code> bean with a proxy that
046  * delegates to a pool of target objects. The targets are obtained by
047  * converting the original <code>myBean</code> definition to a
048  * prototype-scoped bean definition and setting that as the
049  * targetBeanName of a CommonsPoolTargetSource. The
050  * CommonsPoolTargetSource is then used as the target source for a
051  * standard ProxyFactoryBean, which is created with the scope specified
052  * in the original <code>myBean</code> definition (usually singleton).
053  </p>
054  
055  <p>
056  * If the pooled-proxy element has an attribute proxy-target-class, this
057  * value is passed through to the generated ProxyFactoryBean. All other
058  * attributes are passed through to the CommonsPoolTargetSource. The
059  * element also supports an initial-size attribute. If specified it will
060  * pre-populate the pool with this number of instances, which is useful
061  * if you want to load several copies of a saved application up-front
062  * (the normal behaviour of a Spring pool is to only instantiate the
063  * pooled objects as and when they are required).
064  </p>
065  
066  <p>
067  <b>NOTE</b> In addition to the <code>spring-aop</code> JAR, you also
068  * need the Apache <code>commons-pool</code> JAR and its dependencies
069  * available to your application in order to use this class.
070  </p>
071  */
072 public class PooledProxyBeanDefinitionDecorator implements
073                                                BeanDefinitionDecorator {
074 
075   public static final String TARGET_PREFIX = "gate.util.spring.pool-target.";
076 
077   public static final String TARGET_SOURCE_PREFIX =
078           "gate.util.spring.pool-target-source.";
079 
080   public static final String POOL_FILLER_PREFIX =
081           "gate.util.spring.pool-filler.";
082 
083   private static final String PROXY_TARGET_CLASS = "proxy-target-class";
084 
085   private static final String INITIAL_SIZE = "initial-size";
086 
087   private static final String TARGET_SOURCE_CLASS = "target-source-class";
088 
089   @Override
090   public BeanDefinitionHolder decorate(Node node,
091           BeanDefinitionHolder definition, ParserContext parserContext) {
092     String originalBeanName = definition.getBeanName();
093     String[] originalAliases = definition.getAliases();
094     String targetBeanName = TARGET_PREFIX + originalBeanName;
095 
096     BeanDefinition targetDefinition = definition.getBeanDefinition();
097     BeanDefinitionRegistry reg = parserContext.getRegistry();
098 
099     // remember the scope of the original definition,
100     // change the target bean to be prototype.
101     String originalScope = targetDefinition.getScope();
102     targetDefinition.setScope("prototype");
103 
104     // create a bean definition for the target source, pointing at the
105     // target bean name
106     RootBeanDefinition targetSourceDefinition = new RootBeanDefinition();
107     targetSourceDefinition.setScope(originalScope);
108     String targetSourceClassName = null;
109     if(node instanceof Element
110             && ((Element)node).hasAttribute(TARGET_SOURCE_CLASS)) {
111       targetSourceClassName = ((Element)node).getAttribute(TARGET_SOURCE_CLASS);
112     else {
113       targetSourceClassName =
114               "org.springframework.aop.target.CommonsPoolTargetSource";
115     }
116     targetSourceDefinition.setBeanClassName(targetSourceClassName);
117     targetSourceDefinition.getPropertyValues().addPropertyValue(
118             "targetBeanName", targetBeanName);
119     String targetSourceBeanName = TARGET_SOURCE_PREFIX + originalBeanName;
120 
121     // apply any attributes of the pooled-proxy element except
122     // proxy-target-class and initial-size as properties of the
123     // target source
124     if(node instanceof Element) {
125       Element ele = (Element)node;
126       NamedNodeMap attrs = ele.getAttributes();
127       for(int i = 0; i < attrs.getLength(); i++) {
128         Attr att = (Attr)attrs.item(i);
129         if(!XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(att.getNamespaceURI())
130                 && !XMLConstants.XML_NS_URI.equals(att.getNamespaceURI())
131                 && !XMLConstants.XMLNS_ATTRIBUTE.equals(att.getLocalName())
132                 && !PROXY_TARGET_CLASS.equals(att.getLocalName())
133                 && !INITIAL_SIZE.equals(att.getLocalName())
134                 && !TARGET_SOURCE_CLASS.equals(att.getLocalName())) {
135           String propName =
136                   Conventions.attributeNameToPropertyName(att.getLocalName());
137           targetSourceDefinition.getPropertyValues().addPropertyValue(propName,
138                   att.getValue());
139         }
140       }
141     }
142 
143     // create a bean definition for the proxy, pointing to the target
144     // source
145     RootBeanDefinition proxyDefinition = new RootBeanDefinition();
146     proxyDefinition.setScope(originalScope);
147     proxyDefinition
148             .setBeanClassName("org.springframework.aop.framework.ProxyFactoryBean");
149     proxyDefinition.getPropertyValues().addPropertyValue("targetSource",
150             new RuntimeBeanReference(targetSourceBeanName));
151 
152     Boolean proxyTargetClass = Boolean.TRUE;
153     if(node instanceof Element) {
154       Element ele = (Element)node;
155       if(ele.hasAttribute(PROXY_TARGET_CLASS)) {
156         proxyTargetClass =
157                 Boolean.valueOf(ele.getAttribute(PROXY_TARGET_CLASS));
158       }
159     }
160     proxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass",
161             proxyTargetClass);
162 
163     if(targetDefinition instanceof AbstractBeanDefinition) {
164       AbstractBeanDefinition abd = (AbstractBeanDefinition)targetDefinition;
165       proxyDefinition.setDependsOn(abd.getDependsOn());
166       proxyDefinition.setAutowireCandidate(abd.isAutowireCandidate());
167       // The target bean should be ignored in favor of the scoped proxy.
168       abd.setAutowireCandidate(false);
169     }
170 
171     // if we have an initial-size attribute, create a pool filler bean
172     // to pre-fill the pool to the stated initial size
173     if(node instanceof Element && ((Element)node).hasAttribute(INITIAL_SIZE)) {
174       RootBeanDefinition poolFillerDefinition =
175               new RootBeanDefinition(PoolFiller.class);
176       String poolFillerBeanName = POOL_FILLER_PREFIX + originalBeanName;
177       poolFillerDefinition.getPropertyValues().addPropertyValue("targetSource",
178               new RuntimeBeanReference(targetSourceBeanName));
179       poolFillerDefinition.getPropertyValues().addPropertyValue("numInstances",
180               ((Element)node).getAttribute(INITIAL_SIZE));
181       poolFillerDefinition.setScope(targetSourceDefinition.getScope());
182       // make the proxy depend on the pool filler
183       String[] proxyDepends = proxyDefinition.getDependsOn();
184       if(proxyDepends == null) {
185         proxyDepends = new String[1];
186       else {
187         String[] newDepends = new String[proxyDepends.length + 1];
188         System.arraycopy(proxyDepends, 0, newDepends, 0, proxyDepends.length);
189         proxyDepends = newDepends;
190       }
191       proxyDepends[proxyDepends.length - 1= poolFillerBeanName;
192       proxyDefinition.setDependsOn(proxyDepends);
193 
194       reg.registerBeanDefinition(poolFillerBeanName, poolFillerDefinition);
195     }
196 
197     // register the (prototype) target bean under its new name
198     reg.registerBeanDefinition(targetBeanName, targetDefinition);
199     // register the target source
200     reg.registerBeanDefinition(targetSourceBeanName, targetSourceDefinition);
201 
202     // return the bean definition for the proxy
203     return new BeanDefinitionHolder(proxyDefinition, originalBeanName,
204             originalAliases);
205   }
206 
207 }