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 			String id = StringUtils.leftPad(Thread.currentThread().getId() + "", 2);
147 			String name = Thread.currentThread().getName();
148 			logger.debug("id: " + id + "  name: " + name + " ctx=" + ctx.getClass().getSimpleName() + "@" + Integer.toHexString(ctx.hashCode()));
149 			ctx.refresh();
150 			SpringUtils.debugQuietly(ctx);
151 		} finally {
152 			SpringUtils.closeQuietly(ctx);
153 		}
154 	}
155 
156 	@Override
157 	public void load(String location, Map<String, Object> contextBeans, PropertySource<?> propertySource) {
158 		// Make sure the location isn't empty
159 		Assert.hasText(location, "location is null");
160 
161 		// Setup a SpringContext
162 		SpringContext context = new SpringContext();
163 		context.setLocations(Arrays.asList(location));
164 		context.setPropertySourceContext(new PropertySourceContext(PropertySourceUtils.asList(propertySource)));
165 
166 		// Null safe handling for non-required parameters
167 		context.setContextBeans(CollectionUtils.toEmptyMap(contextBeans));
168 
169 		// Load the location using a SpringContext
170 		load(context);
171 	}
172 
173 	@Override
174 	public void load(Class<?> annotatedClass) {
175 		load(annotatedClass, (String) null, (Object) null);
176 	}
177 
178 	@Override
179 	public void load(String location) {
180 		load(location, (String) null, (Object) null);
181 	}
182 
183 	/**
184 	 * Add id and display name to the ApplicationContext if they are not blank
185 	 */
186 	protected void addMetaInfo(AnnotationConfigApplicationContext ctx, SpringContext sc) {
187 		if (!StringUtils.isBlank(sc.getId())) {
188 			ctx.setId(sc.getId());
189 		}
190 		if (!StringUtils.isBlank(sc.getDisplayName())) {
191 			ctx.setDisplayName(sc.getDisplayName());
192 		}
193 	}
194 
195 	/**
196 	 * Add id and display name to the ApplicationContext if they are not blank
197 	 */
198 	protected void addMetaInfo(ClassPathXmlApplicationContext ctx, SpringContext sc) {
199 		if (!StringUtils.isBlank(sc.getId())) {
200 			ctx.setId(sc.getId());
201 		}
202 		if (!StringUtils.isBlank(sc.getDisplayName())) {
203 			ctx.setDisplayName(sc.getDisplayName());
204 		}
205 	}
206 
207 	protected AbstractApplicationContext getAnnotationContext(SpringContext context, ConfigurableApplicationContext parent) {
208 		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
209 		if (parent != null) {
210 			ctx.setParent(parent);
211 		} else {
212 			addMetaInfo(ctx, context);
213 		}
214 		for (Class<?> annotatedClass : context.getAnnotatedClasses()) {
215 			ctx.register(annotatedClass);
216 		}
217 		return ctx;
218 	}
219 
220 	protected void setActiveProfiles(ConfigurableApplicationContext applicationContext, List<String> activeProfiles) {
221 		if (!CollectionUtils.isEmpty(activeProfiles)) {
222 			ConfigurableEnvironment env = applicationContext.getEnvironment();
223 			env.setActiveProfiles(CollectionUtils.toStringArray(activeProfiles));
224 		}
225 	}
226 
227 	protected void setDefaultProfiles(ConfigurableApplicationContext applicationContext, List<String> defaultProfiles) {
228 		if (!CollectionUtils.isEmpty(defaultProfiles)) {
229 			ConfigurableEnvironment env = applicationContext.getEnvironment();
230 			env.setDefaultProfiles(CollectionUtils.toStringArray(defaultProfiles));
231 		}
232 	}
233 
234 	protected void addPropertySources(SpringContext context, ConfigurableApplicationContext applicationContext) {
235 		PropertySourceContext psc = context.getPropertySourceContext();
236 		if (psc == null) {
237 			return;
238 		}
239 		ConfigurableEnvironment env = applicationContext.getEnvironment();
240 		if (psc.isRemoveExistingSources()) {
241 			logger.debug("Removing all existing property sources");
242 			PropertySourceUtils.removeAllPropertySources(env);
243 		}
244 
245 		if (CollectionUtils.isEmpty(psc.getSources())) {
246 			return;
247 		}
248 		List<PropertySource<?>> propertySources = psc.getSources();
249 		MutablePropertySources sources = env.getPropertySources();
250 		if (psc.isLastOneInWins()) {
251 			Collections.reverse(propertySources);
252 		}
253 		PropertySourceAddPriority priority = psc.getPriority();
254 		for (PropertySource<?> propertySource : propertySources) {
255 			Object[] args = { propertySource.getName(), propertySource.getClass().getName(), priority };
256 			logger.debug("Adding property source - [{}] -> [{}] Priority=[{}]", args);
257 			switch (priority) {
258 			case FIRST:
259 				sources.addFirst(propertySource);
260 				break;
261 			case LAST:
262 				sources.addLast(propertySource);
263 				break;
264 			default:
265 				throw new IllegalStateException(priority + " is an unknown priority");
266 			}
267 		}
268 	}
269 
270 	/**
271 	 * 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.
272 	 */
273 	protected List<String> getConvertedLocations(List<String> locations) {
274 		List<String> converted = new ArrayList<String>();
275 		for (String location : locations) {
276 			if (LocationUtils.isExistingFile(location)) {
277 				File file = new File(location);
278 				// ClassPathXmlApplicationContext needs a fully qualified URL, not a filename
279 				String url = LocationUtils.getCanonicalURLString(file);
280 				converted.add(url);
281 			} else {
282 				converted.add(location);
283 			}
284 		}
285 		return converted;
286 	}
287 
288 	@Deprecated
289 	@Override
290 	public void load(Class<?> annotatedClass, String beanName, Object bean, PropertySource<?> propertySource) {
291 		load(annotatedClass, CollectionUtils.toEmptyMap(beanName, bean), propertySource);
292 	}
293 
294 	@Deprecated
295 	@Override
296 	public void load(Class<?> annotatedClass, String beanName, Object bean) {
297 		load(annotatedClass, beanName, bean, null);
298 	}
299 
300 	@Deprecated
301 	@Override
302 	public void load(String location, String beanName, Object bean, PropertySource<?> propertySource) {
303 		load(location, CollectionUtils.toEmptyMap(beanName, bean), propertySource);
304 	}
305 
306 	@Deprecated
307 	@Override
308 	public void load(String location, String beanName, Object bean) {
309 		load(location, beanName, bean, null);
310 	}
311 
312 }