001 /**
002 * Copyright 2010-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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 * http://www.opensource.org/licenses/ecl2.php
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 package org.kuali.common.util.service;
017
018 import java.io.File;
019 import java.util.ArrayList;
020 import java.util.Arrays;
021 import java.util.Collections;
022 import java.util.List;
023 import java.util.Map;
024
025 import org.kuali.common.util.CollectionUtils;
026 import org.kuali.common.util.LocationUtils;
027 import org.slf4j.Logger;
028 import org.slf4j.LoggerFactory;
029 import org.springframework.beans.factory.BeanFactoryUtils;
030 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
031 import org.springframework.context.ConfigurableApplicationContext;
032 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
033 import org.springframework.context.support.ClassPathXmlApplicationContext;
034 import org.springframework.context.support.GenericXmlApplicationContext;
035 import org.springframework.core.env.ConfigurableEnvironment;
036 import org.springframework.core.env.MutablePropertySources;
037 import org.springframework.core.env.PropertySource;
038 import org.springframework.util.Assert;
039
040 public class DefaultSpringService implements SpringService {
041
042 private static final Logger logger = LoggerFactory.getLogger(DefaultSpringService.class);
043
044 @Override
045 public List<PropertySource<?>> getPropertySources(String location) {
046 // Load the indicated location
047 ConfigurableApplicationContext context = new GenericXmlApplicationContext(location);
048
049 // Extract PropertySources (if any)
050 List<PropertySource<?>> sources = getPropertySources(context);
051
052 // Close the context
053 closeQuietly(context);
054
055 // Return the list
056 return sources;
057 }
058
059 @Override
060 public List<PropertySource<?>> getPropertySources(ConfigurableApplicationContext context) {
061
062 // Extract all beans that implement the PropertySource interface
063 @SuppressWarnings("rawtypes")
064 Map<String, PropertySource> map = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, PropertySource.class);
065
066 // Convert the Map to a List
067 List<PropertySource<?>> list = new ArrayList<PropertySource<?>>();
068 for (PropertySource<?> source : map.values()) {
069 list.add(source);
070 }
071
072 // Return the list
073 return list;
074 }
075
076 @Override
077 public void load(Class<?> annotatedClass) {
078 load(annotatedClass, null, null);
079 }
080
081 @Override
082 public void load(Class<?> annotatedClass, String beanName, Object bean, PropertySource<?> propertySource) {
083 // Make sure the location isn't empty
084 Assert.notNull(annotatedClass);
085
086 List<Class<?>> annotatedClasses = new ArrayList<Class<?>>();
087 annotatedClasses.add(annotatedClass);
088
089 List<PropertySource<?>> propertySources = new ArrayList<PropertySource<?>>();
090 if (propertySource != null) {
091 propertySources.add(propertySource);
092 }
093
094 // Setup a SpringContext
095 SpringContext context = new SpringContext();
096 context.setAnnotatedClasses(annotatedClasses);
097 context.setPropertySources(propertySources);
098
099 // Null safe handling for non-required parameters
100 context.setBeanNames(CollectionUtils.toEmptyList(beanName));
101 context.setBeans(CollectionUtils.toEmptyList(bean));
102
103 // Load the configuration from the annotated class
104 load(context);
105 }
106
107 @Override
108 public void load(Class<?> annotatedClass, String beanName, Object bean) {
109 load(annotatedClass, beanName, bean, null);
110 }
111
112 @Override
113 public void load(String location) {
114 load(location, null, null);
115 }
116
117 @Override
118 public void load(String location, String beanName, Object bean, PropertySource<?> propertySource) {
119 // Make sure the location isn't empty
120 Assert.hasText(location);
121
122 List<PropertySource<?>> propertySources = new ArrayList<PropertySource<?>>();
123 if (propertySource != null) {
124 propertySources.add(propertySource);
125 }
126
127 // Setup a SpringContext
128 SpringContext context = new SpringContext();
129 context.setLocations(Arrays.asList(location));
130 context.setPropertySources(propertySources);
131
132 // Null safe handling for non-required parameters
133 context.setBeanNames(CollectionUtils.toEmptyList(beanName));
134 context.setBeans(CollectionUtils.toEmptyList(bean));
135
136 // Load the location using a SpringContext
137 load(context);
138 }
139
140 @Override
141 public void load(String location, String beanName, Object bean) {
142 load(location, beanName, bean, null);
143 }
144
145 @Override
146 public void load(SpringContext context) {
147
148 // Null-safe handling for parameters
149 context.setBeanNames(CollectionUtils.toEmptyList(context.getBeanNames()));
150 context.setBeans(CollectionUtils.toEmptyList(context.getBeans()));
151 context.setAnnotatedClasses(CollectionUtils.toEmptyList(context.getAnnotatedClasses()));
152 context.setLocations(CollectionUtils.toEmptyList(context.getLocations()));
153
154 // Make sure we have at least one location or annotated class
155 boolean notEmpty = !CollectionUtils.isEmpty(context.getLocations()) || !CollectionUtils.isEmpty(context.getAnnotatedClasses());
156 Assert.isTrue(notEmpty, "Both locations and annotatedClasses are empty");
157
158 // Make sure we have a name for every bean
159 Assert.isTrue(context.getBeanNames().size() == context.getBeans().size());
160
161 // Make sure all of the locations exist
162 validate(context.getLocations());
163
164 // Convert any file names to fully qualified file system URL's
165 List<String> convertedLocations = getConvertedLocations(context.getLocations());
166
167 // The Spring classes prefer array's
168 String[] locationsArray = CollectionUtils.toStringArray(convertedLocations);
169
170 ConfigurableApplicationContext parent = null;
171 ConfigurableApplicationContext xmlChild = null;
172 AnnotationConfigApplicationContext annotationChild = null;
173 try {
174 if (isParentContextRequired(context)) {
175 // Construct a parent context if necessary
176 parent = getContextWithPreRegisteredBeans(context.getBeanNames(), context.getBeans());
177 }
178
179 if (!CollectionUtils.isEmpty(context.getAnnotatedClasses())) {
180 // Create an annotation based application context wrapped in a parent context
181 annotationChild = getAnnotationContext(context, parent);
182 // Add custom property sources (if any)
183 addPropertySources(context, annotationChild);
184
185 }
186
187 if (!CollectionUtils.isEmpty(context.getLocations())) {
188 // Create an XML application context wrapped in a parent context
189 xmlChild = new ClassPathXmlApplicationContext(locationsArray, false, parent);
190 // Add custom property sources (if any)
191 addPropertySources(context, xmlChild);
192 }
193
194 // Invoke refresh to load the context
195 refreshQuietly(annotationChild);
196 refreshQuietly(xmlChild);
197 } finally {
198 // cleanup
199 closeQuietly(annotationChild);
200 closeQuietly(xmlChild);
201 closeQuietly(parent);
202 }
203 }
204
205 protected AnnotationConfigApplicationContext getAnnotationContext(SpringContext context, ConfigurableApplicationContext parent) {
206 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
207 if (parent != null) {
208 ctx.setParent(parent);
209 }
210 for (Class<?> annotatedClass : context.getAnnotatedClasses()) {
211 ctx.register(annotatedClass);
212 }
213 return ctx;
214 }
215
216 protected void refreshQuietly(ConfigurableApplicationContext context) {
217 if (context != null) {
218 context.refresh();
219 }
220 }
221
222 /**
223 * Null safe close for a context
224 */
225 protected void closeQuietly(ConfigurableApplicationContext context) {
226 if (context != null) {
227 context.close();
228 }
229 }
230
231 /**
232 * Return an <code>AbstractApplicationContext</code> with <code>beans</code> and <code>PropertySource's</code> registered as dictated by the <code>SpringContext</code>
233 */
234 @Override
235 public ConfigurableApplicationContext getContextWithPreRegisteredBeans(List<String> beanNames, List<Object> beans) {
236 Assert.isTrue(beanNames.size() == beans.size());
237 GenericXmlApplicationContext appContext = new GenericXmlApplicationContext();
238 appContext.refresh();
239 ConfigurableListableBeanFactory factory = appContext.getBeanFactory();
240 for (int i = 0; i < beanNames.size(); i++) {
241 String beanName = beanNames.get(i);
242 Object bean = beans.get(i);
243 logger.debug("Registering bean - [{}] -> [{}]", beanName, bean.getClass().getName());
244 factory.registerSingleton(beanName, bean);
245 }
246 return appContext;
247 }
248
249 @Override
250 public ConfigurableApplicationContext getContextWithPreRegisteredBean(String beanName, Object bean) {
251 return getContextWithPreRegisteredBeans(Arrays.asList(beanName), Arrays.asList(bean));
252 }
253
254 protected void addPropertySources(SpringContext context, ConfigurableApplicationContext applicationContext) {
255 if (CollectionUtils.isEmpty(context.getPropertySources())) {
256 return;
257 }
258 List<PropertySource<?>> propertySources = context.getPropertySources();
259 ConfigurableEnvironment environment = applicationContext.getEnvironment();
260 MutablePropertySources sources = environment.getPropertySources();
261 if (context.isLastOneInWins()) {
262 Collections.reverse(propertySources);
263 }
264 for (PropertySource<?> propertySource : propertySources) {
265 logger.debug("Adding property source - [{}] -> [{}]", propertySource.getName(), propertySource.getClass().getName());
266 sources.addLast(propertySource);
267 }
268 }
269
270 /**
271 * Return true if the context contains any beans or beanNames, false otherwise.
272 */
273 protected boolean isParentContextRequired(SpringContext context) {
274 if (!CollectionUtils.isEmpty(context.getBeanNames())) {
275 return true;
276 } else if (!CollectionUtils.isEmpty(context.getBeans())) {
277 return true;
278 } else {
279 return false;
280 }
281 }
282
283 /**
284 * Make sure all of the locations actually exist
285 */
286 protected void validate(List<String> locations) {
287 StringBuilder sb = new StringBuilder();
288 for (String location : locations) {
289 if (!LocationUtils.exists(location)) {
290 sb.append("Location [" + location + "] does not exist\n");
291 }
292 }
293 if (sb.length() > 0) {
294 throw new IllegalArgumentException(sb.toString());
295 }
296 }
297
298 /**
299 * Format file names into fully qualified file system URL's
300 */
301 protected List<String> getConvertedLocations(List<String> locations) {
302 List<String> converted = new ArrayList<String>();
303 for (String location : locations) {
304 if (LocationUtils.isExistingFile(location)) {
305 File file = new File(location);
306 // ClassPathXmlApplicationContext needs a fully qualified URL, not a filename
307 String url = LocationUtils.getCanonicalURLString(file);
308 converted.add(url);
309 } else {
310 converted.add(location);
311 }
312 }
313 return converted;
314 }
315
316 }