Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade Jackson 1.x to Jackson 2.x #22

Merged
merged 4 commits into from
Mar 10, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ libraryDependencies ++= Seq(
"com.sun.jersey.contribs" % "jersey-multipart" % "1.9.1",
"com.fasterxml.jackson.core" % "jackson-core" % "2.8.6",
"com.fasterxml.jackson.module" % "jackson-module-jaxb-annotations" % "2.8.6",
"com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.8.6",
"com.fasterxml.jackson.jaxrs" % "jackson-jaxrs-json-provider" % "2.8.6",
"com.google.guava" % "guava" % "12.0",
"org.apache.commons" % "commons-lang3" % "3.1",
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/com/socrata/api/HttpLowLevel.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.socrata.model.SodaErrorResponse;
import com.socrata.model.requests.SodaRequest;
import com.socrata.utils.JacksonObjectMapperProvider;
import com.socrata.utils.ObjectMapperFactory;
import com.socrata.utils.streams.CompressingGzipInputStream;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
Expand Down Expand Up @@ -175,7 +176,7 @@ public static final HttpLowLevel instantiateBasic(@Nonnull final String url, @No
}

public HttpLowLevel(final Client client, final String url) {
this(client, url, new ObjectMapper());
this(client, url, ObjectMapperFactory.create());
}


Expand Down Expand Up @@ -581,6 +582,14 @@ public void close() {
}
}

/**
* make the {@link ObjectMapper} available to other classes in this package so that they can
* share the same configured instance instead of potentially using differently configured ObjectMappers
*/
ObjectMapper getObjectMapper() {
return mapper;
}


/**
* Internal API to add any common parameters. In this case, it sets the version parameter
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/socrata/api/Soda2Producer.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public static final Soda2Producer newProducerWithRequestId(final String url, Str
public Soda2Producer(HttpLowLevel httpLowLevel)
{
super(httpLowLevel);
factory = new JsonFactory();
factory = httpLowLevel.getObjectMapper().getFactory();
}


Expand Down
12 changes: 1 addition & 11 deletions src/main/java/com/socrata/api/SodaDdl.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.socrata.api;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.google.common.base.Preconditions;
import com.socrata.exceptions.LongRunningQueryException;
import com.socrata.exceptions.SodaError;
Expand All @@ -15,7 +13,6 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;

Expand Down Expand Up @@ -355,16 +352,9 @@ public ClientResponse issueRequest() throws LongRunningQueryException, SodaError

try {
final ClientResponse response = requester.issueRequest();
return mapper.readValue(response.getEntity(InputStream.class), AssetResponse.class);
//return response.getEntity(AssetResponse.class);
return response.getEntity(AssetResponse.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

response.getEntity doesn't work with creating assets /api/assets. Keep the original mapper.readValue and error handlers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot find any documentation for the "assets" resource of the Import API. Does response.getEntity not work because this resource does not accept requests for application/json so the MessageBodyReader will not be triggered?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jackson looks for json content-type in getEntity. But the actual content type returned from the server is text/plain despite that it is json. Using readValue from input stream get around this problem.

} catch (LongRunningQueryException e) {
return getHttpLowLevel().getAsyncResults(e.location, e.timeToRetry, getHttpLowLevel().getMaxRetries(), AssetResponse.class, requester);
} catch (JsonMappingException e) {
throw new SodaError("Illegal response from the service.");
} catch (JsonParseException e) {
throw new SodaError("Invalid JSON returned from the service.");
} catch (IOException e) {
throw new SodaError("Error communicating with service.");
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/socrata/api/SodaWorkflow.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public class SodaWorkflow

protected final URI geocodingUri;
protected final HttpLowLevel httpLowLevel;
protected final ObjectMapper mapper;
protected final URI viewUri;
protected final ObjectMapper mapper;

/**
* Create a new SodaWorkflow object, using the supplied credentials for authentication.
Expand Down Expand Up @@ -70,7 +70,7 @@ public SodaWorkflow(HttpLowLevel httpLowLevel)
.path(VIEWS_BASE_PATH)
.build();

mapper = new ObjectMapper();
mapper = httpLowLevel.getObjectMapper();
}

/**
Expand Down
116 changes: 7 additions & 109 deletions src/main/java/com/socrata/utils/JacksonObjectMapperProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,133 +2,31 @@


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.util.StdDateFormat;

import javax.annotation.concurrent.Immutable;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import java.text.*;
import java.util.Date;
import java.util.TimeZone;

/**
* Class to provide a customer ObjectMapper to Jersey (for Jackson).<p/>
*
* This is required to get "slightly" custom date parsing. The default Jackson behaviour will
* turn a floating timestamp <code>2012-06-20T07:00:00</code> into Zulu time <code>2012-06-20T07:00:00<b>Z</b></code>
*
* This leads to problems if transforming into a Date or DateTime (although Joda's LocalDateTime would work fine)
*/
@Immutable
@Provider
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>
{
protected static final SimpleDateFormat SOCRATA_WRITING_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
private final ObjectMapper mapper;

protected static final SimpleDateFormat SOCRATA_FLOATING_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
protected static final SimpleDateFormat SOCRATA_FLOATING_FORMAT_MILLIS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

protected static final DateFormat[] SPECIAL_DATE_LIST = new DateFormat[] {SOCRATA_FLOATING_FORMAT_MILLIS, SOCRATA_FLOATING_FORMAT} ;
// An ObjectMapper with our custom date formatting
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

static
{
OBJECT_MAPPER.setDateFormat(new SocrataDateFormat());
public JacksonObjectMapperProvider() {
this(ObjectMapperFactory.create());
}

public JacksonObjectMapperProvider(final ObjectMapper mapper) {
this.mapper = mapper;
}

@Override
public ObjectMapper getContext(Class<?> type)
{
return OBJECT_MAPPER;
return mapper;
}


/**
* A class that special cases ISO 8601 dates, and assumes that no "Z" at
* the end means that it should be translated as local time instead of Zulu time.
*
* This makes the Socrata floating date types work.
*/
protected static class SocrataDateFormat extends StdDateFormat
{

//Although, SimpleDateFormat is not thread safe, the entire SocrataDateFormat should be getting cloned by Jackson.
final SimpleDateFormat SOCRATA_WRITE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
final SimpleDateFormat SOCRATA_FLOATING_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
final SimpleDateFormat SOCRATA_FLOATING_FORMAT_MILLIS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

final DateFormat[] SPECIAL_DATE_LIST = new DateFormat[] {SOCRATA_FLOATING_FORMAT_MILLIS, SOCRATA_FLOATING_FORMAT} ;


public SocrataDateFormat()
{
final TimeZone localTimezone = TimeZone.getDefault();
SOCRATA_WRITE_FORMAT.setTimeZone(localTimezone);
}

/**
* Overrides this method to special case ISO 8601 dates without 'Z'
* to be local date/time.
*
* @param dateStr the string to parse
* @param pos current position in the string
* @return the Date returned.
*/
@Override
protected Date parseAsISO8601(String dateStr, ParsePosition pos, boolean throwErrors) throws ParseException {
final Date retVal = parseAsFloatingISO8601(dateStr, pos);
if (retVal != null) {
return retVal;
}
return super.parseAsISO8601(dateStr, pos, throwErrors);
}

@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition)
{
return SOCRATA_WRITING_FORMAT.format(date, toAppendTo, fieldPosition);
}

/**
* Parses an ISO8601 that does not contain a Z as a floating type.
*
* @param dateString the date string to parse
* @param pos the position to start parsin gat.
* @return
*/
private Date parseAsFloatingISO8601(final String dateString, final ParsePosition pos)
{
final int len = dateString.length();
final char c = dateString.charAt(len-1);
if (c != 'z' && c != 'Z') {

for (DateFormat format : SPECIAL_DATE_LIST)
{
final ParsePosition testPos = new ParsePosition(pos.getIndex());
final DateFormat safeFormat = (DateFormat) format.clone();
final Date retVal = safeFormat.parse(dateString, testPos);

if (retVal != null && testPos.getIndex()==len) {
pos.setIndex(testPos.getIndex());
return retVal;
}
}
}
return null;
}

/**
* Need to override this, because StdDateFormat overrode this.
*
* @return A new SocrataDateFormat
*/
@Override
public StdDateFormat clone()
{
return new SocrataDateFormat();
}
}

}
122 changes: 122 additions & 0 deletions src/main/java/com/socrata/utils/ObjectMapperFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package com.socrata.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import com.fasterxml.jackson.datatype.joda.JodaModule;

import java.text.*;
import java.util.Date;
import java.util.TimeZone;

/**
* Produces an instances of {@link ObjectMapper} for use with the Socrata API
*
* This is required to get "slightly" custom date parsing. The default Jackson behaviour will
* turn a floating timestamp <code>2012-06-20T07:00:00</code> into Zulu time <code>2012-06-20T07:00:00<b>Z</b></code>
*
* This leads to problems if transforming into a Date or DateTime (although Joda's LocalDateTime would work fine)
*/
public final class ObjectMapperFactory {

protected static final SimpleDateFormat SOCRATA_WRITING_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
protected static final SimpleDateFormat SOCRATA_FLOATING_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
protected static final SimpleDateFormat SOCRATA_FLOATING_FORMAT_MILLIS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
protected static final DateFormat[] SPECIAL_DATE_LIST = new DateFormat[] {SOCRATA_FLOATING_FORMAT_MILLIS, SOCRATA_FLOATING_FORMAT} ;

public static ObjectMapper create() {
return new ObjectMapper()
.registerModule(new JodaModule())
.setDateFormat(new ObjectMapperFactory.SocrataDateFormat());
}

/**
* A class that special cases ISO 8601 dates, and assumes that no "Z" at
* the end means that it should be translated as local time instead of Zulu time.
*
* This makes the Socrata floating date types work.
*/
protected static class SocrataDateFormat extends StdDateFormat
{

//Although, SimpleDateFormat is not thread safe, the entire SocrataDateFormat should be getting cloned by Jackson.
final SimpleDateFormat SOCRATA_WRITE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
final SimpleDateFormat SOCRATA_FLOATING_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
final SimpleDateFormat SOCRATA_FLOATING_FORMAT_MILLIS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

final DateFormat[] SPECIAL_DATE_LIST = new DateFormat[] {SOCRATA_FLOATING_FORMAT_MILLIS, SOCRATA_FLOATING_FORMAT} ;


public SocrataDateFormat()
{
final TimeZone localTimezone = TimeZone.getDefault();
SOCRATA_WRITE_FORMAT.setTimeZone(localTimezone);
}

/**
* Overrides this method to special case ISO 8601 dates without 'Z'
* to be local date/time.
*
* @param dateStr the string to parse
* @param pos current position in the string
* @return the Date returned.
*/
@Override
protected Date parseAsISO8601(String dateStr, ParsePosition pos, boolean throwErrors) throws ParseException {
final Date retVal = parseAsFloatingISO8601(dateStr, pos);
if (retVal != null) {
return retVal;
}
return super.parseAsISO8601(dateStr, pos, throwErrors);
}

@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition)
{
return SOCRATA_WRITING_FORMAT.format(date, toAppendTo, fieldPosition);
}

/**
* Parses an ISO8601 that does not contain a Z as a floating type.
*
* @param dateString the date string to parse
* @param pos the position to start parsin gat.
* @return
*/
private Date parseAsFloatingISO8601(final String dateString, final ParsePosition pos)
{
final int len = dateString.length();
final char c = dateString.charAt(len-1);
if (c != 'z' && c != 'Z') {

for (DateFormat format : SPECIAL_DATE_LIST)
{
final ParsePosition testPos = new ParsePosition(pos.getIndex());
final DateFormat safeFormat = (DateFormat) format.clone();
final Date retVal = safeFormat.parse(dateString, testPos);

if (retVal != null && testPos.getIndex()==len) {
pos.setIndex(testPos.getIndex());
return retVal;
}
}
}
return null;
}

/**
* Need to override this, because StdDateFormat overrode this.
*
* @return A new SocrataDateFormat
*/
@Override
public StdDateFormat clone()
{
return new SocrataDateFormat();
}
}

/**
* static members only
*/
private ObjectMapperFactory() { }
}
6 changes: 1 addition & 5 deletions src/test/java/com/socrata/DatesTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.socrata;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.socrata.api.Soda2Consumer;
Expand All @@ -15,7 +14,6 @@
import com.socrata.model.soql.SoqlQuery;
import com.socrata.utils.ColumnUtil;
import junit.framework.TestCase;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.time.DateUtils;
import org.joda.time.DateTimeZone;
import org.junit.Test;
Expand Down Expand Up @@ -183,7 +181,7 @@ public void testDateCRUD() throws IOException, InterruptedException, SodaError

final Meta objectMetadata = producer.addObject(UPDATE_DATA_SET, nomination);
final Nomination createdNomination = producer.getById(UPDATE_DATA_SET, objectMetadata.getId(), Nomination.class);
TestCase.assertTrue(EqualsBuilder.reflectionEquals(nomination, createdNomination));
TestCase.assertEquals(nomination, createdNomination);

final Meta updateMeta= producer.update(UPDATE_DATA_SET, objectMetadata.getId(), nominationUpdate);
TestCase.assertEquals(objectMetadata.getId(), updateMeta.getId());
Expand All @@ -198,8 +196,6 @@ public void testDateCRUD() throws IOException, InterruptedException, SodaError
//TestCase.assertTrue(EqualsBuilder.reflectionEquals(nominationUpdate, updatedNomination));

List l = Lists.newArrayList(new DeleteRecord(objectMetadata.getId(), true));
ObjectMapper m = new ObjectMapper();
System.out.println(m.writeValueAsString(l));
UpsertResult result = producer.upsert(UPDATE_DATA_SET, l);
try {
final Nomination deletedNomination = producer.getById(UPDATE_DATA_SET, objectMetadata.getId(), Nomination.class);
Expand Down
Loading