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 }