org.codice.ddf.ui.searchui.query.service.SearchService.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.ui.searchui.query.service.SearchService.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.ddf.ui.searchui.query.service;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;

import org.apache.commons.lang.StringUtils;
import org.codice.ddf.ui.searchui.query.controller.SearchController;
import org.codice.ddf.ui.searchui.query.model.Search;
import org.codice.ddf.ui.searchui.query.model.SearchRequest;
import org.cometd.annotation.Listener;
import org.cometd.annotation.Service;
import org.cometd.annotation.Session;
import org.cometd.bayeux.Message;
import org.cometd.bayeux.server.BayeuxContext;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.ConfigurableServerChannel;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.bayeux.server.ServerSession;
import org.cometd.server.ServerMessageImpl;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.filter.text.ecql.ECQL;
import org.opengis.filter.Filter;
import org.opengis.filter.sort.SortBy;
import org.opengis.filter.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ddf.catalog.data.Metacard;
import ddf.catalog.data.Result;
import ddf.catalog.filter.FilterBuilder;
import ddf.catalog.filter.FilterDelegate;
import ddf.catalog.filter.impl.SortByImpl;
import ddf.catalog.operation.Query;
import ddf.catalog.operation.SourceInfoResponse;
import ddf.catalog.operation.impl.QueryImpl;
import ddf.catalog.operation.impl.SourceInfoRequestEnterprise;
import ddf.catalog.source.SourceDescriptor;
import ddf.catalog.source.SourceUnavailableException;
import ddf.security.SecurityConstants;
import ddf.security.Subject;

/**
 * This class performs the searches when a client communicates with the cometd endpoint
 */
@Service
public class SearchService {

    public static final String LOCAL_SOURCE = "local";

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

    private static final String ID = "id";

    private static final String SOURCES = "src";

    private static final String MAX_TIMEOUT = "timeout";

    private static final String START_INDEX = "start";

    private static final String COUNT = "count";

    private static final String CQL_FILTER = "cql";

    private static final String SORT = "sort";

    private static final String DEFAULT_SORT_ORDER = "desc";

    private static final long DEFAULT_TIMEOUT = 300000;

    private static final int DEFAULT_COUNT = 10;

    private static final int DEFAULT_START_INDEX = 1;

    private final FilterBuilder filterBuilder;

    private final SearchController searchController;

    @Inject
    private BayeuxServer bayeux;

    @Session
    private ServerSession serverSession;

    /**
     * Creates a new SearchService
     *
     * @param filterBuilder
     *            - FilterBuilder to use for queries
     * @param searchController
     *            - SearchController to handle async queries
     */
    public SearchService(FilterBuilder filterBuilder, SearchController searchController) {
        this.filterBuilder = filterBuilder;
        this.searchController = searchController;
    }

    /**
     * Service method called by Cometd when something arrives on the service channel
     *
     * @param remote
     *            - Client session
     * @param message
     *            - JSON message
     */
    @Listener("/service/query")
    public void processQuery(final ServerSession remote, Message message) {

        ServerMessage.Mutable reply = new ServerMessageImpl();

        Map<String, Object> queryMessage = message.getDataAsMap();

        if (queryMessage != null && queryMessage.containsKey(Search.ID)) {
            bayeux.createChannelIfAbsent("/" + queryMessage.get(Search.ID),
                    new ConfigurableServerChannel.Initializer() {
                        public void configureChannel(ConfigurableServerChannel channel) {
                            channel.setPersistent(true);
                        }
                    });

            BayeuxContext context = bayeux.getContext();
            Subject subject = null;
            if (context != null) {
                subject = (Subject) context.getRequestAttribute(SecurityConstants.SECURITY_SUBJECT);
            }

            // kick off the query
            executeQuery(queryMessage, subject);

            reply.put(Search.SUCCESSFUL, true);
            remote.deliver(serverSession, reply);
        } else {
            reply.put(Search.SUCCESSFUL, false);
            reply.put("status", "ERROR: unable to return results, no id in query request");
            remote.deliver(serverSession, reply);
        }

    }

    @SuppressWarnings("unchecked")
    private <T> T castObject(Class<T> targetClass, Object o) {
        if (o != null) {
            if (o instanceof Number) {
                if (targetClass.equals(Double.class)) {
                    return (T) Double.valueOf(((Number) o).doubleValue());
                } else if (targetClass.equals(Long.class)) {
                    return (T) Long.valueOf(((Number) o).longValue());
                } else {
                    // unhandled conversion so trying best effort
                    return (T) o;
                }
            } else {
                return (T) o.toString();
            }
        } else {
            return null;
        }
    }

    /**
     * Creates the query requests for each source and hands off the query to the Search Controller
     *
     * @param queryMessage
     *            - JSON message received from cometd
     */
    public void executeQuery(Map<String, Object> queryMessage, Subject subject) {
        String sources = castObject(String.class, queryMessage.get(SOURCES));
        Long maxTimeout = castObject(Long.class, queryMessage.get(MAX_TIMEOUT));
        Long startIndex = castObject(Long.class, queryMessage.get(START_INDEX));
        Long count = castObject(Long.class, queryMessage.get(COUNT));
        String cql = castObject(String.class, queryMessage.get(CQL_FILTER));
        String sort = castObject(String.class, queryMessage.get(SORT));
        String id = castObject(String.class, queryMessage.get(ID));

        Set<String> sourceIds = getSourceIds(sources);

        Filter filter = null;
        try {
            filter = ECQL.toFilter(cql);
        } catch (CQLException e) {
            LOGGER.warn("Unable to parse CQL filter", e);
            return;
        }

        Query query = createQuery(filter, startIndex, count, sort, maxTimeout);
        SearchRequest searchRequest = new SearchRequest(sourceIds, query, id);

        try {
            // Hand off to the search controller for the actual query
            searchController.executeQuery(searchRequest, serverSession, subject);
        } catch (Exception e) {
            LOGGER.warn("Exception while executing a query", e);
        }

    }

    private Set<String> getSourceIds(String sources) {
        Set<String> sourceIds;
        if (StringUtils.equalsIgnoreCase(sources, LOCAL_SOURCE)) {
            LOGGER.debug("Received local query");
            sourceIds = new HashSet<String>(Arrays.asList(searchController.getFramework().getId()));
        } else if (!(StringUtils.isEmpty(sources))) {
            LOGGER.debug("Received source names from client: {}", sources);
            sourceIds = new HashSet<String>(Arrays.asList(StringUtils.stripAll(sources.split(","))));
        } else {
            LOGGER.debug("Received enterprise query");
            SourceInfoResponse sourceInfo = null;
            try {
                sourceInfo = searchController.getFramework().getSourceInfo(new SourceInfoRequestEnterprise(true));
            } catch (SourceUnavailableException e) {
                LOGGER.debug("Exception while getting source status. Defaulting to all sources. "
                        + "This could include unavailable sources.", e);
            }

            if (sourceInfo != null) {
                sourceIds = new HashSet<String>();
                for (SourceDescriptor source : sourceInfo.getSourceInfo()) {
                    if (source.isAvailable()) {
                        sourceIds.add(source.getSourceId());
                    }
                }
            } else {
                sourceIds = searchController.getFramework().getSourceIds();
            }
        }
        return sourceIds;
    }

    /**
     * Creates a new query from the incoming parameters
     *
     * @param filter
     *            - Filter to query
     * @param startIndexLng
     *            - Start index for the query
     * @param countLng
     *            - number of results for the query
     * @param sortStr
     *            - How to sort the query results
     * @param maxTimeoutLng
     *            - timeout value on the query execution
     * @return - the new query
     */
    private Query createQuery(Filter filter, Long startIndexLng, Long countLng, String sortStr,
            Long maxTimeoutLng) {
        // default values
        String sortField = Result.TEMPORAL;
        String sortOrder = DEFAULT_SORT_ORDER;
        Long startIndex = startIndexLng == null ? Long.valueOf(DEFAULT_START_INDEX) : startIndexLng;
        Long count = countLng == null ? Long.valueOf(DEFAULT_COUNT) : countLng;
        long maxTimeout = maxTimeoutLng == null ? DEFAULT_TIMEOUT : maxTimeoutLng;

        // Updated to use the passed in index if valid (=> 1)
        // and to use the default if no value, or an invalid value (< 1)
        // is specified
        if (!(StringUtils.isEmpty(sortStr))) {
            String[] sortAry = sortStr.split(":");
            if (sortAry.length > 1) {
                sortField = sortAry[0];
                sortOrder = sortAry[1];
            }
        }

        // Query must specify a valid sort order if a sort field was specified, i.e., query
        // cannot specify just "date:", must specify "date:asc"
        SortBy sort;
        if ("asc".equalsIgnoreCase(sortOrder)) {
            sort = new SortByImpl(sortField, SortOrder.ASCENDING);
        } else if ("desc".equalsIgnoreCase(sortOrder)) {
            sort = new SortByImpl(sortField, SortOrder.DESCENDING);
        } else {
            throw new IllegalArgumentException("Incorrect sort order received, must be 'asc' or 'desc'");
        }

        LOGGER.debug("Retrieved query settings: \n sortField: {} \nsortOrder: {}", sortField, sortOrder);
        if (filter == null) {
            LOGGER.debug("Received an empty filter. Using a wildcard contextual filter instead.");
            filter = filterBuilder.attribute(Metacard.ANY_TEXT).is().like().text(FilterDelegate.WILDCARD_CHAR);
        }
        return new QueryImpl(filter, startIndex.intValue(), count.intValue(), sort, true, maxTimeout);
    }
}