/** * Copyright (c) Codice Foundation * * 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. * * 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 org.codice.ddf.spatial.ogc.csw.catalog.converter; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Serializable; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.text.DateFormat; import java.text.ParseException; import java.util.Arrays; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import javax.activation.MimeType; import javax.xml.XMLConstants; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.namespace.QName; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.map.CaseInsensitiveMap; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.codice.ddf.spatial.ogc.catalog.common.converter.XmlNode; import org.codice.ddf.spatial.ogc.csw.catalog.common.BoundingBoxReader; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswRecordMetacardType; import org.joda.time.format.ISODateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.XStreamException; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.core.TreeMarshaller; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.naming.NoNameCoder; import com.thoughtworks.xstream.io.xml.CompactWriter; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.Xpp3Driver; import com.thoughtworks.xstream.io.xml.XppReader; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import ddf.catalog.data.Attribute; import ddf.catalog.data.AttributeDescriptor; import ddf.catalog.data.AttributeType.AttributeFormat; import ddf.catalog.data.BinaryContent; import ddf.catalog.data.Metacard; import ddf.catalog.data.impl.AttributeDescriptorImpl; import ddf.catalog.data.impl.AttributeImpl; import ddf.catalog.data.impl.BasicTypes; import ddf.catalog.data.impl.BinaryContentImpl; import ddf.catalog.data.impl.MetacardImpl; import ddf.catalog.transform.CatalogTransformerException; import ddf.catalog.transform.InputTransformer; import ddf.catalog.transform.MetacardTransformer; import net.opengis.cat.csw.v_2_0_2.ElementSetType; /** * Converts CSW Record to a Metacard. * * @author rodgersh */ public class CswRecordConverter implements Converter, MetacardTransformer, InputTransformer { private static final Logger LOGGER = LoggerFactory.getLogger(CswRecordConverter.class); private static final String UTF8_ENCODING = "UTF-8"; private static final DatatypeFactory XSD_FACTORY; private static final CswRecordMetacardType CSW_METACARD_TYPE = new CswRecordMetacardType(); /** * The map of metacard attributes that both the basic DDF MetacardTypeImpl and the CSW * MetacardType define as attributes. This is used to detect these element tags when * unmarshalling XML so that the tag name can be modified with a CSW-unique prefix before * attempting to lookup the attribute descriptor corresponding to the tag. */ private static final List<String> CSW_OVERLAPPING_ATTRIBUTE_NAMES = Arrays .asList(Metacard.TITLE, Metacard.CREATED, Metacard.MODIFIED); static { DatatypeFactory factory = null; try { factory = DatatypeFactory.newInstance(); } catch (DatatypeConfigurationException e) { LOGGER.error("Failed to create xsdFactory: {}", e.getMessage()); } XSD_FACTORY = factory; } protected XStreamAttributeCopier copier = new XStreamAttributeCopier(); protected NoNameCoder noNameCoder = new NoNameCoder(); private XStream xstream; public CswRecordConverter() { xstream = new XStream(new Xpp3Driver()); xstream.setClassLoader(this.getClass().getClassLoader()); xstream.registerConverter(this); xstream.alias(CswConstants.CSW_RECORD_LOCAL_NAME, Metacard.class); xstream.alias(CswConstants.CSW_RECORD, Metacard.class); } /** * Converts properties in CSW records that overlap with same name as a basic Metacard attribute, * e.g., title. This conversion method is needed mainly because CSW records express all dates as * strings, whereas MetacardImpl expresses them as java.util.Date types. * * @param attributeFormat * @param value * @return */ public static Serializable convertStringValueToMetacardValue(AttributeFormat attributeFormat, String value) { LOGGER.debug("converting csw record property {}", value); Serializable ser = null; if (attributeFormat == null) { LOGGER.debug("AttributeFormat was null when converting {}", value); return ser; } switch (attributeFormat) { case BOOLEAN: ser = Boolean.valueOf(value); break; case DOUBLE: ser = Double.valueOf(value); break; case FLOAT: ser = Float.valueOf(value); break; case INTEGER: ser = Integer.valueOf(value); break; case LONG: ser = Long.valueOf(value); break; case SHORT: ser = Short.valueOf(value); break; case XML: case STRING: ser = value; break; case DATE: ser = convertToDate(value); break; default: break; } return ser; } private static Date convertToDate(String value) { // Dates are strings and expected to be in ISO8601 format, YYYY-MM-DD'T'hh:mm:ss.sss, // per annotations in the CSW Record schema. At least the date portion must be present; // the time zone and time are optional. try { return ISODateTimeFormat.dateOptionalTimeParser().parseDateTime(value).toDate(); } catch (IllegalArgumentException e) { LOGGER.debug("Failed to convert to date {} from ISO Format: {}", value, e); } // failed to convert iso format, attempt to convert from xsd:date or xsd:datetime format // this format is used by the NSG interoperability CITE tests try { return XSD_FACTORY.newXMLGregorianCalendar(value).toGregorianCalendar().getTime(); } catch (IllegalArgumentException e) { LOGGER.debug("Unable to convert date {} from XSD format {} ", value, e); } // try from java date serialization for the default locale try { return DateFormat.getDateInstance().parse(value); } catch (ParseException e) { LOGGER.debug("Unable to convert date {} from default locale format {} ", value, e); } // default to current date LOGGER.warn("Unable to convert {} to a date object, defaulting to current time", value); return new Date(); } @Override public boolean canConvert(Class clazz) { return Metacard.class.isAssignableFrom(clazz); } @Override public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { if (source == null || !(source instanceof Metacard)) { LOGGER.warn("Failed to marshal Metacard: {}", source); return; } Map<String, Object> arguments = getArguments(context); writer.startNode((String) arguments.get(CswConstants.ROOT_NODE_NAME)); if ((Boolean) arguments.get(CswConstants.WRITE_NAMESPACES)) { writer.addAttribute("xmlns:" + CswConstants.CSW_NAMESPACE_PREFIX, CswConstants.CSW_OUTPUT_SCHEMA); writer.addAttribute("xmlns:" + CswConstants.DUBLIN_CORE_NAMESPACE_PREFIX, CswConstants.DUBLIN_CORE_SCHEMA); writer.addAttribute("xmlns:" + CswConstants.DUBLIN_CORE_TERMS_NAMESPACE_PREFIX, CswConstants.DUBLIN_CORE_TERMS_SCHEMA); writer.addAttribute("xmlns:" + CswConstants.OWS_NAMESPACE_PREFIX, CswConstants.OWS_NAMESPACE); } MetacardImpl metacard = new MetacardImpl((Metacard) source); List<QName> fieldsToWrite = (List<QName>) arguments.get(CswConstants.ELEMENT_NAMES); if (fieldsToWrite != null) { for (QName qName : fieldsToWrite) { if (qName != null && !qName.equals(CswRecordMetacardType.OWS_BOUNDING_BOX_QNAME)) { String attrName = DefaultCswRecordMap.getDefaultCswRecordMap() .getDefaultMetacardFieldFor(qName); AttributeDescriptor ad = metacard.getMetacardType() .getAttributeDescriptor(attrName); if (ad == null) { ad = new AttributeDescriptorImpl(attrName, false, false, false, false, BasicTypes.STRING_TYPE); } writeAttribute(writer, context, metacard, ad, qName); } } } else { // write all fields Set<AttributeDescriptor> attrDescs = metacard.getMetacardType() .getAttributeDescriptors(); for (AttributeDescriptor ad : attrDescs) { List<QName> qNames = DefaultCswRecordMap.getDefaultCswRecordMap() .getCswFieldsFor(ad.getName()); for (QName qName : qNames) { writeAttribute(writer, context, metacard, ad, qName); } } } if ((fieldsToWrite == null || fieldsToWrite .contains(CswRecordMetacardType.CSW_TEMPORAL_QNAME)) && metacard.getEffectiveDate() != null && metacard.getExpirationDate() != null) { StringBuilder sb = new StringBuilder(); sb.append(ISODateTimeFormat.dateTime() .print(((Date) metacard.getEffectiveDate()).getTime())).append(" to ") .append(ISODateTimeFormat.dateTime() .print(((Date) metacard.getExpirationDate()).getTime())); writeValue(writer, context, null, CswRecordMetacardType.CSW_TEMPORAL_QNAME, sb.toString()); } if ((fieldsToWrite == null || fieldsToWrite .contains(CswRecordMetacardType.CSW_SOURCE_QNAME)) && metacard.getSourceId() != null) { writeValue(writer, context, null, CswRecordMetacardType.CSW_PUBLISHER_QNAME, metacard.getSourceId()); } if (fieldsToWrite == null || fieldsToWrite .contains(CswRecordMetacardType.OWS_BOUNDING_BOX_QNAME)) { writeBoundingBox(writer, context, metacard); } writer.endNode(); } private Map<String, Object> getArguments(MarshallingContext context) { Map<String, Object> args = new HashMap<String, Object>(); Object writeNamespaceObj = context.get(CswConstants.WRITE_NAMESPACES); Boolean doWriteNamespaces = false; if (writeNamespaceObj instanceof Boolean) { doWriteNamespaces = (Boolean) writeNamespaceObj; args.put(CswConstants.WRITE_NAMESPACES, doWriteNamespaces); } else { args.put(CswConstants.WRITE_NAMESPACES, doWriteNamespaces); } Object elementSetObj = context.get(CswConstants.ELEMENT_SET_TYPE); Object elementNamesObj = context.get(CswConstants.ELEMENT_NAMES); String rootNodeName = CswConstants.CSW_RECORD; if (elementSetObj instanceof ElementSetType) { List<QName> elementsToWrite; ElementSetType elementSetType = (ElementSetType) elementSetObj; switch (elementSetType) { case BRIEF: elementsToWrite = CswRecordMetacardType.BRIEF_CSW_RECORD_FIELDS; rootNodeName = CswConstants.CSW_BRIEF_RECORD; break; case SUMMARY: elementsToWrite = CswRecordMetacardType.SUMMARY_CSW_RECORD_FIELDS; rootNodeName = CswConstants.CSW_SUMMARY_RECORD; break; case FULL: default: elementsToWrite = CswRecordMetacardType.FULL_CSW_RECORD_FIELDS; break; } args.put(CswConstants.ELEMENT_NAMES, elementsToWrite); args.put(CswConstants.ROOT_NODE_NAME, rootNodeName); } else if (elementNamesObj instanceof List<?>) { args.put(CswConstants.ELEMENT_NAMES, (List<?>) elementNamesObj); args.put(CswConstants.ROOT_NODE_NAME, rootNodeName); } else { args.put(CswConstants.ROOT_NODE_NAME, rootNodeName); args.put(CswConstants.ELEMENT_NAMES, CswRecordMetacardType.FULL_CSW_RECORD_FIELDS); } return args; } private void writeBoundingBox(HierarchicalStreamWriter writer, MarshallingContext context, Metacard metacard) { Set<AttributeDescriptor> attrDescs = metacard.getMetacardType().getAttributeDescriptors(); List<Geometry> geometries = new LinkedList<Geometry>(); for (AttributeDescriptor ad : attrDescs) { if (ad.getType() != null && AttributeFormat.GEOMETRY .equals(ad.getType().getAttributeFormat())) { Attribute attr = metacard.getAttribute(ad.getName()); if (attr != null) { if (ad.isMultiValued()) { for (Serializable value : attr.getValues()) { geometries.add(XmlNode.readGeometry((String) value)); } } else { geometries.add(XmlNode.readGeometry((String) attr.getValue())); } } } } Geometry allGeometry = new GeometryCollection( geometries.toArray(new Geometry[geometries.size()]), new GeometryFactory()); Envelope bounds = allGeometry.getEnvelopeInternal(); if (!bounds.isNull()) { String bbox = CswConstants.OWS_NAMESPACE_PREFIX + CswConstants.NAMESPACE_DELIMITER + CswRecordMetacardType.OWS_BOUNDING_BOX; String lower = CswConstants.OWS_NAMESPACE_PREFIX + CswConstants.NAMESPACE_DELIMITER + CswConstants.OWS_LOWER_CORNER; String upper = CswConstants.OWS_NAMESPACE_PREFIX + CswConstants.NAMESPACE_DELIMITER + CswConstants.OWS_UPPER_CORNER; writer.startNode(bbox); writer.addAttribute(CswConstants.CRS, CswConstants.SRS_URL); writer.startNode(lower); writer.setValue(bounds.getMinX() + " " + bounds.getMinY()); writer.endNode(); writer.startNode(upper); writer.setValue(bounds.getMaxX() + " " + bounds.getMaxY()); writer.endNode(); writer.endNode(); } } private void writeAttribute(HierarchicalStreamWriter writer, MarshallingContext context, Metacard metacard, AttributeDescriptor attributeDescriptor, QName field) { if (attributeDescriptor != null) { Attribute attr = metacard.getAttribute(attributeDescriptor.getName()); if (attr != null) { if (attributeDescriptor.isMultiValued()) { for (Serializable value : attr.getValues()) { writeValue(writer, context, attributeDescriptor, field, value); } } else { writeValue(writer, context, attributeDescriptor, field, attr.getValue()); } } else if (CswRecordMetacardType.REQUIRED_FIELDS.contains(field)) { writeValue(writer, context, attributeDescriptor, field, ""); } } } private void writeValue(HierarchicalStreamWriter writer, MarshallingContext context, AttributeDescriptor attributeDescriptor, QName field, Serializable value) { String xmlValue = null; AttributeFormat attrFormat = null; if (attributeDescriptor != null && attributeDescriptor.getType() != null) { attrFormat = attributeDescriptor.getType().getAttributeFormat(); } if (attrFormat == null) { attrFormat = AttributeFormat.STRING; } String name = null; if (!StringUtils.isBlank(field.getNamespaceURI())) { if (!StringUtils.isBlank(field.getPrefix())) { name = field.getPrefix() + CswConstants.NAMESPACE_DELIMITER + field.getLocalPart(); } else { name = field.getLocalPart(); } } else { name = field.getLocalPart(); } switch (attrFormat) { case BINARY: xmlValue = Base64.encodeBase64String((byte[]) value); break; case DATE: GregorianCalendar cal = new GregorianCalendar(); cal.setTime((Date) value); xmlValue = XSD_FACTORY.newXMLGregorianCalendar(cal).toXMLFormat(); break; case OBJECT: break; case GEOMETRY: case XML: default: xmlValue = value.toString(); break; } // Write the node if we were able to convert it. if (xmlValue != null) { writer.startNode(name); if (!StringUtils.isBlank(field.getNamespaceURI())) { if (!StringUtils.isBlank(field.getPrefix())) { writeNamespace(writer, field); } else { writer.addAttribute(XMLConstants.XMLNS_ATTRIBUTE, field.getNamespaceURI()); } } writer.setValue(xmlValue); writer.endNode(); } } // TODO - do we really need this?? private void writeNamespace(HierarchicalStreamWriter writer, QName field) { // if (prefixToUriMapping == null // || !prefixToUriMapping.containsKey(field.getPrefix()) // || !prefixToUriMapping.get(field.getPrefix()).equals( // field.getNamespaceURI())) { // writer.addAttribute(XMLConstants.XMLNS_ATTRIBUTE // + CswConstants.NAMESPACE_DELIMITER + field.getPrefix(), // field.getNamespaceURI()); // } } @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Map<String, String> cswAttrMap = new CaseInsensitiveMap( DefaultCswRecordMap.getDefaultCswRecordMap().getCswToMetacardAttributeNames()); Object mappingObj = context.get(CswConstants.CSW_MAPPING); if (mappingObj instanceof Map<?, ?>) { // If we got mappings passed in, remove the existing mappings for that attribute Map<String, String> customMappings = new CaseInsensitiveMap( (Map<String, String>) mappingObj); Map<String, String> convertedMappings = new CaseInsensitiveMap(); for (Entry<String, String> customMapEntry : customMappings.entrySet()) { Iterator<Entry<String, String>> existingMapIter = cswAttrMap.entrySet().iterator(); while (existingMapIter.hasNext()) { Entry<String, String> existingMapEntry = existingMapIter.next(); if (existingMapEntry.getValue().equalsIgnoreCase(customMapEntry.getValue())) { existingMapIter.remove(); } } String key = convertToCswField(customMapEntry.getKey()); String value = customMapEntry.getValue(); LOGGER.debug("Adding key: {} & value: {}", key, value); convertedMappings.put(key, value); } cswAttrMap.putAll(convertedMappings); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Map contents: {}", Arrays.toString(cswAttrMap.entrySet().toArray())); } } String resourceUriMapping = (isString(context.get(Metacard.RESOURCE_URI))) ? (String) context.get(Metacard.RESOURCE_URI) : null; String thumbnailMapping = (isString(context.get(Metacard.THUMBNAIL))) ? (String) context.get(Metacard.THUMBNAIL) : null; boolean isLonLatOrder = false; Object lonLatObj = context.get(CswConstants.IS_LON_LAT_ORDER_PROPERTY); if (lonLatObj instanceof Boolean) { isLonLatOrder = (Boolean) lonLatObj; } Map<String, String> namespaceMap = null; Object namespaceObj = context.get(CswConstants.WRITE_NAMESPACES); if (namespaceObj instanceof Map<?, ?>) { namespaceMap = (Map<String, String>) namespaceObj; } Metacard metacard = createMetacardFromCswRecord(reader, cswAttrMap, resourceUriMapping, thumbnailMapping, isLonLatOrder, namespaceMap); Object sourceIdObj = context.get(Metacard.SOURCE_ID); if (sourceIdObj instanceof String) { metacard.setSourceId((String) sourceIdObj); } return metacard; } private boolean isString(Object object) { return object instanceof String; } protected HierarchicalStreamReader copyXml(HierarchicalStreamReader hreader, StringWriter writer, Map<String, String> attributeMap) { copier.copyAttributes(hreader, new CompactWriter(writer, noNameCoder), attributeMap); XmlPullParser parser = null; try { parser = XmlPullParserFactory.newInstance().newPullParser(); } catch (XmlPullParserException e) { throw new ConversionException("Unable to create XmlPullParser, cannot parse XML.", e); } try { // NOTE: must specify encoding here, otherwise the platform default // encoding will be used which will not always work return new XppReader( new InputStreamReader(IOUtils.toInputStream(writer.toString(), UTF8_ENCODING)), parser); } catch (IOException e) { LOGGER.warn("Unable create reader with UTF-8 encoding, Exception {}", e); return new XppReader(new InputStreamReader(IOUtils.toInputStream(writer.toString())), parser); } } protected MetacardImpl createMetacardFromCswRecord(HierarchicalStreamReader hreader, Map<String, String> cswToMetacardAttributeNames, String resourceUriMapping, String thumbnailMapping, boolean isLatLonOrder, Map<String, String> namespaceMap) { StringWriter metadataWriter = new StringWriter(); HierarchicalStreamReader reader = copyXml(hreader, metadataWriter, namespaceMap); MetacardImpl mc = new MetacardImpl(CSW_METACARD_TYPE); Map<String, Attribute> attributes = new TreeMap<>(); while (reader.hasMoreChildren()) { reader.moveDown(); String nodeName = reader.getNodeName(); LOGGER.debug("node name: {}.", nodeName); // Remove the prefix if it exists String name = nodeName; if (StringUtils.contains(nodeName, CswConstants.NAMESPACE_DELIMITER)) { name = StringUtils.split(nodeName, CswConstants.NAMESPACE_DELIMITER)[1]; } // Some attribute names overlap with basic Metacard attribute names, // e.g., "title". // So if this is one of those attribute names, get the CSW // attribute for the name to be looked up. name = convertToCswField(name); LOGGER.debug("Processing node {}", name); AttributeDescriptor attributeDescriptor = CSW_METACARD_TYPE .getAttributeDescriptor(name); Serializable value = null; // If XML node name matched an attribute descriptor in the // metacardType AND // the XML node has a non-blank value OR this is geometry/spatial // data, // then convert the CSW Record's property value for this XML node to // the // corresponding metacard attribute's value if (attributeDescriptor != null && (StringUtils.isNotBlank(reader.getValue()) || BasicTypes.GEO_TYPE.equals(attributeDescriptor.getType()))) { value = convertRecordPropertyToMetacardAttribute( attributeDescriptor.getType().getAttributeFormat(), reader, isLatLonOrder); } if (null != value) { if (attributeDescriptor.isMultiValued()) { if (attributes.containsKey(name)) { AttributeImpl attribute = (AttributeImpl) attributes.get(name); attribute.addValue(value); } else { attributes.put(name, new AttributeImpl(name, value)); } } else { attributes.put(name, new AttributeImpl(name, value)); } if (BasicTypes.GEO_TYPE.getAttributeFormat() .equals(attributeDescriptor.getType().getAttributeFormat())) { mc.setLocation((String) value); } } reader.moveUp(); } for (String attrName : attributes.keySet()) { Attribute attr = attributes.get(attrName); mc.setAttribute(attr); // If this CSW attribute also maps to a basic metacard attribute, // (e.g., title, modified date, etc.) // then populate the basic metacard attribute with this attribute's // value. if (cswToMetacardAttributeNames.containsKey(attrName)) { String metacardAttrName = cswToMetacardAttributeNames.get(attrName); if (mc.getAttribute(metacardAttrName) == null) { AttributeFormat cswAttributeFormat = CSW_METACARD_TYPE .getAttributeDescriptor(attrName).getType().getAttributeFormat(); AttributeDescriptor metacardAttributeDescriptor = CSW_METACARD_TYPE .getAttributeDescriptor(metacardAttrName); AttributeFormat metacardAttrFormat = metacardAttributeDescriptor.getType() .getAttributeFormat(); LOGGER.debug("Setting overlapping Metacard attribute [{}] to value in " + "CSW attribute [{}] that has value [{}] and format {}", metacardAttrName, attrName, attr.getValue(), metacardAttrFormat); if (cswAttributeFormat.equals(metacardAttrFormat)) { mc.setAttribute(metacardAttrName, attr.getValue()); } else { Serializable value = convertStringValueToMetacardValue(metacardAttrFormat, attr.getValue().toString()); mc.setAttribute(metacardAttrName, value); } } } } // Save entire CSW Record XML as the metacard's metadata string mc.setMetadata(metadataWriter.toString()); // Set Metacard ID to the CSW Record's identifier // TODO: may need to sterilize the CSW Record identifier if it has // special chars that clash // with usage in a URL - empirical testing with various CSW sites will // determine this. mc.setId((String) mc.getAttribute(CswRecordMetacardType.CSW_IDENTIFIER).getValue()); try { URI namespaceUri = new URI(CSW_METACARD_TYPE.getNamespaceURI()); mc.setTargetNamespace(namespaceUri); } catch (URISyntaxException e) { LOGGER.info("Error setting target namespace uri on metacard, Exception {}", e); } Date genericDate = new Date(); if (mc.getEffectiveDate() == null) { mc.setEffectiveDate(genericDate); } if (mc.getCreatedDate() == null) { mc.setCreatedDate(genericDate); } if (mc.getModifiedDate() == null) { LOGGER.debug("modified date was null, setting to current date"); mc.setModifiedDate(genericDate); } // Determine the csw field mapped to the resource uri and set that value // on the Metacard.RESOURCE_URI attribute // Default is for <source> field to define URI for product to be downloaded Attribute resourceUriAttr = mc.getAttribute(resourceUriMapping); if (resourceUriAttr != null && resourceUriAttr.getValue() != null) { String source = (String) resourceUriAttr.getValue(); try { mc.setResourceURI(new URI(source)); } catch (URISyntaxException e) { LOGGER.info("Error setting resource URI on metacard: {}, Exception {}", source, e); } } // determine the csw field mapped to the thumbnail and set that value on // the Metacard.THUMBNAIL // attribute Attribute thumbnailAttr = mc.getAttribute(thumbnailMapping); if (thumbnailAttr != null && thumbnailAttr.getValue() != null) { String thumbnail = (String) thumbnailAttr.getValue(); URL url; InputStream is = null; try { url = new URL(thumbnail); is = url.openStream(); mc.setThumbnail(IOUtils.toByteArray(url.openStream())); } catch (MalformedURLException e) { LOGGER.info("Error setting thumbnail data on metacard: {}, Exception {}", thumbnail, e); } catch (IOException e) { LOGGER.info("Error setting thumbnail data on metacard: {}, Exception {}", thumbnail, e); } finally { IOUtils.closeQuietly(is); } } return mc; } private String convertToCswField(String name) { if (CSW_OVERLAPPING_ATTRIBUTE_NAMES.contains(name)) { return CswRecordMetacardType.CSW_ATTRIBUTE_PREFIX + name; } return name; } /** * Converts the CSW record property to the specified Metacard attribute format. * * @param attributeFormat * @param reader * @return */ protected Serializable convertRecordPropertyToMetacardAttribute(AttributeFormat attributeFormat, HierarchicalStreamReader reader, boolean isLonLatOrder) { LOGGER.debug("converting csw record property {}", reader.getValue()); Serializable ser = null; switch (attributeFormat) { case BOOLEAN: ser = Boolean.valueOf(reader.getValue()); break; case DOUBLE: ser = Double.valueOf(reader.getValue()); break; case FLOAT: ser = Float.valueOf(reader.getValue()); break; case INTEGER: ser = Integer.valueOf(reader.getValue()); break; case LONG: ser = Long.valueOf(reader.getValue()); break; case SHORT: ser = Short.valueOf(reader.getValue()); break; case XML: case STRING: ser = reader.getValue(); break; case DATE: ser = convertStringValueToMetacardValue(attributeFormat, reader.getValue()); break; case GEOMETRY: // We pass in isLonLatOrder, so we can determine coord order (LAT/LON vs // LON/LAT). ser = new BoundingBoxReader(reader, isLonLatOrder).getWkt(); LOGGER.debug("WKT = {}", (String) ser); break; case BINARY: try { ser = reader.getValue().getBytes(UTF8_ENCODING); } catch (UnsupportedEncodingException e) { LOGGER.warn("Error encoding the binary value into the metacard.", e); } break; default: break; } return ser; } @Override public Metacard transform(InputStream inputStream) throws IOException, CatalogTransformerException { return transform(inputStream, null); } @Override public Metacard transform(InputStream inputStream, String id) throws IOException, CatalogTransformerException { Metacard metacard = null; try { metacard = (Metacard) xstream.fromXML(inputStream); if (StringUtils.isNotEmpty(id)) { metacard.setAttribute(new AttributeImpl(Metacard.ID, id)); } } catch (XStreamException e) { throw new CatalogTransformerException( "Unable to transform from CSW Record to Metacard.", e); } finally { IOUtils.closeQuietly(inputStream); } if (metacard == null) { throw new CatalogTransformerException( "Unable to transform from CSW Record to Metacard."); } return metacard; } @Override public BinaryContent transform(Metacard metacard, Map<String, Serializable> arguments) throws CatalogTransformerException { StringWriter stringWriter = new StringWriter(); Boolean omitXmlDec = (Boolean) arguments.get(CswConstants.OMIT_XML_DECLARATION); if (omitXmlDec == null || !omitXmlDec) { stringWriter.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"); } PrettyPrintWriter writer = new PrettyPrintWriter(stringWriter); MarshallingContext context = new TreeMarshaller(writer, null, null); context.put(CswConstants.WRITE_NAMESPACES, true); copyArgumentsToContext(context, arguments); this.marshal(metacard, writer, context); BinaryContent transformedContent = null; ByteArrayInputStream bais = new ByteArrayInputStream(stringWriter.toString().getBytes()); transformedContent = new BinaryContentImpl(bais, new MimeType()); return transformedContent; } private void copyArgumentsToContext(MarshallingContext context, Map<String, Serializable> arguments) { if (context == null || arguments == null) { return; } for (String key : arguments.keySet()) { context.put(key, arguments.get(key)); } } }