001/*
002 * Copyright 2002-2012 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.beans.factory.access;
018
019import java.io.IOException;
020import java.util.HashMap;
021import java.util.Map;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025
026import org.springframework.beans.BeansException;
027import org.springframework.beans.FatalBeanException;
028import org.springframework.beans.factory.BeanDefinitionStoreException;
029import org.springframework.beans.factory.BeanFactory;
030import org.springframework.beans.factory.config.ConfigurableBeanFactory;
031import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
032import org.springframework.beans.factory.support.DefaultListableBeanFactory;
033import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
034import org.springframework.core.io.Resource;
035import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
036import org.springframework.core.io.support.ResourcePatternResolver;
037import org.springframework.core.io.support.ResourcePatternUtils;
038
039/**
040 * <p>Keyed-singleton implementation of {@link BeanFactoryLocator},
041 * which accesses shared Spring {@link BeanFactory} instances.</p>
042 *
043 * <p>Please see the warning in BeanFactoryLocator's javadoc about appropriate usage
044 * of singleton style BeanFactoryLocator implementations. It is the opinion of the
045 * Spring team that the use of this class and similar classes is unnecessary except
046 * (sometimes) for a small amount of glue code. Excessive usage will lead to code
047 * that is more tightly coupled, and harder to modify or test.</p>
048 *
049 * <p>In this implementation, a BeanFactory is built up from one or more XML
050 * definition file fragments, accessed as resources. The default resource name
051 * searched for is 'classpath*:beanRefFactory.xml', with the Spring-standard
052 * 'classpath*:' prefix ensuring that if the classpath contains multiple copies
053 * of this file (perhaps one in each component jar) they will be combined. To
054 * override the default resource name, instead of using the no-arg
055 * {@link #getInstance()} method, use the {@link #getInstance(String selector)}
056 * variant, which will treat the 'selector' argument as the resource name to
057 * search for.</p>
058 *
059 * <p>The purpose of this 'outer' BeanFactory is to create and hold a copy of one
060 * or more 'inner' BeanFactory or ApplicationContext instances, and allow those
061 * to be obtained either directly or via an alias. As such, this class provides
062 * both singleton style access to one or more BeanFactories/ApplicationContexts,
063 * and also a level of indirection, allowing multiple pieces of code, which are
064 * not able to work in a Dependency Injection fashion, to refer to and use the
065 * same target BeanFactory/ApplicationContext instance(s), by different names.<p>
066 *
067 * <p>Consider an example application scenario:
068 *
069 * <ul>
070 * <li>{@code com.mycompany.myapp.util.applicationContext.xml} -
071 * ApplicationContext definition file which defines beans for 'util' layer.
072 * <li>{@code com.mycompany.myapp.dataaccess-applicationContext.xml} -
073 * ApplicationContext definition file which defines beans for 'data access' layer.
074 * Depends on the above.
075 * <li>{@code com.mycompany.myapp.services.applicationContext.xml} -
076 * ApplicationContext definition file which defines beans for 'services' layer.
077 * Depends on the above.
078 * </ul>
079 *
080 * <p>In an ideal scenario, these would be combined to create one ApplicationContext,
081 * or created as three hierarchical ApplicationContexts, by one piece of code
082 * somewhere at application startup (perhaps a Servlet filter), from which all other
083 * code in the application would flow, obtained as beans from the context(s). However
084 * when third party code enters into the picture, things can get problematic. If the
085 * third party code needs to create user classes, which should normally be obtained
086 * from a Spring BeanFactory/ApplicationContext, but can handle only newInstance()
087 * style object creation, then some extra work is required to actually access and
088 * use object from a BeanFactory/ApplicationContext. One solutions is to make the
089 * class created by the third party code be just a stub or proxy, which gets the
090 * real object from a BeanFactory/ApplicationContext, and delegates to it. However,
091 * it is not normally workable for the stub to create the BeanFactory on each
092 * use, as depending on what is inside it, that can be an expensive operation.
093 * Additionally, there is a fairly tight coupling between the stub and the name of
094 * the definition resource for the BeanFactory/ApplicationContext. This is where
095 * SingletonBeanFactoryLocator comes in. The stub can obtain a
096 * SingletonBeanFactoryLocator instance, which is effectively a singleton, and
097 * ask it for an appropriate BeanFactory. A subsequent invocation (assuming the
098 * same class loader is involved) by the stub or another piece of code, will obtain
099 * the same instance. The simple aliasing mechanism allows the context to be asked
100 * for by a name which is appropriate for (or describes) the user. The deployer can
101 * match alias names to actual context names.
102 *
103 * <p>Another use of SingletonBeanFactoryLocator, is to demand-load/use one or more
104 * BeanFactories/ApplicationContexts. Because the definition can contain one of more
105 * BeanFactories/ApplicationContexts, which can be independent or in a hierarchy, if
106 * they are set to lazy-initialize, they will only be created when actually requested
107 * for use.
108 *
109 * <p>Given the above-mentioned three ApplicationContexts, consider the simplest
110 * SingletonBeanFactoryLocator usage scenario, where there is only one single
111 * {@code beanRefFactory.xml} definition file:
112 *
113 * <pre class="code">&lt;?xml version="1.0" encoding="UTF-8"?>
114 * &lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
115 *
116 * &lt;beans>
117 *
118 *   &lt;bean id="com.mycompany.myapp"
119 *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
120 *     &lt;constructor-arg>
121 *       &lt;list>
122 *         &lt;value>com/mycompany/myapp/util/applicationContext.xml&lt;/value>
123 *         &lt;value>com/mycompany/myapp/dataaccess/applicationContext.xml&lt;/value>
124 *         &lt;value>com/mycompany/myapp/dataaccess/services.xml&lt;/value>
125 *       &lt;/list>
126 *     &lt;/constructor-arg>
127 *   &lt;/bean>
128 *
129 * &lt;/beans>
130 * </pre>
131 *
132 * The client code is as simple as:
133 *
134 * <pre class="code">
135 * BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
136 * BeanFactoryReference bf = bfl.useBeanFactory("com.mycompany.myapp");
137 * // now use some bean from factory
138 * MyClass zed = bf.getFactory().getBean("mybean");
139 * </pre>
140 *
141 * Another relatively simple variation of the {@code beanRefFactory.xml} definition file could be:
142 *
143 * <pre class="code">&lt;?xml version="1.0" encoding="UTF-8"?>
144 * &lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
145 *
146 * &lt;beans>
147 *
148 *   &lt;bean id="com.mycompany.myapp.util" lazy-init="true"
149 *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
150 *     &lt;constructor-arg>
151 *       &lt;value>com/mycompany/myapp/util/applicationContext.xml&lt;/value>
152 *     &lt;/constructor-arg>
153 *   &lt;/bean>
154 *
155 *   &lt;!-- child of above -->
156 *   &lt;bean id="com.mycompany.myapp.dataaccess" lazy-init="true"
157 *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
158 *     &lt;constructor-arg>
159 *       &lt;list>&lt;value>com/mycompany/myapp/dataaccess/applicationContext.xml&lt;/value>&lt;/list>
160 *     &lt;/constructor-arg>
161 *     &lt;constructor-arg>
162 *       &lt;ref bean="com.mycompany.myapp.util"/>
163 *     &lt;/constructor-arg>
164 *   &lt;/bean>
165 *
166 *   &lt;!-- child of above -->
167 *   &lt;bean id="com.mycompany.myapp.services" lazy-init="true"
168 *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
169 *     &lt;constructor-arg>
170 *       &lt;list>&lt;value>com/mycompany/myapp/dataaccess.services.xml&lt;/value>&lt;/value>
171 *     &lt;/constructor-arg>
172 *     &lt;constructor-arg>
173 *       &lt;ref bean="com.mycompany.myapp.dataaccess"/>
174 *     &lt;/constructor-arg>
175 *   &lt;/bean>
176 *
177 *   &lt;!-- define an alias -->
178 *   &lt;bean id="com.mycompany.myapp.mypackage"
179 *         class="java.lang.String">
180 *     &lt;constructor-arg>
181 *       &lt;value>com.mycompany.myapp.services&lt;/value>
182 *     &lt;/constructor-arg>
183 *   &lt;/bean>
184 *
185 * &lt;/beans>
186 * </pre>
187 *
188 * <p>In this example, there is a hierarchy of three contexts created. The (potential)
189 * advantage is that if the lazy flag is set to true, a context will only be created
190 * if it's actually used. If there is some code that is only needed some of the time,
191 * this mechanism can save some resources. Additionally, an alias to the last context
192 * has been created. Aliases allow usage of the idiom where client code asks for a
193 * context with an id which represents the package or module the code is in, and the
194 * actual definition file(s) for the SingletonBeanFactoryLocator maps that id to
195 * a real context id.
196 *
197 * <p>A final example is more complex, with a {@code beanRefFactory.xml} for every module.
198 * All the files are automatically combined to create the final definition.
199 *
200 * <p>{@code beanRefFactory.xml} file inside jar for util module:
201 *
202 * <pre class="code">&lt;?xml version="1.0" encoding="UTF-8"?>
203 * &lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
204 *
205 * &lt;beans>
206 *   &lt;bean id="com.mycompany.myapp.util" lazy-init="true"
207 *        class="org.springframework.context.support.ClassPathXmlApplicationContext">
208 *     &lt;constructor-arg>
209 *       &lt;value>com/mycompany/myapp/util/applicationContext.xml&lt;/value>
210 *     &lt;/constructor-arg>
211 *   &lt;/bean>
212 * &lt;/beans>
213 * </pre>
214 *
215 * {@code beanRefFactory.xml} file inside jar for data-access module:<br>
216 *
217 * <pre class="code">&lt;?xml version="1.0" encoding="UTF-8"?>
218 * &lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
219 *
220 * &lt;beans>
221 *   &lt;!-- child of util -->
222 *   &lt;bean id="com.mycompany.myapp.dataaccess" lazy-init="true"
223 *        class="org.springframework.context.support.ClassPathXmlApplicationContext">
224 *     &lt;constructor-arg>
225 *       &lt;list>&lt;value>com/mycompany/myapp/dataaccess/applicationContext.xml&lt;/value>&lt;/list>
226 *     &lt;/constructor-arg>
227 *     &lt;constructor-arg>
228 *       &lt;ref bean="com.mycompany.myapp.util"/>
229 *     &lt;/constructor-arg>
230 *   &lt;/bean>
231 * &lt;/beans>
232 * </pre>
233 *
234 * {@code beanRefFactory.xml} file inside jar for services module:
235 *
236 * <pre class="code">&lt;?xml version="1.0" encoding="UTF-8"?>
237 * &lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
238 *
239 * &lt;beans>
240 *   &lt;!-- child of data-access -->
241 *   &lt;bean id="com.mycompany.myapp.services" lazy-init="true"
242 *        class="org.springframework.context.support.ClassPathXmlApplicationContext">
243 *     &lt;constructor-arg>
244 *       &lt;list>&lt;value>com/mycompany/myapp/dataaccess/services.xml&lt;/value>&lt;/list>
245 *     &lt;/constructor-arg>
246 *     &lt;constructor-arg>
247 *       &lt;ref bean="com.mycompany.myapp.dataaccess"/>
248 *     &lt;/constructor-arg>
249 *   &lt;/bean>
250 * &lt;/beans>
251 * </pre>
252 *
253 * {@code beanRefFactory.xml} file inside jar for mypackage module. This doesn't
254 * create any of its own contexts, but allows the other ones to be referred to be
255 * a name known to this module:
256 *
257 * <pre class="code">&lt;?xml version="1.0" encoding="UTF-8"?>
258 * &lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
259 *
260 * &lt;beans>
261 *   &lt;!-- define an alias for "com.mycompany.myapp.services" -->
262 *   &lt;alias name="com.mycompany.myapp.services" alias="com.mycompany.myapp.mypackage"/&gt;
263 * &lt;/beans>
264 * </pre>
265 *
266 * @author Colin Sampaleanu
267 * @author Juergen Hoeller
268 * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator
269 * @see org.springframework.context.access.DefaultLocatorFactory
270 */
271public class SingletonBeanFactoryLocator implements BeanFactoryLocator {
272
273        private static final String DEFAULT_RESOURCE_LOCATION = "classpath*:beanRefFactory.xml";
274
275        protected static final Log logger = LogFactory.getLog(SingletonBeanFactoryLocator.class);
276
277        /** The keyed BeanFactory instances */
278        private static final Map<String, BeanFactoryLocator> instances = new HashMap<String, BeanFactoryLocator>();
279
280
281        /**
282         * Returns an instance which uses the default "classpath*:beanRefFactory.xml",
283         * as the name of the definition file(s). All resources returned by calling the
284         * current thread context ClassLoader's {@code getResources} method with
285         * this name will be combined to create a BeanFactory definition set.
286         * @return the corresponding BeanFactoryLocator instance
287         * @throws BeansException in case of factory loading failure
288         */
289        public static BeanFactoryLocator getInstance() throws BeansException {
290                return getInstance(null);
291        }
292
293        /**
294         * Returns an instance which uses the specified selector, as the name of the
295         * definition file(s). In the case of a name with a Spring 'classpath*:' prefix,
296         * or with no prefix, which is treated the same, the current thread context
297         * ClassLoader's {@code getResources} method will be called with this value
298         * to get all resources having that name. These resources will then be combined to
299         * form a definition. In the case where the name uses a Spring 'classpath:' prefix,
300         * or a standard URL prefix, then only one resource file will be loaded as the
301         * definition.
302         * @param selector the name of the resource(s) which will be read and
303         * combined to form the definition for the BeanFactoryLocator instance.
304         * Any such files must form a valid BeanFactory definition.
305         * @return the corresponding BeanFactoryLocator instance
306         * @throws BeansException in case of factory loading failure
307         */
308        public static BeanFactoryLocator getInstance(String selector) throws BeansException {
309                String resourceLocation = selector;
310                if (resourceLocation == null) {
311                        resourceLocation = DEFAULT_RESOURCE_LOCATION;
312                }
313
314                // For backwards compatibility, we prepend 'classpath*:' to the selector name if there
315                // is no other prefix (i.e. classpath*:, classpath:, or some URL prefix.
316                if (!ResourcePatternUtils.isUrl(resourceLocation)) {
317                        resourceLocation = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resourceLocation;
318                }
319
320                synchronized (instances) {
321                        if (logger.isTraceEnabled()) {
322                                logger.trace("SingletonBeanFactoryLocator.getInstance(): instances.hashCode=" +
323                                                instances.hashCode() + ", instances=" + instances);
324                        }
325                        BeanFactoryLocator bfl = instances.get(resourceLocation);
326                        if (bfl == null) {
327                                bfl = new SingletonBeanFactoryLocator(resourceLocation);
328                                instances.put(resourceLocation, bfl);
329                        }
330                        return bfl;
331                }
332        }
333
334
335        // We map BeanFactoryGroup objects by String keys, and by the definition object.
336        private final Map<String, BeanFactoryGroup> bfgInstancesByKey = new HashMap<String, BeanFactoryGroup>();
337
338        private final Map<BeanFactory, BeanFactoryGroup> bfgInstancesByObj = new HashMap<BeanFactory, BeanFactoryGroup>();
339
340        private final String resourceLocation;
341
342
343        /**
344         * Constructor which uses the specified name as the resource name
345         * of the definition file(s).
346         * @param resourceLocation the Spring resource location to use
347         * (either a URL or a "classpath:" / "classpath*:" pseudo URL)
348         */
349        protected SingletonBeanFactoryLocator(String resourceLocation) {
350                this.resourceLocation = resourceLocation;
351        }
352
353        @Override
354        public BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException {
355                synchronized (this.bfgInstancesByKey) {
356                        BeanFactoryGroup bfg = this.bfgInstancesByKey.get(this.resourceLocation);
357
358                        if (bfg != null) {
359                                bfg.refCount++;
360                        }
361                        else {
362                                // This group definition doesn't exist, we need to try to load it.
363                                if (logger.isTraceEnabled()) {
364                                        logger.trace("Factory group with resource name [" + this.resourceLocation +
365                                                        "] requested. Creating new instance.");
366                                }
367
368                                // Create the BeanFactory but don't initialize it.
369                                BeanFactory groupContext = createDefinition(this.resourceLocation, factoryKey);
370
371                                // Record its existence now, before instantiating any singletons.
372                                bfg = new BeanFactoryGroup();
373                                bfg.definition = groupContext;
374                                bfg.refCount = 1;
375                                this.bfgInstancesByKey.put(this.resourceLocation, bfg);
376                                this.bfgInstancesByObj.put(groupContext, bfg);
377
378                                // Now initialize the BeanFactory. This may cause a re-entrant invocation
379                                // of this method, but since we've already added the BeanFactory to our
380                                // mappings, the next time it will be found and simply have its
381                                // reference count incremented.
382                                try {
383                                        initializeDefinition(groupContext);
384                                }
385                                catch (BeansException ex) {
386                                        this.bfgInstancesByKey.remove(this.resourceLocation);
387                                        this.bfgInstancesByObj.remove(groupContext);
388                                        throw new BootstrapException("Unable to initialize group definition. " +
389                                                        "Group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]", ex);
390                                }
391                        }
392
393                        try {
394                                BeanFactory beanFactory;
395                                if (factoryKey != null) {
396                                        beanFactory = bfg.definition.getBean(factoryKey, BeanFactory.class);
397                                }
398                                else {
399                                        beanFactory = bfg.definition.getBean(BeanFactory.class);
400                                }
401                                return new CountingBeanFactoryReference(beanFactory, bfg.definition);
402                        }
403                        catch (BeansException ex) {
404                                throw new BootstrapException("Unable to return specified BeanFactory instance: factory key [" +
405                                                factoryKey + "], from group with resource name [" + this.resourceLocation + "]", ex);
406                        }
407
408                }
409        }
410
411        /**
412         * Actually creates definition in the form of a BeanFactory, given a resource name
413         * which supports standard Spring resource prefixes ('classpath:', 'classpath*:', etc.)
414         * This is split out as a separate method so that subclasses can override the actual
415         * type used (to be an ApplicationContext, for example).
416         * <p>The default implementation simply builds a
417         * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}
418         * and populates it using an
419         * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}.
420         * <p>This method should not instantiate any singletons. That function is performed
421         * by {@link #initializeDefinition initializeDefinition()}, which should also be
422         * overridden if this method is.
423         * @param resourceLocation the resource location for this factory group
424         * @param factoryKey the bean name of the factory to obtain
425         * @return the corresponding BeanFactory reference
426         */
427        protected BeanFactory createDefinition(String resourceLocation, String factoryKey) {
428                DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
429                XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
430                ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
431
432                try {
433                        Resource[] configResources = resourcePatternResolver.getResources(resourceLocation);
434                        if (configResources.length == 0) {
435                                throw new FatalBeanException("Unable to find resource for specified definition. " +
436                                                "Group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]");
437                        }
438                        reader.loadBeanDefinitions(configResources);
439                }
440                catch (IOException ex) {
441                        throw new BeanDefinitionStoreException(
442                                        "Error accessing bean definition resource [" + this.resourceLocation + "]", ex);
443                }
444                catch (BeanDefinitionStoreException ex) {
445                        throw new FatalBeanException("Unable to load group definition: " +
446                                        "group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]", ex);
447                }
448
449                return factory;
450        }
451
452        /**
453         * Instantiate singletons and do any other normal initialization of the factory.
454         * Subclasses that override {@link #createDefinition createDefinition()} should
455         * also override this method.
456         * @param groupDef the factory returned by {@link #createDefinition createDefinition()}
457         */
458        protected void initializeDefinition(BeanFactory groupDef) {
459                if (groupDef instanceof ConfigurableListableBeanFactory) {
460                        ((ConfigurableListableBeanFactory) groupDef).preInstantiateSingletons();
461                }
462        }
463
464        /**
465         * Destroy definition in separate method so subclass may work with other definition types.
466         * @param groupDef the factory returned by {@link #createDefinition createDefinition()}
467         * @param selector the resource location for this factory group
468         */
469        protected void destroyDefinition(BeanFactory groupDef, String selector) {
470                if (groupDef instanceof ConfigurableBeanFactory) {
471                        if (logger.isTraceEnabled()) {
472                                logger.trace("Factory group with selector '" + selector +
473                                                "' being released, as there are no more references to it");
474                        }
475                        ((ConfigurableBeanFactory) groupDef).destroySingletons();
476                }
477        }
478
479
480        /**
481         * We track BeanFactory instances with this class.
482         */
483        private static class BeanFactoryGroup {
484
485                private BeanFactory definition;
486
487                private int refCount = 0;
488        }
489
490
491        /**
492         * BeanFactoryReference implementation for this locator.
493         */
494        private class CountingBeanFactoryReference implements BeanFactoryReference {
495
496                private BeanFactory beanFactory;
497
498                private BeanFactory groupContextRef;
499
500                public CountingBeanFactoryReference(BeanFactory beanFactory, BeanFactory groupContext) {
501                        this.beanFactory = beanFactory;
502                        this.groupContextRef = groupContext;
503                }
504
505                @Override
506                public BeanFactory getFactory() {
507                        return this.beanFactory;
508                }
509
510                // Note that it's legal to call release more than once!
511                @Override
512                public void release() throws FatalBeanException {
513                        synchronized (bfgInstancesByKey) {
514                                BeanFactory savedRef = this.groupContextRef;
515                                if (savedRef != null) {
516                                        this.groupContextRef = null;
517                                        BeanFactoryGroup bfg = bfgInstancesByObj.get(savedRef);
518                                        if (bfg != null) {
519                                                bfg.refCount--;
520                                                if (bfg.refCount == 0) {
521                                                        destroyDefinition(savedRef, resourceLocation);
522                                                        bfgInstancesByKey.remove(resourceLocation);
523                                                        bfgInstancesByObj.remove(savedRef);
524                                                }
525                                        }
526                                        else {
527                                                // This should be impossible.
528                                                logger.warn("Tried to release a SingletonBeanFactoryLocator group definition " +
529                                                                "more times than it has actually been used. Resource name [" + resourceLocation + "]");
530                                        }
531                                }
532                        }
533                }
534        }
535
536}