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.spring.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  import java.util.Map;
24  
25  import org.apache.commons.lang3.StringUtils;
26  import org.kuali.common.util.Assert;
27  import org.kuali.common.util.CollectionUtils;
28  import org.kuali.common.util.LocationUtils;
29  import org.kuali.common.util.spring.PropertySourceUtils;
30  import org.kuali.common.util.spring.SpringUtils;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  import org.springframework.context.ConfigurableApplicationContext;
34  import org.springframework.context.annotation.AnnotationConfigApplicationContext;
35  import org.springframework.context.support.AbstractApplicationContext;
36  import org.springframework.context.support.ClassPathXmlApplicationContext;
37  import org.springframework.core.env.ConfigurableEnvironment;
38  import org.springframework.core.env.MutablePropertySources;
39  import org.springframework.core.env.PropertySource;
40  
41  /**
42   * This service must be stateless
43   */
44  public class DefaultSpringService implements SpringService {
45  
46  	private static final Logger logger = LoggerFactory.getLogger(DefaultSpringService.class);
47  
48  	@Override
49  	public void load(Class<?> annotatedClass, Map<String, Object> contextBeans) {
50  		load(annotatedClass, contextBeans, null);
51  	}
52  
53  	@Override
54  	public void load(Class<?> annotatedClass, Map<String, Object> contextBeans, PropertySource<?> propertySource) {
55  		// Make sure the annotatedClass isn't null
56  		Assert.notNull(annotatedClass, "annotatedClass is null");
57  
58  		// Setup a SpringContext
59  		SpringContext context = new SpringContext();
60  		context.setAnnotatedClasses(CollectionUtils.asList(annotatedClass));
61  		context.setPropertySourceContext(new PropertySourceContext(PropertySourceUtils.asList(propertySource)));
62  
63  		// Null safe handling for non-required parameters
64  		context.setContextBeans(CollectionUtils.toEmptyMap(contextBeans));
65  
66  		// Load the context
67  		load(context);
68  	}
69  
70  	@Override
71  	public void load(String location, Map<String, Object> contextBeans) {
72  		load(location, contextBeans, null);
73  	}
74  
75  	@SuppressWarnings("deprecation")
76  	protected Map<String, Object> getContextBeans(SpringContext context) {
77  		// Null-safe handling for parameters
78  		Map<String, Object> contextBeans = CollectionUtils.toModifiableEmptyMap(context.getContextBeans());
79  		CollectionUtils.combine(contextBeans, context.getBeanNames(), context.getBeans());
80  		return contextBeans;
81  	}
82  
83  	protected AbstractApplicationContext getXmlChild(String[] locationsArray, AbstractApplicationContext parent, SpringContext context) {
84  		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(locationsArray, false, parent);
85  		if (parent == null) {
86  			addMetaInfo(ctx, context);
87  		}
88  		return ctx;
89  	}
90  
91  	@Override
92  	public ConfigurableApplicationContext getApplicationContext(SpringContext context) {
93  		// Null-safe handling for optional parameters
94  		context.setContextBeans(getContextBeans(context));
95  		context.setAnnotatedClasses(CollectionUtils.toEmptyList(context.getAnnotatedClasses()));
96  		context.setLocations(CollectionUtils.toEmptyList(context.getLocations()));
97  		context.setActiveProfiles(CollectionUtils.toEmptyList(context.getActiveProfiles()));
98  		context.setDefaultProfiles(CollectionUtils.toEmptyList(context.getDefaultProfiles()));
99  
100 		// Make sure we have at least one location or annotated class
101 		boolean empty = CollectionUtils.isEmpty(context.getLocations()) && CollectionUtils.isEmpty(context.getAnnotatedClasses());
102 		Assert.isFalse(empty, "Both locations and annotatedClasses are empty");
103 
104 		// Make sure all of the locations exist
105 		LocationUtils.validateExists(context.getLocations());
106 
107 		// Convert any file names to fully qualified file system URL's
108 		List<String> convertedLocations = getConvertedLocations(context.getLocations());
109 
110 		// The Spring classes prefer array's
111 		String[] locationsArray = CollectionUtils.toStringArray(convertedLocations);
112 
113 		AbstractApplicationContext parent = null;
114 
115 		// Construct a parent context if necessary
116 		if (!CollectionUtils.isEmpty(context.getContextBeans())) {
117 			parent = SpringUtils.getContextWithPreRegisteredBeans(context.getId(), context.getDisplayName(), context.getContextBeans());
118 		}
119 
120 		AbstractApplicationContext child = null;
121 		if (!CollectionUtils.isEmpty(context.getAnnotatedClasses())) {
122 			child = getAnnotationContext(context, parent);
123 		} else if (!CollectionUtils.isEmpty(context.getLocations())) {
124 			child = getXmlChild(locationsArray, parent, context);
125 		} else {
126 			throw new IllegalStateException("Must provide either annotated classes or locations");
127 		}
128 
129 		// Add custom property sources (if any)
130 		addPropertySources(context, child);
131 
132 		// Set default profiles (if any)
133 		setDefaultProfiles(child, context.getDefaultProfiles());
134 
135 		// Set active profiles (if any)
136 		setActiveProfiles(child, context.getActiveProfiles());
137 
138 		// Return the fully configured context
139 		return child;
140 	}
141 
142 	@Override
143 	public void load(SpringContext context) {
144 		ConfigurableApplicationContext ctx = getApplicationContext(context);
145 		try {
146 			ctx.refresh();
147 			SpringUtils.debugQuietly(ctx);
148 		} finally {
149 			SpringUtils.closeQuietly(ctx);
150 		}
151 	}
152 
153 	@Override
154 	public void load(String location, Map<String, Object> contextBeans, PropertySource<?> propertySource) {
155 		// Make sure the location isn't empty
156 		Assert.hasText(location, "location is null");
157 
158 		// Setup a SpringContext
159 		SpringContext context = new SpringContext();
160 		context.setLocations(Arrays.asList(location));
161 		context.setPropertySourceContext(new PropertySourceContext(PropertySourceUtils.asList(propertySource)));
162 
163 		// Null safe handling for non-required parameters
164 		context.setContextBeans(CollectionUtils.toEmptyMap(contextBeans));
165 
166 		// Load the location using a SpringContext
167 		load(context);
168 	}
169 
170 	@Override
171 	public void load(Class<?> annotatedClass) {
172 		load(annotatedClass, (String) null, (Object) null);
173 	}
174 
175 	@Override
176 	public void load(String location) {
177 		load(location, (String) null, (Object) null);
178 	}
179 
180 	/**
181 	 * Add id and display name to the ApplicationContext if they are not blank
182 	 */
183 	protected void addMetaInfo(AnnotationConfigApplicationContext ctx, SpringContext sc) {
184 		if (!StringUtils.isBlank(sc.getId())) {
185 			ctx.setId(sc.getId());
186 		}
187 		if (!StringUtils.isBlank(sc.getDisplayName())) {
188 			ctx.setDisplayName(sc.getDisplayName());
189 		}
190 	}
191 
192 	/**
193 	 * Add id and display name to the ApplicationContext if they are not blank
194 	 */
195 	protected void addMetaInfo(ClassPathXmlApplicationContext ctx, SpringContext sc) {
196 		if (!StringUtils.isBlank(sc.getId())) {
197 			ctx.setId(sc.getId());
198 		}
199 		if (!StringUtils.isBlank(sc.getDisplayName())) {
200 			ctx.setDisplayName(sc.getDisplayName());
201 		}
202 	}
203 
204 	protected AbstractApplicationContext getAnnotationContext(SpringContext context, ConfigurableApplicationContext parent) {
205 		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
206 		if (parent != null) {
207 			ctx.setParent(parent);
208 		} else {
209 			addMetaInfo(ctx, context);
210 		}
211 		for (Class<?> annotatedClass : context.getAnnotatedClasses()) {
212 			ctx.register(annotatedClass);
213 		}
214 		return ctx;
215 	}
216 
217 	protected void setActiveProfiles(ConfigurableApplicationContext applicationContext, List<String> activeProfiles) {
218 		if (!CollectionUtils.isEmpty(activeProfiles)) {
219 			ConfigurableEnvironment env = applicationContext.getEnvironment();
220 			env.setActiveProfiles(CollectionUtils.toStringArray(activeProfiles));
221 		}
222 	}
223 
224 	protected void setDefaultProfiles(ConfigurableApplicationContext applicationContext, List<String> defaultProfiles) {
225 		if (!CollectionUtils.isEmpty(defaultProfiles)) {
226 			ConfigurableEnvironment env = applicationContext.getEnvironment();
227 			env.setDefaultProfiles(CollectionUtils.toStringArray(defaultProfiles));
228 		}
229 	}
230 
231 	protected void addPropertySources(SpringContext context, ConfigurableApplicationContext applicationContext) {
232 		PropertySourceContext psc = context.getPropertySourceContext();
233 		if (psc == null) {
234 			return;
235 		}
236 		ConfigurableEnvironment env = applicationContext.getEnvironment();
237 		if (psc.isRemoveExistingSources()) {
238 			logger.debug("Removing all existing property sources");
239 			PropertySourceUtils.removeAllPropertySources(env);
240 		}
241 
242 		if (CollectionUtils.isEmpty(psc.getSources())) {
243 			return;
244 		}
245 		List<PropertySource<?>> propertySources = psc.getSources();
246 		MutablePropertySources sources = env.getPropertySources();
247 		if (psc.isLastOneInWins()) {
248 			Collections.reverse(propertySources);
249 		}
250 		PropertySourceAddPriority priority = psc.getPriority();
251 		for (PropertySource<?> propertySource : propertySources) {
252 			Object[] args = { propertySource.getName(), propertySource.getClass().getName(), priority };
253 			logger.debug("Adding property source - [{}] -> [{}] Priority=[{}]", args);
254 			switch (priority) {
255 			case FIRST:
256 				sources.addFirst(propertySource);
257 				break;
258 			case LAST:
259 				sources.addLast(propertySource);
260 				break;
261 			default:
262 				throw new IllegalStateException(priority + " is an unknown priority");
263 			}
264 		}
265 	}
266 
267 	/**
268 	 * 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.
269 	 */
270 	protected List<String> getConvertedLocations(List<String> locations) {
271 		List<String> converted = new ArrayList<String>();
272 		for (String location : locations) {
273 			if (LocationUtils.isExistingFile(location)) {
274 				File file = new File(location);
275 				// ClassPathXmlApplicationContext needs a fully qualified URL, not a filename
276 				String url = LocationUtils.getCanonicalURLString(file);
277 				converted.add(url);
278 			} else {
279 				converted.add(location);
280 			}
281 		}
282 		return converted;
283 	}
284 
285 	@Deprecated
286 	@Override
287 	public void load(Class<?> annotatedClass, String beanName, Object bean, PropertySource<?> propertySource) {
288 		load(annotatedClass, CollectionUtils.toEmptyMap(beanName, bean), propertySource);
289 	}
290 
291 	@Deprecated
292 	@Override
293 	public void load(Class<?> annotatedClass, String beanName, Object bean) {
294 		load(annotatedClass, beanName, bean, null);
295 	}
296 
297 	@Deprecated
298 	@Override
299 	public void load(String location, String beanName, Object bean, PropertySource<?> propertySource) {
300 		load(location, CollectionUtils.toEmptyMap(beanName, bean), propertySource);
301 	}
302 
303 	@Deprecated
304 	@Override
305 	public void load(String location, String beanName, Object bean) {
306 		load(location, beanName, bean, null);
307 	}
308 
309 }