001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.gbif.api.model.predicate; 015 016import org.gbif.api.annotation.Experimental; 017import org.gbif.api.model.common.search.SearchParameter; 018import org.gbif.api.model.occurrence.search.OccurrenceSearchParameter; 019import org.gbif.api.util.IsoDateInterval; 020import org.gbif.api.util.SearchTypeValidator; 021 022import java.util.Date; 023import java.util.Objects; 024import java.util.Optional; 025import java.util.StringJoiner; 026 027import javax.annotation.Nullable; 028import javax.validation.constraints.NotNull; 029 030import io.swagger.v3.oas.annotations.media.Schema; 031 032import static org.gbif.api.util.PreconditionUtils.checkArgument; 033 034public class SimplePredicate<S extends SearchParameter> implements Predicate { 035 036 @Schema( 037 description = "The search parameter to test.", 038 implementation = OccurrenceSearchParameter.class 039 ) 040 @NotNull 041 private final S key; 042 043 @Schema( 044 description = "The value to test for." 045 ) 046 @NotNull 047 private final String value; 048 049 @Schema( 050 description = "Whether to match letter case (UPPER or lower case) on string value comparisons." 051 ) 052 @Experimental 053 @Nullable 054 private final Boolean matchCase; 055 056 protected SimplePredicate(boolean checkForNonEquals, S key, String value, Boolean matchCase) { 057 this.matchCase = matchCase; 058 Objects.requireNonNull(key, "<key> may not be null"); 059 Objects.requireNonNull(value, "<value> may not be null"); 060 checkArgument(!value.isEmpty(), "<value> may not be empty"); 061 // make sure the value is of the right type according to the key given 062 SearchTypeValidator.validate(key, value); 063 064 this.key = key; 065 this.value = value; 066 067 checkPredicateAllowed(); 068 if (checkForNonEquals) { 069 checkNonEqualsComparatorAllowed(); 070 } 071 } 072 073 public S getKey() { 074 return key; 075 } 076 077 public String getValue() { 078 return value; 079 } 080 081 /** 082 * This flag enables the use of case-sensitive matches and aggregations on certain search parameters. 083 * <p> 084 * Fields that support this feature are: occurrenceId, recordedBy, samplingProtocol, catalogNumber, collectionCode, 085 * institutionCode, eventId, parentEventId, waterBody, stateProvince, recordNumber, identifiedBy, organismId and locality. 086 * <p> 087 * This is an experimental feature and its implementation map change or be removed at any time. 088 */ 089 @Experimental 090 public Boolean isMatchCase() { 091 return Optional.ofNullable(matchCase).orElse(Boolean.FALSE); 092 } 093 094 /** 095 * @throws IllegalArgumentException if the key SearchParameter is Geometry 096 */ 097 private void checkPredicateAllowed() { 098 if (OccurrenceSearchParameter.GEOMETRY == key) { 099 throw new IllegalArgumentException("Geometry parameter must use a Within predicate"); 100 } 101 } 102 103 /** 104 * @throws IllegalArgumentException if the key SearchParameter allows other comparators than 105 * equals 106 */ 107 private void checkNonEqualsComparatorAllowed() { 108 if (!(Number.class.isAssignableFrom(key.type()) || Date.class.isAssignableFrom(key.type()) || IsoDateInterval.class.isAssignableFrom(key.type()))) { 109 throw new IllegalArgumentException( 110 "Only equals comparisons are allowed for search parameter " + key + " of type " + key.type()); 111 } 112 } 113 114 @Override 115 public boolean equals(Object o) { 116 if (this == o) { 117 return true; 118 } 119 if (o == null || getClass() != o.getClass()) { 120 return false; 121 } 122 SimplePredicate<S> that = (SimplePredicate<S>) o; 123 return key == that.key && Objects.equals(value, that.value) && matchCase == that.matchCase; 124 } 125 126 @Override 127 public int hashCode() { 128 return Objects.hash(key, value, matchCase); 129 } 130 131 @Override 132 public String toString() { 133 return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") 134 .add("key=" + key) 135 .add("value='" + value + "'") 136 .add("matchCase='" + matchCase + "'") 137 .toString(); 138 } 139}