001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.beanquery; 018 019import java.util.*; 020 021/** 022 * A {@link BeanQuery} after validation: the engine-neutral input to every engine hook. 023 * 024 * <p> 025 * Produced only by {@link BeanQueryContext#resolve(BeanQuery)} and {@link BeanQuerySession#resolve(BeanQuery)}. 026 * Every column in it is allowed, every expression is parsed, depth-checked and type-checked, the limit is clamped, 027 * and {@link #counts()} is never {@link CountPolicy#IF_REQUESTED}. Engines never see raw request strings. 028 * 029 * <h5 class='section'>Example:</h5> 030 * <p class='bjava'> 031 * <jc>// Validate without executing, e.g. to fail fast or to inspect what a request will do.</jc> 032 * ResolvedQuery <jv>q</jv> = <jv>context</jv>.resolve(BeanQuery.<jsm>create</jsm>().gt(<js>"age"</js>, 21).sortDesc(<js>"name"</js>).build()); 033 * <jk>if</jk> (<jv>q</jv>.filter() <jk>instanceof</jk> Filter.Leaf <jv>leaf</jv>) 034 * <jv>log</jv>.fine(<jv>leaf</jv>.column() + <js>" ("</js> + <jv>leaf</jv>.type() + <js>"): "</js> + <jv>leaf</jv>.expression()); 035 * <jk>boolean</jk> <jv>newestFirst</jv> = <jv>q</jv>.sort().get(0).descending(); 036 * </p> 037 * 038 * <p> 039 * The nested {@link SortKey} type is covered by this example. 040 * 041 * @since 10.0.0 042 */ 043public final class ResolvedQuery { 044 045 /** One sort key: its column, the column's value type, and the direction. */ 046 public static final class SortKey { 047 private final String column; 048 private final SearchType type; 049 private final boolean descending; 050 051 SortKey(String column, SearchType type, boolean descending) { 052 this.column = column; 053 this.type = type; 054 this.descending = descending; 055 } 056 057 /** @return The column name. */ 058 public String column() { 059 return column; 060 } 061 062 /** @return The column's value type. */ 063 public SearchType type() { 064 return type; 065 } 066 067 /** @return <jk>true</jk> for descending order. */ 068 public boolean descending() { 069 return descending; 070 } 071 072 @Override /* Object */ 073 public String toString() { 074 return column + (descending ? ":desc" : ""); 075 } 076 } 077 078 private final Filter filter; 079 private final List<SortKey> sort; 080 private final List<String> view; 081 private final int position; 082 private final Integer limit; 083 private final CountPolicy counts; 084 private final BeanQueryContext<?> origin; 085 086 ResolvedQuery(Filter filter, List<SortKey> sort, List<String> view, int position, Integer limit, CountPolicy counts, BeanQueryContext<?> origin) { 087 this.filter = filter; 088 this.sort = List.copyOf(sort); 089 this.view = List.copyOf(view); 090 this.position = position; 091 this.limit = limit; 092 this.counts = counts; 093 this.origin = origin; 094 } 095 096 /** 097 * @return The resolved search tree, AND-ed and OR-ed across columns as the request's search expressed it; 098 * never <jk>null</jk> — an empty search resolves to an empty {@link Filter.And}, which matches every row. 099 */ 100 public Filter filter() { 101 return filter; 102 } 103 104 /** @return The sort keys, most significant first (empty for source order). */ 105 public List<SortKey> sort() { 106 return sort; 107 } 108 109 /** @return The view columns; never empty (defaults to every allowed column in declaration order). */ 110 public List<String> view() { 111 return view; 112 } 113 114 /** @return The zero-based offset of the first row (never negative). */ 115 public int position() { 116 return position; 117 } 118 119 /** @return The maximum number of rows, or <jk>null</jk> for all rows (only when {@code maxLimit} is unset). */ 120 public Integer limit() { 121 return limit; 122 } 123 124 /** @return The effective count policy: {@link CountPolicy#NONE}, {@link CountPolicy#MATCHED} or {@link CountPolicy#BOTH}. */ 125 public CountPolicy counts() { 126 return counts; 127 } 128 129 @Override /* Object */ 130 public String toString() { 131 return "ResolvedQuery(filter=" + filter + ", sort=" + sort + ", view=" + view + ", position=" + position + ", limit=" + limit + ", counts=" + counts + ")"; 132 } 133 134 /** 135 * @param context The context to check. 136 * @return <jk>true</jk> if the given context is the one that resolved this query (directly, or through a session built 137 * from it). Not public: an engine's rendering/execution code uses this only to confirm a caller-supplied query 138 * matches the context it is about to be used against; it is not part of this type's engine-neutral public contract. 139 */ 140 boolean isResolvedBy(BeanQueryContext<?> context) { 141 return origin == context; 142 } 143}