ddf.catalog.source.opensearch.OpenSearchSiteUtil.java Source code

Java tutorial

Introduction

Here is the source code for ddf.catalog.source.opensearch.OpenSearchSiteUtil.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.source.opensearch;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.Principal;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.cxf.jaxrs.client.WebClient;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.sort.SortBy;
import org.opengis.filter.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ddf.catalog.data.Result;
import ddf.catalog.impl.filter.SpatialDistanceFilter;
import ddf.catalog.impl.filter.SpatialFilter;
import ddf.catalog.impl.filter.TemporalFilter;
import ddf.catalog.operation.Query;
import ddf.catalog.source.UnsupportedQueryException;
import ddf.security.Subject;
import ddf.security.assertion.SecurityAssertion;

/**
 * Utility helper class that performs much of the translation logic used in CddaOpenSearchSite.
 */
public final class OpenSearchSiteUtil {

    // OpenSearch defined parameters
    public static final String SEARCH_TERMS = "q";

    // temporal
    public static final String TIME_START = "dtstart";

    public static final String TIME_END = "dtend";

    public static final String TIME_NAME = "dateName";

    // geospatial
    public static final String GEO_LAT = "lat";

    public static final String GEO_LON = "lon";

    public static final String GEO_RADIUS = "radius";

    public static final String GEO_POLY = "polygon";

    public static final String GEO_BBOX = "bbox";

    // general options
    public static final String SRC = "src";

    public static final String MAX_RESULTS = "mr";

    public static final String COUNT = "count";

    public static final String MAX_TIMEOUT = "mt";

    public static final String USER_DN = "dn";

    public static final String SORT = "sort";

    public static final String FILTER = "filter";

    // only for async searches
    public static final String START_INDEX = "start";

    // geospatial constants
    public static final double LAT_DEGREE_M = 111325;

    public static final Integer DEFAULT_TOTAL_MAX = 1000;

    public static final Integer MAX_LAT = 90;

    public static final Integer MIN_LAT = -90;

    public static final Integer MAX_LON = 180;

    public static final Integer MIN_LON = -180;

    public static final Integer MAX_ROTATION = 360;

    public static final Integer MAX_BBOX_POINTS = 4;

    public static final String ORDER_ASCENDING = "asc";

    public static final String ORDER_DESCENDING = "desc";

    public static final String SORT_DELIMITER = ":";

    public static final String SORT_RELEVANCE = "relevance";

    public static final String SORT_TEMPORAL = "date";

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

    private OpenSearchSiteUtil() {

    }

    /**
     * Populates general site information.
     *
     * @param client  Initial StringBuilder url that is not filled in.
     * @param query
     * @param subject
     */
    public static void populateSearchOptions(WebClient client, Query query, Subject subject,
            List<String> parameters) {
        String maxTotalSize = null;
        String maxPerPage = null;
        String routeTo = "";
        String timeout = null;
        String start = "1";
        String dn = null;
        String filterStr = "";
        String sortStr = null;

        if (query != null) {

            maxPerPage = String.valueOf(query.getPageSize());
            if (query.getPageSize() > DEFAULT_TOTAL_MAX) {
                maxTotalSize = maxPerPage;
            } else if (query.getPageSize() <= 0) {
                maxTotalSize = String.valueOf(DEFAULT_TOTAL_MAX);
            }

            start = Integer.toString(query.getStartIndex());

            timeout = Long.toString(query.getTimeoutMillis());

            sortStr = translateToOpenSearchSort(query.getSortBy());

            if (subject != null && subject.getPrincipals() != null && !subject.getPrincipals().isEmpty()) {
                List principals = subject.getPrincipals().asList();
                for (Object principal : principals) {
                    if (principal instanceof SecurityAssertion) {
                        SecurityAssertion assertion = (SecurityAssertion) principal;
                        Principal assertionPrincipal = assertion.getPrincipal();
                        if (assertionPrincipal != null) {
                            dn = assertionPrincipal.getName();
                        }
                    }
                }
            }
        }

        checkAndReplace(client, start, START_INDEX, parameters);
        checkAndReplace(client, maxPerPage, COUNT, parameters);
        checkAndReplace(client, maxTotalSize, MAX_RESULTS, parameters);
        checkAndReplace(client, routeTo, SRC, parameters);
        checkAndReplace(client, timeout, MAX_TIMEOUT, parameters);
        checkAndReplace(client, dn, USER_DN, parameters);
        checkAndReplace(client, filterStr, FILTER, parameters);
        checkAndReplace(client, sortStr, SORT, parameters);
    }

    public static String translateToOpenSearchSort(SortBy ddfSort) {
        String openSearchSortStr = null;
        String orderType = null;

        if (ddfSort == null || ddfSort.getSortOrder() == null) {
            return openSearchSortStr;
        }

        if (ddfSort.getSortOrder().equals(SortOrder.ASCENDING)) {
            orderType = ORDER_ASCENDING;
        } else {
            orderType = ORDER_DESCENDING;
        }

        // QualifiedString type = ddfSort.getType();
        PropertyName sortByField = ddfSort.getPropertyName();

        if (Result.RELEVANCE.equals(sortByField.getPropertyName())) {
            // asc relevance not supported by spec
            openSearchSortStr = SORT_RELEVANCE + SORT_DELIMITER + ORDER_DESCENDING;
        } else if (Result.TEMPORAL.equals(sortByField.getPropertyName())) {
            openSearchSortStr = SORT_TEMPORAL + SORT_DELIMITER + orderType;
        } else {
            LOGGER.warn("Couldn't determine sort policy, not adding sorting in request to federated site.");
        }

        return openSearchSortStr;
    }

    /**
     * Fills in the OpenSearch query URL with contextual information (Note: Section 2.2 - Query: The
     * OpenSearch specification does not define a syntax for its primary query parameter,
     * searchTerms, but it is generally used to support simple keyword queries.)
     *
     * @param client
     * @param searchPhrase
     */
    public static void populateContextual(WebClient client, final String searchPhrase, List<String> parameters) {
        String queryStr = searchPhrase;
        if (queryStr != null) {
            try {
                queryStr = URLEncoder.encode(queryStr, "UTF-8");
            } catch (UnsupportedEncodingException uee) {
                LOGGER.warn("Could not encode contextual string: {}", uee.getMessage());
            }
        }

        checkAndReplace(client, queryStr, SEARCH_TERMS, parameters);
    }

    /**
     * Fills in the opensearch query URL with temporal information (Start, End, and Name). Currently
     * name is empty due to incompatibility with endpoints.
     *
     * @param client   OpenSearch URL to populate
     * @param temporal TemporalCriteria that contains temporal data
     */
    public static void populateTemporal(WebClient client, TemporalFilter temporal, List<String> parameters) {
        DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
        String start = "";
        String end = "";
        String name = "";
        if (temporal != null) {
            long startLng = (temporal.getStartDate() != null) ? temporal.getStartDate().getTime() : 0;
            start = fmt.print(startLng);
            long endLng = (temporal.getEndDate() != null) ? temporal.getEndDate().getTime()
                    : System.currentTimeMillis();
            end = fmt.print(endLng);
        }
        checkAndReplace(client, start, TIME_START, parameters);
        checkAndReplace(client, end, TIME_END, parameters);
        checkAndReplace(client, name, TIME_NAME, parameters);
    }

    /**
     * Fills in the OpenSearch query URL with geospatial information (poly, lat, lon, and radius).
     *
     * @param client  OpenSearch URL to populate
     * @param spatial SpatialCriteria that contains the spatial data
     */
    public static void populateGeospatial(WebClient client, SpatialDistanceFilter spatial,
            boolean shouldConvertToBBox, List<String> parameters) throws UnsupportedQueryException {
        String lat = "";
        String lon = "";
        String radiusStr = "";
        StringBuilder bbox = new StringBuilder("");
        StringBuilder poly = new StringBuilder("");

        if (spatial != null) {
            String wktStr = spatial.getGeometryWkt();
            double radius = spatial.getDistanceInMeters();

            if (wktStr.indexOf("POINT") != -1) {
                String[] latLon = createLatLonAryFromWKT(wktStr);
                lon = latLon[0];
                lat = latLon[1];
                radiusStr = Double.toString(radius);
                if (shouldConvertToBBox) {
                    double[] bboxCoords = createBBoxFromPointRadius(Double.parseDouble(lon),
                            Double.parseDouble(lat), radius);
                    for (int i = 0; i < MAX_BBOX_POINTS; i++) {
                        if (i > 0) {
                            bbox.append(",");
                        }
                        bbox.append(bboxCoords[i]);
                    }
                    lon = "";
                    lat = "";
                    radiusStr = "";
                }
            } else {
                LOGGER.warn("WKT ({}) not supported for POINT-RADIUS search, use POINT.", wktStr);
            }
        }

        checkAndReplace(client, lat, GEO_LAT, parameters);
        checkAndReplace(client, lon, GEO_LON, parameters);
        checkAndReplace(client, radiusStr, GEO_RADIUS, parameters);
        checkAndReplace(client, poly.toString(), GEO_POLY, parameters);
        checkAndReplace(client, bbox.toString(), GEO_BBOX, parameters);
    }

    /**
     * Fills in the OpenSearch query URL with geospatial information (poly, lat, lon, and radius).
     *
     * @param client  OpenSearch URL to populate
     * @param spatial SpatialCriteria that contains the spatial data
     */
    public static void populateGeospatial(WebClient client, SpatialFilter spatial, boolean shouldConvertToBBox,
            List<String> parameters) throws UnsupportedQueryException {
        String lat = "";
        String lon = "";
        String radiusStr = "";
        StringBuilder bbox = new StringBuilder("");
        StringBuilder poly = new StringBuilder("");

        if (spatial != null) {
            String wktStr = spatial.getGeometryWkt();
            if (wktStr.indexOf("POLYGON") != -1) {
                String[] polyAry = createPolyAryFromWKT(wktStr);
                if (shouldConvertToBBox) {
                    double[] bboxCoords = createBBoxFromPolygon(polyAry);
                    for (int i = 0; i < MAX_BBOX_POINTS; i++) {
                        if (i > 0) {
                            bbox.append(",");
                        }
                        bbox.append(bboxCoords[i]);
                    }
                } else {
                    for (int i = 0; i < polyAry.length - 1; i += 2) {
                        if (i != 0) {
                            poly.append(",");
                        }
                        poly.append(polyAry[i + 1] + "," + polyAry[i]);
                    }
                }
            } else {
                LOGGER.warn("WKT ({}) not supported for SPATIAL search, use POLYGON.", wktStr);
            }
        }

        checkAndReplace(client, lat, GEO_LAT, parameters);
        checkAndReplace(client, lon, GEO_LON, parameters);
        checkAndReplace(client, radiusStr, GEO_RADIUS, parameters);
        checkAndReplace(client, poly.toString(), GEO_POLY, parameters);
        checkAndReplace(client, bbox.toString(), GEO_BBOX, parameters);
    }

    /**
     * Parses a WKT polygon string and returns a string array containing the lon and lat.
     *
     * @param wkt WKT String in the form of POLYGON((Lon Lat, Lon Lat...))
     * @return Lon on even # and Lat on odd #
     */
    public static String[] createPolyAryFromWKT(String wkt) {
        String lonLat = wkt.substring(wkt.indexOf("((") + 2, wkt.indexOf("))"));
        return lonLat.split(" |,\\p{Space}?");
    }

    /**
     * Parses a WKT Point string and returns a string array containing the lon and lat.
     *
     * @param wkt WKT String in the form of POINT( Lon Lat)
     * @return Lon at position 0, Lat at position 1
     */
    public static String[] createLatLonAryFromWKT(String wkt) {
        String lonLat = wkt.substring(wkt.indexOf('(') + 1, wkt.indexOf(')'));
        return lonLat.split(" ");
    }

    /**
     * Checks the input and replaces the items inside of the url.
     *
     * @param client     The URL to do the replacement on. <b>NOTE:</b> replacement is done directly on
     *                   this object.
     * @param inputStr   Item to put into the URL.
     * @param definition Area inside of the URL to be replaced by.
     */
    private static void checkAndReplace(WebClient client, String inputStr, String definition,
            List<String> parameters) {
        if (hasParameter(definition, parameters)) {
            if (StringUtils.isNotEmpty(inputStr)) {
                client.replaceQueryParam(definition, inputStr);
            }
        }
    }

    private static boolean hasParameter(String parameter, List<String> parameters) {
        for (String param : parameters) {
            if (param != null && param.equalsIgnoreCase(parameter)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Takes in a point radius search and converts it to a (rough approximation) bounding box.
     *
     * @param lon    latitude in decimal degrees (WGS-84)
     * @param lat    longitude in decimal degrees (WGS-84)
     * @param radius radius, in meters
     * @return Array of bounding box coordinates in the following order: West South East North. Also
     * described as minX, minY, maxX, maxY (where longitude is the X-axis, and latitude is
     * the Y-axis).
     */
    public static double[] createBBoxFromPointRadius(double lon, double lat, double radius) {
        double minX;
        double minY;
        double maxX;
        double maxY;

        double lonDifference = radius / (LAT_DEGREE_M * Math.cos(lat));
        double latDifference = radius / LAT_DEGREE_M;
        minX = lon - lonDifference;
        if (minX < MIN_LON) {
            minX += MAX_ROTATION;
        }
        maxX = lon + lonDifference;
        if (maxX > MAX_LON) {
            maxX -= MAX_ROTATION;
        }
        minY = lat - latDifference;
        if (minY < MIN_LAT) {
            minY = Math.abs(minY + MAX_LAT) - MAX_LAT;
        }
        maxY = lat + latDifference;
        if (maxY > MAX_LAT) {
            maxY = MAX_LAT - (maxY - MAX_LAT);
        }

        return new double[] { minX, minY, maxX, maxY };
    }

    /**
     * Takes in an array of coordinates and converts it to a (rough approximation) bounding box.
     * <p/>
     * Note: Searches being performed where the polygon goes through the international date line may
     * return a bad bounding box.
     *
     * @param polyAry array of coordinates (lon,lat,lon,lat,lon,lat..etc)
     * @return Array of bounding box coordinates in the following order: West South East North. Also
     * described as minX, minY, maxX, maxY (where longitude is the X-axis, and latitude is
     * the Y-axis).
     */
    public static double[] createBBoxFromPolygon(String[] polyAry) {
        double minX = Double.POSITIVE_INFINITY;
        double minY = Double.POSITIVE_INFINITY;
        double maxX = Double.NEGATIVE_INFINITY;
        double maxY = Double.NEGATIVE_INFINITY;

        double curX, curY;
        for (int i = 0; i < polyAry.length - 1; i += 2) {
            LOGGER.debug("polyToBBox: lon - {} lat - {}", polyAry[i], polyAry[i + 1]);
            curX = Double.parseDouble(polyAry[i]);
            curY = Double.parseDouble(polyAry[i + 1]);
            if (curX < minX) {
                minX = curX;
            }
            if (curX > maxX) {
                maxX = curX;
            }
            if (curY < minY) {
                minY = curY;
            }
            if (curY > maxY) {
                maxY = curY;
            }
        }
        return new double[] { minX, minY, maxX, maxY };
    }

}