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.JobContext;
021import org.springframework.batch.core.scope.context.JobSynchronizationManager;
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 job 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 * job. 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 JobContext} using #{..} placeholders. Using this feature,
036 * bean properties can be pulled from the job 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;job&quot;&gt;
041 *      &lt;property name=&quot;name&quot; value=&quot;#{jobParameters[input]}&quot; /&gt;
042 * &lt;/bean&gt;
043 *
044 * &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;job&quot;&gt;
045 *      &lt;property name=&quot;name&quot; value=&quot;#{jobExecutionContext['input.stem']}.txt&quot; /&gt;
046 * &lt;/bean&gt;
047 * </pre>
048 *
049 * The {@link JobContext} is referenced using standard bean property paths (as
050 * per {@link BeanWrapper}). The examples above all show the use of the Map
051 * accessors provided as a convenience for job attributes.
052 *
053 * @author Dave Syer
054 * @author Jimmy Praet (create JobScope based on {@link StepScope})
055 * @author Michael Minella
056 * @since 3.0
057 */
058public class JobScope extends BatchScopeSupport {
059
060        private static final String TARGET_NAME_PREFIX = "jobScopedTarget.";
061
062        private Log logger = LogFactory.getLog(getClass());
063
064        private final Object mutex = new Object();
065
066        /**
067         * Context key for clients to use for conversation identifier.
068         */
069        public static final String ID_KEY = "JOB_IDENTIFIER";
070
071        public JobScope() {
072                super();
073                setName("job");
074        }
075
076        /**
077         * This will be used to resolve expressions in job-scoped beans.
078         */
079        @Override
080        public Object resolveContextualObject(String key) {
081                JobContext context = getContext();
082                // TODO: support for attributes as well maybe (setters not exposed yet
083                // so not urgent).
084                return new BeanWrapperImpl(context).getPropertyValue(key);
085        }
086
087        /**
088         * @see Scope#get(String, ObjectFactory)
089         */
090        @Override
091        public Object get(String name, ObjectFactory<?> objectFactory) {
092                JobContext context = getContext();
093                Object scopedObject = context.getAttribute(name);
094
095                if (scopedObject == null) {
096
097                        synchronized (mutex) {
098                                scopedObject = context.getAttribute(name);
099                                if (scopedObject == null) {
100
101                                        if (logger.isDebugEnabled()) {
102                                                logger.debug(String.format("Creating object in scope=%s, name=%s", this.getName(), name));
103                                        }
104
105                                        scopedObject = objectFactory.getObject();
106                                        context.setAttribute(name, scopedObject);
107
108                                }
109
110                        }
111
112                }
113                return scopedObject;
114        }
115
116        /**
117         * @see Scope#getConversationId()
118         */
119        @Override
120        public String getConversationId() {
121                JobContext context = getContext();
122                return context.getId();
123        }
124
125        /**
126         * @see Scope#registerDestructionCallback(String, Runnable)
127         */
128        @Override
129        public void registerDestructionCallback(String name, Runnable callback) {
130                JobContext context = getContext();
131                if (logger.isDebugEnabled()) {
132                        logger.debug(String.format("Registered destruction callback in scope=%s, name=%s", this.getName(), name));
133                }
134                context.registerDestructionCallback(name, callback);
135        }
136
137        /**
138         * @see Scope#remove(String)
139         */
140        @Override
141        public Object remove(String name) {
142                JobContext context = getContext();
143                if (logger.isDebugEnabled()) {
144                        logger.debug(String.format("Removing from scope=%s, name=%s", this.getName(), name));
145                }
146                return context.removeAttribute(name);
147        }
148
149        /**
150         * Get an attribute accessor in the form of a {@link JobContext} that can
151         * be used to store scoped bean instances.
152         *
153         * @return the current job context which we can use as a scope storage
154         *         medium
155         */
156        private JobContext getContext() {
157                JobContext context = JobSynchronizationManager.getContext();
158                if (context == null) {
159                        throw new IllegalStateException("No context holder available for job scope");
160                }
161                return context;
162        }
163
164        @Override
165        public String getTargetNamePrefix() {
166                return TARGET_NAME_PREFIX;
167        }
168}