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.http; 018 019import org.apache.juneau.commons.Schema; 020import org.apache.juneau.commons.beanquery.*; 021 022/** 023 * The HTTP-binding {@link Request @Request} bean for {@link BeanQuery}-style list queries. 024 * 025 * <p> 026 * This is a concrete {@link BeanQuery} subclass — not an interface — so it <b>is</b> the carrier the query 027 * engine consumes. Binding a list REST operation to a single {@link BeanQueryRequest} argument populates the inherited 028 * {@code search} / {@code view} / {@code sort} / {@code position} / {@code limit} / {@code opts} fields straight from 029 * the request, with no adapter step. 030 * </p> 031 * 032 * <h5 class='section'>Example:</h5> 033 * <p class='bjava'> 034 * <jk>private static final</jk> InMemoryBeanQueryContext<Row> <jsf>ROWS</jsf> = InMemoryBeanQueryContext 035 * .<jsm>create</jsm>(Row.<jk>class</jk>) 036 * .build(); 037 * 038 * <ja>@RestGet</ja> 039 * <jk>public</jk> List<Row> getList(BeanQueryRequest <jv>q</jv>) { 040 * <jk>try</jk> (InMemoryBeanQuerySession<Row> <jv>s</jv> = <jsf>ROWS</jsf>.getSession(<jv>rows</jv>)) { 041 * <jk>return</jk> <jv>s</jv>.find(<jv>q</jv>).rows(); 042 * } 043 * } 044 * </p> 045 * 046 * <h5 class='section'>How it binds</h5> 047 * <p> 048 * The type is annotated {@link Request @Request}, so a parameter of this type needs no second <ja>@Request</ja> on the 049 * parameter. Because it is a concrete class, the framework instantiates it with its public no-arg constructor and 050 * calls the JavaBeans setter matching each annotated getter (so {@link #getOpts()} is populated via 051 * {@link BeanQuery#setOpts(String) setOpts}). Each getter is a single-valued {@link Query @Query}: exactly one 052 * {@code search} parameter and one {@code opts} parameter bind — the {@code $}-language and the 053 * {@code key=value} opts bag live <b>inside</b> those single strings and are decoded by the session, not on the wire. 054 * 055 * <h5 class='section'>Wire names</h5> 056 * <p> 057 * Every {@code @Query} name equals its Java property name: {@code search}, {@code view}, {@code sort}, 058 * {@code position}, {@code limit}, {@code opts}. A resource that wants a different name (or extra parameters) 059 * subclasses this bean and redeclares the getter with its own {@link Query @Query} name. 060 * 061 * <h5 class='section'>Swagger</h5> 062 * <p> 063 * Each getter carries a {@link Schema @Schema} description, so an operation taking this bean documents all six 064 * parameters automatically; {@code position} also declares {@code minimum=0}, so a negative value fails part 065 * validation (HTTP 400) before the query runs. {@code limit} has no minimum: the context clamps it instead. 066 * 067 * <h5 class='section'>Saved-view clash</h5> 068 * <p> 069 * {@link #getView()} is a <b>column projection</b> (field names), not a saved-view id. Saved-view CRUD endpoints must 070 * <b>not</b> take {@link BeanQueryRequest}; they keep their own {@link Query @Query}(<js>"view"</js>) id parameter, or 071 * the wrong meaning of {@code view} would bind. 072 * 073 * <h5 class='section'>Errors</h5> 074 * <p> 075 * A malformed {@code search} (or any other clause the session rejects) throws {@link BeanQuerySyntaxException}, 076 * which {@code RestContext.convertThrowable} maps to an HTTP 400 carrying an {@value #ERROR_HEADER} header and, 077 * with problem details on, a {@code "code"} member: 078 * </p> 079 * <p class='bcode'> 080 * GET /people?search=pasword=x&sort=name,age:desc&view=name,age&position=20&limit=10 081 * 082 * HTTP/1.1 400 Bad Request 083 * X-BeanQuery-Error: UNKNOWN_COLUMN 084 * 085 * { 086 * "status": 400, 087 * "title": "Bad Request", 088 * "detail": "Unknown column 'pasword' in search.", 089 * "code": "UNKNOWN_COLUMN" 090 * } 091 * </p> 092 * 093 * <h5 class='section'>See Also:</h5><ul> 094 * <li class='jc'>{@link BeanQuery} 095 * <li class='ja'>{@link Request} 096 * <li class='ja'>{@link Query} 097 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Request">@Request</a> 098 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsBeanQuery">BeanQuery Package</a> 099 * </ul> 100 * 101 * @since 10.0.0 102 */ 103@Request 104public class BeanQueryRequest extends BeanQuery { 105 106 /** 107 * The response header {@code RestContext.convertThrowable} sets to a {@link BeanQuerySyntaxException}'s 108 * {@link BeanQuerySyntaxException#code() code()} name on every 400 it produces from that exception (with or without 109 * problem details). 110 */ 111 public static final String ERROR_HEADER = "X-BeanQuery-Error"; 112 113 @Override /* BeanQuery */ 114 @Query(schema=@Schema(d="Search expression: comma-separated column=expression clauses, e.g. " 115 + "name=$contains(smith),age=$gte(18). See the BeanQuery search language docs: " 116 + "https://juneau.apache.org/docs/topics/JuneauCommonsBeanQuery")) 117 public String getSearch() { 118 return super.getSearch(); 119 } 120 121 @Override /* BeanQuery */ 122 @Query(schema=@Schema(d="Comma-separated columns to return, in order.")) 123 public String getView() { 124 return super.getView(); 125 } 126 127 @Override /* BeanQuery */ 128 @Query(schema=@Schema(d="Comma-separated sort columns; append \":desc\" (or \"-\") for descending.")) 129 public String getSort() { 130 return super.getSort(); 131 } 132 133 @Override /* BeanQuery */ 134 @Query(schema=@Schema(d="0-based index of the first row.", minimum="0")) 135 public Integer getPosition() { 136 return super.getPosition(); 137 } 138 139 @Override /* BeanQuery */ 140 @Query(schema=@Schema(d="Maximum rows to return. Defaults and caps are set by the server.")) 141 public Integer getLimit() { 142 return super.getLimit(); 143 } 144 145 @Override /* BeanQuery */ 146 @Query(schema=@Schema(d="Options as key=value clauses, e.g. counts=true.")) 147 public String getOpts() { 148 return super.getOpts(); 149 } 150}