ddf.catalog.transformer.input.geojson.GeoJsonInputTransformer.java Source code

Java tutorial

Introduction

Here is the source code for ddf.catalog.transformer.input.geojson.GeoJsonInputTransformer.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package ddf.catalog.transformer.input.geojson;

import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.isNotEmpty;
import static org.codice.gsonsupport.GsonTypeAdapters.MAP_STRING_TO_OBJECT_TYPE;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import ddf.catalog.data.AttributeDescriptor;
import ddf.catalog.data.AttributeRegistry;
import ddf.catalog.data.AttributeType.AttributeFormat;
import ddf.catalog.data.Metacard;
import ddf.catalog.data.MetacardType;
import ddf.catalog.data.impl.MetacardImpl;
import ddf.catalog.transform.CatalogTransformerException;
import ddf.catalog.transform.InputTransformer;
import ddf.geo.formatter.CompositeGeometry;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;
import java.util.function.Function;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.StringUtils;
import org.codice.ddf.platform.util.SortedServiceList;
import org.codice.gsonsupport.GsonTypeAdapters.LongDoubleTypeAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Converts standard GeoJSON (geojson.org) into a Metacard. The limitation on the GeoJSON is that it
 * must conform to the {@link ddf.catalog.data.impl.MetacardImpl#BASIC_METACARD} {@link
 * MetacardType}.
 */
public class GeoJsonInputTransformer implements InputTransformer {

    static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";

    private static final Gson GSON = new GsonBuilder().disableHtmlEscaping()
            .registerTypeAdapterFactory(LongDoubleTypeAdapter.FACTORY).create();

    private static final String METACARD_TYPE_PROPERTY_KEY = "metacard-type";

    private static final String ID = "geojson";

    private static final String MIME_TYPE = "application/json";

    private static final String SOURCE_ID_PROPERTY = "source-id";

    private static final Logger LOGGER = LoggerFactory.getLogger(GeoJsonInputTransformer.class);

    private List<MetacardType> metacardTypes;

    private AttributeRegistry attributeRegistry;

    private SortedServiceList inputTransformers;

    /** Transforms GeoJson (http://www.geojson.org/) into a {@link Metacard} */
    @Override
    public Metacard transform(InputStream input) throws IOException, CatalogTransformerException {
        return transform(input, null);
    }

    @Override
    public Metacard transform(InputStream input, String id) throws IOException, CatalogTransformerException {

        validateInput(input);
        Map<String, Object> rootObject = getRootObject(input);
        validateTypeValue(rootObject);
        Map<String, Object> properties = getProperties(rootObject);

        final String propertyTypeName = (String) properties.get(METACARD_TYPE_PROPERTY_KEY);
        MetacardImpl metacard = getMetacard(propertyTypeName, properties);

        MetacardType metacardType = metacard.getMetacardType();
        LOGGER.debug("Metacard type name: {}", metacardType.getName());

        // retrieve geometry
        CompositeGeometry geoJsonGeometry = getCompositeGeometry(rootObject);

        if (geoJsonGeometry != null && StringUtils.isNotEmpty(geoJsonGeometry.toWkt())) {
            metacard.setLocation(geoJsonGeometry.toWkt());
        }

        Map<String, AttributeDescriptor> attributeDescriptorMap = metacardType.getAttributeDescriptors().stream()
                .collect(toMap(AttributeDescriptor::getName, Function.identity()));

        properties.entrySet().forEach(entry -> addAttributeToMetacard(metacard, attributeDescriptorMap, entry));

        if (isNotEmpty(metacard.getSourceId())) {
            properties.put(SOURCE_ID_PROPERTY, metacard.getSourceId());
        }

        if (id != null) {
            metacard.setId(id);
        }

        return metacard;
    }

    public void setMetacardTypes(List<MetacardType> metacardTypes) {
        this.metacardTypes = metacardTypes;
    }

    public void setAttributeRegistry(AttributeRegistry attributeRegistry) {
        this.attributeRegistry = attributeRegistry;
    }

    @Override
    public String toString() {
        return "InputTransformer {Impl=" + this.getClass().getName() + ", id=" + ID + ", mime-type=" + MIME_TYPE
                + "}";
    }

    private void validateInput(InputStream input) throws CatalogTransformerException {
        if (input == null) {
            throw new CatalogTransformerException("Cannot transform null input.");
        }
    }

    private Map<String, Object> getRootObject(InputStream input) throws CatalogTransformerException {
        Map<String, Object> rootObject;
        try {
            rootObject = GSON.fromJson(new InputStreamReader(input, StandardCharsets.UTF_8),
                    MAP_STRING_TO_OBJECT_TYPE);
        } catch (JsonParseException e) {
            throw new CatalogTransformerException("Invalid JSON input", e);
        }

        if (rootObject == null) {
            throw new CatalogTransformerException("Unable to parse JSON for metacard.");
        }
        return rootObject;
    }

    private void validateTypeValue(Map<String, Object> rootObject) throws CatalogTransformerException {
        Object typeValue = rootObject.get(CompositeGeometry.TYPE_KEY);
        if (!"Feature".equals(typeValue)) {
            throw new CatalogTransformerException(
                    new UnsupportedOperationException("Only supported type is Feature, not [" + typeValue + "]"));
        }
    }

    private Map<String, Object> getProperties(Map<String, Object> rootObject) throws CatalogTransformerException {
        Map<String, Object> properties = (Map<String, Object>) rootObject.get(CompositeGeometry.PROPERTIES_KEY);

        if (properties == null) {
            throw new CatalogTransformerException("Properties are required to create a Metacard.");
        }
        return properties;
    }

    private MetacardImpl getMetacard(String propertyTypeName, Map<String, Object> properties)
            throws CatalogTransformerException {
        if (isEmpty(propertyTypeName) || metacardTypes == null) {
            LOGGER.debug("MetacardType specified in input is null or empty.  Trying all transformers in order...");
            Optional<MetacardImpl> first = inputTransformers == null ? Optional.of(new MetacardImpl())
                    : inputTransformers.stream().map(service -> tryTransformers(properties, service))
                            .filter(Objects::nonNull).findFirst();
            return first.orElse(new MetacardImpl());
        } else {
            MetacardType metacardType = metacardTypes.stream()
                    .filter(type -> type.getName().equals(propertyTypeName)).findFirst()
                    .orElseThrow(() -> new CatalogTransformerException(
                            "MetacardType specified in input has not been registered with the system."
                                    + " Cannot parse input. MetacardType name: " + propertyTypeName));

            LOGGER.debug("Found registered MetacardType: {}", propertyTypeName);
            return new MetacardImpl(metacardType);
        }
    }

    private MetacardImpl tryTransformers(Map<String, Object> properties, Object service) {
        InputTransformer inputTransformer = null;
        try {
            inputTransformer = (InputTransformer) service;
            return new MetacardImpl(inputTransformer
                    .transform(new ByteArrayInputStream(((String) properties.get("metadata")).getBytes())));
        } catch (Exception e) {
            LOGGER.debug("Error calling transformer: " + inputTransformer.toString(), e);
        }
        return null;
    }

    private CompositeGeometry getCompositeGeometry(Map<String, Object> rootObject) {
        Map<String, Object> geometryJson = (Map<String, Object>) rootObject.get(CompositeGeometry.GEOMETRY_KEY);
        CompositeGeometry geoJsonGeometry = null;
        if (geometryJson != null) {
            if (geometryJson.get(CompositeGeometry.TYPE_KEY) != null
                    && (geometryJson.get(CompositeGeometry.COORDINATES_KEY) != null
                            || geometryJson.get(CompositeGeometry.GEOMETRIES_KEY) != null)) {

                String geometryTypeJson = geometryJson.get(CompositeGeometry.TYPE_KEY).toString();

                geoJsonGeometry = CompositeGeometry.getCompositeGeometry(geometryTypeJson, geometryJson);

            } else {
                LOGGER.debug("Could not find geometry type.");
            }
        }
        return geoJsonGeometry;
    }

    private void addAttributeToMetacard(MetacardImpl metacard,
            Map<String, AttributeDescriptor> attributeDescriptorMap, Entry<String, Object> entry) {
        final String key = entry.getKey();
        final Object value = entry.getValue();
        try {
            if (attributeDescriptorMap.containsKey(key)) {
                AttributeDescriptor ad = attributeDescriptorMap.get(key);
                metacard.setAttribute(key, convertProperty(value, ad));
            } else {
                Optional<AttributeDescriptor> optional = attributeRegistry.lookup(key);
                if (optional.isPresent()) {
                    metacard.setAttribute(key, convertProperty(value, optional.get()));
                }
            }
        } catch (NumberFormatException | ParseException e) {
            LOGGER.info("GeoJSON input for attribute name '{}' does not match the expected AttributeType. "
                    + "This attribute will not be added to the metacard.", key, e);
        }
    }

    private Serializable convertProperty(Object property, AttributeDescriptor descriptor) throws ParseException {
        AttributeFormat format = descriptor.getType().getAttributeFormat();
        if (descriptor.isMultiValued() && property instanceof List) {
            List<Serializable> values = new ArrayList<>();
            for (Object value : (List) property) {
                values.add(convertValue(value, format));
            }
            return (Serializable) values;
        } else {
            return convertValue(property, format);
        }
    }

    private Serializable convertValue(Object value, AttributeFormat format) throws ParseException {
        if (value == null) {
            return null;
        }

        switch (format) {
        case BINARY:
            return DatatypeConverter.parseBase64Binary(value.toString());
        case DATE:
            SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_8601_DATE_FORMAT);
            dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
            return dateFormat.parse(value.toString());
        case GEOMETRY:
        case STRING:
        case XML:
            return value.toString();
        case BOOLEAN:
            return Boolean.parseBoolean(value.toString());
        case SHORT:
            return Short.parseShort(value.toString());
        case INTEGER:
            return Integer.parseInt(value.toString());
        case LONG:
            return Long.parseLong(value.toString());
        case FLOAT:
            return Float.parseFloat(value.toString());
        case DOUBLE:
            return Double.parseDouble(value.toString());
        default:
            return null;
        }
    }

    public SortedServiceList getInputTransformers() {
        return inputTransformers;
    }

    public void setInputTransformers(SortedServiceList inputTransformers) {
        this.inputTransformers = inputTransformers;
    }
}