001/*
002 * Copyright 2002-2014 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.context.support;
018
019import groovy.lang.GroovyObject;
020import groovy.lang.GroovySystem;
021import groovy.lang.MetaClass;
022
023import org.springframework.beans.BeanWrapper;
024import org.springframework.beans.BeanWrapperImpl;
025import org.springframework.beans.factory.NoSuchBeanDefinitionException;
026import org.springframework.beans.factory.config.BeanDefinition;
027import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
028import org.springframework.core.env.ConfigurableEnvironment;
029import org.springframework.core.io.ClassPathResource;
030import org.springframework.core.io.Resource;
031
032/**
033 * An {@link org.springframework.context.ApplicationContext} implementation that extends
034 * {@link GenericApplicationContext} and implements {@link GroovyObject} such that beans
035 * can be retrieved with the dot de-reference syntax instead of using {@link #getBean}.
036 *
037 * <p>Consider this as the equivalent of {@link GenericXmlApplicationContext} for
038 * Groovy bean definitions, or even an upgrade thereof since it seamlessly understands
039 * XML bean definition files as well. The main difference is that, within a Groovy
040 * script, the context can be used with an inline bean definition closure as follows:
041 *
042 * <pre class="code">
043 * import org.hibernate.SessionFactory
044 * import org.apache.commons.dbcp.BasicDataSource
045 *
046 * def context = new GenericGroovyApplicationContext()
047 * context.reader.beans {
048 *     dataSource(BasicDataSource) {                  // <--- invokeMethod
049 *         driverClassName = "org.hsqldb.jdbcDriver"
050 *         url = "jdbc:hsqldb:mem:grailsDB"
051 *         username = "sa"                            // <-- setProperty
052 *         password = ""
053 *         settings = [mynew:"setting"]
054 *     }
055 *     sessionFactory(SessionFactory) {
056 *         dataSource = dataSource                    // <-- getProperty for retrieving references
057 *     }
058 *     myService(MyService) {
059 *         nestedBean = { AnotherBean bean ->         // <-- setProperty with closure for nested bean
060 *             dataSource = dataSource
061 *         }
062 *     }
063 * }
064 * context.refresh()
065 * </pre>
066 *
067 * <p>Alternatively, load a Groovy bean definition script like the following
068 * from an external resource (e.g. an "applicationContext.groovy" file):
069 *
070 * <pre class="code">
071 * import org.hibernate.SessionFactory
072 * import org.apache.commons.dbcp.BasicDataSource
073 *
074 * beans {
075 *     dataSource(BasicDataSource) {
076 *         driverClassName = "org.hsqldb.jdbcDriver"
077 *         url = "jdbc:hsqldb:mem:grailsDB"
078 *         username = "sa"
079 *         password = ""
080 *         settings = [mynew:"setting"]
081 *     }
082 *     sessionFactory(SessionFactory) {
083 *         dataSource = dataSource
084 *     }
085 *     myService(MyService) {
086 *         nestedBean = { AnotherBean bean ->
087 *             dataSource = dataSource
088 *         }
089 *     }
090 * }
091 * </pre>
092 *
093 * <p>With the following Java code creating the {@code GenericGroovyApplicationContext}
094 * (potentially using Ant-style '*'/'**' location patterns):
095 *
096 * <pre class="code">
097 * GenericGroovyApplicationContext context = new GenericGroovyApplicationContext();
098 * context.load("org/myapp/applicationContext.groovy");
099 * context.refresh();
100 * </pre>
101 *
102 * <p>Or even more concise, provided that no extra configuration is needed:
103 *
104 * <pre class="code">
105 * ApplicationContext context = new GenericGroovyApplicationContext("org/myapp/applicationContext.groovy");
106 * </pre>
107 *
108 * <p><b>This application context also understands XML bean definition files,
109 * allowing for seamless mixing and matching with Groovy bean definition files.</b>
110 * ".xml" files will be parsed as XML content; all other kinds of resources will
111 * be parsed as Groovy scripts.
112 *
113 * @author Juergen Hoeller
114 * @author Jeff Brown
115 * @since 4.0
116 * @see org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader
117 */
118public class GenericGroovyApplicationContext extends GenericApplicationContext implements GroovyObject {
119
120        private final GroovyBeanDefinitionReader reader = new GroovyBeanDefinitionReader(this);
121
122        private final BeanWrapper contextWrapper = new BeanWrapperImpl(this);
123
124    private MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(getClass());
125
126
127        /**
128         * Create a new GenericGroovyApplicationContext that needs to be
129         * {@link #load loaded} and then manually {@link #refresh refreshed}.
130         */
131        public GenericGroovyApplicationContext() {
132        }
133
134        /**
135         * Create a new GenericGroovyApplicationContext, loading bean definitions
136         * from the given resources and automatically refreshing the context.
137         * @param resources the resources to load from
138         */
139        public GenericGroovyApplicationContext(Resource... resources) {
140                load(resources);
141                refresh();
142        }
143
144        /**
145         * Create a new GenericGroovyApplicationContext, loading bean definitions
146         * from the given resource locations and automatically refreshing the context.
147         * @param resourceLocations the resources to load from
148         */
149        public GenericGroovyApplicationContext(String... resourceLocations) {
150                load(resourceLocations);
151                refresh();
152        }
153
154        /**
155         * Create a new GenericGroovyApplicationContext, loading bean definitions
156         * from the given resource locations and automatically refreshing the context.
157         * @param relativeClass class whose package will be used as a prefix when
158         * loading each specified resource name
159         * @param resourceNames relatively-qualified names of resources to load
160         */
161        public GenericGroovyApplicationContext(Class<?> relativeClass, String... resourceNames) {
162                load(relativeClass, resourceNames);
163                refresh();
164        }
165
166
167        /**
168         * Exposes the underlying {@link GroovyBeanDefinitionReader} for convenient access
169         * to the {@code loadBeanDefinition} methods on it as well as the ability
170         * to specify an inline Groovy bean definition closure.
171         * @see GroovyBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource...)
172         * @see GroovyBeanDefinitionReader#loadBeanDefinitions(String...)
173         */
174        public final GroovyBeanDefinitionReader getReader() {
175                return this.reader;
176        }
177
178        /**
179         * Delegates the given environment to underlying {@link GroovyBeanDefinitionReader}.
180         * Should be called before any call to {@code #load}.
181         */
182        @Override
183        public void setEnvironment(ConfigurableEnvironment environment) {
184                super.setEnvironment(environment);
185                this.reader.setEnvironment(getEnvironment());
186        }
187
188        /**
189         * Load bean definitions from the given Groovy scripts or XML files.
190         * <p>Note that ".xml" files will be parsed as XML content; all other kinds
191         * of resources will be parsed as Groovy scripts.
192         * @param resources one or more resources to load from
193         */
194        public void load(Resource... resources) {
195                this.reader.loadBeanDefinitions(resources);
196        }
197
198        /**
199         * Load bean definitions from the given Groovy scripts or XML files.
200         * <p>Note that ".xml" files will be parsed as XML content; all other kinds
201         * of resources will be parsed as Groovy scripts.
202         * @param resourceLocations one or more resource locations to load from
203         */
204        public void load(String... resourceLocations) {
205                this.reader.loadBeanDefinitions(resourceLocations);
206        }
207
208        /**
209         * Load bean definitions from the given Groovy scripts or XML files.
210         * <p>Note that ".xml" files will be parsed as XML content; all other kinds
211         * of resources will be parsed as Groovy scripts.
212         * @param relativeClass class whose package will be used as a prefix when
213         * loading each specified resource name
214         * @param resourceNames relatively-qualified names of resources to load
215         */
216        public void load(Class<?> relativeClass, String... resourceNames) {
217                Resource[] resources = new Resource[resourceNames.length];
218                for (int i = 0; i < resourceNames.length; i++) {
219                        resources[i] = new ClassPathResource(resourceNames[i], relativeClass);
220                }
221                load(resources);
222        }
223
224
225        // Implementation of the GroovyObject interface
226
227        public void setMetaClass(MetaClass metaClass) {
228                this.metaClass = metaClass;
229        }
230
231    public MetaClass getMetaClass() {
232                return this.metaClass;
233        }
234
235        public Object invokeMethod(String name, Object args) {
236                return this.metaClass.invokeMethod(this, name, args);
237        }
238
239        public void setProperty(String property, Object newValue) {
240                if (newValue instanceof BeanDefinition) {
241                        registerBeanDefinition(property, (BeanDefinition) newValue);
242                }
243                else {
244                        this.metaClass.setProperty(this, property, newValue);
245                }
246        }
247
248    public Object getProperty(String property) {
249                if (containsBean(property)) {
250                        return getBean(property);
251                }
252                else if (this.contextWrapper.isReadableProperty(property)) {
253                        return this.contextWrapper.getPropertyValue(property);
254                }
255                throw new NoSuchBeanDefinitionException(property);
256        }
257
258}