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}