001/*
002 * Copyright 2002-2018 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.web.context.support;
018
019import java.io.IOException;
020
021import groovy.lang.GroovyObject;
022import groovy.lang.GroovySystem;
023import groovy.lang.MetaClass;
024
025import org.springframework.beans.BeanWrapper;
026import org.springframework.beans.BeanWrapperImpl;
027import org.springframework.beans.BeansException;
028import org.springframework.beans.factory.NoSuchBeanDefinitionException;
029import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
030import org.springframework.beans.factory.support.DefaultListableBeanFactory;
031import org.springframework.lang.Nullable;
032
033/**
034 * {@link org.springframework.web.context.WebApplicationContext} implementation which takes
035 * its configuration from Groovy bean definition scripts and/or XML files, as understood by
036 * a {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader}.
037 * This is essentially the equivalent of
038 * {@link org.springframework.context.support.GenericGroovyApplicationContext}
039 * for a web environment.
040 *
041 * <p>By default, the configuration will be taken from "/WEB-INF/applicationContext.groovy"
042 * for the root context, and "/WEB-INF/test-servlet.groovy" for a context with the namespace
043 * "test-servlet" (like for a DispatcherServlet instance with the servlet-name "test").
044 *
045 * <p>The config location defaults can be overridden via the "contextConfigLocation"
046 * context-param of {@link org.springframework.web.context.ContextLoader} and servlet
047 * init-param of {@link org.springframework.web.servlet.FrameworkServlet}. Config locations
048 * can either denote concrete files like "/WEB-INF/context.groovy" or Ant-style patterns
049 * like "/WEB-INF/*-context.groovy" (see {@link org.springframework.util.PathMatcher}
050 * javadoc for pattern details). Note that ".xml" files will be parsed as XML content;
051 * all other kinds of resources will be parsed as Groovy scripts.
052 *
053 * <p>Note: In case of multiple config locations, later bean definitions will
054 * override ones defined in earlier loaded files. This can be leveraged to
055 * deliberately override certain bean definitions via an extra Groovy script.
056 *
057 * <p><b>For a WebApplicationContext that reads in a different bean definition format,
058 * create an analogous subclass of {@link AbstractRefreshableWebApplicationContext}.</b>
059 * Such a context implementation can be specified as "contextClass" context-param
060 * for ContextLoader or "contextClass" init-param for FrameworkServlet.
061 *
062 * @author Juergen Hoeller
063 * @since 4.1
064 * @see #setNamespace
065 * @see #setConfigLocations
066 * @see org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader
067 * @see org.springframework.web.context.ContextLoader#initWebApplicationContext
068 * @see org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
069 */
070public class GroovyWebApplicationContext extends AbstractRefreshableWebApplicationContext implements GroovyObject {
071
072        /** Default config location for the root context. */
073        public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.groovy";
074
075        /** Default prefix for building a config location for a namespace. */
076        public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
077
078        /** Default suffix for building a config location for a namespace. */
079        public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".groovy";
080
081
082        private final BeanWrapper contextWrapper = new BeanWrapperImpl(this);
083
084        private MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(getClass());
085
086
087        /**
088         * Loads the bean definitions via an GroovyBeanDefinitionReader.
089         * @see org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader
090         * @see #initBeanDefinitionReader
091         * @see #loadBeanDefinitions
092         */
093        @Override
094        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
095                // Create a new XmlBeanDefinitionReader for the given BeanFactory.
096                GroovyBeanDefinitionReader beanDefinitionReader = new GroovyBeanDefinitionReader(beanFactory);
097
098                // Configure the bean definition reader with this context's
099                // resource loading environment.
100                beanDefinitionReader.setEnvironment(getEnvironment());
101                beanDefinitionReader.setResourceLoader(this);
102
103                // Allow a subclass to provide custom initialization of the reader,
104                // then proceed with actually loading the bean definitions.
105                initBeanDefinitionReader(beanDefinitionReader);
106                loadBeanDefinitions(beanDefinitionReader);
107        }
108
109        /**
110         * Initialize the bean definition reader used for loading the bean
111         * definitions of this context. Default implementation is empty.
112         * <p>Can be overridden in subclasses.
113         * @param beanDefinitionReader the bean definition reader used by this context
114         */
115        protected void initBeanDefinitionReader(GroovyBeanDefinitionReader beanDefinitionReader) {
116        }
117
118        /**
119         * Load the bean definitions with the given GroovyBeanDefinitionReader.
120         * <p>The lifecycle of the bean factory is handled by the refreshBeanFactory method;
121         * therefore this method is just supposed to load and/or register bean definitions.
122         * <p>Delegates to a ResourcePatternResolver for resolving location patterns
123         * into Resource instances.
124         * @throws IOException if the required Groovy script or XML file isn't found
125         * @see #refreshBeanFactory
126         * @see #getConfigLocations
127         * @see #getResources
128         * @see #getResourcePatternResolver
129         */
130        protected void loadBeanDefinitions(GroovyBeanDefinitionReader reader) throws IOException {
131                String[] configLocations = getConfigLocations();
132                if (configLocations != null) {
133                        for (String configLocation : configLocations) {
134                                reader.loadBeanDefinitions(configLocation);
135                        }
136                }
137        }
138
139        /**
140         * The default location for the root context is "/WEB-INF/applicationContext.groovy",
141         * and "/WEB-INF/test-servlet.groovy" for a context with the namespace "test-servlet"
142         * (like for a DispatcherServlet instance with the servlet-name "test").
143         */
144        @Override
145        protected String[] getDefaultConfigLocations() {
146                if (getNamespace() != null) {
147                        return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
148                }
149                else {
150                        return new String[] {DEFAULT_CONFIG_LOCATION};
151                }
152        }
153
154
155        // Implementation of the GroovyObject interface
156
157        @Override
158        public void setMetaClass(MetaClass metaClass) {
159                this.metaClass = metaClass;
160        }
161
162        @Override
163        public MetaClass getMetaClass() {
164                return this.metaClass;
165        }
166
167        @Override
168        public Object invokeMethod(String name, Object args) {
169                return this.metaClass.invokeMethod(this, name, args);
170        }
171
172        @Override
173        public void setProperty(String property, Object newValue) {
174                this.metaClass.setProperty(this, property, newValue);
175        }
176
177        @Override
178        @Nullable
179        public Object getProperty(String property) {
180                if (containsBean(property)) {
181                        return getBean(property);
182                }
183                else if (this.contextWrapper.isReadableProperty(property)) {
184                        return this.contextWrapper.getPropertyValue(property);
185                }
186                throw new NoSuchBeanDefinitionException(property);
187        }
188
189}