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            initAnnotedFields();
081    
082            // Initialize key value pair parameter(s)
083            initMessageParameters();
084    
085        }
086    
087        public void initAnnotedFields() {
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                    annotedLinkFields.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, 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, 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, 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    }