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    }