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.List;
025    import java.util.Map;
026    import java.util.TreeMap;
027    
028    import org.apache.camel.dataformat.bindy.annotation.KeyValuePairField;
029    import org.apache.camel.dataformat.bindy.annotation.Link;
030    import org.apache.camel.dataformat.bindy.annotation.Message;
031    import org.apache.camel.dataformat.bindy.annotation.Section;
032    import org.apache.camel.dataformat.bindy.util.Converter;
033    import org.apache.camel.spi.PackageScanClassResolver;
034    import org.apache.camel.util.ObjectHelper;
035    import org.apache.commons.logging.Log;
036    import org.apache.commons.logging.LogFactory;
037    
038    /**
039     * The BindyKeyValuePairFactory is the class who allows to bind data of type key
040     * value pair. Such format exist in financial messages FIX. This class allows to
041     * generate a model associated to message, bind data from a message to the
042     * POJOs, export data of POJOs to a message and format data into String, Date,
043     * Double, ... according to the format/pattern defined
044     */
045    public class BindyKeyValuePairFactory extends BindyAbstractFactory implements BindyFactory {
046    
047        private static final transient Log LOG = LogFactory.getLog(BindyKeyValuePairFactory.class);
048    
049        private Map<Integer, KeyValuePairField> keyValuePairFields = new LinkedHashMap<Integer, KeyValuePairField>();
050        private Map<Integer, Field> annotedFields = new LinkedHashMap<Integer, Field>();
051        private Map<String, Integer> sections = new HashMap<String, Integer>();
052    
053        private String keyValuePairSeparator;
054        private String pairSeparator;
055        private boolean messageOrdered;
056    
057        public BindyKeyValuePairFactory(PackageScanClassResolver resolver, String... packageNames) throws Exception {
058    
059            super(resolver, packageNames);
060    
061            // Initialize what is specific to Key Value Pair model
062            initKeyValuePairModel();
063        }
064    
065        /**
066         * method uses to initialize the model representing the classes who will
067         * bind the data This process will scan for classes according to the package
068         * name provided, check the annotated classes and fields. Next, we retrieve
069         * the parameters required like : Pair Separator & key value pair separator
070         * 
071         * @throws Exception
072         */
073        public void initKeyValuePairModel() throws Exception {
074    
075            // Find annotated KeyValuePairfields declared in the Model classes
076            initAnnotedFields();
077    
078            // Initialize key value pair parameter(s)
079            initMessageParameters();
080    
081        }
082    
083        public void initAnnotedFields() {
084    
085            for (Class<?> cl : models) {
086    
087                List<Field> linkFields = new ArrayList<Field>();
088    
089                for (Field field : cl.getDeclaredFields()) {
090                    KeyValuePairField keyValuePairField = field.getAnnotation(KeyValuePairField.class);
091                    if (keyValuePairField != null) {
092                        if (LOG.isDebugEnabled()) {
093                            LOG.debug("Key declared in the class : " + cl.getName() + ", key : "
094                                      + keyValuePairField.tag() + ", Field : " + keyValuePairField.toString());
095                        }
096                        keyValuePairFields.put(keyValuePairField.tag(), keyValuePairField);
097                        annotedFields.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  : " + cl.getName() + ", Field" + field.toString());
105                        }
106                        linkFields.add(field);
107                    }
108                }
109    
110                if (!linkFields.isEmpty()) {
111                    annotedLinkFields.put(cl.getName(), linkFields);
112                }
113    
114            }
115        }
116    
117        public void bind(List<String> data, Map<String, Object> model) throws Exception {
118    
119            int pos = 0;
120    
121            if (LOG.isDebugEnabled()) {
122                LOG.debug("Data : " + data);
123            }
124    
125            while (pos < data.size()) {
126    
127                if (!data.get(pos).equals("")) {
128    
129                    // Separate the key from its value
130                    // e.g 8=FIX 4.1 --> key = 8 and Value = FIX 4.1
131                    ObjectHelper.notNull(this.keyValuePairSeparator,
132                                         "Key Value Pair not defined in the @Message annotation");
133                    String[] keyValuePair = data.get(pos).split(this.getKeyValuePairSeparator());
134    
135                    int tag = Integer.parseInt(keyValuePair[0]);
136                    String keyValue = keyValuePair[1];
137    
138                    if (LOG.isDebugEnabled()) {
139                        LOG.debug("Key : " + tag + ", value : " + keyValue);
140                    }
141    
142                    KeyValuePairField keyValuePairField = keyValuePairFields.get(tag);
143                    ObjectHelper.notNull(keyValuePairField, "No tag defined for the field : " + tag);
144    
145                    Field field = annotedFields.get(tag);
146                    field.setAccessible(true);
147    
148                    if (LOG.isDebugEnabled()) {
149                        LOG.debug("Tag : " + tag + ", Data : " + keyValue + ", Field type : " + field.getType());
150                    }
151    
152                    Format<?> format;
153    
154                    // Get pattern defined for the field
155                    String pattern = keyValuePairField.pattern();
156    
157                    // Create format object to format the field
158                    format = FormatFactory.getFormat(field.getType(), pattern, keyValuePairField.precision());
159    
160                    // field object to be set
161                    Object modelField = model.get(field.getDeclaringClass().getName());
162    
163                    // format the value of the key received
164                    Object value = format.parse(keyValue);
165    
166                    field.set(modelField, value);
167    
168                }
169    
170                pos++;
171            }
172    
173        }
174    
175        public String unbind(Map<String, Object> model) throws Exception {
176    
177            StringBuilder builder = new StringBuilder();
178    
179            Map<Integer, KeyValuePairField> keyValuePairFieldsSorted = new TreeMap<Integer, KeyValuePairField>(keyValuePairFields);
180            Iterator<Integer> it = keyValuePairFieldsSorted.keySet().iterator();
181            
182            // Map containing the OUT position of the field
183            // The key is double and is created using the position of the field and 
184            // location of the class in the message (using section)
185            Map<Integer, String> positions = new TreeMap<Integer, String>();
186    
187            // Check if separator exists
188            ObjectHelper.notNull(this.pairSeparator,
189                    "The pair separator has not been instantiated or property not defined in the @Message annotation");
190    
191            char separator = Converter.getCharDelimitor(this.getPairSeparator());
192    
193            if (LOG.isDebugEnabled()) {
194                LOG.debug("Separator converted : '0x" + Integer.toHexString(separator) + "', from : "
195                        + this.getPairSeparator());
196            }
197    
198            while (it.hasNext()) {
199    
200                KeyValuePairField keyValuePairField = keyValuePairFieldsSorted.get(it.next());
201                ObjectHelper.notNull(keyValuePairField, "KeyValuePair is null !");
202    
203                // Retrieve the field
204                Field field = annotedFields.get(keyValuePairField.tag());
205                // Change accessibility to allow to read protected/private fields
206                field.setAccessible(true);
207    
208                if (LOG.isDebugEnabled()) {
209                    LOG.debug("Tag : " + keyValuePairField.tag() + ", Field type : " + field.getType()
210                              + ", class : " + field.getDeclaringClass().getName());
211                }
212    
213                // Retrieve the format, pattern and precision associated to the type
214                Class type = field.getType();
215                String pattern = keyValuePairField.pattern();
216                int precision = keyValuePairField.precision();
217    
218                // Create format
219                Format format = FormatFactory.getFormat(type, pattern, precision);
220    
221                // Get object to be formatted
222                Object obj = model.get(field.getDeclaringClass().getName());
223    
224                if (obj != null) {
225    
226                    // Get field value
227                    Object keyValue = field.get(obj);
228    
229                    if (this.isMessageOrdered()) {
230                        // Generate a key using the number of the section
231                        // and the position of the field
232                        Integer key1 = sections.get(obj.getClass().getName());
233                        Integer key2 = keyValuePairField.position();
234                        Integer keyGenerated = generateKey(key1, key2);
235    
236                        if (LOG.isDebugEnabled()) {
237                            LOG.debug("Key generated : " + String.valueOf(keyGenerated) + ", for section : "
238                                      + key1);
239                        }
240    
241                        // Add value to the list if not null
242                        if (keyValue != null) {
243    
244                            // Format field value
245                            String valueFormated = format.format(keyValue);
246    
247                            // Create the key value string
248                            String value = keyValuePairField.tag() + this.getKeyValuePairSeparator()
249                                           + valueFormated;
250    
251                            // Add the content to the TreeMap according to the
252                            // position defined
253                            positions.put(keyGenerated, value);
254    
255                            if (LOG.isDebugEnabled()) {
256                                LOG.debug("Positions size : " + positions.size());
257                            }
258                        }
259                    } else {
260    
261                        // Add value to the list if not null
262                        if (keyValue != null) {
263    
264                            // Format field value
265                            String valueFormated = format.format(keyValue);
266    
267                            // Create the key value string
268                            String value = keyValuePairField.tag() + this.getKeyValuePairSeparator()
269                                           + valueFormated + separator;
270    
271                            // Add content to the stringBuilder
272                            builder.append(value);
273    
274                            if (LOG.isDebugEnabled()) {
275                                LOG.debug("Value added : " + keyValuePairField.tag()
276                                          + this.getKeyValuePairSeparator() + valueFormated + separator);
277                            }
278                        }
279                    }
280                }
281            }
282    
283            // Iterate through the list to generate
284            // the message according to the order/position
285            if (this.isMessageOrdered()) {
286    
287                Iterator<Integer> posit = positions.keySet().iterator();
288                
289                while (posit.hasNext()) {
290                    String value = positions.get(posit.next());
291                    
292                    if (LOG.isDebugEnabled()) {
293                        LOG.debug("Value added at the position (" + posit + ") : " + value + separator);
294                    }
295                    
296                    builder.append(value + separator);
297                }
298            }
299    
300    
301            return builder.toString();
302        }
303    
304        /**
305         * Find the pair separator used to delimit the key value pair fields
306         */
307        public String getPairSeparator() {
308            return pairSeparator;
309        }
310    
311        /**
312         * Find the key value pair separator used to link the key with its value
313         */
314        public String getKeyValuePairSeparator() {
315            return keyValuePairSeparator;
316        }
317        
318        /**
319         * Flag indicating if the message must be ordered
320         * 
321         * @return boolean
322         */
323        public boolean isMessageOrdered() {
324            return messageOrdered;
325        }
326    
327        /**
328         * Get parameters defined in @Message annotation
329         */
330        private void initMessageParameters() {
331            if ((pairSeparator == null) || (keyValuePairSeparator == null)) {
332                for (Class<?> cl : models) {
333                    // Get annotation @Message from the class
334                    Message message = cl.getAnnotation(Message.class);
335                    
336                    // Get annotation @Section from the class
337                    Section section = cl.getAnnotation(Section.class);
338    
339                    if (message != null) {
340                        // Get Pair Separator parameter
341                        ObjectHelper.notNull(message.pairSeparator(),
342                                "No Pair Separator has been defined in the @Message annotation !");
343                        pairSeparator = message.pairSeparator();
344                        if (LOG.isDebugEnabled()) {
345                            LOG.debug("Pair Separator defined for the message : " + pairSeparator);
346                        }
347    
348                        // Get KeyValuePair Separator parameter
349                        ObjectHelper.notNull(message.keyValuePairSeparator(),
350                                "No Key Value Pair Separator has been defined in the @Message annotation !");
351                        keyValuePairSeparator = message.keyValuePairSeparator();
352                        if (LOG.isDebugEnabled()) {
353                            LOG.debug("Key Value Pair Separator defined for the message : " + keyValuePairSeparator);
354                        }
355    
356                        // Get carriage return parameter
357                        crlf = message.crlf();
358                        if (LOG.isDebugEnabled()) {
359                            LOG.debug("Carriage return defined for the message : " + crlf);
360                        }
361    
362                        // Get isOrderer parameter
363                        messageOrdered = message.isOrdered();
364                        if (LOG.isDebugEnabled()) {
365                            LOG.debug("Is the message ordered in output : " + messageOrdered);
366                        }
367                    }
368                    
369                    if (section != null) {
370                        // Test if section number is not null
371                        ObjectHelper.notNull(section.number(), "No number has been defined for the section !");
372                        
373                        // Get section number and add it to the sections
374                        sections.put(cl.getName(), section.number());
375                    }
376                }
377            }
378        }
379    }