001 /**
002 * Copyright 2010-2013 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
024 import org.apache.commons.lang3.StringUtils;
025 import org.kuali.common.util.Assert;
026 import org.kuali.common.util.CollectionUtils;
027 import org.kuali.common.util.LocationUtils;
028 import org.kuali.common.util.spring.SpringUtils;
029 import org.slf4j.Logger;
030 import org.slf4j.LoggerFactory;
031 import org.springframework.context.ApplicationContext;
032 import org.springframework.context.ConfigurableApplicationContext;
033 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
034 import org.springframework.context.support.ClassPathXmlApplicationContext;
035 import org.springframework.core.env.ConfigurableEnvironment;
036 import org.springframework.core.env.MutablePropertySources;
037 import org.springframework.core.env.PropertySource;
038
039 public class DefaultSpringService implements SpringService {
040
041 private static final Logger logger = LoggerFactory.getLogger(DefaultSpringService.class);
042
043 @Override
044 public void load(Class<?> annotatedClass) {
045 load(annotatedClass, null, null);
046 }
047
048 @Override
049 public void load(Class<?> annotatedClass, String beanName, Object bean, PropertySource<?> propertySource) {
050 // Make sure the annotatedClass isn't null
051 Assert.notNull(annotatedClass, "annotatedClass is null");
052
053 // Setup a SpringContext
054 SpringContext context = new SpringContext();
055 context.setAnnotatedClasses(CollectionUtils.asList(annotatedClass));
056 context.setPropertySourceContext(new PropertySourceContext(SpringUtils.asList(propertySource)));
057
058 // Null safe handling for non-required parameters
059 context.setBeanNames(CollectionUtils.toEmptyList(beanName));
060 context.setBeans(CollectionUtils.toEmptyList(bean));
061
062 // Load the configuration from the annotated class
063 load(context);
064 }
065
066 @Override
067 public void load(Class<?> annotatedClass, String beanName, Object bean) {
068 load(annotatedClass, beanName, bean, null);
069 }
070
071 @Override
072 public void load(String location) {
073 load(location, null, null);
074 }
075
076 @Override
077 public void load(String location, String beanName, Object bean, PropertySource<?> propertySource) {
078 // Make sure the location isn't empty
079 Assert.hasText(location, "location is null");
080
081 // Setup a SpringContext
082 SpringContext context = new SpringContext();
083 context.setLocations(Arrays.asList(location));
084 context.setPropertySourceContext(new PropertySourceContext(SpringUtils.asList(propertySource)));
085
086 // Null safe handling for non-required parameters
087 context.setBeanNames(CollectionUtils.toEmptyList(beanName));
088 context.setBeans(CollectionUtils.toEmptyList(bean));
089
090 // Load the location using a SpringContext
091 load(context);
092 }
093
094 @Override
095 public void load(String location, String beanName, Object bean) {
096 load(location, beanName, bean, null);
097 }
098
099 @Override
100 public void load(SpringContext context) {
101
102 // Null-safe handling for parameters
103 context.setBeanNames(CollectionUtils.toEmptyList(context.getBeanNames()));
104 context.setBeans(CollectionUtils.toEmptyList(context.getBeans()));
105 context.setAnnotatedClasses(CollectionUtils.toEmptyList(context.getAnnotatedClasses()));
106 context.setLocations(CollectionUtils.toEmptyList(context.getLocations()));
107
108 // Make sure we have at least one location or annotated class
109 boolean empty = CollectionUtils.isEmpty(context.getLocations()) && CollectionUtils.isEmpty(context.getAnnotatedClasses());
110 Assert.isFalse(empty, "Both locations and annotatedClasses are empty");
111
112 // Make sure we have a name for every bean
113 Assert.isTrue(context.getBeanNames().size() == context.getBeans().size());
114
115 // Make sure all of the locations exist
116 SpringUtils.validateExists(context.getLocations());
117
118 // Convert any file names to fully qualified file system URL's
119 List<String> convertedLocations = getConvertedLocations(context.getLocations());
120
121 // The Spring classes prefer array's
122 String[] locationsArray = CollectionUtils.toStringArray(convertedLocations);
123
124 ConfigurableApplicationContext parent = null;
125 ClassPathXmlApplicationContext xmlChild = null;
126 AnnotationConfigApplicationContext annotationChild = null;
127 try {
128 if (isParentContextRequired(context)) {
129 // Construct a parent context if necessary
130 parent = SpringUtils.getContextWithPreRegisteredBeans(context.getId(), context.getDisplayName(), context.getBeanNames(), context.getBeans());
131 }
132
133 if (!CollectionUtils.isEmpty(context.getAnnotatedClasses())) {
134 // Create an annotation based application context wrapped in a parent context
135 annotationChild = getAnnotationContext(context, parent);
136 // Add custom property sources (if any)
137 addPropertySources(context, annotationChild);
138
139 }
140
141 if (!CollectionUtils.isEmpty(context.getLocations())) {
142 // Create an XML application context wrapped in a parent context
143 xmlChild = new ClassPathXmlApplicationContext(locationsArray, false, parent);
144 if (parent == null) {
145 addMetaInfo(xmlChild, context);
146 }
147 // Add custom property sources (if any)
148 addPropertySources(context, xmlChild);
149 }
150
151 // Invoke refresh to load the context
152 SpringUtils.refreshQuietly(annotationChild);
153 SpringUtils.refreshQuietly(xmlChild);
154 debugQuietly(parent, annotationChild, xmlChild);
155 } finally {
156 // cleanup
157 // closeQuietly(annotationChild);
158 // closeQuietly(xmlChild);
159 // closeQuietly(parent);
160 }
161 }
162
163 protected void debugQuietly(ApplicationContext parent, ApplicationContext child1, ApplicationContext child2) {
164 if (!logger.isDebugEnabled()) {
165 return;
166 }
167 if (parent != null) {
168 SpringUtils.debug(parent);
169 } else {
170 if (child1 != null) {
171 SpringUtils.debug(child1);
172 }
173 if (child2 != null) {
174 SpringUtils.debug(child2);
175 }
176 }
177 }
178
179 /**
180 * Add id and display name to the ApplicationContext if they are not blank
181 */
182 protected void addMetaInfo(AnnotationConfigApplicationContext ctx, SpringContext sc) {
183 if (!StringUtils.isBlank(sc.getId())) {
184 ctx.setId(sc.getId());
185 }
186 if (!StringUtils.isBlank(sc.getDisplayName())) {
187 ctx.setDisplayName(sc.getDisplayName());
188 }
189 }
190
191 /**
192 * Add id and display name to the ApplicationContext if they are not blank
193 */
194 protected void addMetaInfo(ClassPathXmlApplicationContext ctx, SpringContext sc) {
195 if (!StringUtils.isBlank(sc.getId())) {
196 ctx.setId(sc.getId());
197 }
198 if (!StringUtils.isBlank(sc.getDisplayName())) {
199 ctx.setDisplayName(sc.getDisplayName());
200 }
201 }
202
203 protected AnnotationConfigApplicationContext getAnnotationContext(SpringContext context, ConfigurableApplicationContext parent) {
204 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
205 if (parent != null) {
206 ctx.setParent(parent);
207 } else {
208 addMetaInfo(ctx, context);
209 }
210 for (Class<?> annotatedClass : context.getAnnotatedClasses()) {
211 ctx.register(annotatedClass);
212 }
213 return ctx;
214 }
215
216 protected void addPropertySources(SpringContext context, ConfigurableApplicationContext applicationContext) {
217 PropertySourceContext psc = context.getPropertySourceContext();
218 ConfigurableEnvironment env = applicationContext.getEnvironment();
219 if (psc.isRemoveExistingSources()) {
220 logger.debug("Removing all existing property sources");
221 SpringUtils.removeAllPropertySources(env);
222 }
223
224 if (CollectionUtils.isEmpty(psc.getSources())) {
225 return;
226 }
227 List<PropertySource<?>> propertySources = psc.getSources();
228 MutablePropertySources sources = env.getPropertySources();
229 if (psc.isLastOneInWins()) {
230 Collections.reverse(propertySources);
231 }
232 PropertySourceAddPriority priority = psc.getPriority();
233 for (PropertySource<?> propertySource : propertySources) {
234 Object[] args = { propertySource.getName(), propertySource.getClass().getName(), priority };
235 logger.debug("Adding property source - [{}] -> [{}] Priority=[{}]", args);
236 switch (priority) {
237 case FIRST:
238 sources.addFirst(propertySource);
239 break;
240 case LAST:
241 sources.addLast(propertySource);
242 break;
243 default:
244 throw new IllegalStateException(priority + " is an unknown priority");
245 }
246 }
247 }
248
249 /**
250 * Return true if the context contains any beans or beanNames, false otherwise.
251 */
252 protected boolean isParentContextRequired(SpringContext context) {
253 if (!CollectionUtils.isEmpty(context.getBeanNames())) {
254 return true;
255 } else if (!CollectionUtils.isEmpty(context.getBeans())) {
256 return true;
257 } else {
258 return false;
259 }
260 }
261
262 /**
263 * Convert any locations representing an existing file into a fully qualified file system url. Leave any locations that do not resolve to an existing file alone.
264 */
265 protected List<String> getConvertedLocations(List<String> locations) {
266 List<String> converted = new ArrayList<String>();
267 for (String location : locations) {
268 if (LocationUtils.isExistingFile(location)) {
269 File file = new File(location);
270 // ClassPathXmlApplicationContext needs a fully qualified URL, not a filename
271 String url = LocationUtils.getCanonicalURLString(file);
272 converted.add(url);
273 } else {
274 converted.add(location);
275 }
276 }
277 return converted;
278 }
279
280 }