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.CsvRecord;
027    import org.apache.camel.dataformat.bindy.annotation.DataField;
028    import org.apache.camel.dataformat.bindy.annotation.Link;
029    import org.apache.camel.spi.PackageScanClassResolver;
030    import org.apache.camel.util.ObjectHelper;
031    import org.apache.commons.logging.Log;
032    import org.apache.commons.logging.LogFactory;
033    
034    /**
035     * The BindyCsvFactory is the class who allows to :
036     * Generate a model associated to a CSV record, bind data from a record
037     * to the POJOs, export data of POJOs to a CSV record and format data
038     * into String, Date, Double, ... according to the format/pattern defined
039     */
040    public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactory  {
041    
042        private static final transient Log LOG = LogFactory.getLog(BindyCsvFactory.class);
043    
044        private Map<Integer, DataField> mapDataField = new LinkedHashMap<Integer, DataField>();
045        private Map<Integer, Field> mapAnnotedField = new LinkedHashMap<Integer, Field>();
046    
047        private String separator;
048        private boolean skipFirstLine;
049    
050        public BindyCsvFactory(PackageScanClassResolver resolver, String packageName) throws Exception {
051            super(resolver, packageName);
052            
053            // initialize specific parameters of the csv model
054            initCsvModel();
055        }
056    
057        /**
058         * method uses to initialize the model representing the classes who will
059         * bind the data This process will scan for classes according to the package
060         * name provided, check the classes and fields annoted and retrieve the
061         * separator of the CSV record
062         * 
063         * @throws Exception
064         */
065        public void initCsvModel() throws Exception {
066            
067            // Find annotated Datafields declared in the Model classes
068            initAnnotedFields();
069            
070            // initialize Csv parameter(s)
071            // separator and skip first line from @CSVrecord annotation
072            initCsvRecordParameters();
073        }
074        
075        public void initAnnotedFields() {
076            for (Class<?> cl : models) {
077                for (Field field : cl.getDeclaredFields()) {
078                    DataField dataField = field.getAnnotation(DataField.class);
079                    if (dataField != null) {
080                        if (LOG.isDebugEnabled()) {
081                            LOG.debug("Position defined in the class : " + cl.getName() + ", position : "
082                                + dataField.pos() + ", Field : " + dataField.toString());
083                        }
084                        mapDataField.put(dataField.pos(), dataField);
085                        mapAnnotedField.put(dataField.pos(), field);
086                    }
087    
088                    Link linkField = field.getAnnotation(Link.class);
089    
090                    if (linkField != null) {
091                        if (LOG.isDebugEnabled()) {
092                            LOG.debug("Class linked  : " + cl.getName() + ", Field" + field.toString());
093                        }
094                        mapAnnotedLinkField.put(cl.getName(), field);
095                    }
096                }
097            }
098        }
099    
100        public void bind(List<String> data, Map<String, Object> model) throws Exception {
101    
102            int pos = 0;
103            while (pos < data.size()) {
104    
105                // Set the field with the data received
106                // Only when no empty line is provided
107                // Data is transformed according to the pattern defined or by
108                // default the type of the field (int, double, String, ...)
109    
110                if (!data.get(pos).equals("")) {
111    
112                    DataField dataField = mapDataField.get(pos);
113                    ObjectHelper.notNull(dataField, "No position defined for the field positoned : " + pos);
114                    Field field = mapAnnotedField.get(pos);
115                    field.setAccessible(true);
116                    
117                    if (LOG.isDebugEnabled()) {
118                        LOG.debug("Pos : " + pos + ", Data : " + data.get(pos) + ", Field type : " + field.getType());
119                    }
120    
121                    Format<?> format;
122                    String pattern = dataField.pattern();
123    
124                    format = FormatFactory.getFormat(field.getType(), pattern, dataField.precision());
125                    field.set(model.get(field.getDeclaringClass().getName()), format.parse(data.get(pos)));
126                }
127                pos++;
128            }
129        }
130    
131        public String unbind(Map<String, Object> model) throws Exception {
132    
133            StringBuilder builder = new StringBuilder();
134    
135            Map<Integer, DataField> dataFields = new TreeMap<Integer, DataField>(mapDataField);
136            Iterator<Integer> it = dataFields.keySet().iterator();
137    
138            // Check if separator exists
139            ObjectHelper.notNull(this.separator, "The separator has not been instantiated or property not defined in the @CsvRecord annotation");
140    
141            while (it.hasNext()) {
142    
143                DataField dataField = mapDataField.get(it.next());
144    
145                // Retrieve the field
146                Field field = mapAnnotedField.get(dataField.pos());
147                // Change accessibility to allow to read protected/private fields
148                field.setAccessible(true);
149    
150                // Retrieve the format associated to the type
151                Format format = FormatFactory.getFormat(field.getType(), dataField.pattern(), dataField.precision());
152                Object obj = model.get(field.getDeclaringClass().getName());
153    
154                // Convert the content to a String and append it to the builder
155                builder.append(format.format(field.get(obj)));
156                if (it.hasNext()) {
157                    builder.append(this.getSeparator());
158                }
159            }
160            return builder.toString();
161        }
162    
163        /**
164         * Find the separator used to delimit the CSV fields
165         */
166        public String getSeparator() {
167            return separator;
168        }
169        
170        /**
171         * Find the separator used to delimit the CSV fields
172         */
173        public boolean getSkipFirstLine() {
174            return skipFirstLine;
175        }
176    
177        /**
178         * Get paramaters defined in @Csvrecord annotation
179         */
180        private void initCsvRecordParameters() {
181            if (separator == null) {
182                for (Class<?> cl : models) {
183                    // Get annotation @CsvRecord from the class
184                    CsvRecord record = cl.getAnnotation(CsvRecord.class);
185    
186                    if (record != null) {
187    
188                        // Get skipFirstLine parameter
189                        skipFirstLine = record.skipFirstLine();
190                        if (LOG.isDebugEnabled()) {
191                            LOG.debug("Skip First Line parameter of the CSV : " + skipFirstLine);
192                        }
193    
194                        // Get Separator parameter
195                        ObjectHelper.notNull(record.separator(),
196                            "No separator has been defined in the @Record annotation !");
197                        separator = record.separator();
198                        if (LOG.isDebugEnabled()) {
199                            LOG.debug("Separator defined for the CSV : " + separator);
200                        }
201                        
202                        // Get carriage return parameter
203                        crlf = record.crlf();
204                        if (LOG.isDebugEnabled()) {
205                            LOG.debug("Carriage return defined for the CSV : " + crlf);
206                        }
207                    }
208                }
209            }
210        }
211    }