001/*
002 * Copyright 2006-2018 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;
018
019import java.util.Date;
020import java.util.HashMap;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Properties;
025
026import org.springframework.batch.core.explore.JobExplorer;
027import org.springframework.util.Assert;
028
029/**
030 * Helper class for creating {@link JobParameters}. Useful because all
031 * {@link JobParameter} objects are immutable, and must be instantiated separately
032 * to ensure type safety. Once created, it can be used in the
033 * same was a java.lang.StringBuilder (except, order is irrelevant), by adding
034 * various parameter types and creating a valid {@link JobParameters} once
035 * finished.<br>
036 * <br>
037 * Using the identifying flag indicates if the parameter will be used
038 * in the identification of a JobInstance.  That flag defaults to true.
039 *
040 * @author Lucas Ward
041 * @author Michael Minella
042 * @author Glenn Renfro
043 * @author Mahmoud Ben Hassine
044 * @since 1.0
045 * @see JobParameters
046 * @see JobParameter
047 */
048public class JobParametersBuilder {
049
050        private Map<String, JobParameter> parameterMap;
051
052        private JobExplorer jobExplorer;
053
054        /**
055         * Default constructor. Initializes the builder with empty parameters.
056         */
057        public JobParametersBuilder() {
058                this.parameterMap = new LinkedHashMap<>();
059        }
060
061        /**
062         * @param jobExplorer {@link JobExplorer} used for looking up previous job parameter information
063         */
064        public JobParametersBuilder(JobExplorer jobExplorer) {
065                this.jobExplorer = jobExplorer;
066                this.parameterMap = new LinkedHashMap<>();
067        }
068
069        /**
070         * Copy constructor. Initializes the builder with the supplied parameters.
071         * @param jobParameters {@link JobParameters} instance used to initialize the builder.
072         */
073        public JobParametersBuilder(JobParameters jobParameters) {
074                this(jobParameters, null);
075        }
076
077        /**
078         * Constructor to add conversion capabilities to support JSR-352.  Per the spec, it is expected that all
079         * keys and values in the provided {@link Properties} instance are Strings
080         *
081         * @param properties the job parameters to be used
082         */
083        public JobParametersBuilder(Properties properties) {
084                this.parameterMap = new LinkedHashMap<>();
085
086                if(properties != null) {
087                        for (Map.Entry<Object, Object> curProperty : properties.entrySet()) {
088                                this.parameterMap.put((String) curProperty.getKey(), new JobParameter((String) curProperty.getValue(), false));
089                        }
090                }
091        }
092
093        /**
094         * Copy constructor. Initializes the builder with the supplied parameters.
095         * @param jobParameters {@link JobParameters} instance used to initialize the builder.
096         * @param jobExplorer {@link JobExplorer} used for looking up previous job parameter information
097         */
098        public JobParametersBuilder(JobParameters jobParameters, JobExplorer jobExplorer) {
099                this.jobExplorer = jobExplorer;
100                this.parameterMap = new LinkedHashMap<>(jobParameters.getParameters());
101        }
102
103        /**
104         * Add a new identifying String parameter for the given key.
105         *
106         * @param key - parameter accessor.
107         * @param parameter - runtime parameter
108         * @return a reference to this object.
109         */
110        public JobParametersBuilder addString(String key, String parameter) {
111                this.parameterMap.put(key, new JobParameter(parameter, true));
112                return this;
113        }
114
115        /**
116         * Add a new String parameter for the given key.
117         *
118         * @param key - parameter accessor.
119         * @param parameter - runtime parameter
120         * @param identifying - indicates if the parameter is used as part of identifying a job instance
121         * @return a reference to this object.
122         */
123        public JobParametersBuilder addString(String key, String parameter, boolean identifying) {
124                this.parameterMap.put(key, new JobParameter(parameter, identifying));
125                return this;
126        }
127
128        /**
129         * Add a new identifying {@link Date} parameter for the given key.
130         *
131         * @param key - parameter accessor.
132         * @param parameter - runtime parameter
133         * @return a reference to this object.
134         */
135        public JobParametersBuilder addDate(String key, Date parameter) {
136                this.parameterMap.put(key, new JobParameter(parameter, true));
137                return this;
138        }
139
140        /**
141         * Add a new {@link Date} parameter for the given key.
142         *
143         * @param key - parameter accessor.
144         * @param parameter - runtime parameter
145         * @param identifying - indicates if the parameter is used as part of identifying a job instance
146         * @return a reference to this object.
147         */
148        public JobParametersBuilder addDate(String key, Date parameter, boolean identifying) {
149                this.parameterMap.put(key, new JobParameter(parameter, identifying));
150                return this;
151        }
152
153        /**
154         * Add a new identifying Long parameter for the given key.
155         *
156         * @param key - parameter accessor.
157         * @param parameter - runtime parameter
158         * @return a reference to this object.
159         */
160        public JobParametersBuilder addLong(String key, Long parameter) {
161                this.parameterMap.put(key, new JobParameter(parameter, true));
162                return this;
163        }
164
165        /**
166         * Add a new Long parameter for the given key.
167         *
168         * @param key - parameter accessor.
169         * @param parameter - runtime parameter
170         * @param identifying - indicates if the parameter is used as part of identifying a job instance
171         * @return a reference to this object.
172         */
173        public JobParametersBuilder addLong(String key, Long parameter, boolean identifying) {
174                this.parameterMap.put(key, new JobParameter(parameter, identifying));
175                return this;
176        }
177
178        /**
179         * Add a new identifying Double parameter for the given key.
180         *
181         * @param key - parameter accessor.
182         * @param parameter - runtime parameter
183         * @return a reference to this object.
184         */
185        public JobParametersBuilder addDouble(String key, Double parameter) {
186                this.parameterMap.put(key, new JobParameter(parameter, true));
187                return this;
188        }
189
190        /**
191         * Add a new Double parameter for the given key.
192         *
193         * @param key - parameter accessor.
194         * @param parameter - runtime parameter
195         * @param identifying - indicates if the parameter is used as part of identifying a job instance
196         * @return a reference to this object.
197         */
198        public JobParametersBuilder addDouble(String key, Double parameter, boolean identifying) {
199                this.parameterMap.put(key, new JobParameter(parameter, identifying));
200                return this;
201        }
202
203        /**
204         * Conversion method that takes the current state of this builder and
205         * returns it as a JobParameters object.
206         *
207         * @return a valid {@link JobParameters} object.
208         */
209        public JobParameters toJobParameters() {
210                return new JobParameters(this.parameterMap);
211        }
212
213        /**
214         * Add a new {@link JobParameter} for the given key.
215         *
216         * @param key - parameter accessor
217         * @param jobParameter - runtime parameter
218         * @return a reference to this object.
219         */
220        public JobParametersBuilder addParameter(String key, JobParameter jobParameter) {
221                Assert.notNull(jobParameter, "JobParameter must not be null");
222                this.parameterMap.put(key, jobParameter);
223                return this;
224        }
225
226        /**
227         * Copy job parameters into the current state.
228         * @param jobParameters parameters to copy in
229         * @return a reference to this object.
230         */
231        public JobParametersBuilder addJobParameters(JobParameters jobParameters) {
232                Assert.notNull(jobParameters, "jobParameters must not be null");
233
234                this.parameterMap.putAll(jobParameters.getParameters());
235
236                return this;
237        }
238
239        /**
240         * Initializes the {@link JobParameters} based on the state of the {@link Job}.  This
241         * should be called after all parameters have been entered into the builder.
242         * All parameters already set on this builder instance will be appended to
243         * those retrieved from the job incrementer, overriding any with the same key (Same
244         * behaviour as {@link org.springframework.batch.core.launch.support.CommandLineJobRunner}
245         * with "-next" option and {@link org.springframework.batch.core.launch.JobOperator#startNextInstance(String)})
246         *
247         * @param job the job for which the {@link JobParameters} are being constructed.
248         * @return a reference to this object.
249         *
250         * @since 4.0
251         */
252        public JobParametersBuilder getNextJobParameters(Job job) {
253                Assert.state(this.jobExplorer != null, "A JobExplorer is required to get next job parameters");
254                Assert.notNull(job, "Job must not be null");
255                Assert.notNull(job.getJobParametersIncrementer(), "No job parameters incrementer found for job=" + job.getName());
256
257                String name = job.getName();
258                JobParameters nextParameters;
259                List<JobInstance> lastInstances = this.jobExplorer.getJobInstances(name, 0, 1);
260                JobParametersIncrementer incrementer = job.getJobParametersIncrementer();
261                if (lastInstances.isEmpty()) {
262                        // Start from a completely clean sheet
263                        nextParameters = incrementer.getNext(new JobParameters());
264                }
265                else {
266                        List<JobExecution> previousExecutions = this.jobExplorer.getJobExecutions(lastInstances.get(0));
267                        if (previousExecutions.isEmpty()) {
268                                // Normally this will not happen - an instance exists with no executions
269                                nextParameters = incrementer.getNext(new JobParameters());
270                        }
271                        else {
272                                JobExecution previousExecution = previousExecutions.get(0);
273                                nextParameters = incrementer.getNext(previousExecution.getJobParameters());
274                        }
275                }
276
277                // start with parameters from the incrementer
278                Map<String, JobParameter> nextParametersMap = new HashMap<>(nextParameters.getParameters());
279                // append new parameters (overriding those with the same key)
280                nextParametersMap.putAll(this.parameterMap);
281                this.parameterMap = nextParametersMap;
282                return this;
283        }
284}