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}