001/*
002 * Copyright 2002-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 java.lang.reflect.Field;
020import java.sql.Types;
021import javax.sql.DataSource;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025
026import org.springframework.batch.core.repository.ExecutionContextSerializer;
027import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
028import org.springframework.batch.core.repository.dao.ExecutionContextDao;
029import org.springframework.batch.core.repository.dao.Jackson2ExecutionContextStringSerializer;
030import org.springframework.batch.core.repository.dao.JdbcExecutionContextDao;
031import org.springframework.batch.core.repository.dao.JdbcJobExecutionDao;
032import org.springframework.batch.core.repository.dao.JdbcJobInstanceDao;
033import org.springframework.batch.core.repository.dao.JdbcStepExecutionDao;
034import org.springframework.batch.core.repository.dao.JobExecutionDao;
035import org.springframework.batch.core.repository.dao.JobInstanceDao;
036import org.springframework.batch.core.repository.dao.StepExecutionDao;
037import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory;
038import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory;
039import org.springframework.batch.support.DatabaseType;
040import org.springframework.beans.factory.FactoryBean;
041import org.springframework.beans.factory.InitializingBean;
042import org.springframework.jdbc.core.JdbcOperations;
043import org.springframework.jdbc.core.JdbcTemplate;
044import org.springframework.jdbc.support.lob.DefaultLobHandler;
045import org.springframework.jdbc.support.lob.LobHandler;
046import org.springframework.util.Assert;
047import org.springframework.util.StringUtils;
048
049import static org.springframework.batch.support.DatabaseType.SYBASE;
050
051/**
052 * A {@link FactoryBean} that automates the creation of a
053 * {@link SimpleJobRepository} using JDBC DAO implementations which persist
054 * batch metadata in database. Requires the user to describe what kind of
055 * database they are using.
056 *
057 * @author Ben Hale
058 * @author Lucas Ward
059 * @author Dave Syer
060 * @author Michael Minella
061 */
062public class JobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean implements InitializingBean {
063
064        protected static final Log logger = LogFactory.getLog(JobRepositoryFactoryBean.class);
065
066        private DataSource dataSource;
067
068        private JdbcOperations jdbcOperations;
069
070        private String databaseType;
071
072        private String tablePrefix = AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX;
073
074        private DataFieldMaxValueIncrementerFactory incrementerFactory;
075
076        private int maxVarCharLength = AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH;
077
078        private LobHandler lobHandler;
079
080        private ExecutionContextSerializer serializer;
081
082        private Integer lobType;
083
084        /**
085         * @param type a value from the {@link java.sql.Types} class to indicate the type to use for a CLOB
086         */
087        public void setClobType(int type) {
088                this.lobType = type;
089        }
090
091        /**
092         * A custom implementation of the {@link ExecutionContextSerializer}.
093         * The default, if not injected, is the {@link Jackson2ExecutionContextStringSerializer}.
094         *
095         * @param serializer used to serialize/deserialize {@link org.springframework.batch.item.ExecutionContext}
096         * @see ExecutionContextSerializer
097         */
098        public void setSerializer(ExecutionContextSerializer serializer) {
099                this.serializer = serializer;
100        }
101
102        /**
103         * A special handler for large objects. The default is usually fine, except
104         * for some (usually older) versions of Oracle. The default is determined
105         * from the data base type.
106         *
107         * @param lobHandler the {@link LobHandler} to set
108         *
109         * @see LobHandler
110         */
111        public void setLobHandler(LobHandler lobHandler) {
112                this.lobHandler = lobHandler;
113        }
114
115        /**
116         * Public setter for the length of long string columns in database. Do not
117         * set this if you haven't modified the schema. Note this value will be used
118         * for the exit message in both {@link JdbcJobExecutionDao} and
119         * {@link JdbcStepExecutionDao} and also the short version of the execution
120         * context in {@link JdbcExecutionContextDao} . For databases with
121         * multi-byte character sets this number can be smaller (by up to a factor
122         * of 2 for 2-byte characters) than the declaration of the column length in
123         * the DDL for the tables.
124         *
125         * @param maxVarCharLength the exitMessageLength to set
126         */
127        public void setMaxVarCharLength(int maxVarCharLength) {
128                this.maxVarCharLength = maxVarCharLength;
129        }
130
131        /**
132         * Public setter for the {@link DataSource}.
133         * @param dataSource a {@link DataSource}
134         */
135        public void setDataSource(DataSource dataSource) {
136                this.dataSource = dataSource;
137        }
138        
139        /**
140         * Public setter for the {@link JdbcOperations}. If this property is not set explicitly,
141         * a new {@link JdbcTemplate} will be created for the configured DataSource by default.
142         * @param jdbcOperations a {@link JdbcOperations}
143         */
144        public void setJdbcOperations(JdbcOperations jdbcOperations) {
145                this.jdbcOperations = jdbcOperations;
146        }
147
148        /**
149         * Sets the database type.
150         * @param dbType as specified by
151         * {@link DefaultDataFieldMaxValueIncrementerFactory}
152         */
153        public void setDatabaseType(String dbType) {
154                this.databaseType = dbType;
155        }
156
157        /**
158         * Sets the table prefix for all the batch meta-data tables.
159         * @param tablePrefix prefix prepended to batch meta-data tables
160         */
161        public void setTablePrefix(String tablePrefix) {
162                this.tablePrefix = tablePrefix;
163        }
164
165        public void setIncrementerFactory(DataFieldMaxValueIncrementerFactory incrementerFactory) {
166                this.incrementerFactory = incrementerFactory;
167        }
168
169        @Override
170        public void afterPropertiesSet() throws Exception {
171
172                Assert.notNull(dataSource, "DataSource must not be null.");
173
174                if (jdbcOperations == null) {
175                        jdbcOperations = new JdbcTemplate(dataSource);  
176                }               
177
178                if (incrementerFactory == null) {
179                        incrementerFactory = new DefaultDataFieldMaxValueIncrementerFactory(dataSource);
180                }
181
182                if (databaseType == null) {
183                        databaseType = DatabaseType.fromMetaData(dataSource).name();
184                        logger.info("No database type set, using meta data indicating: " + databaseType);
185                }
186
187                if (lobHandler == null && databaseType.equalsIgnoreCase(DatabaseType.ORACLE.toString())) {
188                        lobHandler = new DefaultLobHandler();
189                }
190
191                if(serializer == null) {
192                        Jackson2ExecutionContextStringSerializer defaultSerializer = new Jackson2ExecutionContextStringSerializer();
193
194                        serializer = defaultSerializer;
195                }
196
197                Assert.isTrue(incrementerFactory.isSupportedIncrementerType(databaseType), "'" + databaseType
198                                + "' is an unsupported database type.  The supported database types are "
199                                + StringUtils.arrayToCommaDelimitedString(incrementerFactory.getSupportedIncrementerTypes()));
200
201                if(lobType != null) {
202                        Assert.isTrue(isValidTypes(lobType), "lobType must be a value from the java.sql.Types class");
203                }
204
205                super.afterPropertiesSet();
206        }
207
208        @Override
209        protected JobInstanceDao createJobInstanceDao() throws Exception {
210                JdbcJobInstanceDao dao = new JdbcJobInstanceDao();
211                dao.setJdbcTemplate(jdbcOperations);
212                dao.setJobIncrementer(incrementerFactory.getIncrementer(databaseType, tablePrefix + "JOB_SEQ"));
213                dao.setTablePrefix(tablePrefix);
214                dao.afterPropertiesSet();
215                return dao;
216        }
217
218        @Override
219        protected JobExecutionDao createJobExecutionDao() throws Exception {
220                JdbcJobExecutionDao dao = new JdbcJobExecutionDao();
221                dao.setJdbcTemplate(jdbcOperations);
222                dao.setJobExecutionIncrementer(incrementerFactory.getIncrementer(databaseType, tablePrefix
223                                + "JOB_EXECUTION_SEQ"));
224                dao.setTablePrefix(tablePrefix);
225                dao.setClobTypeToUse(determineClobTypeToUse(this.databaseType));
226                dao.setExitMessageLength(maxVarCharLength);
227                dao.afterPropertiesSet();
228                return dao;
229        }
230
231        @Override
232        protected StepExecutionDao createStepExecutionDao() throws Exception {
233                JdbcStepExecutionDao dao = new JdbcStepExecutionDao();
234                dao.setJdbcTemplate(jdbcOperations);
235                dao.setStepExecutionIncrementer(incrementerFactory.getIncrementer(databaseType, tablePrefix
236                                + "STEP_EXECUTION_SEQ"));
237                dao.setTablePrefix(tablePrefix);
238                dao.setClobTypeToUse(determineClobTypeToUse(this.databaseType));
239                dao.setExitMessageLength(maxVarCharLength);
240                dao.afterPropertiesSet();
241                return dao;
242        }
243
244        @Override
245        protected ExecutionContextDao createExecutionContextDao() throws Exception {
246                JdbcExecutionContextDao dao = new JdbcExecutionContextDao();
247                dao.setJdbcTemplate(jdbcOperations);
248                dao.setTablePrefix(tablePrefix);
249                dao.setClobTypeToUse(determineClobTypeToUse(this.databaseType));
250                dao.setSerializer(serializer);
251
252                if (lobHandler != null) {
253                        dao.setLobHandler(lobHandler);
254                }
255
256                dao.afterPropertiesSet();
257                // Assume the same length.
258                dao.setShortContextLength(maxVarCharLength);
259                return dao;
260        }
261
262        private int determineClobTypeToUse(String databaseType) throws Exception {
263                if(lobType != null) {
264                        return lobType;
265                } else {
266                        if (SYBASE == DatabaseType.valueOf(databaseType.toUpperCase())) {
267                                return Types.LONGVARCHAR;
268                        }
269                        else {
270                                return Types.CLOB;
271                        }
272                }
273        }
274
275        private boolean isValidTypes(int value) throws Exception {
276                boolean result = false;
277
278                for (Field field : Types.class.getFields()) {
279                        int curValue = field.getInt(null);
280                        if(curValue == value) {
281                                result = true;
282                                break;
283                        }
284                }
285
286                return result;
287        }
288
289}