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 */ 016package org.kuali.common.util.spring.service; 017 018import java.io.File; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.commons.lang3.StringUtils; 026import org.kuali.common.util.Assert; 027import org.kuali.common.util.CollectionUtils; 028import org.kuali.common.util.LocationUtils; 029import org.kuali.common.util.spring.PropertySourceUtils; 030import org.kuali.common.util.spring.SpringUtils; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033import org.springframework.context.ConfigurableApplicationContext; 034import org.springframework.context.annotation.AnnotationConfigApplicationContext; 035import org.springframework.context.support.AbstractApplicationContext; 036import org.springframework.context.support.ClassPathXmlApplicationContext; 037import org.springframework.core.env.ConfigurableEnvironment; 038import org.springframework.core.env.MutablePropertySources; 039import org.springframework.core.env.PropertySource; 040 041/** 042 * This service must be stateless 043 */ 044public class DefaultSpringService implements SpringService { 045 046 private static final Logger logger = LoggerFactory.getLogger(DefaultSpringService.class); 047 048 @Override 049 public void load(Class<?> annotatedClass, Map<String, Object> contextBeans) { 050 load(annotatedClass, contextBeans, null); 051 } 052 053 @Override 054 public void load(Class<?> annotatedClass, Map<String, Object> contextBeans, PropertySource<?> propertySource) { 055 // Make sure the annotatedClass isn't null 056 Assert.notNull(annotatedClass, "annotatedClass is null"); 057 058 // Setup a SpringContext 059 SpringContext context = new SpringContext(); 060 context.setAnnotatedClasses(CollectionUtils.asList(annotatedClass)); 061 context.setPropertySourceContext(new PropertySourceContext(PropertySourceUtils.asList(propertySource))); 062 063 // Null safe handling for non-required parameters 064 context.setContextBeans(CollectionUtils.toEmptyMap(contextBeans)); 065 066 // Load the context 067 load(context); 068 } 069 070 @Override 071 public void load(String location, Map<String, Object> contextBeans) { 072 load(location, contextBeans, null); 073 } 074 075 @SuppressWarnings("deprecation") 076 protected Map<String, Object> getContextBeans(SpringContext context) { 077 // Null-safe handling for parameters 078 Map<String, Object> contextBeans = CollectionUtils.toModifiableEmptyMap(context.getContextBeans()); 079 CollectionUtils.combine(contextBeans, context.getBeanNames(), context.getBeans()); 080 return contextBeans; 081 } 082 083 protected AbstractApplicationContext getXmlChild(String[] locationsArray, AbstractApplicationContext parent, SpringContext context) { 084 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(locationsArray, false, parent); 085 if (parent == null) { 086 addMetaInfo(ctx, context); 087 } 088 return ctx; 089 } 090 091 @Override 092 public ConfigurableApplicationContext getApplicationContext(SpringContext context) { 093 // Null-safe handling for optional parameters 094 context.setContextBeans(getContextBeans(context)); 095 context.setAnnotatedClasses(CollectionUtils.toEmptyList(context.getAnnotatedClasses())); 096 context.setLocations(CollectionUtils.toEmptyList(context.getLocations())); 097 context.setActiveProfiles(CollectionUtils.toEmptyList(context.getActiveProfiles())); 098 context.setDefaultProfiles(CollectionUtils.toEmptyList(context.getDefaultProfiles())); 099 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}