org.codice.alliance.imaging.chip.actionprovider.ImagingChipActionProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.alliance.imaging.chip.actionprovider.ImagingChipActionProvider.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 org.codice.alliance.imaging.chip.actionprovider;

import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import ddf.action.Action;
import ddf.action.MultiActionProvider;
import ddf.action.impl.ActionImpl;
import ddf.catalog.content.data.ContentItem;
import ddf.catalog.data.Attribute;
import ddf.catalog.data.Metacard;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.codice.ddf.configuration.SystemBaseUrl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ImagingChipActionProvider implements MultiActionProvider {

    static final String TITLE = "Chip Image";

    static final String DESCRIPTION = "Opens a new window to enter the boundaries of an image chip for a Metacard.";

    private static final String PATH = "/chipping/chipping.html";

    static final String ID = "catalog.data.metacard.image.chipping";

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

    static final String NITF_IMAGE_METACARD_TYPE = "isr.image";

    private static final String ORIGINAL_QUALIFIER = "original";

    private static final String QUALIFIER_KEY = "qualifier";

    private static final Pattern ALLIANCE_DOWNLOAD_RESOURCE_PATH_PATTERN = Pattern
            .compile("/.*/catalog/sources/.*/.*");

    private final GeometryFactory geometryFactory = new GeometryFactory();

    @Override
    public <T> List<Action> getActions(T subject) {
        // canHandle has already been checked at this point, so no need to verify subject
        final Metacard metacard = (Metacard) subject;

        return getChippingUrl(metacard)
                .map(url -> Collections.singletonList((Action) new ActionImpl(getId(), TITLE, DESCRIPTION, url)))
                .orElseGet(Collections::emptyList);
    }

    private static Optional<URL> getChippingUrl(Metacard metacard) {
        // canHandle has already been checked at this point, so no need to verify isPresent
        final URI derivedResourceUri = getOriginalDerivedResourceUri(metacard).get();
        if (canBeChippedLocally(derivedResourceUri)) {
            final String defaultChippingUrlString = String.format("%s%s?id=%s&source=%s",
                    SystemBaseUrl.EXTERNAL.getBaseUrl(), PATH, metacard.getId(), metacard.getSourceId());
            try {
                return Optional.of(new URL(defaultChippingUrlString));
            } catch (MalformedURLException e) {
                // This should never happen.
            }
        } else {
            // If the resource.derived-uri attribute value matches the usual Alliance download URL format
            // ("[protocol]://[host]:[port]/[services name]/catalog/sources/[source id]/[metacard
            // id]?transform=resource&qualifier=[original or overview]"), assume that there is a
            // chipping URL that can be constructed from the scheme, host, port, source, and id of
            // the value. This allows the {@value TITLE} Action to link directly to the remote
            // system if the derived resource is from another Alliance instance.
            try {
                final URL derivedResourceUrl = derivedResourceUri.toURL();

                final String host = derivedResourceUrl.getHost(); // {@code null} if the host is undefined
                final int port = derivedResourceUrl.getPort(); // -1 if the port is not set
                final String path = derivedResourceUrl.getPath(); // an empty string if one does not exist
                final String query = derivedResourceUrl.getQuery(); // <CODE>null</CODE> if one does not exist
                final String expectedQuery = String.format("transform=resource&qualifier=%s", ORIGINAL_QUALIFIER);
                if (!StringUtils.isEmpty(host) && port != -1
                        && ALLIANCE_DOWNLOAD_RESOURCE_PATH_PATTERN.matcher(path).matches()
                        && StringUtils.equals(query, expectedQuery)) {
                    final String[] paths = path.split("/");
                    final String source = paths[4];
                    final String id = paths[5];

                    final String chippingPathString = String.format("%s?id=%s&source=%s", PATH, id, source);
                    try {
                        return Optional
                                .of(new URL(derivedResourceUrl.getProtocol(), host, port, chippingPathString));
                    } catch (MalformedURLException e) {
                        // This should probably never happen because the parts used to construct the URL have
                        // been validated.
                    }
                } else {
                    // Unable to construct a remote chipping URL because the original resource.derived-uri
                    // does not match the known Alliance format.
                }
            } catch (MalformedURLException e) {
                // Unable to cast derivedResourceUri to a URL, which means that the resource still may be
                // able to be chipped locally but is not yet supported by the canBeChippedLocally method.
            }
        }

        LOGGER.debug(
                "Unable to construct a chipping URL for NITF image metacard id={}, source id={}, resource-uri={}. Not displaying the Chip Image Action.",
                metacard.getId(), metacard.getResourceURI(), metacard.getSourceId());
        return Optional.empty();
    }

    @Override
    public String getId() {
        return ID;
    }

    @Override
    public <T> boolean canHandle(T subject) {
        if (subject instanceof Metacard) {
            final Metacard metacard = (Metacard) subject;

            final boolean isImageNitf = NITF_IMAGE_METACARD_TYPE.equals(metacard.getMetacardType().getName());
            final boolean hasLocation = hasValidLocation(metacard.getLocation());
            // The chipping transformer requires the NITF resource, the overview derived image
            // resource, and the original derived image resource.
            final boolean hasNitfResource = metacard.getResourceURI() != null;
            boolean hasOriginalDerivedImageResource = getOriginalDerivedResourceUri(metacard).isPresent();
            // assume that if there is an original, there is also an overview
            boolean hasOverviewDerivedImageResource = hasOriginalDerivedImageResource;

            return isImageNitf && hasLocation && hasNitfResource && hasOverviewDerivedImageResource
                    && hasOriginalDerivedImageResource;
        } else {
            return false;
        }
    }

    private boolean hasValidLocation(String location) {
        if (StringUtils.isNotBlank(location)) {
            try {
                // parse the WKT location to determine if it has valid format
                final WKTReader wktReader = new WKTReader(geometryFactory);
                wktReader.read(location);
                return true;
            } catch (ParseException e) {
                LOGGER.debug("Location [{}] is invalid. Cannot chip this image", location);
            }
        }

        return false;
    }

    private static Optional<URI> getOriginalDerivedResourceUri(final Metacard metacard) {
        final Attribute derivedResourceUriAttribute = metacard.getAttribute(Metacard.DERIVED_RESOURCE_URI);
        if (derivedResourceUriAttribute == null) {
            return Optional.empty();
        }
        List<String> derivedResourceUriStrings = derivedResourceUriAttribute.getValues().stream()
                .filter(String.class::isInstance).map(String.class::cast).collect(Collectors.toList());

        for (String resourceUri : derivedResourceUriStrings) {
            try {
                final URI derivedResourceUri = new URI(resourceUri);

                if (canBeChippedLocally(derivedResourceUri)
                        && StringUtils.equals(ORIGINAL_QUALIFIER, derivedResourceUri.getFragment())) {
                    return Optional.of(derivedResourceUri);
                }
                for (NameValuePair parameter : URLEncodedUtils.parse(derivedResourceUri,
                        StandardCharsets.UTF_8.name())) {
                    if (QUALIFIER_KEY.equals(parameter.getName())
                            && StringUtils.equals(ORIGINAL_QUALIFIER, parameter.getValue())) {
                        return Optional.of(derivedResourceUri);
                    }
                }
            } catch (URISyntaxException e) {
                // This is not an unexpected exception, there is not enough info to construct a chipping URL
            }
        }
        return Optional.empty();
    }

    /**
     * Assume that the "content" scheme in the resource.derived-uri indicates that the resource can be
     * chipped locally.
     */
    private static boolean canBeChippedLocally(URI derivedResourceUri) {
        return StringUtils.equals(ContentItem.CONTENT_SCHEME, derivedResourceUri.getScheme());
    }
}