EMMA Coverage Report (generated Thu Jan 24 13:37:04 CST 2013)
[all classes][org.springframework.batch.core.scope.util]

COVERAGE SUMMARY FOR SOURCE FILE [PlaceholderTargetSource.java]

nameclass, %method, %block, %line, %
PlaceholderTargetSource.java100% (4/4)90%  (28/31)85%  (733/858)86%  (147.7/171)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class PlaceholderTargetSource$1100% (1/1)100% (3/3)72%  (114/159)78%  (21/27)
convertIfNecessary (Object, Class, MethodParameter): Object 100% (1/1)69%  (99/144)76%  (19/25)
PlaceholderTargetSource$1 (PlaceholderTargetSource, TypeConverter): void 100% (1/1)100% (9/9)100% (1/1)
convertIfNecessary (Object, Class): Object 100% (1/1)100% (6/6)100% (1/1)
     
class PlaceholderTargetSource$PlaceholderBeanDefinitionVisitor100% (1/1)100% (2/2)82%  (140/171)80%  (40/50)
resolveValue (Object): Object 100% (1/1)81%  (128/159)79%  (37/47)
PlaceholderTargetSource$PlaceholderBeanDefinitionVisitor (PlaceholderTargetSo... 100% (1/1)100% (12/12)100% (3/3)
     
class PlaceholderTargetSource100% (1/1)86%  (18/21)89%  (280/315)90%  (58.8/65)
access$300 (PlaceholderTargetSource): Log 0%   (0/1)0%   (0/3)0%   (0/1)
access$400 (PlaceholderTargetSource): Log 0%   (0/1)0%   (0/3)0%   (0/1)
access$900 (PlaceholderTargetSource): Log 0%   (0/1)0%   (0/3)0%   (0/1)
convertToString (Object, TypeConverter): String 100% (1/1)67%  (38/57)75%  (12/16)
getPropertyFromContext (String): Object 100% (1/1)73%  (19/26)71%  (5/7)
PlaceholderTargetSource (): void 100% (1/1)100% (3/3)100% (2/2)
access$000 (PlaceholderTargetSource, String): String 100% (1/1)100% (4/4)100% (1/1)
access$100 (PlaceholderTargetSource, String, Class): Object 100% (1/1)100% (5/5)100% (1/1)
access$1000 (PlaceholderTargetSource): Log 100% (1/1)100% (3/3)100% (1/1)
access$200 (PlaceholderTargetSource, String): Object 100% (1/1)100% (4/4)100% (1/1)
access$500 (PlaceholderTargetSource): Log 100% (1/1)100% (3/3)100% (1/1)
access$700 (PlaceholderTargetSource, String): boolean 100% (1/1)100% (4/4)100% (1/1)
access$800 (PlaceholderTargetSource): Log 100% (1/1)100% (3/3)100% (1/1)
afterPropertiesSet (): void 100% (1/1)100% (20/20)100% (3/3)
convertFromContext (String, Class): Object 100% (1/1)100% (17/17)100% (5/5)
extractKey (String): String 100% (1/1)100% (12/12)100% (1/1)
getTarget (): Object 100% (1/1)100% (93/93)100% (20/20)
getTargetFromContext (): Object 100% (1/1)100% (15/15)100% (4/4)
isKey (String): boolean 100% (1/1)100% (19/19)100% (1/1)
putTargetInContext (Object): void 100% (1/1)100% (14/14)100% (4/4)
setContextFactory (ContextFactory): void 100% (1/1)100% (4/4)100% (2/2)
     
class PlaceholderTargetSource$PlaceholderStringValueResolver100% (1/1)100% (5/5)93%  (199/213)97%  (30/31)
replacePlaceholders (String, TypeConverter): String 100% (1/1)91%  (149/163)95%  (18/19)
PlaceholderTargetSource$PlaceholderStringValueResolver (PlaceholderTargetSour... 100% (1/1)100% (9/9)100% (3/3)
PlaceholderTargetSource$PlaceholderStringValueResolver (PlaceholderTargetSour... 100% (1/1)100% (5/5)100% (1/1)
replaceIfTypeMatches (StringBuilder, int, int, String, Class, TypeConverter):... 100% (1/1)100% (24/24)100% (5/5)
resolveStringValue (String): String 100% (1/1)100% (12/12)100% (3/3)

1/*
2 * Copyright 2006-2007 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.springframework.batch.core.scope.util;
17 
18import java.beans.PropertyEditor;
19import java.util.Date;
20import java.util.List;
21import java.util.Map;
22import java.util.Set;
23 
24import org.springframework.aop.TargetSource;
25import org.springframework.aop.target.SimpleBeanTargetSource;
26import org.springframework.beans.BeanWrapper;
27import org.springframework.beans.BeanWrapperImpl;
28import org.springframework.beans.BeansException;
29import org.springframework.beans.PropertyEditorRegistrySupport;
30import org.springframework.beans.TypeConverter;
31import org.springframework.beans.TypeMismatchException;
32import org.springframework.beans.factory.InitializingBean;
33import org.springframework.beans.factory.config.BeanDefinition;
34import org.springframework.beans.factory.config.BeanDefinitionHolder;
35import org.springframework.beans.factory.config.BeanDefinitionVisitor;
36import org.springframework.beans.factory.config.TypedStringValue;
37import org.springframework.beans.factory.support.DefaultListableBeanFactory;
38import org.springframework.beans.factory.support.GenericBeanDefinition;
39import org.springframework.beans.factory.support.ManagedList;
40import org.springframework.beans.factory.support.ManagedMap;
41import org.springframework.beans.factory.support.ManagedSet;
42import org.springframework.core.AttributeAccessor;
43import org.springframework.core.MethodParameter;
44import org.springframework.util.Assert;
45import org.springframework.util.StringValueResolver;
46 
47/**
48 * A {@link TargetSource} that lazily initializes its target, replacing bean
49 * definition properties dynamically if they are marked as placeholders. String
50 * values with embedded <code>%{key}</code> patterns will be replaced with the
51 * corresponding value from the injected context (which must also be a String).
52 * This includes dynamically locating a bean reference (e.g.
53 * <code>ref="%{foo}"</code>), and partial replacement of patterns (e.g.
54 * <code>value="%{foo}-bar-%{spam}"</code>). These replacements work for context
55 * values that are primitive (String, Long, Integer). You can also replace
56 * non-primitive values directly by making the whole bean property value into a
57 * placeholder (e.g. <code>value="%{foo}"</code> where <code>foo</code> is a
58 * property in the context).
59 * 
60 * @author Dave Syer
61 * 
62 */
63public class PlaceholderTargetSource extends SimpleBeanTargetSource implements InitializingBean {
64 
65        /**
66         * Key for placeholders to be replaced from the properties provided.
67         */
68        private static final String PLACEHOLDER_PREFIX = "%{";
69 
70        private static final String PLACEHOLDER_SUFFIX = "}";
71 
72        private ContextFactory contextFactory;
73 
74        private String beanName;
75 
76        /**
77         * Public setter for the context factory. Used to construct the context root
78         * whenever placeholders are replaced in a bean definition.
79         * 
80         * @param contextFactory the {@link ContextFactory}
81         */
82        public void setContextFactory(ContextFactory contextFactory) {
83                this.contextFactory = contextFactory;
84        }
85 
86        /*
87         * (non-Javadoc)
88         * 
89         * @see
90         * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
91         */
92        public void afterPropertiesSet() {
93                Assert.notNull(contextFactory, "The ContextFactory must be set.");
94                beanName = getTargetBeanName() + "#" + contextFactory.getContextId();
95        }
96 
97        /*
98         * (non-Javadoc)
99         * 
100         * @see org.springframework.aop.target.LazyInitTargetSource#getTarget()
101         */
102        @Override
103        public synchronized Object getTarget() throws BeansException {
104 
105                // Object target;
106                Object target = getTargetFromContext();
107                if (target != null) {
108                        return target;
109                }
110 
111                DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) getBeanFactory();
112 
113                final TypeConverter typeConverter = listableBeanFactory.getTypeConverter();
114 
115                DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(listableBeanFactory);
116                beanFactory.copyConfigurationFrom(listableBeanFactory);
117 
118                final TypeConverter contextTypeConverter = new TypeConverter() {
119                        @SuppressWarnings({ "unchecked", "rawtypes" })
120                        public Object convertIfNecessary(Object value, Class requiredType, MethodParameter methodParam)
121                                        throws TypeMismatchException {
122                                Object result = null;
123                                if (value instanceof String) {
124                                        String key = (String) value;
125                                        if (key.startsWith(PLACEHOLDER_PREFIX) && key.endsWith(PLACEHOLDER_SUFFIX)) {
126                                                key = extractKey(key);
127                                                result = convertFromContext(key, requiredType);
128                                                if (result == null) {
129                                                        Object property = getPropertyFromContext(key);
130                                                        // Give the normal type converter a chance by
131                                                        // reversing to a String
132                                                        if (property != null) {
133                                                                property = convertToString(property, typeConverter);
134                                                                if (property != null) {
135                                                                        value = property;
136                                                                }
137                                                                logger.debug(String.format("Bound %%{%s} to String value [%s]", key, result));
138                                                        }
139                                                        else {
140                                                                throw new IllegalStateException("Cannot bind to placeholder: " + key);
141                                                        }
142                                                }
143                                                else {
144                                                        logger.debug(String.format("Bound %%{%s} to [%s]", key, result));
145                                                }
146                                        }
147                                }
148                                else if (requiredType.isAssignableFrom(value.getClass())) {
149                                        result = value;
150                                }
151                                else if (requiredType.isAssignableFrom(String.class)) {
152                                        result = convertToString(value, typeConverter);
153                                        if (result == null) {
154                                                logger.debug("Falling back on toString for conversion of : [" + value.getClass() + "]");
155                                                result = value.toString();
156                                        }
157                                }
158                                return result != null ? result : typeConverter.convertIfNecessary(value, requiredType, methodParam);
159                        }
160 
161                        @SuppressWarnings("rawtypes")
162                        public Object convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException {
163                                return convertIfNecessary(value, requiredType, null);
164                        }
165                };
166                beanFactory.setTypeConverter(contextTypeConverter);
167 
168                try {
169 
170                        /*
171                         * Need to use the merged bean definition here, otherwise it gets
172                         * cached and "frozen" in and the "regular" bean definition does not
173                         * come back when getBean() is called later on
174                         */
175                        String targetBeanName = getTargetBeanName();
176                        GenericBeanDefinition beanDefinition = new GenericBeanDefinition(listableBeanFactory
177                                        .getMergedBeanDefinition(targetBeanName));
178                        logger.debug("Rehydrating scoped target: [" + targetBeanName + "]");
179 
180                        BeanDefinitionVisitor visitor = new PlaceholderBeanDefinitionVisitor(contextTypeConverter);
181 
182                        beanFactory.registerBeanDefinition(beanName, beanDefinition);
183                        // Make the replacements before the target is hydrated
184                        visitor.visitBeanDefinition(beanDefinition);
185                        target = beanFactory.getBean(beanName);
186                        putTargetInContext(target);
187                        return target;
188 
189                }
190                finally {
191                        beanFactory.removeBeanDefinition(beanName);
192                        beanFactory = null;
193                        // Anything else we can do to clean it up?
194                }
195 
196        }
197 
198        private void putTargetInContext(Object target) {
199                Object context = contextFactory.getContext();
200                if (context instanceof AttributeAccessor) {
201                        ((AttributeAccessor) context).setAttribute(beanName, target);
202                }
203        }
204 
205        private Object getTargetFromContext() {
206                Object context = contextFactory.getContext();
207                if (context instanceof AttributeAccessor) {
208                        return ((AttributeAccessor) context).getAttribute(beanName);
209                }
210                return null;
211        }
212 
213        /**
214         * @param value
215         * @param typeConverter
216         * @return a String representation of the input if possible
217         */
218        protected String convertToString(Object value, TypeConverter typeConverter) {
219                String result = null;
220                try {
221                        // Give it one chance to convert - this forces the default editors
222                        // to be registered
223                        result = (String) typeConverter.convertIfNecessary(value, String.class);
224                }
225                catch (TypeMismatchException e) {
226                        // ignore
227                }
228                if (result == null && typeConverter instanceof PropertyEditorRegistrySupport) {
229                        /*
230                         * PropertyEditorRegistrySupport is de rigeur with TypeConverter
231                         * instances used internally by Spring. If we have one of those then
232                         * we can convert to String but the TypeConverter doesn't know how
233                         * to.
234                         */
235                        PropertyEditorRegistrySupport registry = (PropertyEditorRegistrySupport) typeConverter;
236                        PropertyEditor editor = registry.findCustomEditor(value.getClass(), null);
237                        if (editor != null) {
238                                if (registry.isSharedEditor(editor)) {
239                                        // Synchronized access to shared editor
240                                        // instance.
241                                        synchronized (editor) {
242                                                editor.setValue(value);
243                                                result = editor.getAsText();
244                                        }
245                                }
246                                else {
247                                        editor.setValue(value);
248                                        result = editor.getAsText();
249                                }
250                        }
251                }
252                return result;
253        }
254 
255        /**
256         * @param value
257         * @param requiredType
258         * @return
259         */
260        private Object convertFromContext(String key, Class<?> requiredType) {
261                Object result = null;
262                Object property = getPropertyFromContext(key);
263                if (property == null || requiredType.isAssignableFrom(property.getClass())) {
264                        result = property;
265                }
266                return result;
267        }
268 
269        private Object getPropertyFromContext(String key) {
270                Object context = contextFactory.getContext();
271                if (context == null) {
272                        throw new IllegalStateException("No context available while replacing placeholders.");
273                }
274                BeanWrapper wrapper = new BeanWrapperImpl(context);
275                if (wrapper.isReadableProperty(key)) {
276                        return wrapper.getPropertyValue(key);
277                }
278                return null;
279        }
280 
281        private String extractKey(String value) {
282                return value.substring(value.indexOf(PLACEHOLDER_PREFIX) + PLACEHOLDER_PREFIX.length(), value
283                                .indexOf(PLACEHOLDER_SUFFIX));
284        }
285 
286        /**
287         * Determine whether the input is a whole key in the form
288         * <code>%{...}</code>, i.e. starting with the correct prefix, ending with
289         * the correct suffix and containing only one of each.
290         * 
291         * @param value a String with placeholder patterns
292         * @return true if the value is a key
293         */
294        private boolean isKey(String value) {
295                return value.indexOf(PLACEHOLDER_PREFIX) == value.lastIndexOf(PLACEHOLDER_PREFIX)
296                                && value.startsWith(PLACEHOLDER_PREFIX) && value.endsWith(PLACEHOLDER_SUFFIX);
297        }
298 
299        /**
300         * A {@link BeanDefinitionVisitor} that will replace embedded placeholders
301         * with values from the provided context.
302         * 
303         * @author Dave Syer
304         * 
305         */
306        private final class PlaceholderBeanDefinitionVisitor extends BeanDefinitionVisitor {
307 
308                public PlaceholderBeanDefinitionVisitor(final TypeConverter typeConverter) {
309                        super(new PlaceholderStringValueResolver(typeConverter));
310                }
311 
312                @SuppressWarnings({ "unchecked", "rawtypes" })
313                protected Object resolveValue(Object value) {
314 
315                        if (value instanceof TypedStringValue) {
316 
317                                TypedStringValue typedStringValue = (TypedStringValue) value;
318                                String stringValue = typedStringValue.getValue();
319                                if (stringValue != null) {
320 
321                                        // If the value is a whole key, try to simply replace it
322                                        // from context.
323                                        if (isKey(stringValue)) {
324                                                String key = extractKey(stringValue);
325                                                Object result = getPropertyFromContext(key);
326                                                if (result != null) {
327                                                        value = result;
328                                                        logger.debug(String.format("Resolved %%{%s} to obtain [%s]", key, result));
329                                                }
330                                        }
331                                        else {
332                                                // Otherwise it might contain embedded keys so we try to
333                                                // replace those
334                                                String visitedString = resolveStringValue(stringValue);
335                                                value = new TypedStringValue(visitedString);
336                                        }
337                                }
338 
339                        }
340                        else if (value instanceof Map) {
341 
342                                Map map = (Map) value;
343                                Map newValue = new ManagedMap(map.size());
344                                newValue.putAll(map);
345                                super.visitMap(newValue);
346                                value = newValue;
347 
348                        }
349                        else if (value instanceof List) {
350 
351                                List list = (List) value;
352                                List newValue = new ManagedList(list.size());
353                                newValue.addAll(list);
354                                super.visitList(newValue);
355                                value = newValue;
356 
357                        }
358                        else if (value instanceof Set) {
359 
360                                Set list = (Set) value;
361                                Set newValue = new ManagedSet(list.size());
362                                newValue.addAll(list);
363                                super.visitSet(newValue);
364                                value = newValue;
365 
366                        }
367                        else if (value instanceof BeanDefinition) {
368 
369                                BeanDefinition newValue = new GenericBeanDefinition((BeanDefinition) value);
370                                visitBeanDefinition((BeanDefinition) newValue);
371                                value = newValue;
372 
373                        }
374                        else if (value instanceof BeanDefinitionHolder) {
375 
376                                BeanDefinition newValue = new GenericBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
377                                visitBeanDefinition((BeanDefinition) newValue);
378                                value = newValue;
379 
380                        }
381                        else {
382 
383                                value = super.resolveValue(value);
384 
385                        }
386 
387                        return value;
388 
389                }
390 
391        }
392 
393        private final class PlaceholderStringValueResolver implements StringValueResolver {
394 
395                private final TypeConverter typeConverter;
396 
397                private PlaceholderStringValueResolver(TypeConverter typeConverter) {
398                        this.typeConverter = typeConverter;
399                }
400 
401                public String resolveStringValue(String strVal) {
402                        if (!strVal.contains(PLACEHOLDER_PREFIX)) {
403                                return strVal;
404                        }
405                        return replacePlaceholders(strVal, typeConverter);
406                }
407 
408                /**
409                 * Convenience method to replace all the placeholders in the input.
410                 * 
411                 * @param typeConverter a {@link TypeConverter} that can be used to
412                 * convert placeholder keys to context values
413                 * @param value the value to replace placeholders in
414                 * @return the input with placeholders replaced
415                 */
416                private String replacePlaceholders(String value, TypeConverter typeConverter) {
417 
418                        StringBuilder result = new StringBuilder(value);
419 
420                        int first = result.indexOf(PLACEHOLDER_PREFIX);
421                        int next = result.indexOf(PLACEHOLDER_SUFFIX, first + 1);
422 
423                        while (first >= 0) {
424 
425                                Assert.state(next > 0, String.format("Placeholder key incorrectly specified: use %skey%s (in %s)",
426                                                PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, value));
427 
428                                String key = result.substring(first + PLACEHOLDER_PREFIX.length(), next);
429 
430                                boolean replaced = replaceIfTypeMatches(result, first, next, key, String.class, typeConverter);
431                                replaced |= replaceIfTypeMatches(result, first, next, key, Long.class, typeConverter);
432                                replaced |= replaceIfTypeMatches(result, first, next, key, Integer.class, typeConverter);
433                                replaced |= replaceIfTypeMatches(result, first, next, key, Date.class, typeConverter);
434                                if (!replaced) {
435                                        if (!value.startsWith(PLACEHOLDER_PREFIX) || !value.endsWith(PLACEHOLDER_SUFFIX)) {
436                                                throw new IllegalStateException(String.format("Cannot bind to partial key %%{%s} in %s", key,
437                                                                value));
438                                        }
439                                        logger.debug(String.format("Deferring binding of placeholder: %%{%s}", key));
440                                }
441                                else {
442                                        logger.debug(String.format("Bound %%{%s} to obtain [%s]", key, result));
443                                }
444                                first = result.indexOf(PLACEHOLDER_PREFIX, first + 1);
445                                next = result.indexOf(PLACEHOLDER_SUFFIX, first + 1);
446 
447                        }
448 
449                        return result.toString();
450 
451                }
452 
453                private boolean replaceIfTypeMatches(StringBuilder result, int first, int next, String key,
454                                Class<?> requiredType, TypeConverter typeConverter) {
455                        Object property = convertFromContext(key, requiredType);
456                        if (property != null) {
457                                result.replace(first, next + 1, (String) typeConverter.convertIfNecessary(property, String.class));
458                                return true;
459                        }
460                        return false;
461                }
462 
463        }
464 
465}

[all classes][org.springframework.batch.core.scope.util]
EMMA 2.0.5312 (C) Vladimir Roubtsov