001/*
002 * Copyright 2006-2014 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 */
016
017package org.springframework.batch.core.repository.support;
018
019import org.aopalliance.intercept.MethodInterceptor;
020import org.aopalliance.intercept.MethodInvocation;
021import org.springframework.aop.framework.ProxyFactory;
022import org.springframework.aop.support.DefaultPointcutAdvisor;
023import org.springframework.aop.support.NameMatchMethodPointcut;
024import org.springframework.batch.core.repository.JobRepository;
025import org.springframework.batch.core.repository.dao.ExecutionContextDao;
026import org.springframework.batch.core.repository.dao.JobExecutionDao;
027import org.springframework.batch.core.repository.dao.JobInstanceDao;
028import org.springframework.batch.core.repository.dao.StepExecutionDao;
029import org.springframework.batch.support.PropertiesConverter;
030import org.springframework.beans.factory.FactoryBean;
031import org.springframework.beans.factory.InitializingBean;
032import org.springframework.transaction.PlatformTransactionManager;
033import org.springframework.transaction.interceptor.TransactionInterceptor;
034import org.springframework.transaction.support.TransactionSynchronizationManager;
035import org.springframework.util.Assert;
036
037/**
038 * A {@link FactoryBean} that automates the creation of a
039 * {@link SimpleJobRepository}. Declares abstract methods for providing DAO
040 * object implementations.
041 *
042 * @see JobRepositoryFactoryBean
043 * @see MapJobRepositoryFactoryBean
044 *
045 * @author Ben Hale
046 * @author Lucas Ward
047 * @author Robert Kasanicky
048 */
049public abstract class AbstractJobRepositoryFactoryBean implements FactoryBean<JobRepository>, InitializingBean {
050
051        private PlatformTransactionManager transactionManager;
052
053        private ProxyFactory proxyFactory;
054
055        private String isolationLevelForCreate = DEFAULT_ISOLATION_LEVEL;
056
057        private boolean validateTransactionState = true;
058
059        /**
060         * Default value for isolation level in create* method.
061         */
062        private static final String DEFAULT_ISOLATION_LEVEL = "ISOLATION_SERIALIZABLE";
063
064        /**
065         * @return fully configured {@link JobInstanceDao} implementation.
066         *
067         * @throws Exception thrown if error occurs creating JobInstanceDao.
068         */
069        protected abstract JobInstanceDao createJobInstanceDao() throws Exception;
070
071        /**
072         * @return fully configured {@link JobExecutionDao} implementation.
073         *
074         * @throws Exception thrown if error occurs creating JobExecutionDao.
075         */
076        protected abstract JobExecutionDao createJobExecutionDao() throws Exception;
077
078        /**
079         * @return fully configured {@link StepExecutionDao} implementation.
080         *
081         * @throws Exception thrown if error occurs creating StepExecutionDao.
082         */
083        protected abstract StepExecutionDao createStepExecutionDao() throws Exception;
084
085        /**
086         * @return fully configured {@link ExecutionContextDao} implementation.
087         *
088         * @throws Exception thrown if error occurs creating ExecutionContextDao.
089         */
090        protected abstract ExecutionContextDao createExecutionContextDao() throws Exception;
091
092        /**
093         * The type of object to be returned from {@link #getObject()}.
094         *
095         * @return JobRepository.class
096         * @see org.springframework.beans.factory.FactoryBean#getObjectType()
097         */
098        @Override
099        public Class<JobRepository> getObjectType() {
100                return JobRepository.class;
101        }
102
103        @Override
104        public boolean isSingleton() {
105                return true;
106        }
107
108        /**
109         * Flag to determine whether to check for an existing transaction when a
110         * JobExecution is created. Defaults to true because it is usually a
111         * mistake, and leads to problems with restartability and also to deadlocks
112         * in multi-threaded steps.
113         *
114         * @param validateTransactionState the flag to set
115         */
116        public void setValidateTransactionState(boolean validateTransactionState) {
117                this.validateTransactionState = validateTransactionState;
118        }
119
120        /**
121         * public setter for the isolation level to be used for the transaction when
122         * job execution entities are initially created. The default is
123         * ISOLATION_SERIALIZABLE, which prevents accidental concurrent execution of
124         * the same job (ISOLATION_REPEATABLE_READ would work as well).
125         *
126         * @param isolationLevelForCreate the isolation level name to set
127         *
128         * @see SimpleJobRepository#createJobExecution(String,
129         * org.springframework.batch.core.JobParameters)
130         */
131        public void setIsolationLevelForCreate(String isolationLevelForCreate) {
132                this.isolationLevelForCreate = isolationLevelForCreate;
133        }
134
135        /**
136         * Public setter for the {@link PlatformTransactionManager}.
137         * @param transactionManager the transactionManager to set
138         */
139        public void setTransactionManager(PlatformTransactionManager transactionManager) {
140                this.transactionManager = transactionManager;
141        }
142
143        /**
144         * The transaction manager used in this factory. Useful to inject into steps
145         * and jobs, to ensure that they are using the same instance.
146         *
147         * @return the transactionManager
148         */
149        public PlatformTransactionManager getTransactionManager() {
150                return transactionManager;
151        }
152
153        /**
154         * Convenience method for clients to grab the {@link JobRepository} without
155         * a cast.
156         * @return the {@link JobRepository} from {@link #getObject()}
157         * @throws Exception if the repository could not be created
158         * @deprecated use {@link #getObject()} instead
159         */
160        @Deprecated
161        public JobRepository getJobRepository() throws Exception {
162                return getObject();
163        }
164
165        private void initializeProxy() throws Exception {
166                if (proxyFactory == null) {
167                        proxyFactory = new ProxyFactory();
168                        TransactionInterceptor advice = new TransactionInterceptor(transactionManager,
169                                        PropertiesConverter.stringToProperties("create*=PROPAGATION_REQUIRES_NEW,"
170                                                        + isolationLevelForCreate + "\ngetLastJobExecution*=PROPAGATION_REQUIRES_NEW,"
171                                                        + isolationLevelForCreate + "\n*=PROPAGATION_REQUIRED"));
172                        if (validateTransactionState) {
173                                DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(new MethodInterceptor() {
174                                        @Override
175                                        public Object invoke(MethodInvocation invocation) throws Throwable {
176                                                if (TransactionSynchronizationManager.isActualTransactionActive()) {
177                                                        throw new IllegalStateException(
178                                                                        "Existing transaction detected in JobRepository. "
179                                                                                        + "Please fix this and try again (e.g. remove @Transactional annotations from client).");
180                                                }
181                                                return invocation.proceed();
182                                        }
183                                });
184                                NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
185                                pointcut.addMethodName("create*");
186                                advisor.setPointcut(pointcut);
187                                proxyFactory.addAdvisor(advisor);
188                        }
189                        proxyFactory.addAdvice(advice);
190                        proxyFactory.setProxyTargetClass(false);
191                        proxyFactory.addInterface(JobRepository.class);
192                        proxyFactory.setTarget(getTarget());
193                }
194        }
195
196        @Override
197        public void afterPropertiesSet() throws Exception {
198                Assert.notNull(transactionManager, "TransactionManager must not be null.");
199
200                initializeProxy();
201        }
202
203        private Object getTarget() throws Exception {
204                return new SimpleJobRepository(createJobInstanceDao(), createJobExecutionDao(), createStepExecutionDao(),
205                                createExecutionContextDao());
206        }
207
208        @Override
209        public JobRepository getObject() throws Exception {
210                if (proxyFactory == null) {
211                        afterPropertiesSet();
212                }
213                return (JobRepository) proxyFactory.getProxy(getClass().getClassLoader());
214        }
215
216}