View Javadoc

1   /*
2    * Copyright 2006-2013 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   */
16  
17  package org.springframework.batch.core.repository.support;
18  
19  import org.aopalliance.intercept.MethodInterceptor;
20  import org.aopalliance.intercept.MethodInvocation;
21  import org.springframework.aop.framework.ProxyFactory;
22  import org.springframework.aop.support.DefaultPointcutAdvisor;
23  import org.springframework.aop.support.NameMatchMethodPointcut;
24  import org.springframework.batch.core.repository.JobRepository;
25  import org.springframework.batch.core.repository.dao.ExecutionContextDao;
26  import org.springframework.batch.core.repository.dao.JobExecutionDao;
27  import org.springframework.batch.core.repository.dao.JobInstanceDao;
28  import org.springframework.batch.core.repository.dao.StepExecutionDao;
29  import org.springframework.batch.support.PropertiesConverter;
30  import org.springframework.beans.factory.FactoryBean;
31  import org.springframework.beans.factory.InitializingBean;
32  import org.springframework.transaction.PlatformTransactionManager;
33  import org.springframework.transaction.interceptor.TransactionInterceptor;
34  import org.springframework.transaction.support.TransactionSynchronizationManager;
35  import org.springframework.util.Assert;
36  
37  /**
38   * A {@link FactoryBean} that automates the creation of a
39   * {@link SimpleJobRepository}. Declares abstract methods for providing DAO
40   * object implementations.
41   *
42   * @see JobRepositoryFactoryBean
43   * @see MapJobRepositoryFactoryBean
44   *
45   * @author Ben Hale
46   * @author Lucas Ward
47   * @author Robert Kasanicky
48   */
49  @SuppressWarnings("rawtypes")
50  public abstract class AbstractJobRepositoryFactoryBean implements FactoryBean, InitializingBean {
51  
52  	private PlatformTransactionManager transactionManager;
53  
54  	private ProxyFactory proxyFactory;
55  
56  	private String isolationLevelForCreate = DEFAULT_ISOLATION_LEVEL;
57  
58  	private boolean validateTransactionState = true;
59  
60  	/**
61  	 * Default value for isolation level in create* method.
62  	 */
63  	private static final String DEFAULT_ISOLATION_LEVEL = "ISOLATION_SERIALIZABLE";
64  
65  	/**
66  	 * @return fully configured {@link JobInstanceDao} implementation.
67  	 */
68  	protected abstract JobInstanceDao createJobInstanceDao() throws Exception;
69  
70  	/**
71  	 * @return fully configured {@link JobExecutionDao} implementation.
72  	 */
73  	protected abstract JobExecutionDao createJobExecutionDao() throws Exception;
74  
75  	/**
76  	 * @return fully configured {@link StepExecutionDao} implementation.
77  	 */
78  	protected abstract StepExecutionDao createStepExecutionDao() throws Exception;
79  
80  	/**
81  	 * @return fully configured {@link ExecutionContextDao} implementation.
82  	 */
83  	protected abstract ExecutionContextDao createExecutionContextDao() throws Exception;
84  
85  	/**
86  	 * The type of object to be returned from {@link #getObject()}.
87  	 *
88  	 * @return JobRepository.class
89  	 * @see org.springframework.beans.factory.FactoryBean#getObjectType()
90  	 */
91  	@Override
92  	public Class<JobRepository> getObjectType() {
93  		return JobRepository.class;
94  	}
95  
96  	@Override
97  	public boolean isSingleton() {
98  		return true;
99  	}
100 
101 	/**
102 	 * Flag to determine whether to check for an existing transaction when a
103 	 * JobExecution is created. Defaults to true because it is usually a
104 	 * mistake, and leads to problems with restartability and also to deadlocks
105 	 * in multi-threaded steps.
106 	 *
107 	 * @param validateTransactionState the flag to set
108 	 */
109 	public void setValidateTransactionState(boolean validateTransactionState) {
110 		this.validateTransactionState = validateTransactionState;
111 	}
112 
113 	/**
114 	 * public setter for the isolation level to be used for the transaction when
115 	 * job execution entities are initially created. The default is
116 	 * ISOLATION_SERIALIZABLE, which prevents accidental concurrent execution of
117 	 * the same job (ISOLATION_REPEATABLE_READ would work as well).
118 	 *
119 	 * @param isolationLevelForCreate the isolation level name to set
120 	 *
121 	 * @see SimpleJobRepository#createJobExecution(String,
122 	 * org.springframework.batch.core.JobParameters)
123 	 */
124 	public void setIsolationLevelForCreate(String isolationLevelForCreate) {
125 		this.isolationLevelForCreate = isolationLevelForCreate;
126 	}
127 
128 	/**
129 	 * Public setter for the {@link PlatformTransactionManager}.
130 	 * @param transactionManager the transactionManager to set
131 	 */
132 	public void setTransactionManager(PlatformTransactionManager transactionManager) {
133 		this.transactionManager = transactionManager;
134 	}
135 
136 	/**
137 	 * The transaction manager used in this factory. Useful to inject into steps
138 	 * and jobs, to ensure that they are using the same instance.
139 	 *
140 	 * @return the transactionManager
141 	 */
142 	public PlatformTransactionManager getTransactionManager() {
143 		return transactionManager;
144 	}
145 
146 	/**
147 	 * Convenience method for clients to grab the {@link JobRepository} without
148 	 * a cast.
149 	 * @return the {@link JobRepository} from {@link #getObject()}
150 	 * @throws Exception if the repository could not be created
151 	 */
152 	public JobRepository getJobRepository() throws Exception {
153 		return (JobRepository) getObject();
154 	}
155 
156 	private void initializeProxy() throws Exception {
157 		if (proxyFactory == null) {
158 			proxyFactory = new ProxyFactory();
159 			TransactionInterceptor advice = new TransactionInterceptor(transactionManager,
160 					PropertiesConverter.stringToProperties("create*=PROPAGATION_REQUIRES_NEW,"
161 							+ isolationLevelForCreate + "\ngetLastJobExecution*=PROPAGATION_REQUIRES_NEW,"
162 							+ isolationLevelForCreate + "\n*=PROPAGATION_REQUIRED"));
163 			if (validateTransactionState) {
164 				DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(new MethodInterceptor() {
165 					@Override
166 					public Object invoke(MethodInvocation invocation) throws Throwable {
167 						if (TransactionSynchronizationManager.isActualTransactionActive()) {
168 							throw new IllegalStateException(
169 									"Existing transaction detected in JobRepository. "
170 											+ "Please fix this and try again (e.g. remove @Transactional annotations from client).");
171 						}
172 						return invocation.proceed();
173 					}
174 				});
175 				NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
176 				pointcut.addMethodName("create*");
177 				advisor.setPointcut(pointcut);
178 				proxyFactory.addAdvisor(advisor);
179 			}
180 			proxyFactory.addAdvice(advice);
181 			proxyFactory.setProxyTargetClass(false);
182 			proxyFactory.addInterface(JobRepository.class);
183 			proxyFactory.setTarget(getTarget());
184 		}
185 	}
186 
187 	@Override
188 	public void afterPropertiesSet() throws Exception {
189 		Assert.notNull(transactionManager, "TransactionManager must not be null.");
190 
191 		initializeProxy();
192 	}
193 
194 	private Object getTarget() throws Exception {
195 		return new SimpleJobRepository(createJobInstanceDao(), createJobExecutionDao(), createStepExecutionDao(),
196 				createExecutionContextDao());
197 	}
198 
199 	@Override
200 	public Object getObject() throws Exception {
201 		if (proxyFactory == null) {
202 			afterPropertiesSet();
203 		}
204 		return proxyFactory.getProxy();
205 	}
206 
207 }