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    }