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.LinkedList;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.TreeMap;
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.OneToMany;
033 import org.apache.camel.dataformat.bindy.annotation.Section;
034 import org.apache.camel.dataformat.bindy.format.FormatException;
035 import org.apache.camel.dataformat.bindy.util.Converter;
036 import org.apache.camel.spi.PackageScanClassResolver;
037 import org.apache.camel.util.ObjectHelper;
038 import org.apache.commons.logging.Log;
039 import org.apache.commons.logging.LogFactory;
040
041 /**
042 * The BindyCsvFactory is the class who allows to : Generate a model associated
043 * to a CSV record, bind data from a record to the POJOs, export data of POJOs
044 * to a CSV record and format data into String, Date, Double, ... according to
045 * the format/pattern defined
046 */
047 public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactory {
048
049 private static final transient Log LOG = LogFactory.getLog(BindyCsvFactory.class);
050
051 boolean isOneToMany;
052
053 private Map<Integer, DataField> dataFields = new LinkedHashMap<Integer, DataField>();
054 private Map<Integer, Field> annotedFields = new LinkedHashMap<Integer, Field>();
055 private Map<String, Integer> sections = new HashMap<String, Integer>();
056
057 private Map<Integer, List> results;
058
059 private int numberOptionalFields;
060 private int numberMandatoryFields;
061 private int totalFields;
062
063 private String separator;
064 private boolean skipFirstLine;
065 private boolean generateHeaderColumnNames;
066 private boolean messageOrdered;
067
068 public BindyCsvFactory(PackageScanClassResolver resolver, String... packageNames) throws Exception {
069 super(resolver, packageNames);
070
071 // initialize specific parameters of the csv model
072 initCsvModel();
073 }
074
075 /**
076 * method uses to initialize the model representing the classes who will
077 * bind the data. This process will scan for classes according to the
078 * package name provided, check the annotated classes and fields and
079 * retrieve the separator of the CSV record
080 *
081 * @throws Exception
082 */
083 public void initCsvModel() throws Exception {
084
085 // Find annotated Datafields declared in the Model classes
086 initAnnotedFields();
087
088 // initialize Csv parameter(s)
089 // separator and skip first line from @CSVrecord annotation
090 initCsvRecordParameters();
091 }
092
093 public void initAnnotedFields() {
094
095 for (Class<?> cl : models) {
096
097 List<Field> linkFields = new ArrayList<Field>();
098
099 if (LOG.isDebugEnabled()) {
100 LOG.debug("Class retrieved : " + cl.getName());
101 }
102
103 for (Field field : cl.getDeclaredFields()) {
104 DataField dataField = field.getAnnotation(DataField.class);
105 if (dataField != null) {
106 if (LOG.isDebugEnabled()) {
107 LOG.debug("Position defined in the class : " + cl.getName() + ", position : " + dataField.pos() + ", Field : " + dataField.toString());
108 }
109
110 if (dataField.required()) {
111 ++numberMandatoryFields;
112 } else {
113 ++numberOptionalFields;
114 }
115
116 dataFields.put(dataField.pos(), dataField);
117 annotedFields.put(dataField.pos(), field);
118 }
119
120 Link linkField = field.getAnnotation(Link.class);
121
122 if (linkField != null) {
123 if (LOG.isDebugEnabled()) {
124 LOG.debug("Class linked : " + cl.getName() + ", Field" + field.toString());
125 }
126 linkFields.add(field);
127 }
128
129 }
130
131 if (!linkFields.isEmpty()) {
132 annotedLinkFields.put(cl.getName(), linkFields);
133 }
134
135 totalFields = numberMandatoryFields + numberOptionalFields;
136
137 if (LOG.isDebugEnabled()) {
138 LOG.debug("Number of optional fields : " + numberOptionalFields);
139 LOG.debug("Number of mandatory fields : " + numberMandatoryFields);
140 LOG.debug("Total : " + totalFields);
141 }
142
143 }
144 }
145
146 public void bind(List<String> tokens, Map<String, Object> model, int line) throws Exception {
147
148 int pos = 1;
149 int counterMandatoryFields = 0;
150
151 for (String data : tokens) {
152
153 // Get DataField from model
154 DataField dataField = dataFields.get(pos);
155 ObjectHelper.notNull(dataField, "No position " + pos + " defined for the field : " + data + ", line : " + line);
156
157 if (dataField.required()) {
158 // Increment counter of mandatory fields
159 ++counterMandatoryFields;
160
161 // Check if content of the field is empty
162 // This is not possible for mandatory fields
163 if (data.equals("")) {
164 throw new IllegalArgumentException("The mandatory field defined at the position " + pos + " is empty for the line : " + line);
165 }
166 }
167
168 // Get Field to be setted
169 Field field = annotedFields.get(pos);
170 field.setAccessible(true);
171
172 if (LOG.isDebugEnabled()) {
173 LOG.debug("Pos : " + pos + ", Data : " + data + ", Field type : " + field.getType());
174 }
175
176 Format<?> format;
177
178 // Get pattern defined for the field
179 String pattern = dataField.pattern();
180
181 // Create format object to format the field
182 format = FormatFactory.getFormat(field.getType(), pattern, dataField.precision());
183
184 // field object to be set
185 Object modelField = model.get(field.getDeclaringClass().getName());
186
187 // format the data received
188 Object value = null;
189
190 if (!data.equals("")) {
191 try {
192 value = format.parse(data);
193 } catch (FormatException ie) {
194 throw new IllegalArgumentException(ie.getMessage() + ", position : " + pos + ", line : " + line, ie);
195 } catch (Exception e) {
196 throw new IllegalArgumentException("Parsing error detected for field defined at the position : " + pos + ", line : " + line, e);
197 }
198 } else {
199 value = getDefaultValueforPrimitive(field.getType());
200 }
201
202 field.set(modelField, value);
203
204 ++pos;
205
206 }
207
208 if (LOG.isDebugEnabled()) {
209 LOG.debug("Counter mandatory fields : " + counterMandatoryFields);
210 }
211
212 if (pos < totalFields) {
213 throw new IllegalArgumentException("Some fields are missing (optional or mandatory), line : " + line);
214 }
215
216 if (counterMandatoryFields < numberMandatoryFields) {
217 throw new IllegalArgumentException("Some mandatory fields are missing, line : " + line);
218 }
219
220 }
221
222 /*
223 * public String unbind(Map<String, Object> model) throws Exception {
224 * StringBuilder builder = new StringBuilder(); Map<Integer, DataField>
225 * dataFieldsSorted = new TreeMap<Integer, DataField>(dataFields);
226 * Iterator<Integer> it = dataFieldsSorted.keySet().iterator(); // Map
227 * containing the OUT position of the field // The key is double and is
228 * created using the position of the field and // location of the class in
229 * the message (using section) Map<Integer, String> positions = new
230 * TreeMap<Integer, String>(); // Check if separator exists ObjectHelper
231 * .notNull(this.separator,
232 * "The separator has not been instantiated or property not defined in the @CsvRecord annotation"
233 * ); char separator = Converter.getCharDelimitor(this.getSeparator()); if
234 * (LOG.isDebugEnabled()) { LOG.debug("Separator converted : '0x" +
235 * Integer.toHexString(separator) + "', from : " + this.getSeparator()); }
236 * while (it.hasNext()) { DataField dataField =
237 * dataFieldsSorted.get(it.next()); // Retrieve the field Field field =
238 * annotedFields.get(dataField.pos()); // Change accessibility to allow to
239 * read protected/private fields field.setAccessible(true); // Retrieve the
240 * format, pattern and precision associated to the type Class type =
241 * field.getType(); String pattern = dataField.pattern(); int precision =
242 * dataField.precision(); // Create format Format format =
243 * FormatFactory.getFormat(type, pattern, precision); // Get field from
244 * model Object modelField = model.get(field.getDeclaringClass().getName());
245 * if (modelField != null) { // Get field value Object value =
246 * field.get(modelField); String strValue = ""; if (this.isMessageOrdered())
247 * { // Generate a key using the number of the section // and the position
248 * of the field Integer key1 =
249 * sections.get(modelField.getClass().getName()); Integer key2 =
250 * dataField.position(); Integer keyGenerated = generateKey(key1, key2); if
251 * (LOG.isDebugEnabled()) { LOG.debug("Key generated : " +
252 * String.valueOf(keyGenerated) + ", for section : " + key1); } if (value !=
253 * null) { // Format field value try { strValue = format.format(value); }
254 * catch (Exception e) { throw new
255 * IllegalArgumentException("Formating error detected for the value : " +
256 * value, e); } } // Add the content to the TreeMap according to the //
257 * position defined positions.put(keyGenerated, strValue); if
258 * (LOG.isDebugEnabled()) { LOG.debug("Positions size : " +
259 * positions.size()); } } else { // Add value to the appender if not null if
260 * (value != null) { // Format field value try { strValue =
261 * format.format(value); } catch (Exception e) { throw new
262 * IllegalArgumentException("Formating error detected for the value : " +
263 * value, e); } } if (LOG.isDebugEnabled()) {
264 * LOG.debug("Value to be formatted : " + value + ", position : " +
265 * dataField.pos() + ", and its formated value : " + strValue); }
266 * builder.append(strValue); if (it.hasNext()) { builder.append(separator);
267 * } } } } // Iterate through the list to generate // the message according
268 * to the order/position if (this.isMessageOrdered()) { Iterator<Integer>
269 * posit = positions.keySet().iterator(); while (posit.hasNext()) { String
270 * value = positions.get(posit.next()); if (LOG.isDebugEnabled()) {
271 * LOG.debug("Value added at the position (" + posit + ") : " + value +
272 * separator); } builder.append(value); if (it.hasNext()) {
273 * builder.append(separator); } } } return builder.toString(); }
274 */
275
276 public String unbind(Map<String, Object> model) throws Exception {
277
278 StringBuffer buffer = new StringBuffer();
279 results = new HashMap<Integer, List>();
280
281 // Check if separator exists
282 ObjectHelper.notNull(this.separator, "The separator has not been instantiated or property not defined in the @CsvRecord annotation");
283
284 char separator = Converter.getCharDelimitor(this.getSeparator());
285
286 if (LOG.isDebugEnabled()) {
287 LOG.debug("Separator converted : '0x" + Integer.toHexString(separator) + "', from : " + this.getSeparator());
288 }
289
290 for (Class clazz : models) {
291
292 if (model.containsKey(clazz.getName())) {
293
294 Object obj = model.get(clazz.getName());
295
296 if (LOG.isDebugEnabled()) {
297 LOG.debug("Model object : " + obj + ", class : " + obj.getClass().getName());
298 }
299
300 if (obj != null) {
301
302 // Generate Csv table
303 generateCsvPositionMap(clazz, obj);
304
305 }
306 }
307 }
308
309 // Transpose result
310 List<List> l = new ArrayList<List>();
311
312 if (isOneToMany) {
313
314 l = product(results);
315
316 } else {
317
318 // Convert Map<Integer, List> into List<List>
319 TreeMap<Integer, List> sortValues = new TreeMap<Integer, List>(results);
320 List<String> temp = new ArrayList<String>();
321
322 for (Integer key : sortValues.keySet()) {
323
324 // Get list of values
325 List<String> val = sortValues.get(key);
326
327 // For one to one relation
328 // There is only one item in the list
329 String value = (String)val.get(0);
330
331 // Add the value to the temp array
332 if (value != null) {
333 temp.add(value);
334 } else {
335 temp.add("");
336 }
337 }
338
339 l.add(temp);
340 }
341
342 if (l != null) {
343
344 Iterator it = l.iterator();
345 while (it.hasNext()) {
346
347 List<String> tokens = (ArrayList<String>)it.next();
348 Iterator itx = tokens.iterator();
349
350 while (itx.hasNext()) {
351
352 String res = (String)itx.next();
353
354 if (res != null) {
355 buffer.append(res);
356 } else {
357 buffer.append("");
358 }
359
360 if (itx.hasNext()) {
361 buffer.append(separator);
362 }
363
364 }
365
366 if (it.hasNext()) {
367 buffer.append(Converter.getStringCarriageReturn(getCarriageReturn()));
368 }
369
370 }
371
372 }
373
374 return buffer.toString();
375
376 }
377
378 private List<List> product(Map<Integer, List> values) {
379
380 TreeMap<Integer, List> sortValues = new TreeMap<Integer, List>(values);
381
382 List<List> product = new ArrayList<List>();
383 Map<Integer, Integer> index = new HashMap<Integer, Integer>();
384
385 boolean cont = true;
386 int idx = 0;
387 int idxSize;
388
389 do {
390
391 idxSize = 0;
392 List v = new ArrayList();
393
394 for (int ii = 1; ii <= sortValues.lastKey(); ii++) {
395
396 List l = values.get(ii);
397
398 if (l == null) {
399 v.add("");
400 ++idxSize;
401 continue;
402 }
403
404 if (l.size() >= idx + 1) {
405 v.add(l.get(idx));
406 index.put(ii, idx);
407 if (LOG.isDebugEnabled()) {
408 LOG.debug("Value : " + l.get(idx) + ", pos : " + ii + ", at :" + idx);
409 }
410
411 } else {
412 v.add(l.get(0));
413 index.put(ii, 0);
414 ++idxSize;
415 if (LOG.isDebugEnabled()) {
416 LOG.debug("Value : " + l.get(0) + ", pos : " + ii + ", at index : " + 0);
417 }
418 }
419
420 }
421
422 if (idxSize != sortValues.lastKey()) {
423 product.add(v);
424 }
425 ++idx;
426
427 } while (idxSize != sortValues.lastKey());
428
429 return product;
430 }
431
432 private void generateCsvPositionMap(Class clazz, Object obj) throws Exception {
433
434 String result = "";
435
436 for (Field field : clazz.getDeclaredFields()) {
437
438 field.setAccessible(true);
439
440 DataField datafield = field.getAnnotation(DataField.class);
441
442 if (datafield != null) {
443
444 if (obj != null) {
445
446 // Retrieve the format, pattern and precision associated to
447 // the type
448 Class type = field.getType();
449 String pattern = datafield.pattern();
450 int precision = datafield.precision();
451
452 // Create format
453 Format format = FormatFactory.getFormat(type, pattern, precision);
454
455 // Get field value
456 Object value = field.get(obj);
457
458 result = formatString(format, value);
459
460 if (LOG.isDebugEnabled()) {
461 LOG.debug("Value to be formatted : " + value + ", position : " + datafield.pos() + ", and its formated value : " + result);
462 }
463
464 } else {
465 result = "";
466 }
467
468 Integer key;
469
470 if (isMessageOrdered()) {
471
472 // Generate a key using the number of the section
473 // and the position of the field
474 Integer key1 = sections.get(obj.getClass().getName());
475 Integer key2 = datafield.position();
476 Integer keyGenerated = generateKey(key1, key2);
477
478 if (LOG.isDebugEnabled()) {
479 LOG.debug("Key generated : " + String.valueOf(keyGenerated) + ", for section : " + key1);
480 }
481
482 key = keyGenerated;
483
484 } else {
485
486 key = datafield.pos();
487 }
488
489 if (!results.containsKey(key)) {
490
491 List list = new LinkedList();
492 list.add(result);
493 results.put(key, list);
494
495 } else {
496
497 List list = (LinkedList)results.get(key);
498 list.add(result);
499 }
500
501 }
502
503 OneToMany oneToMany = field.getAnnotation(OneToMany.class);
504 if (oneToMany != null) {
505
506 // Set global variable
507 // Will be used during generation of CSV
508 isOneToMany = true;
509
510 ArrayList list = (ArrayList)field.get(obj);
511
512 if (list != null) {
513
514 Iterator it = list.iterator();
515
516 while (it.hasNext()) {
517
518 Object target = it.next();
519 generateCsvPositionMap(target.getClass(), target);
520
521 }
522
523 } else {
524
525 // Call this function to add empty value
526 // in the table
527 generateCsvPositionMap(field.getClass(), null);
528 }
529
530 }
531 }
532
533 }
534
535 private String formatString(Format format, Object value) throws Exception {
536
537 String strValue = "";
538
539 if (value != null) {
540
541 // Format field value
542 try {
543 strValue = format.format(value);
544 } catch (Exception e) {
545 throw new IllegalArgumentException("Formating error detected for the value : " + value, e);
546 }
547
548 }
549
550 return strValue;
551
552 }
553
554 public String generateHeader() {
555
556 Map<Integer, DataField> dataFieldsSorted = new TreeMap<Integer, DataField>(dataFields);
557 Iterator<Integer> it = dataFieldsSorted.keySet().iterator();
558
559 StringBuilder builderHeader = new StringBuilder();
560
561 while (it.hasNext()) {
562
563 DataField dataField = dataFieldsSorted.get(it.next());
564
565 // Retrieve the field
566 Field field = annotedFields.get(dataField.pos());
567 // Change accessibility to allow to read protected/private fields
568 field.setAccessible(true);
569
570 // Get dataField
571 if (!dataField.columnName().equals("")) {
572 builderHeader.append(dataField.columnName());
573 } else {
574 builderHeader.append(field.getName());
575 }
576
577 if (it.hasNext()) {
578 builderHeader.append(separator);
579 }
580
581 }
582
583 return builderHeader.toString();
584 }
585
586 /**
587 * Get paramaters defined in @Csvrecord annotation
588 */
589 private void initCsvRecordParameters() {
590 if (separator == null) {
591 for (Class<?> cl : models) {
592
593 // Get annotation @CsvRecord from the class
594 CsvRecord record = cl.getAnnotation(CsvRecord.class);
595
596 // Get annotation @Section from the class
597 Section section = cl.getAnnotation(Section.class);
598
599 if (record != null) {
600 if (LOG.isDebugEnabled()) {
601 LOG.debug("Csv record : " + record.toString());
602 }
603
604 // Get skipFirstLine parameter
605 skipFirstLine = record.skipFirstLine();
606 if (LOG.isDebugEnabled()) {
607 LOG.debug("Skip First Line parameter of the CSV : " + skipFirstLine);
608 }
609
610 // Get generateHeaderColumnNames parameter
611 generateHeaderColumnNames = record.generateHeaderColumns();
612 if (LOG.isDebugEnabled()) {
613 LOG.debug("Generate header column names parameter of the CSV : " + generateHeaderColumnNames);
614 }
615
616 // Get Separator parameter
617 ObjectHelper.notNull(record.separator(), "No separator has been defined in the @Record annotation !");
618 separator = record.separator();
619 if (LOG.isDebugEnabled()) {
620 LOG.debug("Separator defined for the CSV : " + separator);
621 }
622
623 // Get carriage return parameter
624 crlf = record.crlf();
625 if (LOG.isDebugEnabled()) {
626 LOG.debug("Carriage return defined for the CSV : " + crlf);
627 }
628
629 // Get isOrdered parameter
630 messageOrdered = record.isOrdered();
631 if (LOG.isDebugEnabled()) {
632 LOG.debug("Must CSV record be ordered ? " + messageOrdered);
633 }
634
635 }
636
637 if (section != null) {
638 // Test if section number is not null
639 ObjectHelper.notNull(section.number(), "No number has been defined for the section !");
640
641 // Get section number and add it to the sections
642 sections.put(cl.getName(), section.number());
643 }
644 }
645 }
646 }
647
648 /**
649 * Find the separator used to delimit the CSV fields
650 */
651 public String getSeparator() {
652 return separator;
653 }
654
655 /**
656 * Flag indicating if the first line of the CSV must be skipped
657 */
658 public boolean getGenerateHeaderColumnNames() {
659 return generateHeaderColumnNames;
660 }
661
662 /**
663 * Find the separator used to delimit the CSV fields
664 */
665 public boolean getSkipFirstLine() {
666 return skipFirstLine;
667 }
668
669 /**
670 * Flag indicating if the message must be ordered
671 *
672 * @return boolean
673 */
674 public boolean isMessageOrdered() {
675 return messageOrdered;
676 }
677 }