KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > petals > engine > csv > CsvProcessor


1 /**
2  * PETALS - PETALS Services Platform.
3  * Copyright (c) 2005 EBM Websourcing, http://www.ebmwebsourcing.com/
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17  *
18  * -------------------------------------------------------------------------
19  * $Id: CsvProcessor.java 1365 2006-12-08 22:17:01Z dutoo $
20  * -------------------------------------------------------------------------
21  */

22 package org.objectweb.petals.engine.csv;
23
24 import java.io.ByteArrayInputStream JavaDoc;
25 import java.io.ByteArrayOutputStream JavaDoc;
26 import java.io.InputStream JavaDoc;
27 import java.io.OutputStream JavaDoc;
28 import java.nio.charset.Charset JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.Map JavaDoc;
31 import java.util.Properties JavaDoc;
32 import java.util.logging.Level JavaDoc;
33 import java.util.logging.Logger JavaDoc;
34
35 import javax.activation.DataHandler JavaDoc;
36 import javax.jbi.messaging.DeliveryChannel;
37 import javax.jbi.messaging.NormalizedMessage;
38 import javax.xml.namespace.QName JavaDoc;
39 import javax.xml.transform.Source JavaDoc;
40
41 import org.apache.commons.io.IOUtils;
42 import org.objectweb.petals.component.common.listener.AbstractInternalMEProcessor;
43 import org.objectweb.petals.component.common.util.SourceHelper;
44 import org.objectweb.petals.component.common.util.StringHelper;
45 import org.objectweb.petals.component.common.util.XMLHelper;
46 import org.objectweb.petals.engine.csv.exception.CsvException;
47 import org.w3c.dom.Document JavaDoc;
48 import org.w3c.dom.Element JavaDoc;
49 import org.w3c.dom.Node JavaDoc;
50 import org.w3c.dom.NodeList JavaDoc;
51 import org.w3c.dom.Text JavaDoc;
52
53 import com.csvreader.CsvReader;
54
55 /**
56  * This processor implements to operations : csvToXml and xmlToCsv.
57  *
58  * <p/>
59  * The csvToXml operation :<br/>
60  * This processor uses Java CSV's CsvReader to transform either
61  * the <content>-encapsulated content or the single attachment of
62  * the message from any CSV format to XML.
63  *
64  * It is configured through properties given by the CSV SU handler.
65  * Here are their meaning :
66  * <ul>
67  * <li>csvInputMode : may be "content", "attachment" or "guess".
68  * Default is "guess", meaning that if there is any attachment this
69  * will be the chosen mode, and if not, it will be in "content" mode.
70  * Not that this works for both ways (xmlToCsv and csvToXml)</li>
71  * <li>customColumnDelimiterChar : default is the comma ","</li>
72  * <li>useCustomRecordDelimiter : whether to use a custom one
73  * instead of the default combination of unix & windows line breaks.
74  * SO beware, using it lessens the original capabilities of the
75  * parser and it is therefore not advised.</li>
76  * <li>customRecordDelimiterChar : custom line delimiter character.
77  * See the "useCustomRecordDelimiter" property to activate this feature.</li>
78  * <li>useDoubleEscapeMode : whether to use doubled backslash to
79  * trigger escaping instead of a single one.</li>
80  * <li>useComment : whether to parse and make comments available.
81  * Default is false.</li>
82  * <li>commentChar : the character used to starts comments.
83  * Default is sharp "#".</li>
84  * </ul>
85  *
86  * <p/>
87  * The xmlToCsv operation :<br/>
88  * It views a two level-deep XML tree as tabular data and converts it
89  * to CSV using the properties csvInputMode, customColumnDelimiterChar,
90  * useCustomRecordDelimiter and customRecordDelimiterChar with the same
91  * meaning as above. The CSV is then returned in the response inside a
92  * <content> top tag.
93  *
94  * @version $Rev: 1081 $Date: {date}
95  * @since Petals 1.0
96  * @author Marc DUTOO - Open Wide
97  *
98  */

99 public class CsvProcessor extends AbstractInternalMEProcessor {
100     
101     public static final String JavaDoc CSV_TO_XML_OPERATION = "csvToXml";
102     public static final String JavaDoc XML_TO_CSV_OPERATION = "xmlToCsv";
103
104     public static final String JavaDoc CSV_INPUT_MODE_CONTENT = "content";
105     public static final String JavaDoc CSV_INPUT_MODE_ATTACHMENT = "attachment";
106     public static final String JavaDoc CSV_INPUT_MODE_GUESS = "guess";
107
108     public static final String JavaDoc CSV_INPUT_MODE_PROP = "csvInputMode";
109     public static final String JavaDoc CUSTOM_COLUMN_DELIMITER_CHAR_PROP = "customColumnDelimiterChar";
110     public static final String JavaDoc USE_CUSTOM_RECORD_DELIMITER_PROP = "useCustomRecordDelimiter";
111     public static final String JavaDoc CUSTOM_RECORD_DELIMITER_CHAR_PROP = "customRecordDelimiterChar";
112     public static final String JavaDoc USE_DOUBLE_ESCAPE_MODE_PROP = "useDoubleEscapeMode";
113     public static final String JavaDoc USE_COMMENT_PROP = "useComment";
114     public static final String JavaDoc COMMENT_CHAR_PROP = "commentChar";
115     
116     // The following constants describe the format of the XML output.
117
// They could be configurable in properties.
118
// The tag to be used to encapsulate CSV content in content input mode
119
private static final String JavaDoc CONTENT_TAG = "content";
120     private static final String JavaDoc DATA_TAG = "data";
121     private static final String JavaDoc ROW_TAG = "row";
122     private static final String JavaDoc CSVXML_NAMESPACE_URI = "http://petals.objectweb.org/components/csv/csvxml";
123     private static final String JavaDoc CELL_TAG = "cell";
124
125     
126     /** service name to component properties map */
127     private Map JavaDoc<String JavaDoc, Properties JavaDoc> mapEndpointCsv;
128     
129     
130     /**
131      * Creates a new CSV processor configured to use the given map of
132      * service names to component properties.
133      * @param c
134      * @param l
135      * @param mapEndpointCsv
136      */

137     public CsvProcessor(DeliveryChannel c, Logger JavaDoc l,
138         Map JavaDoc<String JavaDoc, Properties JavaDoc> mapEndpointCsv) {
139         super(c, l);
140         this.mapEndpointCsv = mapEndpointCsv;
141     }
142
143
144     @Override JavaDoc
145     protected void sendInMessage(QName JavaDoc service, QName JavaDoc operation,
146         NormalizedMessage in) throws CsvException {
147         // not supported : since it is a service engine, it outputs something
148
throw new CsvException("Only InOut messages are supported");
149     }
150
151     @Override JavaDoc
152     protected boolean sendInOutMessage(QName JavaDoc service, QName JavaDoc operation,
153         NormalizedMessage in, NormalizedMessage out, boolean optionalOut)
154     throws CsvException {
155         // getting the service properties
156
Properties JavaDoc csvProperties = getCsvProperties(service.getLocalPart());
157
158         
159         if (CSV_TO_XML_OPERATION.equals(operation.getLocalPart())) {
160             try {
161                 // getting the content to be transformed
162
String JavaDoc csvContent = getCsvInputContent(in, csvProperties);
163                 
164                 if (StringHelper.isEmpty(csvContent)) {
165                     l.log(Level.WARNING, "Empty message content");
166                     // this will be automaticly added to a Fault if the message
167
throw new CsvException("operation : message empty");
168                 }
169                 
170                 // actual CSV to XML transformation
171
ByteArrayOutputStream JavaDoc xmlOut = new ByteArrayOutputStream JavaDoc();
172                 transformCsvToXml(new ByteArrayInputStream JavaDoc(csvContent.getBytes()),
173                     xmlOut, csvProperties);
174     
175                 // Transform Stream result to String, to be send in NormalizeMessage
176
out.setContent(SourceHelper.createSource(new String JavaDoc(xmlOut.toByteArray())));
177             } catch (CsvException e) {
178                 throw e;
179             } catch (Exception JavaDoc e) {
180                 String JavaDoc msg = "Error while transforming csv to XML : " + e.getMessage();
181                 l.log(Level.SEVERE, msg, e);
182                 throw new CsvException(msg, e);
183             }
184         
185             
186         } else if (XML_TO_CSV_OPERATION.equals(operation.getLocalPart())) {
187             Node JavaDoc documentNode = getXmlInputDocument(in, csvProperties);
188             
189             try {
190                 Node JavaDoc xmlTableNode = XMLHelper.getFirstChild(documentNode);
191                 // actual XML to CSV transformation
192
StringBuffer JavaDoc csvBuf = new StringBuffer JavaDoc();
193                 transformXmlToCsv(xmlTableNode, csvBuf, csvProperties);
194         
195                 // Wraps the resulting CSV in a <content> tag and returns it
196
out.setContent(SourceHelper.createContentSource(csvBuf.toString()));
197             } catch (CsvException e) {
198                 throw e;
199             } catch (Exception JavaDoc e) {
200                 String JavaDoc msg = "Error while transforming XML to CSV : " + e.getMessage();
201                 l.log(Level.SEVERE, msg, e);
202                 throw new CsvException(msg, e);
203             }
204             
205         } else {
206             throw new CsvException("Unknown operation " + operation);
207         }
208         
209         return true;
210     }
211
212
213     /**
214      * Transforms the given XML tabular data to CSV.
215      * There is no requirement on the format (tags...) besides the fact that
216      * they have to be a two level tree.
217      * @param xmlTableNode the XML tabular data to be written as CSV
218      * @param csvBuf where the converted CSV data will be written
219      * @param csvProperties the service properties
220      * @throws CsvException if conversion error
221      */

222     private void transformXmlToCsv(Node JavaDoc xmlTableNode, StringBuffer JavaDoc csvBuf,
223         Properties JavaDoc csvProperties) throws CsvException {
224
225         // getting CSV formatting properties
226
char csvColumnDelimiterChar = csvProperties
227               .getProperty(CUSTOM_COLUMN_DELIMITER_CHAR_PROP, ",").charAt(0);
228         char csvCustomRecordDelimiterChar = csvProperties
229               .getProperty(CUSTOM_RECORD_DELIMITER_CHAR_PROP, "\n").charAt(0);
230         boolean useCustomRecordDelimiter = Boolean.valueOf(csvProperties
231             .getProperty(USE_CUSTOM_RECORD_DELIMITER_PROP, "false")).booleanValue();
232         String JavaDoc recordDelimiter = (useCustomRecordDelimiter)
233             ? String.valueOf(csvCustomRecordDelimiterChar) : "\r\n";
234         
235         NodeList JavaDoc rowNodes = xmlTableNode.getChildNodes();
236         int rowNodeNb = rowNodes.getLength();
237         for (int i = 0; i < rowNodeNb; i++) {
238             Node JavaDoc rowNode = rowNodes.item(i);
239             if (!(rowNode instanceof Element JavaDoc)) {
240                 continue;
241             }
242             
243             // now outputting the current row :
244
NodeList JavaDoc columnNodes = rowNode.getChildNodes();
245             int columnNodeNb = columnNodes.getLength();
246             for (int j = 0; j < columnNodeNb; j++) {
247                 Node JavaDoc columnNode = columnNodes.item(j);
248                 if (!(columnNode instanceof Element JavaDoc)) {
249                     continue;
250                 }
251                 
252                 // outputting cell value
253
String JavaDoc cellValue = XMLHelper.getTextContent(columnNode);
254                 csvBuf.append(cellValue);
255                 csvBuf.append(csvColumnDelimiterChar);
256             }
257             if (columnNodeNb != 0) {
258                 // removing the last column delimiter
259
csvBuf.deleteCharAt(csvBuf.length() - 1);
260             }
261             
262             // appending end of row
263
csvBuf.append(recordDelimiter);
264         }
265         
266         if (rowNodeNb != 0) {
267             // removing the last row delimiter
268
csvBuf.deleteCharAt(csvBuf.length() - recordDelimiter.length());
269         }
270     }
271
272
273     /**
274      * Returns the actual XML content from the given incoming message
275      * according to the processor's settings
276      * @param in
277      * @param csvProperties
278      * @return
279      * @throws CsvException
280      */

281     protected Node JavaDoc getXmlInputDocument(NormalizedMessage in, Properties JavaDoc csvProperties)
282             throws CsvException {
283         try {
284             if (isModeAttachment(in, csvProperties)) {
285                 // getting first attachment
286
String JavaDoc firstAttachmentName = null;
287                 for (Iterator JavaDoc nameIt = in.getAttachmentNames().iterator(); nameIt.hasNext();) {
288                     firstAttachmentName = (String JavaDoc) nameIt.next();
289                     break;
290                 }
291                 if (firstAttachmentName == null) {
292                     String JavaDoc msg = "Error while getting XML source content : "
293                         + "inputMode is \"attachment\" but there is none given";
294                     l.log(Level.SEVERE, msg);
295                     throw new CsvException(msg);
296                 }
297                 DataHandler JavaDoc firstAttachment = in.getAttachment(firstAttachmentName);
298                 String JavaDoc contentString = IOUtils.toString(firstAttachment.getInputStream());
299                 return XMLHelper.createDocumentFromString(contentString);
300
301             } else {
302                 Source JavaDoc xmlTableSource = in.getContent();
303                 return XMLHelper.createDOMNodeFromSource(xmlTableSource);
304             }
305         } catch (CsvException e) {
306             throw e;
307         } catch (Exception JavaDoc e) {
308             String JavaDoc msg = "Error while getting XML source content : " + e.getMessage();
309             l.log(Level.SEVERE, msg, e);
310             throw new CsvException(msg, e);
311         }
312     }
313
314
315     /**
316      * Returns the actual CSV content from the given incoming message
317      * according to the processor's settings
318      * @param in
319      * @param csvProperties
320      * @return
321      * @throws Exception
322      * @throws
323      * @throws Exception
324      */

325     protected String JavaDoc getCsvInputContent(NormalizedMessage in,
326             Properties JavaDoc csvProperties) throws Exception JavaDoc {
327         String JavaDoc contentString;
328         if (isModeAttachment(in, csvProperties)) {
329             // getting first attachment
330
String JavaDoc firstAttachmentName = null;
331             for (Iterator JavaDoc nameIt = in.getAttachmentNames().iterator(); nameIt.hasNext();) {
332                 firstAttachmentName = (String JavaDoc) nameIt.next();
333                 break;
334             }
335             if (firstAttachmentName == null) {
336                 return null; // no content
337
}
338             DataHandler JavaDoc firstAttachment = in.getAttachment(firstAttachmentName);
339             contentString = IOUtils.toString(firstAttachment.getInputStream());
340             // NB. XML encoding of the ouput content is done during transformation
341

342         } else {
343             // inlined content mode, inside a <content> tag
344
Source JavaDoc content = in.getContent();
345             contentString = createStringFromContent(content);
346             // NB. since it was already XML content, the values are already XML encoded
347
}
348         return contentString;
349     }
350     
351     /**
352      * Unwrapps the given content source : transforms the source
353      * to a String and removes the (required) encapsulating <content>
354      * tag.
355      * @param content
356      * @return
357      * @throws Exception
358      */

359     protected static String JavaDoc createStringFromContent(Source JavaDoc content) throws Exception JavaDoc {
360         Node JavaDoc contentNode = XMLHelper.getFirstChild(XMLHelper.createDOMNodeFromSource(content));
361         // Checking that node element tag is "content"
362
if (!CONTENT_TAG.equals(contentNode.getLocalName())) {
363             throw new CsvException("Expected <content> encapsulated content "
364                 + "but found " + XMLHelper.createStringFromDOMNode(contentNode));
365         }
366         // Usual case : the original content has been encapsulated as XML
367
String JavaDoc contentString = contentNode.getTextContent();
368         return contentString;
369     }
370
371     /**
372      * Returns whether the CSV is to be looked for in the
373      * message's first attachment instead of in its content
374      * @param in
375      * @param csvProperties
376      * @return
377      */

378     protected boolean isModeAttachment(NormalizedMessage in,
379             Properties JavaDoc csvProperties) {
380         String JavaDoc csvInputMode = csvProperties.getProperty(CSV_INPUT_MODE_PROP, "guess");
381         if (CSV_INPUT_MODE_ATTACHMENT.equals(csvInputMode)) {
382             return true;
383         } else if (CSV_INPUT_MODE_CONTENT.equals(csvInputMode)) {
384             return false;
385         } else { // guessing mode
386
if (!in.getAttachmentNames().isEmpty()) {
387                 return true;
388             }
389         }
390         return false;
391     }
392
393
394     /**
395      * Transforms the given CSV content input in XML and
396      * outputs it
397      * @param csvIn
398      * @param xmlOut where the transformed CSV XML is written
399      * @param csvProperties used to configure CSV parsing
400      * @throws CsvException
401      */

402     protected void transformCsvToXml(InputStream JavaDoc csvIn, OutputStream JavaDoc xmlOut,
403             Properties JavaDoc csvProperties) throws CsvException {
404         if (csvIn == null) {
405             throw new CsvException("Input stream must not be null");
406         }
407         // creating a new CsvReader :
408
CsvReader csvReader = createCsvReader(csvIn, csvProperties);
409         if (csvReader == null) {
410             throw new CsvException("not a CSV file");
411         }
412
413         try {
414             // now creating and outputting the transformed XML :
415
// NB. using DOM so content will be XML encoded
416
Document JavaDoc csvDoc = XMLHelper.createDocumentFromString(
417                 "<" + DATA_TAG + " xmlns=\"" + CSVXML_NAMESPACE_URI + "\"/>");
418             // NB. dataElement same as csvDoc
419
Element JavaDoc dataElement = csvDoc.getDocumentElement();
420             
421             while (csvReader.readRecord()) {
422                 Element JavaDoc rowElement = csvDoc.createElementNS(CSVXML_NAMESPACE_URI, ROW_TAG);
423                 dataElement.appendChild(rowElement);
424                 int columnNb = csvReader.getColumnCount();
425                 
426                 for (int i = 0; i < columnNb; i++) {
427                     String JavaDoc value = csvReader.get(i);
428                     value = (value == null) ? "" : value; //TODO required ??
429

430                     Element JavaDoc cellElement = csvDoc.createElementNS(CSVXML_NAMESPACE_URI, CELL_TAG);
431                     rowElement.appendChild(cellElement);
432                     // NB. we could encapsulating CSV values in CDATA so anything should be ok
433
//CDATASection valueData = csvDoc.createCDATASection(value);
434
Text JavaDoc valueData = csvDoc.createTextNode(value);
435                     cellElement.appendChild(valueData);
436                 }
437             }
438             String JavaDoc csvDocString = XMLHelper.createStringFromDOMDocument(csvDoc);
439             xmlOut.write(csvDocString.getBytes());
440         } catch (Exception JavaDoc e) {
441             throw new CsvException("Error while parsing CSV and building output XML : "
442                 + e.getMessage());
443         }
444     }
445
446
447     /**
448      * Creates a new CsvReader configured according to the
449      * component's properties, ready to parse CSV content.
450      * @param csvIn the CSV content to be parser
451      * @param csvProperties used to configure the CsvReader
452      * @return a new, configured CsvReader
453      * @throws CsvException if creation or configuration error
454      */

455     protected CsvReader createCsvReader(InputStream JavaDoc csvIn,
456             Properties JavaDoc csvProperties) throws CsvException {
457         try {
458             // TODO use BeanUtils to set the properties ??
459
char csvColumnDelimiterChar = csvProperties
460                   .getProperty(CUSTOM_COLUMN_DELIMITER_CHAR_PROP, ",").charAt(0);
461             char csvCustomRecordDelimiterChar = csvProperties
462                   .getProperty(CUSTOM_RECORD_DELIMITER_CHAR_PROP, "\n").charAt(0);
463             boolean useCustomRecordDelimiter = Boolean.valueOf(csvProperties
464                 .getProperty(USE_CUSTOM_RECORD_DELIMITER_PROP, "false")).booleanValue();
465             int escapeMode = (Boolean.valueOf(csvProperties.getProperty(
466                 USE_DOUBLE_ESCAPE_MODE_PROP, "true")).booleanValue()) ?
467                 CsvReader.ESCAPE_MODE_DOUBLED : CsvReader.ESCAPE_MODE_BACKSLASH;
468             boolean useComment = Boolean.valueOf(csvProperties
469                 .getProperty(USE_COMMENT_PROP, "false")).booleanValue();
470             char commentChar = csvProperties.getProperty(COMMENT_CHAR_PROP, "#").charAt(0);
471         
472             // creating CsvReader for given input
473
// csvCharset : Csv SE default is inited in TODO
474
CsvReader csvReader = new CsvReader(csvIn, Charset.defaultCharset());
475             csvReader.setDelimiter(csvColumnDelimiterChar); // CsvReader default is ','
476
if (useCustomRecordDelimiter) {
477                csvReader.setRecordDelimiter(csvCustomRecordDelimiterChar);
478             } // else CsvReader default is a combination of unix & windows cr & lf
479
csvReader.setEscapeMode(escapeMode); // CsvReader default is CsvReader.ESCAPE_MODE_DOUBLED i.e. '\\'
480
csvReader.setUseComments(useComment);
481             csvReader.setComment(commentChar); // CsvReader default is '#'
482
// NB. safetySwitch is activated and may not be disabled for now
483
//csvReader.setMergeConsecutive(arg0) //TODO ???
484
return csvReader;
485         } catch (Exception JavaDoc e) {
486             throw new CsvException("Error setting CSV reader configuration."
487                 + "CSV reader properties are : " + csvProperties);
488         }
489     }
490     
491     
492     /**
493      * Returns the component configuration (for CsvReader etc.)
494      * corresponding to the given service name
495      * @param serviceName
496      * @return
497      * @throws Exception
498      */

499     protected Properties JavaDoc getCsvProperties(String JavaDoc serviceName) throws CsvException{
500         Properties JavaDoc csvProperties = this.mapEndpointCsv.get(serviceName);
501         if (csvProperties != null) {
502             return csvProperties;
503         } else {
504             throw new CsvException("CSV properties not found for service "
505                 + serviceName);
506         }
507     }
508
509 }
510
Popular Tags