EMMA Coverage Report (generated Fri Aug 21 15:59:46 BST 2009)
[all classes][org.springframework.batch.core.scope.util]

COVERAGE SUMMARY FOR SOURCE FILE [PlaceholderTargetSource.java]

nameclass, %method, %block, %line, %
PlaceholderTargetSource.java100% (4/4)100% (26/26)89%  (654/731)93%  (130/140)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class PlaceholderTargetSource$1100% (1/1)100% (3/3)71%  (111/156)81%  (21/26)
convertIfNecessary (Object, Class, MethodParameter): Object 100% (1/1)68%  (96/141)78%  (18/23)
PlaceholderTargetSource$1 (PlaceholderTargetSource, TypeConverter): void 100% (1/1)100% (9/9)100% (2/2)
convertIfNecessary (Object, Class): Object 100% (1/1)100% (6/6)100% (1/1)
     
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%  (19/20)
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)
     
class PlaceholderTargetSource100% (1/1)100% (16/16)94%  (271/289)94%  (64/68)
convertToString (Object, TypeConverter): String 100% (1/1)70%  (38/54)79%  (11/14)
getPropertyFromContext (String): Object 100% (1/1)88%  (15/17)75%  (3/4)
PlaceholderTargetSource (): void 100% (1/1)100% (3/3)100% (1/1)
access$0 (PlaceholderTargetSource, String): boolean 100% (1/1)100% (4/4)100% (1/1)
access$1 (PlaceholderTargetSource, String): String 100% (1/1)100% (4/4)100% (1/1)
access$2 (PlaceholderTargetSource, String): Object 100% (1/1)100% (4/4)100% (1/1)
access$3 (PlaceholderTargetSource): Log 100% (1/1)100% (3/3)100% (1/1)
access$4 (PlaceholderTargetSource, String, Class): Object 100% (1/1)100% (5/5)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% (2/2)
getTarget (): Object 100% (1/1)100% (94/94)100% (23/23)
getTargetFromContext (): Object 100% (1/1)100% (15/15)100% (4/4)
isKey (String): boolean 100% (1/1)100% (19/19)100% (2/2)
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$PlaceholderBeanDefinitionVisitor100% (1/1)100% (2/2)100% (73/73)100% (16/16)
PlaceholderTargetSource$PlaceholderBeanDefinitionVisitor (PlaceholderTargetSo... 100% (1/1)100% (12/12)100% (2/2)
resolveValue (Object): Object 100% (1/1)100% (61/61)100% (14/14)

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

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