/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.importer.rest.converters;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.geoserver.catalog.CatalogFactory;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.catalog.StyleInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.config.util.XStreamPersister;
import org.geoserver.importer.Archive;
import org.geoserver.importer.Database;
import org.geoserver.importer.Directory;
import org.geoserver.importer.FileData;
import org.geoserver.importer.ImportContext;
import org.geoserver.importer.ImportContext.State;
import org.geoserver.importer.ImportData;
import org.geoserver.importer.ImportTask;
import org.geoserver.importer.Importer;
import org.geoserver.importer.RemoteData;
import org.geoserver.importer.UpdateMode;
import org.geoserver.importer.ValidationException;
import org.geoserver.importer.mosaic.Mosaic;
import org.geoserver.importer.mosaic.TimeMode;
import org.geoserver.importer.transform.AttributeRemapTransform;
import org.geoserver.importer.transform.AttributesToPointGeometryTransform;
import org.geoserver.importer.transform.CreateIndexTransform;
import org.geoserver.importer.transform.DateFormatTransform;
import org.geoserver.importer.transform.GdalAddoTransform;
import org.geoserver.importer.transform.GdalTranslateTransform;
import org.geoserver.importer.transform.GdalWarpTransform;
import org.geoserver.importer.transform.ImportTransform;
import org.geoserver.importer.transform.IntegerFieldToDateTransform;
import org.geoserver.importer.transform.RasterTransformChain;
import org.geoserver.importer.transform.ReprojectTransform;
import org.geoserver.importer.transform.TransformChain;
import org.geoserver.importer.transform.VectorTransformChain;
import org.geoserver.rest.converters.BaseMessageConverter;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
/**
* {@link BaseMessageConverter} implementation for reading {@link ImportContext} and {@link ImportTask} objects from JSON
*/
@Component
public class ImportJSONReader {
Importer importer;
@Autowired
public ImportJSONReader(Importer importer) {
this.importer = importer;
}
// public ImportContextJSONConverterReader(Importer importer, InputStream in) throws IOException {
// super(MediaType.APPLICATION_JSON,AbstractCatalogController.TEXT_JSON);
// this.importer = importer;
// JSONObject json = parse(in);
// }
// @Override
// protected boolean supports(Class<?> clazz) {
// return (ImportContext.class.isAssignableFrom(clazz) || ImportTask.class.isAssignableFrom(clazz) ||
// ImportTransform.class.isAssignableFrom(clazz) || TransformChain.class.isAssignableFrom(clazz));
// }
// @Override
// protected boolean canWrite(MediaType mediaType) {
// return false; // write not supported
// }
// @Override
// protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
// throws IOException, HttpMessageNotReadableException {
// InputStream in = inputMessage.getBody();
// JSONObject json = parse(in);
// if (ImportContext.class.isAssignableFrom(clazz)) {
// return context(json);
// } else if (ImportTask.class.isAssignableFrom(clazz)) {
// return task(json);
// } else if (ImportTransform.class.isAssignableFrom(clazz) || TransformChain.class.isAssignableFrom(clazz)) {
// return transform(json);
// }
// return null;
// }
public ImportContext context(JSONObject json) throws IOException {
ImportContext context = null;
if (json.has("import")) {
context = new ImportContext();
json = json.getJSONObject("import");
if (json.has("id")) {
context.setId(json.getLong("id"));
}
if (json.has("state")) {
context.setState(State.valueOf(json.getString("state")));
}
if (json.has("user")) {
context.setUser(json.getString("user"));
}
if (json.has("archive")) {
context.setArchive(json.getBoolean("archive"));
}
if (json.has("targetWorkspace")) {
context.setTargetWorkspace(
fromJSON(json.getJSONObject("targetWorkspace"), WorkspaceInfo.class));
}
if (json.has("targetStore")) {
context.setTargetStore(
fromJSON(json.getJSONObject("targetStore"), StoreInfo.class));
}
if (json.has("data")) {
context.setData(data(json.getJSONObject("data")));
}
if (json.has("transforms")) {
context.getDefaultTransforms().addAll(transforms(json.getJSONArray("transforms")));
}
}
return context;
}
LayerInfo layer(JSONObject json) throws IOException {
CatalogFactory f = importer.getCatalog().getFactory();
if (json.has("layer")) {
json = json.getJSONObject("layer");
}
//TODO: what about coverages?
ResourceInfo r = f.createFeatureType();
if (json.has("name")) {
r.setName(json.getString("name"));
}
if (json.has("nativeName")) {
r.setNativeName(json.getString("nativeName"));
}
if (json.has("srs")) {
r.setSRS(json.getString("srs"));
try {
r.setNativeCRS(CRS.decode(json.getString("srs")));
}
catch(Exception e) {
//should fail later
}
}
if (json.has("bbox")) {
r.setNativeBoundingBox(bbox(json.getJSONObject("bbox")));
}
if (json.has("title")) {
r.setTitle(json.getString("title"));
}
if (json.has("abstract")) {
r.setAbstract(json.getString("abstract"));
}
if (json.has("description")) {
r.setDescription(json.getString("description"));
}
LayerInfo l = f.createLayer();
l.setResource(r);
//l.setName(); don't need to this, layer.name just forwards to name of underlying resource
if (json.has("style")) {
JSONObject sobj = new JSONObject();
sobj.put("defaultStyle", json.get("style"));
JSONObject lobj = new JSONObject();
lobj.put("layer", sobj);
LayerInfo tmp = fromJSON(lobj, LayerInfo.class);
if (tmp.getDefaultStyle() != null) {
l.setDefaultStyle(tmp.getDefaultStyle());
}
else {
sobj = new JSONObject();
sobj.put("style", json.get("style"));
l.setDefaultStyle(fromJSON(sobj, StyleInfo.class));
}
}
return l;
}
public ImportTask task(InputStream inputStream) throws IOException {
JSONObject json = parse(inputStream);
return task(json);
}
public ImportTask task(JSONObject json) throws IOException {
if (json.has("task")) {
json = json.getJSONObject("task");
}
ImportTask task = new ImportTask();
if (json.has("id")) {
task.setId(json.getInt("id"));
}
if (json.has("updateMode")) {
task.setUpdateMode(UpdateMode.valueOf(json.getString("updateMode").toUpperCase()));
} else {
// if it hasn't been specified by the request, set this to null
// or else it's possible to overwrite an existing setting
task.setUpdateMode(null);
}
JSONObject data = null;
if (json.has("data")) {
data = json.getJSONObject("data");
}
else if (json.has("source")) { // backward compatible check for source
data = json.getJSONObject("source");
}
if (data != null) {
// we only support updating the charset
if (data.has("charset")) {
if (task.getData() == null) {
task.setData(new ImportData.TransferObject());
}
task.getData().setCharsetEncoding(data.getString("charset"));
}
}
if (json.has("target")) {
task.setStore(fromJSON(json.getJSONObject("target"), StoreInfo.class));
}
LayerInfo layer = null;
if (json.has("layer")) {
layer = layer(json.getJSONObject("layer"));
} else {
layer = importer.getCatalog().getFactory().createLayer();
}
task.setLayer(layer);
if (json.has("transformChain")) {
task.setTransform(transformChain(json.getJSONObject("transformChain")));
}
return task;
}
TransformChain transformChain(JSONObject json) throws IOException {
String type = json.getString("type");
TransformChain chain = null;
if ("vector".equalsIgnoreCase(type) || "VectorTransformChain".equalsIgnoreCase(type)) {
chain = new VectorTransformChain();
} else if ("raster".equalsIgnoreCase(type) || "RasterTransformChain".equalsIgnoreCase(type)) {
chain = new RasterTransformChain();
} else {
throw new IOException("Unable to parse transformChain of type " + type);
}
JSONArray transforms = json.getJSONArray("transforms");
for (int i = 0; i < transforms.size(); i++) {
chain.add(transform(transforms.getJSONObject(i)));
}
return chain;
}
List<ImportTransform> transforms(JSONArray transforms) throws IOException {
List<ImportTransform> result = new ArrayList<>();
for (int i = 0; i < transforms.size(); i++) {
result.add(transform(transforms.getJSONObject(i)));
}
return result;
}
public ImportTransform transform(String json) throws IOException {
return transform(IOUtils.toInputStream(json));
}
public ImportTransform transform(InputStream inputStream) throws IOException {
JSONObject json = parse(inputStream);
return transform(json);
}
public ImportTransform transform(JSONObject json) throws IOException {
ImportTransform transform;
String type = json.getString("type");
if ("DateFormatTransform".equalsIgnoreCase(type)) {
transform = new DateFormatTransform(json.getString("field"), json.optString("format", null));
} else if ("IntegerFieldToDateTransform".equalsIgnoreCase(type)) {
transform = new IntegerFieldToDateTransform(json.getString("field"));
} else if ("CreateIndexTransform".equalsIgnoreCase(type)) {
transform = new CreateIndexTransform(json.getString("field"));
} else if ("AttributeRemapTransform".equalsIgnoreCase(type)) {
Class clazz;
try {
clazz = Class.forName( json.getString("target") );
} catch (ClassNotFoundException cnfe) {
throw new ValidationException("unable to locate target class " + json.getString("target"));
}
transform = new AttributeRemapTransform(json.getString("field"), clazz);
} else if ("AttributesToPointGeometryTransform".equalsIgnoreCase(type)) {
String latField = json.getString("latField");
String lngField = json.getString("lngField");
transform = new AttributesToPointGeometryTransform(latField, lngField);
} else if ("ReprojectTransform".equalsIgnoreCase(type)){
CoordinateReferenceSystem source = json.has("source") ? crs(json.getString("source")) : null;
CoordinateReferenceSystem target = json.has("target") ? crs(json.getString("target")) : null;
try {
transform = new ReprojectTransform(source, target);
}
catch(Exception e) {
throw new ValidationException("Error parsing reproject transform", e);
}
} else if ("GdalTranslateTransform".equalsIgnoreCase(type)) {
List<String> options = getOptions(json);
transform = new GdalTranslateTransform(options);
} else if ("GdalWarpTransform".equalsIgnoreCase(type)) {
List<String> options = getOptions(json);
transform = new GdalWarpTransform(options);
} else if ("GdalAddoTransform".equalsIgnoreCase(type)) {
List<String> options = getOptions(json);
JSONArray array = json.getJSONArray("levels");
List<Integer> levels = new ArrayList<>();
for (int i = 0; i < array.size(); i++) {
int level = array.getInt(i);
levels.add(level);
}
transform = new GdalAddoTransform(options, levels);
} else {
throw new ValidationException("Invalid transform type '" + type + "'");
}
return transform;
}
List<String> getOptions(JSONObject json) {
JSONArray array = json.getJSONArray("options");
List<String> options = new ArrayList<>();
for (int i = 0; i < array.size(); i++) {
String option = array.getString(i);
options.add(option);
}
return options;
}
ImportData data(JSONObject json) throws IOException {
String type = json.getString("type");
if (type == null) {
throw new IOException("Data object must specify 'type' property");
}
if ("file".equalsIgnoreCase(type)) {
return file(json);
}
else if("directory".equalsIgnoreCase(type)) {
return directory(json);
}
else if("mosaic".equalsIgnoreCase(type)) {
return mosaic(json);
}
else if("archive".equalsIgnoreCase(type)) {
return archive(json);
}
else if ("database".equalsIgnoreCase(type)) {
return database(json);
}
else if ("remote".equalsIgnoreCase(type)) {
return remote(json);
}
else {
throw new IllegalArgumentException("Unknown data type: " + type);
}
}
FileData file(JSONObject json) throws IOException {
if (json.has("file")) {
//TODO: find out if spatial or not
String file = json.getString("file");
return FileData.createFromFile(new File(file));
//return new FileData(new File(file));
}
else {
throw new IOException(
"Could not find 'file' entry in data, mandatory for file type data");
}
}
RemoteData remote(JSONObject json) throws IOException {
if (json.has("location")) {
String location = json.getString("location");
RemoteData data = new RemoteData(location);
if (json.has("username")) {
data.setUsername(json.getString("username"));
}
if (json.has("password")) {
data.setPassword(json.getString("password"));
}
if (json.has("domain")) {
data.setDomain(json.getString("domain"));
}
return data;
} else {
throw new IOException(
"Could not find 'location' entry in data, mandatory for remote type data");
}
}
Mosaic mosaic(JSONObject json) throws IOException {
Mosaic m = new Mosaic(json.has("location") ? new File(json.getString("location")) :
Directory.createNew(importer.getUploadRoot()).getFile());
if (json.has("name")) {
m.setName(json.getString("name"));
}
if (json.containsKey("time")) {
JSONObject time = json.getJSONObject("time");
if (!time.containsKey("mode")) {
throw new IllegalArgumentException("time object must specific mode property as " +
"one of " + TimeMode.values());
}
m.setTimeMode(TimeMode.valueOf(time.getString("mode").toUpperCase()));
m.getTimeHandler().init(time);
}
return m;
}
Archive archive(JSONObject json) throws IOException {
throw new UnsupportedOperationException("TODO: implement");
}
public Directory directory(JSONObject json) throws IOException {
if (json.has("location")) {
return new Directory(new File(json.getString("location")));
}
else {
return Directory.createNew(importer.getUploadRoot());
}
}
Database database(JSONObject json) throws IOException {
throw new UnsupportedOperationException("TODO: implement");
}
ReferencedEnvelope bbox(JSONObject json) {
CoordinateReferenceSystem crs = null;
if (json.has("crs")) {
crs = (CoordinateReferenceSystem)
new XStreamPersister.CRSConverter().fromString(json.getString("crs"));
}
return new ReferencedEnvelope(json.getDouble("minx"), json.getDouble("maxx"),
json.getDouble("miny"), json.getDouble("maxy"), crs);
}
CoordinateReferenceSystem crs(String srs) {
try {
return CRS.decode(srs);
} catch (Exception e) {
throw new RuntimeException("Failing parsing srs: " + srs, e);
}
}
public JSONObject parse(InputStream in) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
IOUtils.copy(in, bout);
return JSONObject.fromObject(new String(bout.toByteArray()));
}
Object read(InputStream in) throws IOException {
Object result = null;
JSONObject json = parse(in);
// @hack - this should return a ImportTask
if (json.containsKey("target")) {
result = fromJSON(json.getJSONObject("target"), DataStoreInfo.class);
}
return result;
}
<T> T fromJSON(JSONObject json, Class<T> clazz) throws IOException {
XStreamPersister xp = importer.createXStreamPersisterJSON();
return xp.load(new ByteArrayInputStream(json.toString().getBytes()), clazz);
}
}