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 * <bean id="..." class="..." scope="step"> 041 * <property name="parent" ref="#{stepExecutionContext[helper]}" /> 042 * </bean> 043 * 044 * <bean id="..." class="..." scope="step"> 045 * <property name="name" value="#{stepExecutionContext['input.name']}" /> 046 * </bean> 047 * 048 * <bean id="..." class="..." scope="step"> 049 * <property name="name" value="#{jobParameters[input]}" /> 050 * </bean> 051 * 052 * <bean id="..." class="..." scope="step"> 053 * <property name="name" value="#{jobExecutionContext['input.stem']}.txt" /> 054 * </bean> 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}