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