| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.camel.dataformat.bindy; |
| |
| import java.lang.reflect.Field; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| import org.apache.camel.dataformat.bindy.annotation.KeyValuePairField; |
| import org.apache.camel.dataformat.bindy.annotation.Link; |
| import org.apache.camel.dataformat.bindy.annotation.Message; |
| import org.apache.camel.dataformat.bindy.annotation.OneToMany; |
| import org.apache.camel.dataformat.bindy.annotation.Section; |
| import org.apache.camel.dataformat.bindy.util.Converter; |
| import org.apache.camel.spi.PackageScanClassResolver; |
| import org.apache.camel.util.ObjectHelper; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * The BindyKeyValuePairFactory is the class who allows to bind data of type key |
| * value pair. Such format exist in financial messages FIX. This class allows to |
| * generate a model associated to message, bind data from a message to the |
| * POJOs, export data of POJOs to a message and format data into String, Date, |
| * Double, ... according to the format/pattern defined |
| */ |
| public class BindyKeyValuePairFactory extends BindyAbstractFactory implements BindyFactory { |
| |
| private static final transient Logger LOG = LoggerFactory.getLogger(BindyKeyValuePairFactory.class); |
| |
| private Map<Integer, KeyValuePairField> keyValuePairFields = new LinkedHashMap<Integer, KeyValuePairField>(); |
| private Map<Integer, Field> annotatedFields = new LinkedHashMap<Integer, Field>(); |
| private Map<String, Integer> sections = new HashMap<String, Integer>(); |
| private String keyValuePairSeparator; |
| private String pairSeparator; |
| private boolean messageOrdered; |
| |
| public BindyKeyValuePairFactory(PackageScanClassResolver resolver, String... packageNames) throws Exception { |
| |
| super(resolver, packageNames); |
| |
| // Initialize what is specific to Key Value Pair model |
| initKeyValuePairModel(); |
| } |
| |
| /** |
| * method uses to initialize the model representing the classes who will |
| * bind the data This process will scan for classes according to the package |
| * name provided, check the annotated classes and fields. Next, we retrieve |
| * the parameters required like : Pair Separator & key value pair separator |
| * |
| * @throws Exception |
| */ |
| public void initKeyValuePairModel() throws Exception { |
| |
| // Find annotated KeyValuePairfields declared in the Model classes |
| initAnnotatedFields(); |
| |
| // Initialize key value pair parameter(s) |
| initMessageParameters(); |
| |
| } |
| |
| public void initAnnotatedFields() { |
| |
| for (Class<?> cl : models) { |
| |
| List<Field> linkFields = new ArrayList<Field>(); |
| |
| for (Field field : cl.getDeclaredFields()) { |
| KeyValuePairField keyValuePairField = field.getAnnotation(KeyValuePairField.class); |
| if (keyValuePairField != null) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Key declared in the class : {}, key : {}, Field : {}", new Object[]{cl.getName(), keyValuePairField.tag(), keyValuePairField}); |
| } |
| keyValuePairFields.put(keyValuePairField.tag(), keyValuePairField); |
| annotatedFields.put(keyValuePairField.tag(), field); |
| } |
| |
| Link linkField = field.getAnnotation(Link.class); |
| |
| if (linkField != null) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Class linked : {}, Field {}", cl.getName(), field); |
| } |
| linkFields.add(field); |
| } |
| } |
| |
| if (!linkFields.isEmpty()) { |
| annotatedLinkFields.put(cl.getName(), linkFields); |
| } |
| |
| } |
| } |
| |
| @Override |
| public void bind(List<String> data, Map<String, Object> model, int line) throws Exception { |
| |
| // Map to hold the model @OneToMany classes while binding |
| Map<String, List<Object>> lists = new HashMap<String, List<Object>>(); |
| |
| bind(data, model, line, lists); |
| } |
| |
| public void bind(List<String> data, Map<String, Object> model, int line, Map<String, List<Object>> lists) throws Exception { |
| |
| Map<Integer, List<String>> results = new HashMap<Integer, List<String>>(); |
| |
| LOG.debug("Key value pairs data : {}", data); |
| |
| // Separate the key from its value |
| // e.g 8=FIX 4.1 --> key = 8 and Value = FIX 4.1 |
| ObjectHelper.notNull(keyValuePairSeparator, "Key Value Pair not defined in the @Message annotation"); |
| |
| // Generate map of key value |
| // We use a Map of List as we can have the same key several times |
| // (relation one to many) |
| for (String s : data) { |
| |
| // Get KeyValuePair |
| String[] keyValuePair = s.split(getKeyValuePairSeparator()); |
| |
| // Extract only if value is populated in key:value pair in incoming message. |
| if (keyValuePair.length > 1) { |
| // Extract Key |
| int key = Integer.parseInt(keyValuePair[0]); |
| |
| // Extract key value |
| String value = keyValuePair[1]; |
| |
| LOG.debug("Key: {}, value: {}", key, value); |
| |
| // Add value to the Map using key value as key |
| if (!results.containsKey(key)) { |
| List<String> list = new LinkedList<String>(); |
| list.add(value); |
| results.put(key, list); |
| } else { |
| List<String> list = results.get(key); |
| list.add(value); |
| } |
| } |
| |
| } |
| |
| // Iterate over the model |
| for (Class clazz : models) { |
| |
| Object obj = model.get(clazz.getName()); |
| |
| if (obj != null) { |
| |
| // Generate model from key value map |
| generateModelFromKeyValueMap(clazz, obj, results, line, lists); |
| |
| } |
| } |
| |
| } |
| |
| private void generateModelFromKeyValueMap(Class clazz, Object obj, Map<Integer, List<String>> results, int line, Map<String, List<Object>> lists) throws Exception { |
| |
| for (Field field : clazz.getDeclaredFields()) { |
| |
| field.setAccessible(true); |
| |
| KeyValuePairField keyValuePairField = field.getAnnotation(KeyValuePairField.class); |
| |
| if (keyValuePairField != null) { |
| |
| // Key |
| int key = keyValuePairField.tag(); |
| |
| // Get Value |
| List<String> values = results.get(key); |
| String value = null; |
| |
| // we don't received data |
| if (values == null) { |
| |
| /* |
| * The relation is one to one So we check if we are in a |
| * target class and if the field is mandatory |
| */ |
| if (obj != null) { |
| |
| // Check mandatory field |
| if (keyValuePairField.required()) { |
| throw new IllegalArgumentException("The mandatory key/tag : " + key + " has not been defined !"); |
| } |
| |
| Object result = getDefaultValueForPrimitive(field.getType()); |
| |
| try { |
| field.set(obj, result); |
| } catch (Exception e) { |
| throw new IllegalArgumentException("Setting of field " + field + " failed for object : " + obj + " and result : " + result); |
| } |
| |
| } else { |
| |
| /* |
| * The relation is one to many So, we create an object |
| * with empty fields and we don't check if the fields |
| * are mandatory |
| */ |
| |
| // Get List from Map |
| List<Object> l = lists.get(clazz.getName()); |
| |
| if (l != null) { |
| |
| // Test if object exist |
| if (!l.isEmpty()) { |
| obj = l.get(0); |
| } else { |
| obj = clazz.newInstance(); |
| } |
| |
| Object result = getDefaultValueForPrimitive(field.getType()); |
| try { |
| field.set(obj, result); |
| } catch (Exception e) { |
| throw new IllegalArgumentException("Setting of field " + field + " failed for object : " + obj + " and result : " + result); |
| } |
| |
| // Add object created to the list |
| if (!l.isEmpty()) { |
| l.set(0, obj); |
| } else { |
| l.add(0, obj); |
| } |
| |
| // and to the Map |
| lists.put(clazz.getName(), l); |
| |
| // Reset obj to null |
| obj = null; |
| |
| } else { |
| throw new IllegalArgumentException("The list of values is empty for the following key : " + key + " defined in the class : " + clazz.getName()); |
| } |
| |
| } // end of test if obj != null |
| |
| } else { |
| |
| // Data have been retrieved from message |
| if (values.size() >= 1) { |
| |
| if (obj != null) { |
| |
| // Relation OneToOne |
| value = (String)values.get(0); |
| Object result = null; |
| |
| if (value != null) { |
| |
| // Get pattern defined for the field |
| String pattern = keyValuePairField.pattern(); |
| |
| // Create format object to format the field |
| Format<?> format = FormatFactory.getFormat(field.getType(), pattern, getLocale(), keyValuePairField.precision()); |
| |
| // format the value of the key received |
| result = formatField(format, value, key, line); |
| |
| LOG.debug("Value formated : {}", result); |
| |
| } else { |
| result = getDefaultValueForPrimitive(field.getType()); |
| } |
| try { |
| field.set(obj, result); |
| } catch (Exception e) { |
| // System.out.println("Exception : " + e); |
| throw new IllegalArgumentException("Setting of field " + field + " failed for object : " + obj + " and result : " + result); |
| } |
| |
| } else { |
| |
| // Get List from Map |
| List<Object> l = lists.get(clazz.getName()); |
| |
| if (l != null) { |
| |
| // Relation OneToMany |
| for (int i = 0; i < values.size(); i++) { |
| |
| // Test if object exist |
| if ((!l.isEmpty()) && (l.size() > i)) { |
| obj = l.get(i); |
| } else { |
| obj = clazz.newInstance(); |
| } |
| |
| value = (String)values.get(i); |
| |
| // Get pattern defined for the field |
| String pattern = keyValuePairField.pattern(); |
| |
| // Create format object to format the field |
| Format<?> format = FormatFactory.getFormat(field.getType(), pattern, getLocale(), keyValuePairField.precision()); |
| |
| // format the value of the key received |
| Object result = formatField(format, value, key, line); |
| |
| LOG.debug("Value formated : {}", result); |
| |
| try { |
| if (value != null) { |
| field.set(obj, result); |
| } else { |
| field.set(obj, getDefaultValueForPrimitive(field.getType())); |
| } |
| } catch (Exception e) { |
| throw new IllegalArgumentException("Setting of field " + field + " failed for object: " + obj + " and result: " + result); |
| } |
| |
| // Add object created to the list |
| if ((!l.isEmpty()) && (l.size() > i)) { |
| l.set(i, obj); |
| } else { |
| l.add(i, obj); |
| } |
| // and to the Map |
| lists.put(clazz.getName(), l); |
| |
| // Reset obj to null |
| obj = null; |
| |
| } |
| |
| } else { |
| throw new IllegalArgumentException("The list of values is empty for the following key: " + key + " defined in the class: " + clazz.getName()); |
| } |
| } |
| |
| } else { |
| |
| // No values found from message |
| Object result = getDefaultValueForPrimitive(field.getType()); |
| |
| try { |
| field.set(obj, result); |
| } catch (Exception e) { |
| throw new IllegalArgumentException("Setting of field " + field + " failed for object: " + obj + " and result: " + result); |
| } |
| } |
| } |
| } |
| |
| OneToMany oneToMany = field.getAnnotation(OneToMany.class); |
| if (oneToMany != null) { |
| |
| String targetClass = oneToMany.mappedTo(); |
| |
| if (!targetClass.equals("")) { |
| // Class cl = Class.forName(targetClass); Does not work in |
| // OSGI when class is defined in another bundle |
| Class cl = null; |
| |
| try { |
| cl = Thread.currentThread().getContextClassLoader().loadClass(targetClass); |
| } catch (ClassNotFoundException e) { |
| cl = getClass().getClassLoader().loadClass(targetClass); |
| } |
| |
| if (!lists.containsKey(cl.getName())) { |
| lists.put(cl.getName(), new ArrayList<Object>()); |
| } |
| |
| generateModelFromKeyValueMap(cl, null, results, line, lists); |
| |
| // Add list of objects |
| field.set(obj, lists.get(cl.getName())); |
| |
| } else { |
| throw new IllegalArgumentException("No target class has been defined in @OneToMany annotation"); |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| /** |
| * |
| */ |
| public String unbind(Map<String, Object> model) throws Exception { |
| |
| StringBuilder builder = new StringBuilder(); |
| |
| Map<Integer, KeyValuePairField> keyValuePairFieldsSorted = new TreeMap<Integer, KeyValuePairField>(keyValuePairFields); |
| Iterator<Integer> it = keyValuePairFieldsSorted.keySet().iterator(); |
| |
| // Map containing the OUT position of the field |
| // The key is double and is created using the position of the field and |
| // location of the class in the message (using section) |
| Map<Integer, String> positions = new TreeMap<Integer, String>(); |
| |
| // Check if separator exists |
| ObjectHelper.notNull(this.pairSeparator, "The pair separator has not been instantiated or property not defined in the @Message annotation"); |
| |
| char separator = Converter.getCharDelimitor(this.getPairSeparator()); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Separator converted: '0x{}', from: {}", Integer.toHexString(separator), this.getPairSeparator()); |
| } |
| |
| while (it.hasNext()) { |
| |
| KeyValuePairField keyValuePairField = keyValuePairFieldsSorted.get(it.next()); |
| ObjectHelper.notNull(keyValuePairField, "KeyValuePair"); |
| |
| // Retrieve the field |
| Field field = annotatedFields.get(keyValuePairField.tag()); |
| // Change accessibility to allow to read protected/private fields |
| field.setAccessible(true); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Tag: {}, Field type: {}, class: {}", new Object[]{keyValuePairField.tag(), field.getType(), field.getDeclaringClass().getName()}); |
| } |
| |
| // Retrieve the format, pattern and precision associated to the type |
| Class<?> type = field.getType(); |
| String pattern = keyValuePairField.pattern(); |
| int precision = keyValuePairField.precision(); |
| |
| // Create format |
| @SuppressWarnings("unchecked") |
| Format<Object> format = (Format<Object>)FormatFactory.getFormat(type, pattern, getLocale(), precision); |
| |
| // Get object to be formatted |
| Object obj = model.get(field.getDeclaringClass().getName()); |
| |
| if (obj != null) { |
| |
| // Get field value |
| Object keyValue = field.get(obj); |
| |
| if (this.isMessageOrdered()) { |
| // Generate a key using the number of the section |
| // and the position of the field |
| Integer key1 = sections.get(obj.getClass().getName()); |
| Integer key2 = keyValuePairField.position(); |
| |
| LOG.debug("Key of the section: {}, and the field: {}", key1, key2); |
| |
| Integer keyGenerated = generateKey(key1, key2); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Key generated: {}, for section: {}", String.valueOf(keyGenerated), key1); |
| } |
| |
| // Add value to the list if not null |
| if (keyValue != null) { |
| |
| // Format field value |
| String valueFormatted; |
| |
| try { |
| valueFormatted = format.format(keyValue); |
| } catch (Exception e) { |
| throw new IllegalArgumentException("Formatting error detected for the tag: " + keyValuePairField.tag(), e); |
| } |
| |
| // Create the key value string |
| String value = keyValuePairField.tag() + this.getKeyValuePairSeparator() + valueFormatted; |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Value to be formatted: {}, for the tag: {}, and its formatted value: {}", new Object[]{keyValue, keyValuePairField.tag(), valueFormatted}); |
| } |
| |
| // Add the content to the TreeMap according to the |
| // position defined |
| positions.put(keyGenerated, value); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Positions size: {}", positions.size()); |
| } |
| } |
| } else { |
| |
| // Add value to the list if not null |
| if (keyValue != null) { |
| |
| // Format field value |
| String valueFormatted; |
| |
| try { |
| valueFormatted = format.format(keyValue); |
| } catch (Exception e) { |
| throw new IllegalArgumentException("Formatting error detected for the tag: " + keyValuePairField.tag(), e); |
| } |
| |
| // Create the key value string |
| String value = keyValuePairField.tag() + this.getKeyValuePairSeparator() + valueFormatted + separator; |
| |
| // Add content to the stringBuilder |
| builder.append(value); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Value added: {}{}{}{}", new Object[]{keyValuePairField.tag(), this.getKeyValuePairSeparator(), valueFormatted, separator}); |
| } |
| } |
| } |
| } |
| } |
| |
| // Iterate through the list to generate |
| // the message according to the order/position |
| if (this.isMessageOrdered()) { |
| |
| Iterator<Integer> posit = positions.keySet().iterator(); |
| |
| while (posit.hasNext()) { |
| String value = positions.get(posit.next()); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Value added at the position ({}) : {}{}", new Object[]{posit, value, separator}); |
| } |
| |
| builder.append(value + separator); |
| } |
| } |
| |
| return builder.toString(); |
| } |
| |
| private Object formatField(Format format, String value, int tag, int line) throws Exception { |
| |
| Object obj = null; |
| |
| if (value != null) { |
| |
| // Format field value |
| try { |
| obj = format.parse(value); |
| } catch (Exception e) { |
| throw new IllegalArgumentException("Parsing error detected for field defined at the tag: " + tag + ", line: " + line, e); |
| } |
| |
| } |
| |
| return obj; |
| |
| } |
| |
| /** |
| * Find the pair separator used to delimit the key value pair fields |
| */ |
| public String getPairSeparator() { |
| return pairSeparator; |
| } |
| |
| /** |
| * Find the key value pair separator used to link the key with its value |
| */ |
| public String getKeyValuePairSeparator() { |
| return keyValuePairSeparator; |
| } |
| |
| /** |
| * Flag indicating if the message must be ordered |
| * |
| * @return boolean |
| */ |
| public boolean isMessageOrdered() { |
| return messageOrdered; |
| } |
| |
| /** |
| * Get parameters defined in @Message annotation |
| */ |
| private void initMessageParameters() { |
| if ((pairSeparator == null) || (keyValuePairSeparator == null)) { |
| for (Class<?> cl : models) { |
| // Get annotation @Message from the class |
| Message message = cl.getAnnotation(Message.class); |
| |
| // Get annotation @Section from the class |
| Section section = cl.getAnnotation(Section.class); |
| |
| if (message != null) { |
| // Get Pair Separator parameter |
| ObjectHelper.notNull(message.pairSeparator(), "No Pair Separator has been defined in the @Message annotation"); |
| pairSeparator = message.pairSeparator(); |
| LOG.debug("Pair Separator defined for the message: {}", pairSeparator); |
| |
| // Get KeyValuePair Separator parameter |
| ObjectHelper.notNull(message.keyValuePairSeparator(), "No Key Value Pair Separator has been defined in the @Message annotation"); |
| keyValuePairSeparator = message.keyValuePairSeparator(); |
| LOG.debug("Key Value Pair Separator defined for the message: {}", keyValuePairSeparator); |
| |
| // Get carriage return parameter |
| crlf = message.crlf(); |
| LOG.debug("Carriage return defined for the message: {}", crlf); |
| |
| // Get isOrdered parameter |
| messageOrdered = message.isOrdered(); |
| LOG.debug("Is the message ordered in output: {}", messageOrdered); |
| } |
| |
| if (section != null) { |
| // Test if section number is not null |
| ObjectHelper.notNull(section.number(), "No number has been defined for the section"); |
| |
| // Get section number and add it to the sections |
| sections.put(cl.getName(), section.number()); |
| } |
| } |
| } |
| } |
| |
| |
| } |