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