/** * 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.metacard.propertyjson; import java.io.ByteArrayInputStream; import java.io.Serializable; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import javax.activation.MimeType; import javax.activation.MimeTypeParseException; import javax.annotation.Nullable; import javax.xml.bind.DatatypeConverter; import org.apache.commons.lang.StringUtils; import org.boon.json.JsonFactory; import org.boon.json.JsonParserFactory; import org.boon.json.JsonSerializer; import org.boon.json.JsonSerializerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddf.catalog.data.Attribute; import ddf.catalog.data.AttributeDescriptor; import ddf.catalog.data.AttributeType; import ddf.catalog.data.BinaryContent; import ddf.catalog.data.Metacard; import ddf.catalog.data.impl.BinaryContentImpl; import ddf.catalog.transform.CatalogTransformerException; import ddf.catalog.transform.MetacardTransformer; /** * Implements the {@link MetacardTransformer} interface to transform a single {@link Metacard} * instance to GeoJSON. This class places what is returned by {@link Metacard#getLocation()} in the * geometry JSON object in the GeoJSON output. The rest of the attributes of the Metacard are placed * in the properties object in the JSON. See geojson.org for the GeoJSON specification. * * @author Ashraf Barakat * @author ddf.isgs@lmco.com * @see MetacardTransformer * @see Metacard * @see Attribute */ /** * Transforms a metacard into a JSON with a format similiar to geojson, but without any * special geojson components. Simply put, a flat mapping of all attributes to K/V pairs * stored inside a root key of "property" */ public class PropertyJsonMetacardTransformer implements MetacardTransformer { private static final Logger LOGGER = LoggerFactory.getLogger(PropertyJsonMetacardTransformer.class); private static final String SOURCE_ID_PROPERTY = "source-id"; private static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; private final JsonSerializer json = JsonFactory.create(new JsonParserFactory(), new JsonSerializerFactory().includeNulls() .includeEmpty() .includeBlank() .setJsonFormatForDates(false)) .serializer(); protected static final String METACARD_TYPE_PROPERTY_KEY = "metacard-type"; protected static final MimeType DEFAULT_MIME_TYPE = new MimeType(); public static final String ID = "propertyjson"; static { try { DEFAULT_MIME_TYPE.setPrimaryType("application"); DEFAULT_MIME_TYPE.setSubType("json"); } catch (MimeTypeParseException e) { LOGGER.info("Failure creating MIME type", e); throw new ExceptionInInitializerError(e); } } public static Map<String, Object> convertToJSON(Metacard metacard) throws CatalogTransformerException { return convertToJSON(metacard, Collections.emptyList()); } public static Map<String, Object> convertToJSON(Metacard metacard, List<AttributeType.AttributeFormat> dontInclude) throws CatalogTransformerException { if (metacard == null) { throw new CatalogTransformerException("Cannot transform null metacard."); } Map<String, Object> rootObject = new HashMap<>(); Map<String, Object> properties = new HashMap<>(); for (AttributeDescriptor ad : metacard.getMetacardType() .getAttributeDescriptors()) { Attribute attribute = metacard.getAttribute(ad.getName()); if (attribute != null) { Object value = convertAttribute(attribute, ad, dontInclude); if (value != null) { properties.put(attribute.getName(), value); } } } properties.put(METACARD_TYPE_PROPERTY_KEY, metacard.getMetacardType() .getName()); if (StringUtils.isNotBlank(metacard.getSourceId())) { properties.put(SOURCE_ID_PROPERTY, metacard.getSourceId()); } rootObject.put("properties", properties); return rootObject; } @Override public BinaryContent transform(Metacard metacard, Map<String, Serializable> arguments) throws CatalogTransformerException { Map<String, Object> rootObject = convertToJSON(metacard); String jsonText = json.serialize(rootObject) .toString(); return new BinaryContentImpl(new ByteArrayInputStream(jsonText.getBytes(StandardCharsets.UTF_8)), DEFAULT_MIME_TYPE); } @Override public String toString() { return String.format("%s {Impl=%s, id=%s, MIME Type=%s}", MetacardTransformer.class.getName(), this.getClass() .getName(), ID, DEFAULT_MIME_TYPE); } @Nullable private static Object convertAttribute(Attribute attribute, AttributeDescriptor descriptor, List<AttributeType.AttributeFormat> dontInclude) throws CatalogTransformerException { if (dontInclude.contains(descriptor.getType() .getAttributeFormat())) { return null; } if (descriptor.isMultiValued()) { List<Object> values = new ArrayList<>(); for (Serializable value : attribute.getValues()) { values.add(convertValue(value, descriptor.getType() .getAttributeFormat())); } return values; } else { return convertValue(attribute.getValue(), descriptor.getType() .getAttributeFormat()); } } @Nullable private static Object convertValue(Serializable value, AttributeType.AttributeFormat format) throws CatalogTransformerException { if (value == null) { return null; } switch (format) { case DATE: // Creating date format instance each time is inefficient, however // it is not a threadsafe class so we are not able to put it in a static // class variable. If this proves to be a slowdown this class should be refactored // such that we don't need this method to be static. SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_8601_DATE_FORMAT); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); return dateFormat.format((Date) value); case BINARY: byte[] bytes = (byte[]) value; return DatatypeConverter.printBase64Binary(bytes); case BOOLEAN: case DOUBLE: case LONG: case INTEGER: case SHORT: return value; case STRING: case XML: case FLOAT: case GEOMETRY: return value.toString(); case OBJECT: default: return null; } } }