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 }