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 com.fasterxml.jackson.annotation.JsonIgnore; 017 018import com.fasterxml.jackson.annotation.JsonProperty; 019 020import org.gbif.api.annotation.Experimental; 021import org.gbif.api.model.common.search.SearchParameter; 022import org.gbif.api.model.occurrence.search.OccurrenceSearchParameter; 023import org.gbif.api.util.IsoDateInterval; 024import org.gbif.api.util.SearchTypeValidator; 025 026import java.util.Date; 027import java.util.Objects; 028import java.util.Optional; 029import java.util.StringJoiner; 030 031import jakarta.annotation.Nullable; 032import jakarta.validation.constraints.NotNull; 033 034import io.swagger.v3.oas.annotations.media.Schema; 035 036import static org.gbif.api.util.PreconditionUtils.checkArgument; 037 038public class SimplePredicate<S extends SearchParameter> implements Predicate { 039 040 @Schema( 041 description = "The search parameter to test.", 042 implementation = SearchParameter.class 043 ) 044 @NotNull 045 private final S key; 046 047 @Schema( 048 description = "The value to test for." 049 ) 050 @NotNull 051 private final String value; 052 053 @Schema( 054 description = "Whether to match letter case (UPPER or lower case) on string value comparisons." 055 ) 056 @Experimental 057 @Nullable 058 private final Boolean matchCase; 059 060 protected SimplePredicate(boolean checkForNonEquals, S key, String value, Boolean matchCase) { 061 this.matchCase = matchCase; 062 Objects.requireNonNull(key, "<key> may not be null"); 063 Objects.requireNonNull(value, "<value> may not be null"); 064 checkArgument(!value.isEmpty(), "<value> may not be empty"); 065 // make sure the value is of the right type according to the key given 066 SearchTypeValidator.validate(key, value); 067 068 this.key = key; 069 this.value = value; 070 071 checkPredicateAllowed(); 072 if (checkForNonEquals) { 073 checkNonEqualsComparatorAllowed(); 074 } 075 } 076 077 public S getKey() { 078 return key; 079 } 080 081 public String getValue() { 082 return value; 083 } 084 085 /** 086 * This flag enables the use of case-sensitive matches and aggregations on certain search parameters. 087 * <p> 088 * Fields that support this feature are: occurrenceId, recordedBy, samplingProtocol, catalogNumber, collectionCode, 089 * institutionCode, eventId, parentEventId, waterBody, stateProvince, recordNumber, identifiedBy, organismId and locality. 090 * <p> 091 * This is an experimental feature and its implementation map change or be removed at any time. 092 */ 093 @Experimental 094 @JsonIgnore 095 public Boolean isMatchCase() { 096 return Optional.ofNullable(matchCase).orElse(Boolean.FALSE); 097 } 098 099 @JsonProperty("matchCase") 100 public Boolean getMatchCase() { 101 return matchCase; 102 } 103 104 /** 105 * @throws IllegalArgumentException if the key SearchParameter is Geometry 106 */ 107 private void checkPredicateAllowed() { 108 if (OccurrenceSearchParameter.GEOMETRY == key) { 109 throw new IllegalArgumentException("Geometry parameter must use a Within predicate"); 110 } 111 } 112 113 /** 114 * @throws IllegalArgumentException if the key SearchParameter allows other comparators than 115 * equals 116 */ 117 private void checkNonEqualsComparatorAllowed() { 118 if (!(Number.class.isAssignableFrom(key.type()) 119 || Date.class.isAssignableFrom(key.type()) 120 || IsoDateInterval.class.isAssignableFrom(key.type()) 121 || OccurrenceSearchParameter.GEOLOGICAL_TIME == key)) { 122 throw new IllegalArgumentException( 123 "Only equals comparisons are allowed for search parameter " 124 + key 125 + " of type " 126 + key.type()); 127 } 128 } 129 130 @Override 131 public boolean equals(Object o) { 132 if (this == o) { 133 return true; 134 } 135 if (o == null || getClass() != o.getClass()) { 136 return false; 137 } 138 SimplePredicate<S> that = (SimplePredicate<S>) o; 139 return key == that.key && Objects.equals(value, that.value) && matchCase == that.matchCase; 140 } 141 142 @Override 143 public int hashCode() { 144 return Objects.hash(key, value, matchCase); 145 } 146 147 @Override 148 public String toString() { 149 return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") 150 .add("key=" + key) 151 .add("value='" + value + "'") 152 .add("matchCase='" + matchCase + "'") 153 .toString(); 154 } 155}