001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.dataformat.bindy;
018
019 import java.lang.reflect.Field;
020 import java.util.ArrayList;
021 import java.util.HashMap;
022 import java.util.Iterator;
023 import java.util.LinkedHashMap;
024 import java.util.List;
025 import java.util.Map;
026 import java.util.TreeMap;
027 import java.util.jar.JarException;
028
029 import org.apache.camel.dataformat.bindy.annotation.CsvRecord;
030 import org.apache.camel.dataformat.bindy.annotation.DataField;
031 import org.apache.camel.dataformat.bindy.annotation.Link;
032 import org.apache.camel.dataformat.bindy.annotation.Section;
033 import org.apache.camel.dataformat.bindy.util.Converter;
034 import org.apache.camel.spi.PackageScanClassResolver;
035 import org.apache.camel.util.ObjectHelper;
036 import org.apache.commons.logging.Log;
037 import org.apache.commons.logging.LogFactory;
038
039 /**
040 * The BindyCsvFactory is the class who allows to : Generate a model associated
041 * to a CSV record, bind data from a record to the POJOs, export data of POJOs
042 * to a CSV record and format data into String, Date, Double, ... according to
043 * the format/pattern defined
044 */
045 public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactory {
046
047 private static final transient Log LOG = LogFactory.getLog(BindyCsvFactory.class);
048
049 private Map<Integer, DataField> dataFields = new LinkedHashMap<Integer, DataField>();
050 private Map<Integer, Field> annotedFields = new LinkedHashMap<Integer, Field>();
051 private Map<String, Integer> sections = new HashMap<String, Integer>();
052 private int numberOptionalFields;
053 private int numberMandatoryFields;
054 private int totalFields;
055
056 private String separator;
057 private boolean skipFirstLine;
058 private boolean messageOrdered;
059
060 public BindyCsvFactory(PackageScanClassResolver resolver, String... packageNames) throws Exception {
061 super(resolver, packageNames);
062
063 // initialize specific parameters of the csv model
064 initCsvModel();
065 }
066
067 /**
068 * method uses to initialize the model representing the classes who will
069 * bind the data. This process will scan for classes according to the package
070 * name provided, check the annotated classes and fields and retrieve the
071 * separator of the CSV record
072 *
073 * @throws Exception
074 */
075 public void initCsvModel() throws Exception {
076
077 // Find annotated Datafields declared in the Model classes
078 initAnnotedFields();
079
080 // initialize Csv parameter(s)
081 // separator and skip first line from @CSVrecord annotation
082 initCsvRecordParameters();
083 }
084
085 public void initAnnotedFields() {
086
087 for (Class<?> cl : models) {
088
089 List<Field> linkFields = new ArrayList<Field>();
090
091 if (LOG.isDebugEnabled()) {
092 LOG.debug("Class retrieved : " + cl.getName());
093 }
094
095 for (Field field : cl.getDeclaredFields()) {
096 DataField dataField = field.getAnnotation(DataField.class);
097 if (dataField != null) {
098 if (LOG.isDebugEnabled()) {
099 LOG.debug("Position defined in the class : " + cl.getName() + ", position : "
100 + dataField.pos() + ", Field : " + dataField.toString());
101 }
102
103 if (dataField.required()) {
104 ++numberMandatoryFields;
105 } else {
106 ++numberOptionalFields;
107 }
108
109 dataFields.put(dataField.pos(), dataField);
110 annotedFields.put(dataField.pos(), field);
111 }
112
113 Link linkField = field.getAnnotation(Link.class);
114
115 if (linkField != null) {
116 if (LOG.isDebugEnabled()) {
117 LOG.debug("Class linked : " + cl.getName() + ", Field" + field.toString());
118 }
119 linkFields.add(field);
120 }
121
122 }
123
124 if (!linkFields.isEmpty()) {
125 annotedLinkFields.put(cl.getName(), linkFields);
126 }
127
128 totalFields = numberMandatoryFields + numberOptionalFields;
129
130 if (LOG.isDebugEnabled()) {
131 LOG.debug("Number of optional fields : " + numberOptionalFields);
132 LOG.debug("Number of mandatory fields : " + numberMandatoryFields);
133 LOG.debug("Total : " + totalFields);
134 }
135
136 }
137 }
138
139 public void bind(List<String> tokens, Map<String, Object> model) throws Exception {
140
141 int pos = 0;
142 int counterMandatoryFields = 0;
143
144 for (String data : tokens) {
145
146 // Get DataField from model
147 DataField dataField = dataFields.get(pos);
148 ObjectHelper.notNull(dataField, "No position " + pos + " defined for the field : " + data);
149
150 if (dataField.required()) {
151 // Increment counter of mandatory fields
152 ++counterMandatoryFields;
153
154 // Check if content of the field is empty
155 // This is not possible for mandatory fields
156 if (data.equals("")) {
157 throw new IllegalArgumentException("The mandatory field defined at the position " + pos
158 + " is empty !");
159 }
160 }
161
162 // Get Field to be setted
163 Field field = annotedFields.get(pos);
164 field.setAccessible(true);
165
166 if (LOG.isDebugEnabled()) {
167 LOG.debug("Pos : " + pos + ", Data : " + data + ", Field type : " + field.getType());
168 }
169
170 Format<?> format;
171
172 // Get pattern defined for the field
173 String pattern = dataField.pattern();
174
175 // Create format object to format the field
176 format = FormatFactory.getFormat(field.getType(), pattern, dataField.precision());
177
178 // field object to be set
179 Object modelField = model.get(field.getDeclaringClass().getName());
180
181 // format the data received
182 Object value = null;
183
184 if (!data.equals("")) {
185 value = format.parse(data);
186 } else {
187 value = getDefaultValueforPrimitive(field.getType());
188 }
189
190 field.set(modelField, value);
191
192 ++pos;
193
194 }
195
196 if (LOG.isDebugEnabled()) {
197 LOG.debug("Counter mandatory fields : " + counterMandatoryFields);
198 }
199
200
201 if (pos < totalFields) {
202 throw new IllegalArgumentException("Some fields are missing (optional or mandatory) !!");
203 }
204
205 if (counterMandatoryFields < numberMandatoryFields) {
206 throw new IllegalArgumentException("Some mandatory fields are missing !!");
207 }
208
209 }
210
211 public String unbind(Map<String, Object> model) throws Exception {
212
213 StringBuilder builder = new StringBuilder();
214
215 Map<Integer, DataField> dataFieldsSorted = new TreeMap<Integer, DataField>(dataFields);
216 Iterator<Integer> it = dataFieldsSorted.keySet().iterator();
217
218 // Map containing the OUT position of the field
219 // The key is double and is created using the position of the field and
220 // location of the class in the message (using section)
221 Map<Integer, String> positions = new TreeMap<Integer, String>();
222
223 // Check if separator exists
224 ObjectHelper.notNull(this.separator, "The separator has not been instantiated or property not defined in the @CsvRecord annotation");
225
226 char separator = Converter.getCharDelimitor(this.getSeparator());
227
228 if (LOG.isDebugEnabled()) {
229 LOG.debug("Separator converted : '0x" + Integer.toHexString(separator) + "', from : "
230 + this.getSeparator());
231 }
232
233 while (it.hasNext()) {
234
235 DataField dataField = dataFieldsSorted.get(it.next());
236
237 // Retrieve the field
238 Field field = annotedFields.get(dataField.pos());
239 // Change accessibility to allow to read protected/private fields
240 field.setAccessible(true);
241
242 // Retrieve the format, pattern and precision associated to the type
243 Class type = field.getType();
244 String pattern = dataField.pattern();
245 int precision = dataField.precision();
246
247 // Create format
248 Format format = FormatFactory.getFormat(type, pattern, precision);
249
250 // Get field from model
251 Object modelField = model.get(field.getDeclaringClass().getName());
252
253 if (modelField != null) {
254
255 if (this.isMessageOrdered()) {
256
257 // Generate a key using the number of the section
258 // and the position of the field
259 Integer key1 = sections.get(modelField.getClass().getName());
260 Integer key2 = dataField.position();
261 Integer keyGenerated = generateKey(key1, key2);
262
263 if (LOG.isDebugEnabled()) {
264 LOG.debug("Key generated : " + String.valueOf(keyGenerated) + ", for section : " + key1);
265 }
266
267 // Get field value
268 Object value = field.get(modelField);
269
270 // Add value to the list if not null
271 if (value != null) {
272
273 // Format field value
274 String valueFormated = format.format(value);
275
276 // Add the content to the TreeMap according to the
277 // position defined
278 positions.put(keyGenerated, valueFormated);
279
280 if (LOG.isDebugEnabled()) {
281 LOG.debug("Positions size : " + positions.size());
282 }
283 }
284 } else {
285 // Get field value
286 Object value = field.get(modelField);
287
288 // Add value to the list if not null
289 if (value != null) {
290
291 // Format field value
292 String valueFormated = format.format(value);
293 builder.append(valueFormated);
294 }
295
296 if (it.hasNext()) {
297 builder.append(separator);
298 }
299 }
300 }
301 }
302
303 // Iterate through the list to generate
304 // the message according to the order/position
305 if (this.isMessageOrdered()) {
306
307 Iterator<Integer> posit = positions.keySet().iterator();
308
309 while (posit.hasNext()) {
310 String value = positions.get(posit.next());
311
312 if (LOG.isDebugEnabled()) {
313 LOG.debug("Value added at the position (" + posit + ") : " + value + separator);
314 }
315
316 builder.append(value);
317 if (it.hasNext()) {
318 builder.append(separator);
319 }
320 }
321 }
322
323 return builder.toString();
324 }
325
326 /**
327 * Find the separator used to delimit the CSV fields
328 */
329 public String getSeparator() {
330 return separator;
331 }
332
333 /**
334 * Find the separator used to delimit the CSV fields
335 */
336 public boolean getSkipFirstLine() {
337 return skipFirstLine;
338 }
339
340 /**
341 * Flag indicating if the message must be ordered
342 *
343 * @return boolean
344 */
345 public boolean isMessageOrdered() {
346 return messageOrdered;
347 }
348
349 /**
350 *
351 * Get paramaters defined in @Csvrecord annotation
352 *
353 */
354 private void initCsvRecordParameters() {
355 if (separator == null) {
356 for (Class<?> cl : models) {
357 // Get annotation @CsvRecord from the class
358 CsvRecord record = cl.getAnnotation(CsvRecord.class);
359
360 // Get annotation @Section from the class
361 Section section = cl.getAnnotation(Section.class);
362
363 if (record != null) {
364 if (LOG.isDebugEnabled()) {
365 LOG.debug("Csv record : " + record.toString());
366 }
367
368 // Get skipFirstLine parameter
369 skipFirstLine = record.skipFirstLine();
370 if (LOG.isDebugEnabled()) {
371 LOG.debug("Skip First Line parameter of the CSV : " + skipFirstLine);
372 }
373
374 // Get Separator parameter
375 ObjectHelper.notNull(record.separator(),
376 "No separator has been defined in the @Record annotation !");
377 separator = record.separator();
378 if (LOG.isDebugEnabled()) {
379 LOG.debug("Separator defined for the CSV : " + separator);
380 }
381
382 // Get carriage return parameter
383 crlf = record.crlf();
384 if (LOG.isDebugEnabled()) {
385 LOG.debug("Carriage return defined for the CSV : " + crlf);
386 }
387 }
388
389 if (section != null) {
390 // Test if section number is not null
391 ObjectHelper.notNull(section.number(), "No number has been defined for the section !");
392
393 // Get section number and add it to the sections
394 sections.put(cl.getName(), section.number());
395 }
396 }
397 }
398 }
399 }