001/*
002 * Copyright 2006-2013 the original author or authors.
003 *
004 * Licensed under the Apache 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 *      https://www.apache.org/licenses/LICENSE-2.0
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.springframework.batch.core.scope;
017
018import org.apache.commons.logging.Log;
019import org.apache.commons.logging.LogFactory;
020import org.springframework.batch.core.scope.context.StepContext;
021import org.springframework.batch.core.scope.context.StepSynchronizationManager;
022import org.springframework.beans.BeanWrapper;
023import org.springframework.beans.BeanWrapperImpl;
024import org.springframework.beans.factory.ObjectFactory;
025import org.springframework.beans.factory.config.Scope;
026
027/**
028 * Scope for step context. Objects in this scope use the Spring container as an
029 * object factory, so there is only one instance of such a bean per executing
030 * step. All objects in this scope are <aop:scoped-proxy/> (no need to
031 * decorate the bean definitions).<br>
032 * <br>
033 *
034 * In addition, support is provided for late binding of references accessible
035 * from the {@link StepContext} using #{..} placeholders. Using this feature,
036 * bean properties can be pulled from the step or job execution context and the
037 * job parameters. E.g.
038 *
039 * <pre>
040 * &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
041 *      &lt;property name=&quot;parent&quot; ref=&quot;#{stepExecutionContext[helper]}&quot; /&gt;
042 * &lt;/bean&gt;
043 *
044 * &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
045 *      &lt;property name=&quot;name&quot; value=&quot;#{stepExecutionContext['input.name']}&quot; /&gt;
046 * &lt;/bean&gt;
047 *
048 * &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
049 *      &lt;property name=&quot;name&quot; value=&quot;#{jobParameters[input]}&quot; /&gt;
050 * &lt;/bean&gt;
051 *
052 * &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
053 *      &lt;property name=&quot;name&quot; value=&quot;#{jobExecutionContext['input.stem']}.txt&quot; /&gt;
054 * &lt;/bean&gt;
055 * </pre>
056 *
057 * The {@link StepContext} is referenced using standard bean property paths (as
058 * per {@link BeanWrapper}). The examples above all show the use of the Map
059 * accessors provided as a convenience for step and job attributes.
060 *
061 * @author Dave Syer
062 * @author Michael Minella
063 * @since 2.0
064 */
065public class StepScope extends BatchScopeSupport {
066
067        private static final String TARGET_NAME_PREFIX = "stepScopedTarget.";
068
069        private Log logger = LogFactory.getLog(getClass());
070
071        private final Object mutex = new Object();
072
073        /**
074         * Context key for clients to use for conversation identifier.
075         */
076        public static final String ID_KEY = "STEP_IDENTIFIER";
077
078        public StepScope() {
079                super();
080                setName("step");
081        }
082
083        /**
084         * This will be used to resolve expressions in step-scoped beans.
085         */
086        @Override
087        public Object resolveContextualObject(String key) {
088                StepContext context = getContext();
089                // TODO: support for attributes as well maybe (setters not exposed yet
090                // so not urgent).
091                return new BeanWrapperImpl(context).getPropertyValue(key);
092        }
093
094        /**
095         * @see Scope#get(String, ObjectFactory)
096         */
097        @Override
098        public Object get(String name, ObjectFactory<?> objectFactory) {
099                StepContext context = getContext();
100                Object scopedObject = context.getAttribute(name);
101
102                if (scopedObject == null) {
103
104                        synchronized (mutex) {
105                                scopedObject = context.getAttribute(name);
106                                if (scopedObject == null) {
107
108                                        if (logger.isDebugEnabled()) {
109                                                logger.debug(String.format("Creating object in scope=%s, name=%s", this.getName(), name));
110                                        }
111
112
113                                        scopedObject = objectFactory.getObject();
114                                        context.setAttribute(name, scopedObject);
115
116                                }
117
118                        }
119
120                }
121                return scopedObject;
122        }
123
124        /**
125         * @see Scope#getConversationId()
126         */
127        @Override
128        public String getConversationId() {
129                StepContext context = getContext();
130                return context.getId();
131        }
132
133        /**
134         * @see Scope#registerDestructionCallback(String, Runnable)
135         */
136        @Override
137        public void registerDestructionCallback(String name, Runnable callback) {
138                StepContext context = getContext();
139                if (logger.isDebugEnabled()) {
140                        logger.debug(String.format("Registered destruction callback in scope=%s, name=%s", this.getName(), name));
141                }
142                context.registerDestructionCallback(name, callback);
143        }
144
145        /**
146         * @see Scope#remove(String)
147         */
148        @Override
149        public Object remove(String name) {
150                StepContext context = getContext();
151                if (logger.isDebugEnabled()) {
152                        logger.debug(String.format("Removing from scope=%s, name=%s", this.getName(), name));
153                }
154                return context.removeAttribute(name);
155        }
156
157        /**
158         * Get an attribute accessor in the form of a {@link StepContext} that can
159         * be used to store scoped bean instances.
160         *
161         * @return the current step context which we can use as a scope storage
162         * medium
163         */
164        private StepContext getContext() {
165                StepContext context = StepSynchronizationManager.getContext();
166                if (context == null) {
167                        throw new IllegalStateException("No context holder available for step scope");
168                }
169                return context;
170        }
171
172        @Override
173        public String getTargetNamePrefix() {
174                return TARGET_NAME_PREFIX;
175        }
176}