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