001/* 002 * Copyright 2022 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.search.query; 017 018import java.time.LocalDate; 019import java.time.LocalDateTime; 020import java.time.LocalTime; 021import java.time.format.DateTimeFormatter; 022import java.time.format.ResolverStyle; 023 024import org.ametys.core.util.date.AdaptableDate; 025 026/** 027 * Base class for all date and operator-based queries. 028 */ 029public abstract class AbstractDateOperatorQuery extends AbstractOperatorQuery<AdaptableDate> 030{ 031 /** The date formatter */ 032 public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") 033 .withResolverStyle(ResolverStyle.STRICT); 034 035 /** 036 * Build a CreationDateQuery. 037 * @param fieldName the Solr field name 038 * @param op the operator. 039 * @param value the value. 040 */ 041 public AbstractDateOperatorQuery(String fieldName, Operator op, AdaptableDate value) 042 { 043 super(fieldName, op, value); 044 } 045 046 @Override 047 public String build() throws QuerySyntaxException 048 { 049 StringBuilder query = new StringBuilder(); 050 051 if (getOperator() == Operator.EXISTS) 052 { 053 query.append(getFieldName()).append(':').append(QueryHelper.EXISTS_VALUE); 054 return query.toString(); 055 } 056 057 if (getOperator() == Operator.NE) 058 { 059 NotQuery.appendNegation(query); 060 } 061 062 query.append(getFieldName()).append(':'); 063 064 appendDateValue(query, getOperator(), getValue()); 065 066 return query.toString(); 067 } 068 069 /** 070 * Format and append the given date to a StringBuilder. 071 * @param query The string builder containing the query being built. 072 * @param operator The query operator. 073 * @param value The test value. 074 */ 075 protected void appendDateValue(StringBuilder query, Operator operator, AdaptableDate value) 076 { 077 if (operator == Operator.EQ || operator == Operator.NE) 078 { 079 LocalDate date = value.resolveDate(); 080 query.append('[') 081 .append(date.atStartOfDay().format(DATE_FORMATTER)) 082 .append(" TO ") 083 .append(date.atTime(LocalTime.MAX).format(DATE_FORMATTER)) 084 .append(']'); 085 return; 086 } 087 088 String strValue = _format(value, operator); 089 if (operator == Operator.GT) 090 { 091 query.append('{').append(strValue).append(" TO *]"); 092 } 093 else if (operator == Operator.GE) 094 { 095 query.append('[').append(strValue).append(" TO *]"); 096 } 097 else if (operator == Operator.LT) 098 { 099 query.append("[* TO ").append(strValue).append('}'); 100 } 101 else if (operator == Operator.LE) 102 { 103 query.append("[* TO ").append(strValue).append(']'); 104 } 105 } 106 107 private String _format(AdaptableDate value, Operator operator) 108 { 109 LocalDate date = value.resolveDate(); 110 // We are doing this because: 111 // In Solr we store datetimes 112 // But (for the moment), we handle querying on dates only 113 // So the AdaptableDate ('value') is resolved as a LocalDate (thus, at the moment of this method call, 'date' has lost any time information) 114 // Then, depending on the operator, we specify the min or max time of the day to handle range 115 // Finally, we format the LocalDateTime to a readable string by Solr parser 116 // 117 // Note: in the future, if we need to support querying on complete datetimes, we should call value.resolveDateTime() instead of value.resolveDate() (in a DateTimeQuery of course) 118 LocalDateTime dateTime; 119 if (Operator.LE == operator || Operator.GT == operator) 120 { 121 // LE: all datetimes of upper bound no matter their time should be included 122 // GT: all datetimes of lower bound no matter their time should be excluded 123 dateTime = date.atTime(LocalTime.MAX); 124 } 125 else 126 { 127 // GE: all datetimes of lower bound no matter their time should be included 128 // LT: all datetimes of upper bound no matter their time should be excluded 129 dateTime = date.atStartOfDay(); 130 } 131 return dateTime.format(DATE_FORMATTER); 132 } 133}