Skip to content
This repository has been archived by the owner on Nov 23, 2021. It is now read-only.

Commit

Permalink
Add CSV mapper for REST server FIX #606
Browse files Browse the repository at this point in the history
Multiple DateFormats
  • Loading branch information
mathieu-lavigne committed Jan 11, 2018
1 parent 2d0c8ee commit e226c5a
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 15 deletions.
128 changes: 114 additions & 14 deletions modules/csv/src/main/java/io/oasp/module/basic/csv/CsvFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -28,12 +31,17 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
Expand Down Expand Up @@ -94,7 +102,7 @@ public class CsvFormat {
/** may be <code>null</code> */
protected final String charset;

protected final DateFormat dateFormat;
protected final DateFormat[] dateFormats;

@Deprecated // delete when Pull Request is merged : https://github.com/FasterXML/jackson-dataformats-text/pull/46
private boolean endingLineSeparator = true;
Expand Down Expand Up @@ -129,14 +137,14 @@ public CsvFormat() {
*/
@Deprecated // delete when Pull Request is merged : https://github.com/FasterXML/jackson-dataformats-text/pull/46
protected CsvFormat(final List<String> columns, final CsvMapper mapper, final CsvSchema schema, final String charset,
final DateFormat dateFormat, final boolean endingLineSeparator) {
final DateFormat[] dateFormats, final boolean endingLineSeparator) {
Objects.requireNonNull(mapper);
Objects.requireNonNull(schema);
this.columns = columns;
this.mapper = mapper;
this.schema = schema;
this.charset = charset;
this.dateFormat = dateFormat;
this.dateFormats = dateFormats;
this.endingLineSeparator = endingLineSeparator;
}

Expand All @@ -160,14 +168,14 @@ protected CsvFormat(final List<String> columns, final CsvMapper mapper, final Cs
*/
@Deprecated // delete when Pull Request is merged : https://github.com/FasterXML/jackson-dataformats-text/pull/46
public CsvFormat(CsvFormat base, CsvSchema newSchema, final boolean endingLineSeparator) {
this(base.columns, base.mapper, newSchema, base.charset, base.dateFormat, endingLineSeparator);
this(base.columns, base.mapper, newSchema, base.charset, base.dateFormats, endingLineSeparator);
}

/**
* @param columns
* @return
*/
public static CsvMapper buildMapper(final List<String> columns, final DateFormat dateFormat) {
public static CsvMapper buildMapper(final List<String> columns, final DateFormat[] dateFormats) {

final CsvMapper mapper = new CsvMapper();

Expand All @@ -180,14 +188,59 @@ public static CsvMapper buildMapper(final List<String> columns, final DateFormat
}
// mapper.configure(JsonGenerator.Feature.IGNORE_UNKNOWN, true);

if (dateFormat != null) {
mapper.setDateFormat(dateFormat);
if (dateFormats != null && dateFormats.length > 0) {

// Multiple formats ?
if (dateFormats.length > 1) {
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new MultiDateDeserializer(dateFormats)); // TODO : withDateFormats
mapper.registerModule(module);
}

// We also set default date format which is required for writing
mapper.setDateFormat(dateFormats[0]);
}

// mapper.configure(Feature.STRICT_CHECK_FOR_QUOTING, true);

return mapper;
}

/**
* Handle multiple DateFormat
*
* @author MLAVIGNE
*/
public static final class MultiDateDeserializer extends StdDeserializer<Date> {

private static final long serialVersionUID = 837636597643725045L;

private DateFormat[] dateFormats;

/**
* The constructor.
*/
public MultiDateDeserializer(DateFormat[] dateFormats) {
super(Date.class);
this.dateFormats = dateFormats;
}

@Override
public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {

final String date = jp.getText();
for (DateFormat dateFormat : this.dateFormats) {
try {
return dateFormat.parse(date);
} catch (ParseException e) {
// TODO : check associated regex instead of throwing/catching Exception
}
}
throw new JsonParseException(jp,
String.format("Unparseable date: \"%s\". Supported formats: %s", date, Arrays.toString(this.dateFormats)));
}
}

/**
* @param columns if <code>null</code> create an {@link CsvSchema#emptySchema() emptySchema} with header
* @return
Expand Down Expand Up @@ -290,7 +343,7 @@ public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider
*/
public CsvFormat withColumns(final List<String> columns) {

final CsvMapper mapper = buildMapper(columns, this.dateFormat);
final CsvMapper mapper = buildMapper(columns, this.dateFormats);
CsvSchema schema = buildSchema(columns) // TODO : use copy constructor
.withColumnSeparator(this.schema.getColumnSeparator())
.withLineSeparator(new String(this.schema.getLineSeparator())).withQuoteChar((char) this.schema.getQuoteChar())
Expand All @@ -306,7 +359,7 @@ public CsvFormat withColumns(final List<String> columns) {
* schema.withoutEndingLineSeparator(); }
*/
// FIXME keep header
return new CsvFormat(columns, mapper, schema, this.charset, this.dateFormat, this.endingLineSeparator);
return new CsvFormat(columns, mapper, schema, this.charset, this.dateFormats, this.endingLineSeparator);
}

/**
Expand Down Expand Up @@ -406,7 +459,7 @@ public CsvFormat withoutQuoteChar() {

public CsvFormat withCharset(final String charset) {

return new CsvFormat(this.columns, this.mapper, this.schema, charset, this.dateFormat, this.endingLineSeparator);
return new CsvFormat(this.columns, this.mapper, this.schema, charset, this.dateFormats, this.endingLineSeparator);
}

public CsvFormat withEncoding(final String encoding) {
Expand All @@ -415,12 +468,47 @@ public CsvFormat withEncoding(final String encoding) {
}

/**
* @param simpleDateFormat
* @return
* @param dateFormat example : {@link SimpleDateFormat}
* @return this
*/
public CsvFormat withDateFormat(DateFormat dateFormat) {

return new CsvFormat(this.columns, buildMapper(this.columns, dateFormat), this.schema, this.charset, dateFormat,
return withDateFormats(dateFormat);
}

/**
* @param patterns {@link SimpleDateFormat} pattern
* @return this
*/
public CsvFormat withDateFormat(String patterns) {

return withDateFormats(patterns);
}

/**
* Jackson does not allow multiple DateFormats. This method will use a MultiDateDeserializer to allow them.
*
* @param patterns {@link SimpleDateFormat} patterns
* @return this CsvFormat instance
*/
public CsvFormat withDateFormats(String... patterns) {

final DateFormat[] dateFormats = new DateFormat[patterns.length];
for (int i = 0; i < patterns.length; i++) {
dateFormats[i] = new SimpleDateFormat(patterns[i]);
}
return withDateFormats(dateFormats);
}

/**
* Jackson does not allow multiple DateFormats. This method will use a MultiDateDeserializer to allow them.
*
* @param dateFormats example : {@link SimpleDateFormat}
* @return this CsvFormat instance
*/
public CsvFormat withDateFormats(DateFormat... dateFormats) {

return new CsvFormat(this.columns, buildMapper(this.columns, dateFormats), this.schema, this.charset, dateFormats,
this.endingLineSeparator);
}

Expand All @@ -430,7 +518,7 @@ public CsvFormat withDateFormat(DateFormat dateFormat) {
*/
public CsvFormat withoutDateFormat() {

return new CsvFormat(this.columns, buildMapper(this.columns, null), this.schema, this.charset, this.dateFormat,
return new CsvFormat(this.columns, buildMapper(this.columns, null), this.schema, this.charset, null,
this.endingLineSeparator);
}

Expand Down Expand Up @@ -567,6 +655,9 @@ public Reader getEncodedReader(final String src) throws UnsupportedEncodingExcep
}

/**
* Don't forget to annotate your ETO with <code>@JsonFilter(CsvFormat.FILTER)</code> to avoid
* "JsonGenerationException: Unrecognized column" exception.
*
* @param out
* @param type
* @return
Expand All @@ -585,6 +676,9 @@ public <E> ObjectWriter writerFor(E value) { // TODO save writers in CsvFormat f
* this.writerFor(value).writeValue(this.getWriterForCharset(out), value)
* </pre>
*
* Don't forget to annotate your ETO with <code>@JsonFilter(CsvFormat.FILTER)</code> to avoid
* "JsonGenerationException: Unrecognized column" exception.
*
* @param out
* @param value
* @return
Expand Down Expand Up @@ -625,6 +719,9 @@ public Writer getEncodedWriter(OutputStream out) throws UnsupportedEncodingExcep
* this.writerFor(value).writeValue(resultFile, value)
* </pre>
*
* Don't forget to annotate your ETO with <code>@JsonFilter(CsvFormat.FILTER)</code> to avoid
* "JsonGenerationException: Unrecognized column" exception.
*
* @param value
* @return
* @throws IOException
Expand Down Expand Up @@ -667,6 +764,9 @@ public Writer getEncodedWriter(final File resultFile) throws IOException {
* this.writerFor(value).writeValueAsString(value)
* </pre>
*
* Don't forget to annotate your ETO with <code>@JsonFilter(CsvFormat.FILTER)</code> to avoid
* "JsonGenerationException: Unrecognized column" exception.
*
* @param value
* @return
* @throws IOException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ public void testWithCharset() throws Exception {
* {@link com.orange.grace.traducteur.general.service.impl.rest.CsvFormat#withDateFormat(java.text.DateFormat)}.
*/
@Test
public void testWithDateFormat() throws Exception {
public void testWithDateFormatWrite() throws Exception {

final Eto eto = new Eto();
eto.setComment("comment_value1");
Expand All @@ -438,6 +438,67 @@ public void testWithDateFormat() throws Exception {
assertThatWrittenContent(eto, format).isEqualTo(sb.toString());
}

/**
* Test method for
* {@link com.orange.grace.traducteur.general.service.impl.rest.CsvFormat#withDateFormat(java.text.DateFormat)}.
*/
@Test
public void testWithDateFormat() throws Exception {

final CsvFormat format = this.twoColumns.withColumns("code,date").withDateFormats("dd/MM/yyyy");

assertThat(format.readValue("CODE1,22/05/1987", Eto.class).getDate()).isEqualTo("1987-05-22");
}

/**
* Test method for
* {@link com.orange.grace.traducteur.general.service.impl.rest.CsvFormat#withDateFormat(java.text.DateFormat)}.
*/
@Test
public void testWithDateFormats() throws Exception {

final CsvFormat format = this.twoColumns.withColumns("code,date").withDateFormats("dd/MM/yyyy", "dd-MM-yyyy");

assertThat(format.readValue("CODE1,22/05/1987", Eto.class).getDate()).isEqualTo("1987-05-22");
assertThat(format.readValue("CODE1,22-05-1987", Eto.class).getDate()).isEqualTo("1987-05-22");
}

/**
* Test method for
* {@link com.orange.grace.traducteur.general.service.impl.rest.CsvFormat#withDateFormat(java.text.DateFormat)}.
*/
@Test(expected = JsonMappingException.class)
public void testWithDateFormatsKO() throws Exception {

final CsvFormat format = this.twoColumns.withColumns("code,date").withDateFormats("dd/MM/yyyy", "dd-MM-yyyy");

assertThat(format.readValue("CODE1,22/05/1987", Eto.class).getDate()).isEqualTo("1987-05-22");
assertThat(format.readValue("CODE1,22-05-1987", Eto.class).getDate()).isEqualTo("1987-05-22");
format.readValue("CODE1,22|05|1987", Eto.class); // unknown format
}

/**
* Test method for
* {@link com.orange.grace.traducteur.general.service.impl.rest.CsvFormat#withDateFormat(java.text.DateFormat)}.
*/
@Test
public void testWithDateFormatsWrite() throws Exception {

final Eto eto = new Eto();
eto.setComment("comment_value1");
final Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2017, 9, 23);
eto.setDate(cal.getTime());

final CsvFormat format =
this.twoColumns.withColumns("code,date").withDateFormats("dd/MM/yyyy", "dd-MM-yyyy");

final StringBuilder sb = new StringBuilder();
sb.append(",23/10/2017").append(defaultLineSeparator);
assertThatWrittenContent(eto, format).isEqualTo(sb.toString());
}

/**
* Test method for {@link com.orange.grace.traducteur.general.service.impl.rest.CsvFormat#withoutDateFormat()}.
*/
Expand Down

0 comments on commit e226c5a

Please sign in to comment.