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.Iterator;
021    import java.util.LinkedHashMap;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.TreeMap;
025    
026    import org.apache.camel.dataformat.bindy.annotation.KeyValuePairField;
027    import org.apache.camel.dataformat.bindy.annotation.Link;
028    import org.apache.camel.dataformat.bindy.annotation.Message;
029    import org.apache.camel.dataformat.bindy.util.Converter;
030    import org.apache.camel.spi.PackageScanClassResolver;
031    import org.apache.camel.util.ObjectHelper;
032    import org.apache.commons.logging.Log;
033    import org.apache.commons.logging.LogFactory;
034    
035    /**
036     * The BindyKeyValuePairFactory is the class who allows to bind data of type
037     * key value pair. Such format exist in financial messages FIX.
038     * This class allows to generate a model associated to message, bind data from a message
039     * to the POJOs, export data of POJOs to a message and format data
040     * into String, Date, Double, ... according to the format/pattern defined
041     */
042    public class BindyKeyValuePairFactory extends BindyAbstractFactory implements BindyFactory  {
043    
044        private static final transient Log LOG = LogFactory.getLog(BindyKeyValuePairFactory.class);
045    
046        private Map<Integer, KeyValuePairField> mapKeyValuePairField = new LinkedHashMap<Integer, KeyValuePairField>();
047        private Map<Integer, Field> mapAnnotedField = new LinkedHashMap<Integer, Field>();
048    
049        private String keyValuePairSeparator;
050        private String pairSeparator;
051    
052        public BindyKeyValuePairFactory(PackageScanClassResolver resolver, String packageName) throws Exception {
053            
054            super(resolver, packageName);
055            
056            // Initialize what is specific to Key Value Pair model
057            initKeyValuePairModel();
058        }
059    
060        /**
061         * method uses to initialize the model representing the classes who will
062         * bind the data This process will scan for classes according to the package
063         * name provided, check the classes and fields annoted. Next, we retrieve the
064         * parameters required like : Pair Separator & key value pair separator
065         * 
066         * @throws Exception
067         */
068        public void initKeyValuePairModel() throws Exception {
069            
070            // Find annotated KeyValuePairfields declared in the Model classes
071            initAnnotedFields();
072            
073            // Initialize key value pair parameter(s) 
074            initMessageParameters();
075    
076        }
077        
078        
079        public void initAnnotedFields() {
080    
081            for (Class<?> cl : models) {
082    
083                for (Field field : cl.getDeclaredFields()) {
084                    KeyValuePairField keyValuePairField = field.getAnnotation(KeyValuePairField.class);
085                    if (keyValuePairField != null) {
086                        if (LOG.isDebugEnabled()) {
087                            LOG.debug("Key declared in the class : " + cl.getName() + ", key : "
088                                + keyValuePairField.tag() + ", Field : " + keyValuePairField.toString());
089                        }
090                        mapKeyValuePairField.put(keyValuePairField.tag(), keyValuePairField);
091                        mapAnnotedField.put(keyValuePairField.tag(), field);
092                    }
093    
094                    Link linkField = field.getAnnotation(Link.class);
095    
096                    if (linkField != null) {
097                        if (LOG.isDebugEnabled()) {
098                            LOG.debug("Class linked  : " + cl.getName() + ", Field" + field.toString());
099                        }
100                        mapAnnotedLinkField.put(cl.getName(), field);
101                    }
102                }
103    
104            }
105        }
106        
107    
108        public void bind(List<String> data, Map<String, Object> model) throws Exception {
109    
110            int pos = 0;
111            
112            if (LOG.isDebugEnabled()) {
113                LOG.debug("Data : " + data);
114            }
115    
116            while (pos < data.size()) {
117            
118                if (!data.get(pos).equals("")) {
119               
120                    // Separate the key from its value
121                    // e.g 8=FIX 4.1 --> key = 8 and Value = FIX 4.1
122                    ObjectHelper.notNull(this.keyValuePairSeparator, "Key Value Pair not defined in the @Message annotation");
123                    String[] keyValuePair = data.get(pos).split(this.getKeyValuePairSeparator());
124                    
125                    int tag = Integer.parseInt(keyValuePair[0]);
126                    String value = keyValuePair[1];
127                    
128                    if (LOG.isDebugEnabled()) {
129                        LOG.debug("Key : " + tag + ", value : " + value);
130                    }
131    
132                    KeyValuePairField keyValuePairField = mapKeyValuePairField.get(tag);
133                    ObjectHelper.notNull(keyValuePairField, "No tag defined for the field : " + tag);
134    
135                    Field field = mapAnnotedField.get(tag);
136                    field.setAccessible(true);
137    
138                    if (LOG.isDebugEnabled()) {
139                        LOG.debug("Tag : " + tag + ", Data : " + value + ", Field type : " + field.getType());
140                    }
141    
142                    Format<?> format;
143                    String pattern = keyValuePairField.pattern();
144    
145                    format = FormatFactory.getFormat(field.getType(), pattern, keyValuePairField.precision());
146                    field.set(model.get(field.getDeclaringClass().getName()), format.parse(value));
147    
148                }
149    
150                pos++;
151            }
152    
153        }
154    
155        public String unbind(Map<String, Object> model) throws Exception {
156    
157            StringBuilder builder = new StringBuilder();
158    
159            Map<Integer, KeyValuePairField> keyValuePairFields = new TreeMap<Integer, KeyValuePairField>(mapKeyValuePairField);
160            Iterator<Integer> it = keyValuePairFields.keySet().iterator();
161    
162            // Check if separator exists
163            ObjectHelper.notNull(this.pairSeparator,
164                 "The pair separator has not been instantiated or property not defined in the @Message annotation");
165    
166            char separator = Converter.getCharDelimitor(this.getPairSeparator());
167    
168            if (LOG.isDebugEnabled()) {
169                LOG.debug("Separator converted : '0x" + Integer.toHexString(separator) + "', from : " + this.getPairSeparator());
170            }
171    
172            while (it.hasNext()) {
173    
174                KeyValuePairField keyValuePairField = mapKeyValuePairField.get(it.next());
175                ObjectHelper.notNull(keyValuePairField, "KeyValuePair is null !");
176    
177                // Retrieve the field
178                Field field = mapAnnotedField.get(keyValuePairField.tag());
179                // Change accessibility to allow to read protected/private fields
180                field.setAccessible(true);
181    
182                if (LOG.isDebugEnabled()) {
183                    LOG.debug("Tag : " + keyValuePairField.tag() + ", Field type : " + field.getType()
184                        + ", class : " + field.getDeclaringClass().getName());
185                }
186    
187                // Retrieve the format associated to the type
188                Format format;
189    
190                String pattern = keyValuePairField.pattern();
191                format = FormatFactory.getFormat(field.getType(), pattern, keyValuePairField.precision());
192    
193                Object obj = model.get(field.getDeclaringClass().getName());
194    
195                if (LOG.isDebugEnabled()) {
196                    LOG.debug("Model object : " + obj.toString());
197                }
198    
199                // Convert the content to a String and append it to the builder
200                // Add the tag followed by its key value pair separator
201                // the data and finish by the pair separator
202                builder.append(keyValuePairField.tag() + this.getKeyValuePairSeparator() 
203                               + format.format(field.get(obj)) + separator);
204            }
205    
206            return builder.toString();
207        }
208    
209        /**
210         * Find the pair separator used to delimit the key value pair fields
211         */
212        public String getPairSeparator() {
213            return pairSeparator;
214        }
215        
216        /**
217         * Find the key value pair separator used to link the key with its value
218         */
219        public String getKeyValuePairSeparator() {
220            return keyValuePairSeparator;
221        }
222    
223        /**
224         * Get parameters defined in @Message annotation
225         */
226        private void initMessageParameters() {
227    
228            if ((pairSeparator == null) || (keyValuePairSeparator == null)) {
229    
230                for (Class<?> cl : models) {
231                    
232                    // Get annotation @Message from the class
233                    Message message = cl.getAnnotation(Message.class);
234    
235                    if (message != null) {
236                        
237                        // Get Pair Separator parameter
238                        ObjectHelper.notNull(message.pairSeparator(),
239                            "No Pair Separator has been defined in the @Message annotation !");
240                        pairSeparator = message.pairSeparator();
241                        if (LOG.isDebugEnabled()) {
242                            LOG.debug("Pair Separator defined for the message : " + pairSeparator);
243                        }
244    
245                        // Get KeyValuePair Separator parameter
246                        ObjectHelper.notNull(message.keyValuePairSeparator(),
247                            "No Key Value Pair Separator has been defined in the @Message annotation !");
248                        keyValuePairSeparator = message.keyValuePairSeparator();
249                        if (LOG.isDebugEnabled()) {
250                            LOG.debug("Key Value Pair Separator defined for the message : "
251                                + keyValuePairSeparator);
252                        }
253    
254                        // Get carriage return parameter
255                        crlf = message.crlf();
256                        if (LOG.isDebugEnabled()) {
257                            LOG.debug("Carriage return defined for the message : " + crlf);
258                        }
259                    }
260                }
261            }
262        }
263    }