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 &mdash; not an interface &mdash; 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&lt;Row&gt; <jsf>ROWS</jsf> = InMemoryBeanQueryContext
035 *       .<jsm>create</jsm>(Row.<jk>class</jk>)
036 *       .build();
037 *
038 *    <ja>@RestGet</ja>
039 *    <jk>public</jk> List&lt;Row&gt; getList(BeanQueryRequest <jv>q</jv>) {
040 *       <jk>try</jk> (InMemoryBeanQuerySession&lt;Row&gt; <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 &mdash; 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&nbsp;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&nbsp;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&amp;sort=name,age:desc&amp;view=name,age&amp;position=20&amp;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}