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.KeyValuePairField;
030 import org.apache.camel.dataformat.bindy.annotation.Link;
031 import org.apache.camel.dataformat.bindy.annotation.Message;
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.util.Converter;
035 import org.apache.camel.spi.PackageScanClassResolver;
036 import org.apache.camel.util.ObjectHelper;
037 import org.apache.commons.logging.Log;
038 import org.apache.commons.logging.LogFactory;
039
040 /**
041 * The BindyKeyValuePairFactory is the class who allows to bind data of type key
042 * value pair. Such format exist in financial messages FIX. This class allows to
043 * generate a model associated to message, bind data from a message to the
044 * POJOs, export data of POJOs to a message and format data into String, Date,
045 * Double, ... according to the format/pattern defined
046 */
047 public class BindyKeyValuePairFactory extends BindyAbstractFactory implements BindyFactory {
048
049 private static final transient Log LOG = LogFactory.getLog(BindyKeyValuePairFactory.class);
050
051 private Map<Integer, KeyValuePairField> keyValuePairFields = new LinkedHashMap<Integer, KeyValuePairField>();
052 private Map<Integer, Field> annotedFields = new LinkedHashMap<Integer, Field>();
053 private Map<String, Integer> sections = new HashMap<String, Integer>();
054
055 private Map<String, List> lists = new HashMap<String, List>();
056
057 private String keyValuePairSeparator;
058 private String pairSeparator;
059 private boolean messageOrdered;
060
061 public BindyKeyValuePairFactory(PackageScanClassResolver resolver, String... packageNames) throws Exception {
062
063 super(resolver, packageNames);
064
065 // Initialize what is specific to Key Value Pair model
066 initKeyValuePairModel();
067 }
068
069 /**
070 * method uses to initialize the model representing the classes who will
071 * bind the data This process will scan for classes according to the package
072 * name provided, check the annotated classes and fields. Next, we retrieve
073 * the parameters required like : Pair Separator & key value pair separator
074 *
075 * @throws Exception
076 */
077 public void initKeyValuePairModel() throws Exception {
078
079 // Find annotated KeyValuePairfields declared in the Model classes
080 initAnnotatedFields();
081
082 // Initialize key value pair parameter(s)
083 initMessageParameters();
084
085 }
086
087 public void initAnnotatedFields() {
088
089 for (Class<?> cl : models) {
090
091 List<Field> linkFields = new ArrayList<Field>();
092
093 for (Field field : cl.getDeclaredFields()) {
094 KeyValuePairField keyValuePairField = field.getAnnotation(KeyValuePairField.class);
095 if (keyValuePairField != null) {
096 if (LOG.isDebugEnabled()) {
097 LOG.debug("Key declared in the class : " + cl.getName() + ", key : " + keyValuePairField.tag() + ", Field : " + keyValuePairField.toString());
098 }
099 keyValuePairFields.put(keyValuePairField.tag(), keyValuePairField);
100 annotedFields.put(keyValuePairField.tag(), field);
101 }
102
103 Link linkField = field.getAnnotation(Link.class);
104
105 if (linkField != null) {
106 if (LOG.isDebugEnabled()) {
107 LOG.debug("Class linked : " + cl.getName() + ", Field" + field.toString());
108 }
109 linkFields.add(field);
110 }
111 }
112
113 if (!linkFields.isEmpty()) {
114 annotatedLinkFields.put(cl.getName(), linkFields);
115 }
116
117 }
118 }
119
120 /**
121 *
122 */
123 public void bind(List<String> data, Map<String, Object> model, int line) throws Exception {
124
125 Map<Integer, List> results = new HashMap<Integer, List>();
126
127 if (LOG.isDebugEnabled()) {
128 LOG.debug("Key value pairs data : " + data);
129 }
130
131 // Separate the key from its value
132 // e.g 8=FIX 4.1 --> key = 8 and Value = FIX 4.1
133 ObjectHelper.notNull(keyValuePairSeparator, "Key Value Pair not defined in the @Message annotation");
134
135 // Generate map of key value
136 // We use a Map of List as we can have the same key several times
137 // (relation one to many)
138 for (String s : data) {
139
140 // Get KeyValuePair
141 String[] keyValuePair = s.split(getKeyValuePairSeparator());
142
143 // Extract Key
144 int key = Integer.parseInt(keyValuePair[0]);
145
146 // Extract key value
147 String value = keyValuePair[1];
148
149 if (LOG.isDebugEnabled()) {
150 LOG.debug("Key : " + key + ", value : " + value);
151 }
152
153 // Add value to the Map using key value as key
154 if (!results.containsKey(key)) {
155
156 List list = new LinkedList();
157 list.add(value);
158 results.put(key, list);
159
160 } else {
161
162 List list = (LinkedList)results.get(key);
163 list.add(value);
164 }
165
166 }
167
168 // Iterate over the model
169 for (Class clazz : models) {
170
171 Object obj = model.get(clazz.getName());
172
173 if (obj != null) {
174
175 // Generate model from key value map
176 generateModelFromKeyValueMap(clazz, obj, results, line);
177
178 }
179 }
180
181 }
182
183 /**
184 * @param clazz
185 * @param obj
186 * @param results
187 * @param line
188 * @throws Exception
189 */
190 private void generateModelFromKeyValueMap(Class clazz, Object obj, Map<Integer, List> results, int line) throws Exception {
191
192 for (Field field : clazz.getDeclaredFields()) {
193
194 field.setAccessible(true);
195
196 KeyValuePairField keyValuePairField = field.getAnnotation(KeyValuePairField.class);
197
198 if (keyValuePairField != null) {
199
200 // Key
201 int key = keyValuePairField.tag();
202
203 // Get Value
204 List<String> values = results.get(key);
205 String value = null;
206
207 // we don't received data
208 if (values == null) {
209
210 /*
211 * The relation is one to one So we check if we are in a
212 * target class and if the field is mandatory
213 */
214 if (obj != null) {
215
216 // Check mandatory field
217 if (keyValuePairField.required() && values == null) {
218 throw new IllegalArgumentException("The mandatory key/tag : " + key + " has not been defined !");
219 }
220
221 Object result = getDefaultValueForPrimitive(field.getType());
222
223 try {
224 field.set(obj, result);
225 } catch (Exception e) {
226 throw new IllegalArgumentException("Setting of field " + field + " failed for object : " + obj + " and result : " + result);
227 }
228
229 } else {
230
231 /*
232 * The relation is one to many So, we create an object
233 * with empty fields and we don't check if the fields
234 * are mandatory
235 */
236
237 // Get List from Map
238 List l = lists.get(clazz.getName());
239
240 if (l != null) {
241
242 // Test if object exist
243 if (!l.isEmpty()) {
244 obj = l.get(0);
245 } else {
246 obj = clazz.newInstance();
247 }
248
249 Object result = getDefaultValueForPrimitive(field.getType());
250 try {
251 field.set(obj, result);
252 } catch (Exception e) {
253 throw new IllegalArgumentException("Setting of field " + field + " failed for object : " + obj + " and result : " + result);
254 }
255
256 // Add object created to the list
257 if (!l.isEmpty()) {
258 l.set(0, obj);
259 } else {
260 l.add(0, obj);
261 }
262
263 // and to the Map
264 lists.put(clazz.getName(), l);
265
266 // Reset obj to null
267 obj = null;
268
269 } else {
270 throw new IllegalArgumentException("The list of values is empty for the following key : " + key + " defined in the class : " + clazz.getName());
271 }
272
273 } // end of test if obj != null
274
275 } else {
276
277 // Data have been retrieved from message
278 if (values.size() >= 1) {
279
280 if (obj != null) {
281
282 // Relation OneToOne
283 value = (String)values.get(0);
284 Object result = null;
285
286 if (value != null) {
287
288 // Get pattern defined for the field
289 String pattern = keyValuePairField.pattern();
290
291 // Create format object to format the field
292 Format<?> format = FormatFactory.getFormat(field.getType(), pattern, getLocale(), keyValuePairField.precision());
293
294 // format the value of the key received
295 result = formatField(format, value, key, line);
296
297 if (LOG.isDebugEnabled()) {
298 LOG.debug("Value formated : " + result);
299 }
300
301 } else {
302 result = getDefaultValueForPrimitive(field.getType());
303 }
304 try {
305 field.set(obj, result);
306 } catch (Exception e) {
307 // System.out.println("Exception : " + e);
308 throw new IllegalArgumentException("Setting of field " + field + " failed for object : " + obj + " and result : " + result);
309 }
310
311 } else {
312
313 // Get List from Map
314 List l = lists.get(clazz.getName());
315
316 if (l != null) {
317
318 // Relation OneToMany
319 for (int i = 0; i < values.size(); i++) {
320
321 // Test if object exist
322 if ((!l.isEmpty()) && (l.size() > i)) {
323 obj = l.get(i);
324 } else {
325 obj = clazz.newInstance();
326 }
327
328 value = (String)values.get(i);
329
330 // Get pattern defined for the field
331 String pattern = keyValuePairField.pattern();
332
333 // Create format object to format the field
334 Format<?> format = FormatFactory.getFormat(field.getType(), pattern, getLocale(), keyValuePairField.precision());
335
336 // format the value of the key received
337 Object result = formatField(format, value, key, line);
338
339 if (LOG.isDebugEnabled()) {
340 LOG.debug("Value formated : " + result);
341 }
342
343 try {
344 if (value != null) {
345 field.set(obj, result);
346 } else {
347 field.set(obj, getDefaultValueForPrimitive(field.getType()));
348 }
349 } catch (Exception e) {
350 throw new IllegalArgumentException("Setting of field " + field + " failed for object : " + obj + " and result : " + result);
351 }
352
353 // Add object created to the list
354 if ((!l.isEmpty()) && (l.size() > i)) {
355 l.set(i, obj);
356 } else {
357 l.add(i, obj);
358 }
359 // and to the Map
360 lists.put(clazz.getName(), l);
361
362 // Reset obj to null
363 obj = null;
364
365 }
366
367 } else {
368 throw new IllegalArgumentException("The list of values is empty for the following key : " + key + " defined in the class : " + clazz.getName());
369 }
370 }
371
372 } else {
373
374 // No values found from message
375 Object result = getDefaultValueForPrimitive(field.getType());
376
377 try {
378 field.set(obj, result);
379 } catch (Exception e) {
380 throw new IllegalArgumentException("Setting of field " + field + " failed for object : " + obj + " and result : " + result);
381 }
382 }
383 }
384 }
385
386 OneToMany oneToMany = field.getAnnotation(OneToMany.class);
387 if (oneToMany != null) {
388
389 String targetClass = oneToMany.mappedTo();
390
391 if (!targetClass.equals("")) {
392 // Class cl = Class.forName(targetClass); Does not work in
393 // OSGI when class is defined in another bundle
394 Class cl = null;
395
396 try {
397 cl = Thread.currentThread().getContextClassLoader().loadClass(targetClass);
398 } catch (ClassNotFoundException e) {
399 cl = getClass().getClassLoader().loadClass(targetClass);
400 }
401
402 if (!lists.containsKey(cl.getName())) {
403 lists.put(cl.getName(), new ArrayList());
404 }
405
406 generateModelFromKeyValueMap(cl, null, results, line);
407
408 // Add list of objects
409 field.set(obj, lists.get(cl.getName()));
410
411 } else {
412 throw new IllegalArgumentException("No target class has been defined in @OneToMany annotation !");
413 }
414
415 }
416
417 }
418
419 }
420
421 /**
422 *
423 */
424 public String unbind(Map<String, Object> model) throws Exception {
425
426 StringBuilder builder = new StringBuilder();
427
428 Map<Integer, KeyValuePairField> keyValuePairFieldsSorted = new TreeMap<Integer, KeyValuePairField>(keyValuePairFields);
429 Iterator<Integer> it = keyValuePairFieldsSorted.keySet().iterator();
430
431 // Map containing the OUT position of the field
432 // The key is double and is created using the position of the field and
433 // location of the class in the message (using section)
434 Map<Integer, String> positions = new TreeMap<Integer, String>();
435
436 // Check if separator exists
437 ObjectHelper.notNull(this.pairSeparator, "The pair separator has not been instantiated or property not defined in the @Message annotation");
438
439 char separator = Converter.getCharDelimitor(this.getPairSeparator());
440
441 if (LOG.isDebugEnabled()) {
442 LOG.debug("Separator converted : '0x" + Integer.toHexString(separator) + "', from : " + this.getPairSeparator());
443 }
444
445 while (it.hasNext()) {
446
447 KeyValuePairField keyValuePairField = keyValuePairFieldsSorted.get(it.next());
448 ObjectHelper.notNull(keyValuePairField, "KeyValuePair is null !");
449
450 // Retrieve the field
451 Field field = annotedFields.get(keyValuePairField.tag());
452 // Change accessibility to allow to read protected/private fields
453 field.setAccessible(true);
454
455 if (LOG.isDebugEnabled()) {
456 LOG.debug("Tag : " + keyValuePairField.tag() + ", Field type : " + field.getType() + ", class : " + field.getDeclaringClass().getName());
457 }
458
459 // Retrieve the format, pattern and precision associated to the type
460 Class<?> type = field.getType();
461 String pattern = keyValuePairField.pattern();
462 int precision = keyValuePairField.precision();
463
464 // Create format
465 Format format = FormatFactory.getFormat(type, pattern, getLocale(), precision);
466
467 // Get object to be formatted
468 Object obj = model.get(field.getDeclaringClass().getName());
469
470 if (obj != null) {
471
472 // Get field value
473 Object keyValue = field.get(obj);
474
475 if (this.isMessageOrdered()) {
476 // Generate a key using the number of the section
477 // and the position of the field
478 Integer key1 = sections.get(obj.getClass().getName());
479 Integer key2 = keyValuePairField.position();
480
481 if (LOG.isDebugEnabled()) {
482 LOG.debug("Key of the section : " + key1 + ", and the field : " + key2);
483 }
484
485 Integer keyGenerated = generateKey(key1, key2);
486
487 if (LOG.isDebugEnabled()) {
488 LOG.debug("Key generated : " + String.valueOf(keyGenerated) + ", for section : " + key1);
489 }
490
491 // Add value to the list if not null
492 if (keyValue != null) {
493
494 // Format field value
495 String valueFormated;
496
497 try {
498 valueFormated = format.format(keyValue);
499 } catch (Exception e) {
500 throw new IllegalArgumentException("Formating error detected for the tag : " + keyValuePairField.tag(), e);
501 }
502
503 // Create the key value string
504 String value = keyValuePairField.tag() + this.getKeyValuePairSeparator() + valueFormated;
505
506 if (LOG.isDebugEnabled()) {
507 LOG.debug("Value to be formatted : " + keyValue + ", for the tag : " + keyValuePairField.tag() + ", and its formated value : " + valueFormated);
508 }
509
510 // Add the content to the TreeMap according to the
511 // position defined
512 positions.put(keyGenerated, value);
513
514 if (LOG.isDebugEnabled()) {
515 LOG.debug("Positions size : " + positions.size());
516 }
517 }
518 } else {
519
520 // Add value to the list if not null
521 if (keyValue != null) {
522
523 // Format field value
524 String valueFormated;
525
526 try {
527 valueFormated = format.format(keyValue);
528 } catch (Exception e) {
529 throw new IllegalArgumentException("Formating error detected for the tag : " + keyValuePairField.tag(), e);
530 }
531
532 // Create the key value string
533 String value = keyValuePairField.tag() + this.getKeyValuePairSeparator() + valueFormated + separator;
534
535 // Add content to the stringBuilder
536 builder.append(value);
537
538 if (LOG.isDebugEnabled()) {
539 LOG.debug("Value added : " + keyValuePairField.tag() + this.getKeyValuePairSeparator() + valueFormated + separator);
540 }
541 }
542 }
543 }
544 }
545
546 // Iterate through the list to generate
547 // the message according to the order/position
548 if (this.isMessageOrdered()) {
549
550 Iterator<Integer> posit = positions.keySet().iterator();
551
552 while (posit.hasNext()) {
553 String value = positions.get(posit.next());
554
555 if (LOG.isDebugEnabled()) {
556 LOG.debug("Value added at the position (" + posit + ") : " + value + separator);
557 }
558
559 builder.append(value + separator);
560 }
561 }
562
563 return builder.toString();
564 }
565
566 private Object formatField(Format format, String value, int tag, int line) throws Exception {
567
568 Object obj = null;
569
570 if (value != null) {
571
572 // Format field value
573 try {
574 obj = format.parse(value);
575 } catch (Exception e) {
576 throw new IllegalArgumentException("Parsing error detected for field defined at the tag : " + tag + ", line : " + line, e);
577 }
578
579 }
580
581 return obj;
582
583 }
584
585 /**
586 * Find the pair separator used to delimit the key value pair fields
587 */
588 public String getPairSeparator() {
589 return pairSeparator;
590 }
591
592 /**
593 * Find the key value pair separator used to link the key with its value
594 */
595 public String getKeyValuePairSeparator() {
596 return keyValuePairSeparator;
597 }
598
599 /**
600 * Flag indicating if the message must be ordered
601 *
602 * @return boolean
603 */
604 public boolean isMessageOrdered() {
605 return messageOrdered;
606 }
607
608 /**
609 * Get parameters defined in @Message annotation
610 */
611 private void initMessageParameters() {
612 if ((pairSeparator == null) || (keyValuePairSeparator == null)) {
613 for (Class<?> cl : models) {
614 // Get annotation @Message from the class
615 Message message = cl.getAnnotation(Message.class);
616
617 // Get annotation @Section from the class
618 Section section = cl.getAnnotation(Section.class);
619
620 if (message != null) {
621 // Get Pair Separator parameter
622 ObjectHelper.notNull(message.pairSeparator(), "No Pair Separator has been defined in the @Message annotation !");
623 pairSeparator = message.pairSeparator();
624 if (LOG.isDebugEnabled()) {
625 LOG.debug("Pair Separator defined for the message : " + pairSeparator);
626 }
627
628 // Get KeyValuePair Separator parameter
629 ObjectHelper.notNull(message.keyValuePairSeparator(), "No Key Value Pair Separator has been defined in the @Message annotation !");
630 keyValuePairSeparator = message.keyValuePairSeparator();
631 if (LOG.isDebugEnabled()) {
632 LOG.debug("Key Value Pair Separator defined for the message : " + keyValuePairSeparator);
633 }
634
635 // Get carriage return parameter
636 crlf = message.crlf();
637 if (LOG.isDebugEnabled()) {
638 LOG.debug("Carriage return defined for the message : " + crlf);
639 }
640
641 // Get isOrderer parameter
642 messageOrdered = message.isOrdered();
643 if (LOG.isDebugEnabled()) {
644 LOG.debug("Is the message ordered in output : " + messageOrdered);
645 }
646 }
647
648 if (section != null) {
649 // Test if section number is not null
650 ObjectHelper.notNull(section.number(), "No number has been defined for the section !");
651
652 // Get section number and add it to the sections
653 sections.put(cl.getName(), section.number());
654 }
655 }
656 }
657 }
658 }