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

Add simple JSON encoder #15

Merged
merged 3 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.camptocamp.opendata.ogc.features.autoconfigure.geotools.SampleDataBackendAutoConfiguration;
import com.camptocamp.opendata.ogc.features.http.codec.MimeTypes;
import com.camptocamp.opendata.ogc.features.http.codec.csv.CsvFeatureCollectionHttpMessageConverter;
import com.camptocamp.opendata.ogc.features.http.codec.json.SimpleJsonFeatureCollectionHttpMessageConverter;
import com.camptocamp.opendata.ogc.features.http.codec.shp.ShapefileFeatureCollectionHttpMessageConverter;
import com.camptocamp.opendata.ogc.features.http.codec.xls.Excel2007FeatureCollectionHttpMessageConverter;
import com.camptocamp.opendata.ogc.features.repository.CollectionRepository;
Expand Down Expand Up @@ -68,11 +69,17 @@ CollectionsApiController collectionsApiController(CollectionsApiDelegate delegat
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(simpleJsonFeatureCollectionHttpMessageConverter());
converters.add(csvFeatureCollectionHttpMessageConverter());
converters.add(shapefileFeatureCollectionHttpMessageConverter());
converters.add(excel2007FeatureCollectionHttpMessageConverter());
}

@Bean
SimpleJsonFeatureCollectionHttpMessageConverter simpleJsonFeatureCollectionHttpMessageConverter() {
return new SimpleJsonFeatureCollectionHttpMessageConverter();
}

@Bean
Excel2007FeatureCollectionHttpMessageConverter excel2007FeatureCollectionHttpMessageConverter() {
return new Excel2007FeatureCollectionHttpMessageConverter();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.camptocamp.opendata.ogc.features.http.codec.json;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Type;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractGenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.MimeType;

import com.camptocamp.opendata.model.GeodataRecord;
import com.camptocamp.opendata.ogc.features.http.codec.MimeTypes;
import com.camptocamp.opendata.ogc.features.model.FeatureCollection;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
* Simpler, geometry-less, JSON encoder for {@link FeatureCollection} and
* {@link GeodataRecord}.
*
* <p>
* Sample output:
*
* <pre>
* <code>
* {
* "numberMatched":16,
* "numberReturned":2,
* "records":[
* {
* "@id":"1",
* "city":" Trento",
* "number":140,
* "year":2002
* },
* ...
* ],
* "links":[
* {
* "href":"http://localhost:8080/ogcapi/collections/locations/items?f=json&offset=0&limit=2",
* "rel":"self",
* "type":"application/json",
* "title":"This document"
* },
* ...
* ]
* }
* </code>
* </pre>
*
*/
public class SimpleJsonFeatureCollectionHttpMessageConverter
extends AbstractGenericHttpMessageConverter<FeatureCollection> {

private static final MimeType MIME_TYPE = MimeTypes.JSON.getMimeType();
private static final MediaType MEDIA_TYPE = new MediaType(MIME_TYPE);

private ObjectMapper mapper;

public SimpleJsonFeatureCollectionHttpMessageConverter() {
super(MEDIA_TYPE);
mapper = new ObjectMapper();
Jackson2ObjectMapperBuilder.json().configure(mapper);
mapper.registerModule(new SimpleJsonModule());
}

/**
* {@inheritDoc}
*/
protected @Override boolean supports(Class<?> clazz) {
return FeatureCollection.class.isAssignableFrom(clazz);
}

/**
* {@inheritDoc}
*/
protected @Override MediaType getDefaultContentType(FeatureCollection message) {
return MEDIA_TYPE;
}

protected @Override void writeInternal(FeatureCollection message, Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {

OutputStream body = outputMessage.getBody();
mapper.writeValue(body, message);
body.flush();
}

protected @Override FeatureCollection readInternal(Class<? extends FeatureCollection> clazz,
HttpInputMessage inputMessage) {
throw new UnsupportedOperationException();
}

@Override
public FeatureCollection read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) {
throw new UnsupportedOperationException();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.camptocamp.opendata.ogc.features.http.codec.json;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.List;

import com.camptocamp.opendata.model.GeodataRecord;
import com.camptocamp.opendata.model.GeometryProperty;
import com.camptocamp.opendata.model.SimpleProperty;
import com.camptocamp.opendata.ogc.features.model.FeatureCollection;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

@SuppressWarnings("serial")
class SimpleJsonModule extends SimpleModule {

private static final Version VERSION = new Version(1, 0, 0, null, null, null);

public SimpleJsonModule() {
super(SimpleJsonModule.class.getSimpleName(), VERSION);

addSerializer(new FeatureCollectionSerializer());
addSerializer(new GeodataRecordSerializer());
}

static class FeatureCollectionSerializer extends StdSerializer<FeatureCollection> {

protected FeatureCollectionSerializer() {
super(FeatureCollection.class);
}

@Override
public void serializeWithType(FeatureCollection collection, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {

WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(collection, JsonToken.START_OBJECT));

serializeContent(collection, gen);

typeSer.writeTypeSuffix(gen, typeIdDef);
}

@Override
public void serialize(FeatureCollection collection, JsonGenerator generator, SerializerProvider serializers)
throws IOException {

if (collection == null) {
generator.writeNull();
return;
}
generator.writeStartObject();
serializeContent(collection, generator);
generator.writeEndObject();
}

private void serializeContent(FeatureCollection collection, JsonGenerator generator) throws IOException {
generator.writeNumberField("numberMatched", collection.getNumberMatched());
generator.writeNumberField("numberReturned", collection.getNumberReturned());

generator.writeFieldName("records");
generator.writeStartArray();
collection.getFeatures().forEach(rec -> write(rec, generator));
generator.writeEndArray();

generator.writeFieldName("links");
generator.writeStartArray();
collection.getLinks().forEach(link -> write(link, generator));
generator.writeEndArray();
}

private void write(Object obj, JsonGenerator generator) {
try {
generator.writeObject(obj);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

}

static class GeodataRecordSerializer extends StdSerializer<GeodataRecord> {

public GeodataRecordSerializer() {
super(GeodataRecord.class);
}

@Override
public void serializeWithType(GeodataRecord rec, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {

WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(rec, JsonToken.START_OBJECT));

serializeContent(rec, gen);

typeSer.writeTypeSuffix(gen, typeIdDef);
}

@Override
public void serialize(GeodataRecord rec, JsonGenerator generator, SerializerProvider serializers)
throws IOException {

if (rec == null) {
generator.writeNull();
return;
}
generator.writeStartObject();
serializeContent(rec, generator);
generator.writeEndObject();
}

private void serializeContent(GeodataRecord rec, JsonGenerator generator) throws IOException {
if (null != rec.getId()) {
generator.writeStringField("@id", rec.getId());
}
writeProperties(generator, rec.getProperties());
}

private void writeProperties(JsonGenerator generator, List<? extends SimpleProperty<?>> properties)
throws IOException {
for (SimpleProperty<?> p : properties) {
if (!(p instanceof GeometryProperty)) {
generator.writeObjectField(p.getName(), p.getValue());
}
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,66 @@ components:
$ref: "#/components/schemas/numberMatched"
numberReturned:
$ref: "#/components/schemas/numberReturned"
example:
{
"type":"FeatureCollection",
"numberMatched":16,
"numberReturned":2,
"features":[
{
"type":"Feature",
"@typeName":"locations",
"@id":"1",
"geometry":{"type":"Point","@name":"geom","@srs":"EPSG:4326","coordinates":[11.116667,46.066667]},
"properties":{"city":" Trento","number":140,"year":2002}
},
{
"type":"Feature",
"@typeName":"locations",
"@id":"10",
"geometry":{"type":"Point","@name":"geom","@srs":"EPSG:4326","coordinates":[2.183333,41.383333]},
"properties":{"city":" Barcelona","number":914,"year":2010}
}
],
"links":[
{
"href":"http://localhost:<port>/ogcapi/collections/locations/items?f=geojson&offset=0&limit=2",
"rel":"self",
"type":"application/geo+json",
"title":"This document"
},
{
"href":"http://localhost:<port>/ogcapi/collections/locations/items?f=geojson&offset=2&limit=2",
"rel":"next",
"type":"application/geo+json",
"title":"Next page"
},
{
"href":"http://localhost:<port>/ogcapi/collections/locations/items?offset=0&limit=2&f=json",
"rel":"alternate",
"type":"application/json",
"title":"This document as JSON"
},
{
"href":"http://localhost:<port>/ogcapi/collections/locations/items?offset=0&limit=2&f=shapefile",
"rel":"alternate",
"type":"application/x-shapefile",
"title":"This document as Esri Shapefile"
},
{
"href":"http://localhost:<port>/ogcapi/collections/locations/items?offset=0&limit=2&f=csv",
"rel":"alternate",
"type":"text/csv;charset=UTF-8",
"title":"This document as Comma Separated Values"
},
{
"href":"http://localhost:<port>/ogcapi/collections/locations/items?offset=0&limit=2&f=ooxml",
"rel":"alternate",
"type":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"title":"This document as Excel 2007 / OOXML"
}
]
}
featureGeoJSON:
type: object
required:
Expand Down Expand Up @@ -968,48 +1028,12 @@ components:
returned features (`numberMatched` and `numberReturned`) as well as
links to support paging (link relation `next`).
content:
application/json:
schema:
$ref: '#/components/schemas/featureCollectionGeoJSON'
application/geo+json:
schema:
$ref: '#/components/schemas/featureCollectionGeoJSON'
example:
type: FeatureCollection
links:
- href: 'http://data.example.com/collections/buildings/items.json'
rel: self
type: application/geo+json
title: this document
- href: 'http://data.example.com/collections/buildings/items.html'
rel: alternate
type: text/html
title: this document as HTML
- href: 'http://data.example.com/collections/buildings/items.json&offset=10&limit=2'
rel: next
type: application/geo+json
title: next page
timeStamp: '2018-04-03T14:52:23Z'
numberMatched: 123
numberReturned: 2
features:
- type: Feature
id: '123'
geometry:
type: Polygon
coordinates:
- ...
properties:
function: residential
floors: '2'
lastUpdate: '2015-08-01T12:34:56Z'
- type: Feature
id: '132'
geometry:
type: Polygon
coordinates:
- ...
properties:
function: public use
floors: '10'
lastUpdate: '2013-12-03T10:15:37Z'
'text/csv':
schema:
type: string
Expand Down
Loading
Loading