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.rest.server.httppart;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.util.*;
023
024import org.apache.juneau.commons.collections.*;
025import org.apache.juneau.commons.svl.*;
026import org.apache.juneau.marshall.collections.*;
027import org.apache.juneau.rest.server.*;
028
029import jakarta.servlet.http.*;
030
031/**
032 * Represents the attributes in an HTTP request.
033 *
034 * <p>
035 *    The {@link RequestAttributes} object is the API for accessing the standard servlet attributes on an HTTP request
036 *    (i.e. {@link jakarta.servlet.ServletRequest#getAttribute(String)}).
037 * </p>
038 *
039 * <p class='bjava'>
040 *    <ja>@RestPost</ja>(...)
041 *    <jk>public</jk> Object myMethod(RequestAttributes <jv>attributes</jv>) {...}
042 * </p>
043 *
044 * <h5 class='figure'>Example:</h5>
045 * <p class='bjava'>
046 *    <ja>@RestPost</ja>(...)
047 *    <jk>public</jk> Object myMethod(RequestAttributes <jv>attributes</jv>) {
048 *
049 *       <jc>// Add a default value.</jc>
050 *       <jv>attributes</jv>.addDefault(<js>"Foo"</js>, 123);
051 *
052 *       <jc>// Get an attribute value as a POJO.</jc>
053 *       UUID <jv>etag</jv> = <jv>attributes</jv>.get(<js>"ETag"</js>).as(UUID.<jk>class</jk>).orElse(<jk>null</jk>);
054 *    }
055 * </p>
056 *
057 * <p>
058 *    Some important methods on this class are:
059 * </p>
060 * <ul class='javatree'>
061 *    <li class='jc'>{@link RequestHeaderList}
062 *    <ul class='spaced-list'>
063 *       <li>Methods for retrieving request attributes:
064 *       <ul class='javatreec'>
065 *          <li class='jm'>{@link RequestAttributes#contains(String...) contains(String...)}
066 *          <li class='jm'>{@link RequestAttributes#containsAny(String...) containsAny(String...)}
067 *          <li class='jm'>{@link RequestAttributes#get(String) get(String)}
068 *          <li class='jm'>{@link RequestAttributes#getAll() getAll()}
069 *       </ul>
070 *       <li>Methods for overriding request attributes:
071 *       <ul class='javatreec'>
072 *          <li class='jm'>{@link RequestAttributes#addDefault(List) addDefault(List)}
073 *          <li class='jm'>{@link RequestAttributes#addDefault(NamedAttribute...) addDefault(NamedAttribute...)}
074 *          <li class='jm'>{@link RequestAttributes#addDefault(NamedAttributeMap) addDefault(NamedAttributeMap)}
075 *          <li class='jm'>{@link RequestAttributes#addDefault(String,Object) addDefault(String,Object)}
076 *          <li class='jm'>{@link RequestAttributes#remove(NamedAttribute...) remove(NamedAttribute...)}
077 *          <li class='jm'>{@link RequestAttributes#remove(String...) remove(String...)}
078 *          <li class='jm'>{@link RequestAttributes#set(NamedAttribute...) set(NamedAttribute...)}
079 *          <li class='jm'>{@link RequestAttributes#set(String,Object) set(String,Object)}
080 *       </ul>
081 *       <li>Other methods:
082 *       <ul class='javatreec'>
083 *          <li class='jm'>{@link RequestAttributes#asMap() asMap()}
084 *       </ul>
085 *    </ul>
086 * </ul>
087 *
088 * <p>
089 *    Modifications made to request attributes through the <c>RequestAttributes</c> bean are automatically reflected in
090 *    the underlying servlet request attributes making it possible to mix the usage of both APIs.
091 * </p>
092 *
093 * <h5 class='section'>See Also:</h5><ul>
094 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerHttpParts">HTTP parts in REST servers</a>
095 * </ul>
096 */
097@SuppressWarnings({
098   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
099})
100public class RequestAttributes {
101
102   final RestRequest req;
103   final HttpServletRequest sreq;
104   final VarResolverSession vs;
105
106   /**
107    * Constructor.
108    *
109    * @param req The request creating this bean.  Must not be <jk>null</jk>.
110    */
111   public RequestAttributes(RestRequest req) {
112      this.req = req;
113      this.sreq = req.getHttpServletRequest();
114      this.vs = req.getVarResolverSession();
115   }
116
117   /**
118    * Adds default entries to the request attributes.
119    *
120    * @param pairs
121    *    The default entries.
122    *    <br>Can be <jk>null</jk> (no-op).
123    * @return This object.
124    */
125   public RequestAttributes addDefault(List<NamedAttribute> pairs) {
126      if (pairs == null)
127         return this;
128      for (var p : pairs)
129         if (sreq.getAttribute(p.getName()) == null) {
130            Object o = p.getValue();
131            sreq.setAttribute(p.getName(), o instanceof String ? vs.resolve(o) : o);
132         }
133      return this;
134   }
135
136   /**
137    * Adds default entries to the request attributes.
138    *
139    * @param pairs
140    *    The default entries.
141    *    <br>Can be <jk>null</jk> (no-op).
142    * @return This object.
143    */
144   public RequestAttributes addDefault(NamedAttribute...pairs) {
145      return addDefault(l(pairs));
146   }
147
148   /**
149    * Adds default entries to the request attributes.
150    *
151    * @param pairs
152    *    The default entries.
153    *    <br>Can be <jk>null</jk> (no-op).
154    * @return This object.
155    */
156   public RequestAttributes addDefault(NamedAttributeMap pairs) {
157      if (pairs == null)
158         return this;
159      for (var p : pairs.values())
160         if (sreq.getAttribute(p.getName()) == null) {
161            Object o = p.getValue();
162            sreq.setAttribute(p.getName(), o instanceof String ? vs.resolve(o) : o);
163         }
164      return this;
165   }
166
167   /**
168    * Adds a default entry to the request attributes.
169    *
170    * @param name The name.
171    * @param value The value.
172    * @return This object.
173    */
174   public RequestAttributes addDefault(String name, Object value) {
175      return addDefault(BasicNamedAttribute.of(name, value));
176   }
177
178   /**
179    * Returns the request attributes as a map.
180    *
181    * @return The request attributes as a map.  Never <jk>null</jk>.
182    */
183   public Map<String,Object> asMap() {
184      var m = new JsonMap();
185      Enumeration<String> e = sreq.getAttributeNames();
186      while (e.hasMoreElements()) {
187         var n = e.nextElement();
188         m.put(n, sreq.getAttribute(n));
189      }
190      return m;
191   }
192
193   /**
194    * Returns <jk>true</jk> if the attributes with the specified names are present.
195    *
196    * @param names The attribute names.  Must not be <jk>null</jk>.
197    * @return <jk>true</jk> if the parameters with the specified names are present.
198    */
199   public boolean contains(String...names) {
200      reqnn("names", names);
201      for (var n : names)
202         if (sreq.getAttribute(n) == null)
203            return false;
204      return true;
205   }
206
207   /**
208    * Returns <jk>true</jk> if the attribute with any of the specified names are present.
209    *
210    * @param names The attribute names.  Must not be <jk>null</jk>.
211    * @return <jk>true</jk> if the attribute with any of the specified names are present.
212    */
213   public boolean containsAny(String...names) {
214      reqnn("names", names);
215      for (var n : names)
216         if (nn(sreq.getAttribute(n)))
217            return true;
218      return false;
219   }
220
221   /**
222    * Returns the request attribute with the specified name.
223    *
224    * @param name The attribute name.
225    * @return The request attribute, never <jk>null</jk>.
226    */
227   public RequestAttribute get(String name) {
228      return new RequestAttribute(req, name, sreq.getAttribute(name));
229   }
230
231   /**
232    * Returns all the attribute on this request.
233    *
234    * @return All the attribute on this request.
235    */
236   public List<RequestAttribute> getAll() {
237      List<RequestAttribute> l = list();
238      Enumeration<String> e = sreq.getAttributeNames();
239      while (e.hasMoreElements()) {
240         var n = e.nextElement();
241         l.add(new RequestAttribute(req, n, sreq.getAttribute(n)));
242      }
243      return l;
244   }
245
246   /**
247    * Remove request attributes.
248    *
249    * @param attributes The attributes to remove.  Must not be <jk>null</jk>.
250    * @return This object.
251    */
252   public RequestAttributes remove(NamedAttribute...attributes) {
253      for (var p : attributes)
254         remove(p.getName());
255      return this;
256   }
257
258   /**
259    * Remove request attributes.
260    *
261    * @param name The attribute names.  Must not be <jk>null</jk>.
262    * @return This object.
263    */
264   public RequestAttributes remove(String...name) {
265      reqnn("name", name);
266      for (var n : name) {
267         sreq.removeAttribute(n);
268      }
269      return this;
270   }
271
272   /**
273    * Sets request attributes.
274    *
275    * @param attributes The parameters to set.  Must not be <jk>null</jk> or contain <jk>null</jk>.
276    * @return This object.
277    */
278   public RequestAttributes set(NamedAttribute...attributes) {
279      reqnn("attributes", attributes);
280      for (var p : attributes)
281         set(p.getName(), p.getValue());
282      return this;
283   }
284
285   /**
286    * Sets a request attribute.
287    *
288    * @param name The attribute name.  Must not be <jk>null</jk>.
289    * @param value
290    *    The attribute value.
291    *    <br>Can be <jk>null</jk> (removes the attribute, per {@code HttpServletRequest.setAttribute} semantics).
292    * @return This object.
293    */
294   public RequestAttributes set(String name, Object value) {
295      reqnn("name", name);
296      sreq.setAttribute(name, value);
297      return this;
298   }
299
300   protected FluentMap<String,Object> properties() {
301      // @formatter:off
302      return filteredBeanPropertyMap()
303         .a("attributes", asMap());
304      // @formatter:on
305   }
306
307   @Override /* Overridden from Object */
308   public String toString() {
309      return r(properties());
310   }
311}