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