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