/** * 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.commands.catalog; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.apache.karaf.shell.api.action.Option; import org.apache.karaf.shell.api.action.lifecycle.Reference; import org.geotools.filter.text.cql2.CQL; import org.geotools.filter.text.cql2.CQLException; import org.opengis.filter.Filter; import ddf.catalog.data.AttributeDescriptor; import ddf.catalog.data.AttributeType; import ddf.catalog.data.Metacard; import ddf.catalog.data.MetacardType; import ddf.catalog.data.types.Core; import ddf.catalog.filter.FilterBuilder; public abstract class CqlCommands extends CatalogCommands { public static final String DATE_FORMAT = "MM-dd-yyyy"; public static final String DEFAULT_TEMPORAL_PROPERTY = Core.CREATED; private static final String MUTUALLY_EXCLUSIVE_OPTION_MESSAGE = " NOTE: Does not apply to CQL filters. Does not stack with other --lastXXXX options."; private static final String LASTXXXX_MESSAGE = MUTUALLY_EXCLUSIVE_OPTION_MESSAGE + " Smaller --lastXXXX time units take precedence over larger time units."; private static final String DATE_FORMATTING_MESSAGE = " Dates should be formatted as MM-dd-yyyy such as 06-10-2014."; private static final String SEARCH_PHRASE_OPTION_NAME = "--searchPhrase"; @Option(name = "--cql", required = false, aliases = { "-cqlFilter"}, multiValued = false, description = "Option to filter by metacards that match a CQL Filter expression. It is recommended to use the search command (catalog:search) first to see which metacards will be filtered.\n\n" + "CQL Examples:\n\t" + "Textual: --cql \"title like 'some text'\"\n\t" + "Temporal: --cql \"modified before 2012-09-01T12:30:00Z\"\n\t" + "Spatial: --cql \"DWITHIN(location, POINT (1 2) , 10, kilometers)\"\n\t" + "Complex: --cql \"title like 'some text' AND modified before 2012-09-01T12:30:00Z\"") String cqlFilter = null; @Option(name = "--temporal", required = false, aliases = { "-dt"}, multiValued = false, description = "Option to use temporal criteria to filter. The default is to use \"keyword like " + WILDCARD + " \".") boolean isUseTemporal = false; @Option(name = "--temporalProperty", required = false, aliases = { "-tp"}, multiValued = false, description = "Option to select which temporal property by which to filter with --XXXDate and--lastXXXX " + "options. Valid values include, but are not limited to, \"" + Core.MODIFIED + "\", \"" + Core.CREATED + "\", \"" + Metacard.EFFECTIVE + "\", and \"" + Core.EXPIRATION + "\". Defaults to \"" + DEFAULT_TEMPORAL_PROPERTY + "\" if " + "not specified or input not recognized.") String temporalProperty; @Option(name = "--startDate", required = false, aliases = { "-start"}, multiValued = false, description = "Flag to specify a start date range to by which to filter." + DATE_FORMATTING_MESSAGE) String startDate; @Option(name = "--endDate", required = false, aliases = { "-end"}, multiValued = false, description = "Flag to specify a start date range to by which to filter." + DATE_FORMATTING_MESSAGE) String endDate; @Option(name = "--lastSeconds", required = false, aliases = {"-sec", "-seconds"}, multiValued = false, description = "Option to filter by the last N seconds." + LASTXXXX_MESSAGE) int lastSeconds; @Option(name = "--lastMinutes", required = false, aliases = {"-min", "-minutes"}, multiValued = false, description = "Option to filter by the last N minutes." + LASTXXXX_MESSAGE) int lastMinutes; @Option(name = "--lastHours", required = false, aliases = {"-h", "-hours"}, multiValued = false, description = "Option to filter by the last N hours." + LASTXXXX_MESSAGE) int lastHours; @Option(name = "--lastDays", required = false, aliases = {"-d", "-days"}, multiValued = false, description = "Option to filter by the last N days." + LASTXXXX_MESSAGE) int lastDays; @Option(name = "--lastWeeks", required = false, aliases = {"-w", "-weeks"}, multiValued = false, description = "Option to filter by the last N weeks." + LASTXXXX_MESSAGE) int lastWeeks; @Option(name = "--lastMonths", required = false, aliases = {"-m", "-months"}, multiValued = false, description = "Option to filter by the last N months." + LASTXXXX_MESSAGE) int lastMonths; @Option(name = SEARCH_PHRASE_OPTION_NAME, required = false, aliases = {"-phrase", "-like"}, multiValued = false, description = "Option to filter by a specific phrase." + MUTUALLY_EXCLUSIVE_OPTION_MESSAGE + " --lastXXXX options take precedence over this option.") String searchPhrase = WILDCARD; @Option(name = "--caseSensitive", required = false, aliases = { "-case"}, multiValued = false, description = "Option to set the " + SEARCH_PHRASE_OPTION_NAME + " to be case sensitive.") boolean caseSensitive = false; @Reference List<MetacardType> metacardTypes; private long filterCurrentTime; protected Filter getFilter() throws InterruptedException, ParseException, CQLException { return getFilter(filterBuilder); } protected Filter getFilter(FilterBuilder filterBuilder) throws CQLException, ParseException { filterCurrentTime = System.currentTimeMillis(); if (cqlFilter != null) { return CQL.toFilter(cqlFilter); } final long start = getFilterStartTime(); final long end = filterCurrentTime; final String temporalPropertyToFilter = getTemporalProperty(); final SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT); if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) { return filterBuilder.attribute(temporalPropertyToFilter) .is() .during() .dates(formatter.parse(startDate), formatter.parse(endDate)); } else if (start > 0 && end > 0) { return filterBuilder.attribute(temporalPropertyToFilter) .is() .during() .dates(new Date(start), new Date(end)); } else if (isUseTemporal) { return filterBuilder.attribute(temporalPropertyToFilter) .is() .during() .last(start); } else { if (caseSensitive) { return filterBuilder.attribute(Metacard.ANY_TEXT) .is() .like() .caseSensitiveText(searchPhrase); } else { return filterBuilder.attribute(Metacard.ANY_TEXT) .is() .like() .text(searchPhrase); } } } protected long getFilterStartTime() { if (lastSeconds > 0) { return filterCurrentTime - TimeUnit.SECONDS.toMillis(lastSeconds); } else if (lastMinutes > 0) { return filterCurrentTime - TimeUnit.MINUTES.toMillis(lastMinutes); } else if (lastHours > 0) { return filterCurrentTime - TimeUnit.HOURS.toMillis(lastHours); } else if (lastDays > 0) { return filterCurrentTime - TimeUnit.DAYS.toMillis(lastDays); } else if (lastWeeks > 0) { Calendar weeks = GregorianCalendar.getInstance(); weeks.setTimeInMillis(filterCurrentTime); weeks.add(Calendar.WEEK_OF_YEAR, -1 * lastWeeks); return weeks.getTimeInMillis(); } else if (lastMonths > 0) { Calendar months = GregorianCalendar.getInstance(); months.setTimeInMillis(filterCurrentTime); months.add(Calendar.MONTH, -1 * lastMonths); return months.getTimeInMillis(); } else { return 0; } } protected String getTemporalProperty() { if (metacardTypes != null && StringUtils.isNotEmpty(temporalProperty)) { return metacardTypes.stream() .map(MetacardType::getAttributeDescriptors) .flatMap(Set::stream) .filter(this::isDateType) .map(AttributeDescriptor::getName) .filter(temporalProperty::equalsIgnoreCase) .findFirst() .orElse(DEFAULT_TEMPORAL_PROPERTY); } return DEFAULT_TEMPORAL_PROPERTY; } private Boolean isDateType(AttributeDescriptor attributeDescriptor) { return attributeDescriptor.getType().getAttributeFormat() == AttributeType.AttributeFormat.DATE; } protected Boolean hasFilter() { return isAnyNonNull(cqlFilter, startDate, endDate) || isAnyNotZero(lastSeconds, lastMinutes, lastHours, lastDays, lastWeeks, lastMonths); } private Boolean isAnyNonNull(String... parameters) { return Arrays.stream(parameters) .anyMatch(Objects::nonNull); } private Boolean isAnyNotZero(int... parameters) { return Arrays.stream(parameters) .anyMatch(p -> p > 0); } }