View Javadoc

1   /**
2    * Copyright 2010-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.common.util.service;
17  
18  import java.io.File;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.List;
23  
24  import org.apache.commons.lang3.StringUtils;
25  import org.kuali.common.util.Assert;
26  import org.kuali.common.util.CollectionUtils;
27  import org.kuali.common.util.LocationUtils;
28  import org.kuali.common.util.spring.SpringUtils;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  import org.springframework.context.ApplicationContext;
32  import org.springframework.context.ConfigurableApplicationContext;
33  import org.springframework.context.annotation.AnnotationConfigApplicationContext;
34  import org.springframework.context.support.ClassPathXmlApplicationContext;
35  import org.springframework.core.env.ConfigurableEnvironment;
36  import org.springframework.core.env.MutablePropertySources;
37  import org.springframework.core.env.PropertySource;
38  
39  public class DefaultSpringService implements SpringService {
40  
41  	private static final Logger logger = LoggerFactory.getLogger(DefaultSpringService.class);
42  
43  	@Override
44  	public void load(Class<?> annotatedClass) {
45  		load(annotatedClass, null, null);
46  	}
47  
48  	@Override
49  	public void load(Class<?> annotatedClass, String beanName, Object bean, PropertySource<?> propertySource) {
50  		// Make sure the annotatedClass isn't null
51  		Assert.notNull(annotatedClass, "annotatedClass is null");
52  
53  		// Setup a SpringContext
54  		SpringContext context = new SpringContext();
55  		context.setAnnotatedClasses(CollectionUtils.asList(annotatedClass));
56  		context.setPropertySourceContext(new PropertySourceContext(SpringUtils.asList(propertySource)));
57  
58  		// Null safe handling for non-required parameters
59  		context.setBeanNames(CollectionUtils.toEmptyList(beanName));
60  		context.setBeans(CollectionUtils.toEmptyList(bean));
61  
62  		// Load the configuration from the annotated class
63  		load(context);
64  	}
65  
66  	@Override
67  	public void load(Class<?> annotatedClass, String beanName, Object bean) {
68  		load(annotatedClass, beanName, bean, null);
69  	}
70  
71  	@Override
72  	public void load(String location) {
73  		load(location, null, null);
74  	}
75  
76  	@Override
77  	public void load(String location, String beanName, Object bean, PropertySource<?> propertySource) {
78  		// Make sure the location isn't empty
79  		Assert.hasText(location, "location is null");
80  
81  		// Setup a SpringContext
82  		SpringContext context = new SpringContext();
83  		context.setLocations(Arrays.asList(location));
84  		context.setPropertySourceContext(new PropertySourceContext(SpringUtils.asList(propertySource)));
85  
86  		// Null safe handling for non-required parameters
87  		context.setBeanNames(CollectionUtils.toEmptyList(beanName));
88  		context.setBeans(CollectionUtils.toEmptyList(bean));
89  
90  		// Load the location using a SpringContext
91  		load(context);
92  	}
93  
94  	@Override
95  	public void load(String location, String beanName, Object bean) {
96  		load(location, beanName, bean, null);
97  	}
98  
99  	@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 }