ddf.catalog.registry.transformer.RegistryServiceConverter.java Source code

Java tutorial

Introduction

Here is the source code for ddf.catalog.registry.transformer.RegistryServiceConverter.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.registry.transformer;

import static java.util.stream.Collectors.toList;
import static org.joda.time.format.ISODateTimeFormat.dateOptionalTimeParser;

import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

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.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.copy.HierarchicalStreamCopier;
import com.thoughtworks.xstream.io.xml.CompactWriter;

import ddf.catalog.data.Metacard;
import ddf.catalog.data.impl.MetacardImpl;
import ddf.catalog.registry.common.metacard.RegistryMetacardImpl;
import ddf.catalog.registry.common.metacard.RegistryObjectMetacardType;
import ddf.catalog.registry.common.metacard.RegistryServiceMetacardType;

public class RegistryServiceConverter implements Converter {

    private final Gml3ToWkt gml3ToWkt;

    private static final RegistryServiceMetacardType RSMT = new RegistryServiceMetacardType();

    private static final Map<String, String> CLASSIFICATIONS;

    private static final String ID_PATTERN = "urn:uuid:(.*)";

    private static final Pattern PATTERN = Pattern.compile(ID_PATTERN);

    static {
        CLASSIFICATIONS = new HashMap<>();
        CLASSIFICATIONS.put("liveDate", RegistryServiceMetacardType.LIVE_DATE);
        CLASSIFICATIONS.put("dataStartDate", RegistryServiceMetacardType.DATA_START_DATE);
        CLASSIFICATIONS.put("dataEndDate", RegistryServiceMetacardType.DATA_END_DATE);
        CLASSIFICATIONS.put("links", RegistryServiceMetacardType.LINKS);
        CLASSIFICATIONS.put("region", RegistryServiceMetacardType.REGION);
        CLASSIFICATIONS.put("dataRegion", RegistryServiceMetacardType.DATA_REGION);
        CLASSIFICATIONS.put("inputDataSources", RegistryServiceMetacardType.DATA_SOURCES);
        CLASSIFICATIONS.put("dataTypes", RegistryServiceMetacardType.DATA_TYPES);
        CLASSIFICATIONS.put("securityLevel", RegistryServiceMetacardType.SECURITY_LEVEL);
    }

    public RegistryServiceConverter(Gml3ToWkt gml3ToWkt) {
        this.gml3ToWkt = gml3ToWkt;
    }

    @Override
    public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter,
            MarshallingContext marshallingContext) {
    }

    /**
     * Read and format address info from a rim:Address node.
     *
     * @param reader
     * @return Formatted address
     */
    private String readAddress(HierarchicalStreamReader reader) {
        String street = reader.getAttribute("street");
        String city = reader.getAttribute("city");
        String stateOrProvince = reader.getAttribute("stateOrProvince");
        String postalCode = reader.getAttribute("postalCode");
        String country = reader.getAttribute("country");

        return String.format("%s, %s, %s %s, %s", street, city, stateOrProvince, postalCode, country);
    }

    /**
     * Read and format phone info from a rim:TelephoneNumber node.
     *
     * @param reader
     * @return Formatted phone number
     */
    private String readPhoneNumber(HierarchicalStreamReader reader) {
        String areaCode = reader.getAttribute("areaCode");
        String number = reader.getAttribute("number");
        String extension = reader.getAttribute("extension");

        if (StringUtils.isEmpty(extension)) {
            return String.format("(%s) %s", areaCode, number);
        } else {
            return String.format("(%s) %s extension %s", areaCode, number, extension);
        }
    }

    private void safeMove(HierarchicalStreamReader reader, Integer amount, Runnable callback) {
        for (int i = 0; i < amount; i++) {
            reader.moveDown();
        }
        try {
            callback.run();
        } finally {
            for (int i = 0; i < amount; i++) {
                reader.moveUp();
            }
        }
    }

    private void readOrgInfo(HierarchicalStreamReader reader, RegistryMetacardImpl meta) {
        safeMove(reader, 3, () -> {
            while (reader.hasMoreChildren()) {
                safeMove(reader, 1, () -> {
                    switch (reader.getNodeName()) {
                    case "rim:Name":
                        meta.setAttribute(RegistryObjectMetacardType.ORGANIZATION_NAME, readValue(reader));
                        break;
                    case "rim:EmailAddress":
                        meta.setAttribute(RegistryObjectMetacardType.ORGANIZATION_EMAIL,
                                reader.getAttribute("address"));
                        break;
                    case "rim:TelephoneNumber":
                        meta.setAttribute(RegistryObjectMetacardType.ORGANIZATION_PHONE_NUMBER,
                                readPhoneNumber(reader));
                        break;
                    case "rim:Address":
                        meta.setAttribute(RegistryObjectMetacardType.ORGANIZATION_ADDRESS, readAddress(reader));
                        break;
                    }
                });
            }
        });
    }

    String readValue(HierarchicalStreamReader reader) {
        reader.moveDown();
        String value = reader.getAttribute("value");
        reader.moveUp();
        return value;
    }

    /**
     * Read gml and return a wkt.
     *
     * @param reader
     * @return
     */
    private void readGml(HierarchicalStreamReader reader, MetacardImpl meta, String attributeKey) {

        safeMove(reader, 3, () -> {

            // buffer up the current gml node and all its children
            Writer out = new StringWriter();
            HierarchicalStreamWriter writer = new CompactWriter(out);
            HierarchicalStreamCopier copier = new HierarchicalStreamCopier();
            copier.copy(reader, writer);
            String xml = out.toString();

            xml = xml.replaceFirst(">", " xmlns:gml=\"http://www.opengis.net/gml\">");

            meta.setAttribute(attributeKey, gml3ToWkt.convert(xml));
        });
    }

    /**
     * Read classification info from a rim:Classification node and attach to metacard.
     *
     * @param reader
     * @param meta
     */
    private void readClassification(HierarchicalStreamReader reader, MetacardImpl meta) {
        while (reader.hasMoreChildren()) {
            reader.moveDown();

            switch (reader.getNodeName()) {

            case "rim:Slot":
                String name = reader.getAttribute("name");
                switch (name) {
                case "location":
                    readGml(reader, meta, Metacard.GEOGRAPHY);
                    break;
                default:
                    readClassificationValue(reader, meta, name);
                }
                break;

            }

            reader.moveUp();
        }
    }

    private void readClassificationValue(HierarchicalStreamReader reader, MetacardImpl meta, String name) {
        String key = CLASSIFICATIONS.get(name);
        if (key != null) {
            if ("xs:dateTime".equals(reader.getAttribute("slotType"))) {
                meta.setAttribute(key, readDates(reader).get(0));
            } else {
                meta.setAttribute(key, String.join(", ", readValueList(reader)));
            }
        }
    }

    private List<String> readValueList(HierarchicalStreamReader reader) {

        reader.moveDown();

        List<String> l = new ArrayList<>();
        while (reader.hasMoreChildren()) {
            reader.moveDown();

            switch (reader.getNodeName()) {
            case "rim:Value":
                l.add(reader.getValue());
                break;
            }

            reader.moveUp();
        }

        reader.moveUp();

        return l;
    }

    /**
     * Read name, telephone number, and email from point of contact to attach to metacard.
     *
     * @param reader
     * @param meta
     */
    private void readPointOfContact(HierarchicalStreamReader reader, MetacardImpl meta) {
        reader.moveDown();
        reader.moveDown();
        reader.moveDown();

        Map<String, String> map = new HashMap<>();

        map.put("name", "no name");
        map.put("telephone", "no telephone number");
        map.put("email", "no email address");

        while (reader.hasMoreChildren()) {
            reader.moveDown();

            switch (reader.getNodeName()) {
            case "rim:PersonName":
                map.put("name", reader.getAttribute("firstName") + " " + reader.getAttribute("lastName"));
                break;
            case "rim:TelephoneNumber":
                map.put("telephone", readPhoneNumber(reader));
                break;
            case "rim:EmailAddress":
                map.put("email", reader.getAttribute("address"));
                break;
            }

            reader.moveUp();
        }

        String contact = String.format("%s, %s, %s", map.get("name"), map.get("telephone"), map.get("email"));

        meta.setAttribute(Metacard.POINT_OF_CONTACT, contact);

        reader.moveUp();
        reader.moveUp();
        reader.moveUp();
    }

    /**
     * Quickly validate that a service binding has:
     * - a bindingType slot
     *
     * @param reader
     * @throws ConversionException
     */
    private String validateServiceBinding(HierarchicalStreamReader reader) throws ConversionException {

        String id = reader.getAttribute("id");
        String bindingType = null;

        int bindingTypes = 0;
        while (reader.hasMoreChildren()) {
            reader.moveDown();

            if ("bindingType".equals(reader.getAttribute("name"))) {

                List<String> types = readValueList(reader);

                if (types.size() != 1) {
                    throw new ConversionException(
                            "ServiceBinding with id = " + id + " has invalid bindingType value.");
                }

                bindingType = types.get(0);
                bindingTypes += 1;
            }

            reader.moveUp();

            if (bindingTypes > 1) {
                throw new ConversionException("ServiceBinding with id = " + id + " has too many bindingType.");
            }
        }

        if (bindingTypes == 0) {
            throw new ConversionException("ServiceBinding with id = " + id + " has no bindingType.");
        }

        return bindingType;
    }

    /**
     * Validate that a service has a correct object type and id.
     * <p>
     * NOTE: if the id matches the ID_PATTERN, then it pulls out the uuid.
     *
     * @param reader
     * @return id value
     * @throws ConversionException
     */
    private String validateService(HierarchicalStreamReader reader) throws ConversionException {

        String id = reader.getAttribute("id");

        if (StringUtils.isEmpty(id)) {
            throw new ConversionException("Service missing id.");
        }

        Matcher matcher = PATTERN.matcher(id);

        if (matcher.find()) {
            id = matcher.group(1);
        }

        String type = reader.getAttribute("objectType");

        if (!"urn:service:catalog".equals(type)) {
            throw new ConversionException("Service with id = " + id + " bad objectType = " + type);
        }

        return id;
    }

    private static Date parseISO8601(String str) {
        return dateOptionalTimeParser().parseDateTime(str).toDate();
    }

    private List<Date> readDates(HierarchicalStreamReader reader) {
        return readValueList(reader).stream().map(RegistryServiceConverter::parseISO8601).collect(toList());
    }

    private void readTopLevelSlots(HierarchicalStreamReader reader, RegistryMetacardImpl meta) {
        switch (reader.getAttribute("name")) {
        case "organization":
            readOrgInfo(reader, meta);
            break;
        case "pointOfContact":
            readPointOfContact(reader, meta);
            break;
        case "lastUpdated":
            List<Date> ds = readDates(reader);
            meta.setModifiedDate(ds.get(0));
            break;
        }

    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context)
            throws ConversionException {

        RegistryMetacardImpl meta = new RegistryMetacardImpl(RSMT);

        List<String> bindingTypes = new ArrayList<>();
        String id = validateService(reader);

        meta.setAttribute(Metacard.ID, id);

        while (reader.hasMoreChildren()) {
            reader.moveDown();

            switch (reader.getNodeName()) {
            case "rim:Slot":
                readTopLevelSlots(reader, meta);
                break;
            case "rim:Description":
                meta.setAttribute(Metacard.DESCRIPTION, readValue(reader));
                break;
            case "rim:Name":
                meta.setAttribute(Metacard.TITLE, readValue(reader));
                break;
            case "rim:VersionInfo":
                meta.setAttribute(Metacard.CONTENT_TYPE_VERSION, reader.getAttribute("versionName"));
                break;
            case "rim:Classification":
                readClassification(reader, meta);
                break;
            case "rim:ServiceBinding":
                bindingTypes.add(validateServiceBinding(reader));
                break;
            }

            reader.moveUp();
        }

        meta.setAttribute(RegistryServiceMetacardType.SERVICE_BINDING_TYPES, String.join(", ", bindingTypes));

        return meta;
    }

    @Override
    public boolean canConvert(Class clazz) {
        return Metacard.class.isAssignableFrom(clazz);
    }
}