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 * <bean id="..." class="..." scope="job"> 041 * <property name="name" value="#{jobParameters[input]}" /> 042 * </bean> 043 * 044 * <bean id="..." class="..." scope="job"> 045 * <property name="name" value="#{jobExecutionContext['input.stem']}.txt" /> 046 * </bean> 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}