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  package org.springframework.batch.core.converter;
17  
18  import java.text.DateFormat;
19  import java.text.DecimalFormat;
20  import java.text.NumberFormat;
21  import java.text.ParseException;
22  import java.text.SimpleDateFormat;
23  import java.util.Date;
24  import java.util.Iterator;
25  import java.util.Locale;
26  import java.util.Map;
27  import java.util.Map.Entry;
28  import java.util.Properties;
29  
30  import org.springframework.batch.core.JobParameter;
31  import org.springframework.batch.core.JobParameter.ParameterType;
32  import org.springframework.batch.core.JobParameters;
33  import org.springframework.batch.core.JobParametersBuilder;
34  import org.springframework.util.StringUtils;
35  
36  /**
37   * Converter for {@link JobParameters} instances using a simple naming
38   * convention for property keys. Key names ending with "(<type>)" where
39   * type is one of string, date, long are converted to the corresponding type.
40   * The default type is string. E.g.
41   *
42   * <pre>
43   * schedule.date(date)=2007/12/11
44   * department.id(long)=2345
45   * </pre>
46   *
47   * The literal values are converted to the correct type using the default Spring
48   * strategies, augmented if necessary by the custom editors provided.
49   *
50   * <br/>
51   *
52   * If you need to be able to parse and format local-specific dates and numbers,
53   * you can inject formatters ({@link #setDateFormat(DateFormat)} and
54   * {@link #setNumberFormat(NumberFormat)}).
55   *
56   * @author Dave Syer
57   *
58   */
59  public class DefaultJobParametersConverter implements JobParametersConverter {
60  
61  	public static final String DATE_TYPE = "(date)";
62  
63  	public static final String STRING_TYPE = "(string)";
64  
65  	public static final String LONG_TYPE = "(long)";
66  
67  	private static final String DOUBLE_TYPE = "(double)";
68  
69  	private static NumberFormat DEFAULT_NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
70  
71  	private DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
72  
73  	private NumberFormat numberFormat = DEFAULT_NUMBER_FORMAT;
74  
75  	private final NumberFormat longNumberFormat = new DecimalFormat("#");
76  
77  	/**
78  	 * Check for suffix on keys and use those to decide how to convert the
79  	 * value.
80  	 *
81  	 * @throws IllegalArgumentException if a number or date is passed in that
82  	 * cannot be parsed, or cast to the correct type.
83  	 *
84  	 * @see org.springframework.batch.core.converter.JobParametersConverter#getJobParameters(java.util.Properties)
85  	 */
86  	@Override
87  	public JobParameters getJobParameters(Properties props) {
88  
89  		if (props == null || props.isEmpty()) {
90  			return new JobParameters();
91  		}
92  
93  		JobParametersBuilder propertiesBuilder = new JobParametersBuilder();
94  
95  		for (Iterator<Entry<Object, Object>> it = props.entrySet().iterator(); it.hasNext();) {
96  			Entry<Object, Object> entry = it.next();
97  			String key = (String) entry.getKey();
98  			String value = (String) entry.getValue();
99  			if (key.endsWith(DATE_TYPE)) {
100 				Date date;
101 				try {
102 					date = dateFormat.parse(value);
103 				}
104 				catch (ParseException ex) {
105 					String suffix = (dateFormat instanceof SimpleDateFormat) ? ", use "
106 							+ ((SimpleDateFormat) dateFormat).toPattern() : "";
107 							throw new IllegalArgumentException("Date format is invalid: [" + value + "]" + suffix);
108 				}
109 				propertiesBuilder.addDate(StringUtils.replace(key, DATE_TYPE, ""), date);
110 			}
111 			else if (key.endsWith(LONG_TYPE)) {
112 				Long result;
113 				try {
114 					result = (Long) parseNumber(value);
115 				}
116 				catch (ClassCastException ex) {
117 					throw new IllegalArgumentException("Number format is invalid for long value: [" + value
118 							+ "], use a format with no decimal places");
119 				}
120 				propertiesBuilder.addLong(StringUtils.replace(key, LONG_TYPE, ""), result);
121 			}
122 			else if (key.endsWith(DOUBLE_TYPE)) {
123 				Double result = parseNumber(value).doubleValue();
124 				propertiesBuilder.addDouble(StringUtils.replace(key, DOUBLE_TYPE, ""), result);
125 			}
126 			else if (StringUtils.endsWithIgnoreCase(key, STRING_TYPE)) {
127 				propertiesBuilder.addString(StringUtils.replace(key, STRING_TYPE, ""), value);
128 			}
129 			else {
130 				propertiesBuilder.addString(key, value);
131 			}
132 		}
133 
134 		return propertiesBuilder.toJobParameters();
135 	}
136 
137 	/**
138 	 * Delegate to {@link NumberFormat} to parse the value
139 	 */
140 	private Number parseNumber(String value) {
141 		try {
142 			return numberFormat.parse(value);
143 		}
144 		catch (ParseException ex) {
145 			String suffix = (numberFormat instanceof DecimalFormat) ? ", use "
146 					+ ((DecimalFormat) numberFormat).toPattern() : "";
147 					throw new IllegalArgumentException("Number format is invalid: [" + value + "], use " + suffix);
148 		}
149 	}
150 
151 	/**
152 	 * Use the same suffixes to create properties (omitting the string suffix
153 	 * because it is the default).
154 	 *
155 	 * @see org.springframework.batch.core.converter.JobParametersConverter#getProperties(org.springframework.batch.core.JobParameters)
156 	 */
157 	@Override
158 	public Properties getProperties(JobParameters params) {
159 
160 		if (params == null || params.isEmpty()) {
161 			return new Properties();
162 		}
163 
164 		Map<String, JobParameter> parameters = params.getParameters();
165 		Properties result = new Properties();
166 		for (Entry<String, JobParameter> entry : parameters.entrySet()) {
167 
168 			String key = entry.getKey();
169 			JobParameter jobParameter = entry.getValue();
170 			Object value = jobParameter.getValue();
171 			if (value != null) {
172 				if (jobParameter.getType() == ParameterType.DATE) {
173 					result.setProperty(key + DATE_TYPE, dateFormat.format(value));
174 				}
175 				else if (jobParameter.getType() == ParameterType.LONG) {
176 					result.setProperty(key + LONG_TYPE, longNumberFormat.format(value));
177 				}
178 				else if (jobParameter.getType() == ParameterType.DOUBLE) {
179 					result.setProperty(key + DOUBLE_TYPE, decimalFormat((Double)value));
180 				}
181 				else {
182 					result.setProperty(key, "" + value);
183 				}
184 			}
185 		}
186 		return result;
187 	}
188 
189 	/**
190 	 * @param value a decimal value
191 	 * @return a best guess at the desired format
192 	 */
193 	private String decimalFormat(double value) {
194 		if (numberFormat != DEFAULT_NUMBER_FORMAT) {
195 			return numberFormat.format(value);
196 		}
197 		return Double.toString(value);
198 	}
199 
200 	/**
201 	 * Public setter for injecting a date format.
202 	 *
203 	 * @param dateFormat a {@link DateFormat}, defaults to "yyyy/MM/dd"
204 	 */
205 	public void setDateFormat(DateFormat dateFormat) {
206 		this.dateFormat = dateFormat;
207 	}
208 
209 	/**
210 	 * Public setter for the {@link NumberFormat}. Used to parse longs and
211 	 * doubles, so must not contain decimal place (e.g. use "#" or "#,###").
212 	 *
213 	 * @param numberFormat the {@link NumberFormat} to set
214 	 */
215 	public void setNumberFormat(NumberFormat numberFormat) {
216 		this.numberFormat = numberFormat;
217 	}
218 }