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.mock;
018
019import static java.util.Collections.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.IoUtils.*;
022import static org.apache.juneau.commons.utils.Shorts.*;
023import static org.apache.juneau.commons.utils.StringUtils.*;
024import static org.apache.juneau.commons.utils.StringUtils.isEmpty;
025import static org.apache.juneau.commons.utils.ThrowableUtils.*;
026import static org.apache.juneau.http.HttpHeaders.*;
027
028import java.io.*;
029import java.security.*;
030import java.util.*;
031import java.util.logging.*;
032
033import org.apache.http.*;
034import org.apache.juneau.marshall.marshaller.*;
035import org.apache.juneau.rest.server.util.*;
036
037import jakarta.servlet.*;
038import jakarta.servlet.http.*;
039
040/**
041 * A mutable implementation of {@link HttpServletRequest} for mocking purposes.
042 *
043 * <h5 class='section'>See Also:</h5><ul>
044 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestMock">juneau-rest-mock</a>
045 * </ul>
046 */
047@SuppressWarnings({
048   "java:S3776", // Branching is inherent to applyOverrides() and getQueryString(), which merge request parts and parameters from many sources.
049   "resource" // Streams returned to servlet container; lifecycle managed by the container
050})
051public class MockServletRequest implements HttpServletRequest {
052
053   /**
054    * Creates a new servlet request.
055    *
056    * Initialized with the following:
057    * <ul>
058    *    <li><c>"Accept: text/json5"</c>
059    *    <li><c>"Content-Type: text/json"</c>
060    * </ul>
061    *
062    * @return A new request.
063    */
064   public static MockServletRequest create() {
065      return new MockServletRequest();
066   }
067
068   /**
069    * Creates a new servlet request with the specified method name and request path.
070    *
071    * Initialized with the following:
072    * <ul>
073    *    <li><c>"Accept: text/json5"</c>
074    *    <li><c>"Content-Type: text/json"</c>
075    * </ul>
076    *
077    * @param method The HTTP method  name.
078    * @param uri The request path.
079    * @param pathArgs Optional path arguments.
080    *
081    * @return A new request.
082    */
083   public static MockServletRequest create(String method, String uri, Object...pathArgs) {
084      return create().method(method).uri(f(uri, pathArgs));
085   }
086
087   private String method = "GET";
088   private final Map<String,String[]> queryDataMap = map();
089   private Map<String,String[]> formDataMap;
090   private final Map<String,String[]> headerMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
091   private final Map<String,Object> attributeMap = map();
092   private String characterEncoding = "UTF-8";
093   private byte[] content = {};
094   private String protocol = "HTTP/1.1";
095   private String scheme = "http";
096   private String serverName = "localhost";
097   private int serverPort = 8080;
098   private String remoteAddr = "";
099   private String remoteHost = "";
100   private Locale locale = Locale.ENGLISH;
101   private int remotePort;
102   private String localName;
103   private String localAddr;
104   private int localPort;
105   private final Map<String,RequestDispatcher> requestDispatcher = map();
106   private ServletContext servletContext;
107   private DispatcherType dispatcherType;
108   private String authType;
109   private Cookie[] cookies;
110   private String pathInfo;
111   private String pathTranslated;
112   private String contextPath = "";
113   private String queryString;
114   private String remoteUser;
115   private Principal userPrincipal;
116   private String requestedSessionId;
117   private String requestURI;
118   private String servletPath = "";
119   private HttpSession httpSession = MockHttpSession.create();
120
121   private String uri = "";
122
123   private Set<String> roles = set();
124
125   private Level logLevel;
126
127   /**
128    * If the specified request is a
129    * {@link org.apache.juneau.rest.mock.classic.MockRestRequest}, applies any of the override values
130    * to this servlet request.
131    *
132    * @param req The request to copy overrides from.
133    * @return This object.
134    */
135   public MockServletRequest applyOverrides(HttpRequest req) {
136
137      if (req instanceof org.apache.juneau.rest.mock.classic.MockRestRequest req2) {
138         req2.getAttributeMap().forEach(this::attribute);
139         req2.getRequestDispatcherMap().forEach(this::requestDispatcher);
140         if (nn(req2.getCharacterEncoding()))
141            characterEncoding(req2.getCharacterEncoding());
142         if (nn(req2.getProtocol()))
143            protocol(req2.getProtocol());
144         if (nn(req2.getScheme()))
145            scheme(req2.getScheme());
146         if (nn(req2.getServerName()))
147            serverName(req2.getServerName());
148         if (nn(req2.getRemoteAddr()))
149            remoteAddr(req2.getRemoteAddr());
150         if (nn(req2.getRemoteHost()))
151            remoteHost(req2.getRemoteHost());
152         if (nn(req2.getLocalName()))
153            localName(req2.getLocalName());
154         if (nn(req2.getLocalAddr()))
155            localAddr(req2.getLocalAddr());
156         if (nn(req2.getPathInfo()))
157            pathInfo(req2.getPathInfo());
158         if (nn(req2.getPathTranslated()))
159            pathTranslated(req2.getPathTranslated());
160         if (nn(req2.getContextPath()))
161            contextPath(req2.getContextPath());
162         if (nn(req2.getQueryString()))
163            queryString(req2.getQueryString());
164         if (nn(req2.getRemoteUser()))
165            remoteUser(req2.getRemoteUser());
166         if (nn(req2.getRequestedSessionId()))
167            requestedSessionId(req2.getRequestedSessionId());
168         if (nn(req2.getRequestURI()))
169            requestURI(req2.getRequestURI());
170         if (nn(req2.getServletPath()))
171            servletPath(req2.getServletPath());
172         if (nn(req2.getAuthType()))
173            authType(req2.getAuthType());
174         if (nn(req2.getServerPort()))
175            serverPort(req2.getServerPort());
176         if (nn(req2.getRemotePort()))
177            remotePort(req2.getRemotePort());
178         if (nn(req2.getLocalPort()))
179            localPort(req2.getLocalPort());
180         if (nn(req2.getLocale()))
181            locale(req2.getLocale());
182         if (nn(req2.getServletContext()))
183            servletContext(req2.getServletContext());
184         if (nn(req2.getDispatcherType()))
185            dispatcherType(req2.getDispatcherType());
186         if (nn(req2.getCookies()))
187            cookies(req2.getCookies());
188         if (nn(req2.getUserPrincipal()))
189            userPrincipal(req2.getUserPrincipal());
190         if (nn(req2.getHttpSession()))
191            httpSession(req2.getHttpSession());
192         if (nn(req2.getRoles()))
193            roles(req2.getRoles());
194      }
195
196      return this;
197   }
198
199   /**
200    * Fluent setter.
201    *
202    * @param name Request attribute name.
203    * @param value Request attribute value.
204    * @return This object.
205    */
206   public MockServletRequest attribute(String name, Object value) {
207      this.attributeMap.put(name, value);
208      return this;
209   }
210
211   @Override /* Overridden from HttpServletRequest */
212   public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
213      return false;
214   }
215
216   /**
217    * Fluent setter.
218    *
219    * @param value The auth type.
220    * @return This object.
221    */
222   public MockServletRequest authType(String value) {
223      authType = value;
224      return this;
225   }
226
227   @Override /* Overridden from HttpServletRequest */
228   public String changeSessionId() {
229      return null;
230   }
231
232   /**
233    * Fluent setter.
234    *
235    * @param value The character encoding.
236    * @return This object.
237    */
238   public MockServletRequest characterEncoding(String value) {
239      characterEncoding = value;
240      return this;
241   }
242
243   /**
244    * Fluent setter.
245    *
246    * @param value
247    *    The body of the request.
248    *    <br>Can be any of the following data types:
249    *    <ul>
250    *       <li><code><jk>byte</jk>[]</code>
251    *       <li>{@link Reader}
252    *       <li>{@link InputStream}
253    *       <li>{@link CharSequence}
254    *    </ul>
255    *    Any other types are converted to a string using the <c>toString()</c> method.
256    * @return This object.
257    */
258   public MockServletRequest content(Object value) {
259      try {
260         if (value instanceof byte[] value2)
261            this.content = value2;
262         else if (value instanceof Reader value2)
263            this.content = readBytes(value2);
264         else if (value instanceof InputStream value2)
265            this.content = readBytes(value2);
266         else if (value instanceof CharSequence value2)
267            this.content = value2.toString().getBytes();
268         else if (nn(value))
269            this.content = value.toString().getBytes();
270      } catch (IOException e) {
271         throw toRex(e);
272      }
273      return this;
274   }
275
276   /**
277    * Fluent setter.
278    *
279    * @param value The context path.
280    * @return This object.
281    */
282   public MockServletRequest contextPath(String value) {
283      contextPath = value;
284      return this;
285   }
286
287   /**
288    * Fluent setter.
289    *
290    * @param value The cookies.
291    * @return This object.
292    */
293   public MockServletRequest cookies(Cookie[] value) {
294      cookies = value;
295      return this;
296   }
297
298   /**
299    * Fluent setter.
300    *
301    * @param value The dispatcher type.
302    * @return This object.
303    */
304   public MockServletRequest dispatcherType(DispatcherType value) {
305      dispatcherType = value;
306      return this;
307   }
308
309   @Override /* Overridden from HttpServletRequest */
310   public AsyncContext getAsyncContext() { return null; }
311
312   @Override /* Overridden from HttpServletRequest */
313   public Object getAttribute(String name) {
314      return attributeMap.get(name);
315   }
316
317   @Override /* Overridden from HttpServletRequest */
318   public Enumeration<String> getAttributeNames() { return Collections.enumeration(attributeMap.keySet()); }
319
320   @Override /* Overridden from HttpServletRequest */
321   public String getAuthType() { return authType; }
322
323   @Override /* Overridden from HttpServletRequest */
324   public String getCharacterEncoding() { return characterEncoding; }
325
326   @Override /* Overridden from HttpServletRequest */
327   public int getContentLength() { return content == null ? 0 : content.length; }
328
329   @Override /* Overridden from HttpServletRequest */
330   public long getContentLengthLong() { return content == null ? 0 : content.length; }
331
332   @Override /* Overridden from HttpServletRequest */
333   public String getContentType() { return getHeader("Content-Type"); }
334
335   @Override /* Overridden from HttpServletRequest */
336   public String getContextPath() { return contextPath; }
337
338   @Override /* Overridden from HttpServletRequest */
339   public Cookie[] getCookies() { return cookies; }
340
341   @Override /* Overridden from HttpServletRequest */
342   public long getDateHeader(String name) {
343      var s = getHeader(name);
344      if (s == null)
345         return 0;
346      var zonedDateTimeOpt = date(s).asZonedDateTime();
347      return zonedDateTimeOpt.isPresent() ? zonedDateTimeOpt.get().toInstant().toEpochMilli() : 0;
348   }
349
350   @Override /* Overridden from HttpServletRequest */
351   public DispatcherType getDispatcherType() { return dispatcherType; }
352
353   @Override /* Overridden from HttpServletRequest */
354   public String getHeader(String name) {
355      var s = headerMap.get(name);
356      return s == null || s.length == 0 ? null : s[0];
357   }
358
359   @Override /* Overridden from HttpServletRequest */
360   public Enumeration<String> getHeaderNames() { return Collections.enumeration(headerMap.keySet()); }
361
362   @Override /* Overridden from HttpServletRequest */
363   public Enumeration<String> getHeaders(String name) {
364      var s = headerMap.get(name);
365      return Collections.enumeration(l(s == null ? new String[0] : s));
366   }
367
368   @Override /* Overridden from HttpServletRequest */
369   public ServletInputStream getInputStream() throws IOException {
370      if (nn(formDataMap))
371         content = UrlEncoding.of(formDataMap).getBytes();
372      return new BoundedServletInputStream(new ByteArrayInputStream(content), Integer.MAX_VALUE);
373   }
374
375   @Override /* Overridden from HttpServletRequest */
376   public int getIntHeader(String name) {
377      var s = getHeader(name);
378      return ie(s) ? 0 : Integer.parseInt(s);
379   }
380
381   @Override /* Overridden from HttpServletRequest */
382   public String getLocalAddr() { return localAddr; }
383
384   @Override /* Overridden from HttpServletRequest */
385   public Locale getLocale() { return locale; }
386
387   @Override /* Overridden from HttpServletRequest */
388   public Enumeration<Locale> getLocales() { return Collections.enumeration(l(locale)); }
389
390   @Override /* Overridden from HttpServletRequest */
391   public String getLocalName() { return localName; }
392
393   @Override /* Overridden from HttpServletRequest */
394   public int getLocalPort() { return localPort; }
395
396   @Override /* Overridden from HttpServletRequest */
397   public String getMethod() { return method; }
398
399   @Override /* Overridden from HttpServletRequest */
400   public String getParameter(String name) {
401      var s = getParameterMap().get(name);
402      return s == null || s.length == 0 ? null : s[0];
403   }
404
405   @Override /* Overridden from HttpServletRequest */
406   public Map<String,String[]> getParameterMap() {
407      if (eqic("POST", method)) {
408         if (formDataMap == null) {
409            var listMap = RestUtils.parseQuery(read(content));
410            formDataMap = map();
411            for (var e : listMap.entrySet()) {
412               if (e.getValue() == null)
413                  formDataMap.put(e.getKey(), null);
414               else
415                  formDataMap.put(e.getKey(), array(e.getValue(), String.class));
416            }
417         }
418         return formDataMap;
419      }
420      return queryDataMap;
421   }
422
423   @Override /* Overridden from HttpServletRequest */
424   public Enumeration<String> getParameterNames() { return enumeration(toList(getParameterMap().keySet())); }
425
426   @Override /* Overridden from HttpServletRequest */
427   public String[] getParameterValues(String name) {
428      return getParameterMap().get(name);
429   }
430
431   @Override /* Overridden from HttpServletRequest */
432   public Part getPart(String name) throws IOException, ServletException {
433      return null;
434   }
435
436   @Override /* Overridden from HttpServletRequest */
437   public Collection<Part> getParts() throws IOException, ServletException { return Collections.emptyList(); }
438
439   @Override /* Overridden from HttpServletRequest */
440   public String getPathInfo() {
441      if (pathInfo == null) {
442         pathInfo = getRequestURI();
443         if (ine(contextPath))
444            pathInfo = pathInfo.substring(contextPath.length());
445         if (ine(servletPath))
446            pathInfo = pathInfo.substring(servletPath.length());
447      }
448      return nie(urlDecode(pathInfo));
449   }
450
451   @Override /* Overridden from HttpServletRequest */
452   public String getPathTranslated() {
453      if (pathTranslated == null)
454         pathTranslated = "/mock-path" + getPathInfo();
455      return pathTranslated;
456   }
457
458   @Override /* Overridden from HttpServletRequest */
459   public String getProtocol() { return protocol; }
460
461   @Override
462   public String getProtocolRequestId() { return null; }
463
464   @Override /* Overridden from HttpServletRequest */
465   public String getQueryString() {
466      if (queryString == null) {
467         if (queryDataMap.isEmpty())
468            queryString = "";
469         else {
470            var sb = new StringBuilder();
471            queryDataMap.forEach((k, v) -> {
472               if (v == null)
473                  sb.append(sb.isEmpty() ? "" : "&").append(urlEncode(k));
474               else
475                  for (var v2 : v)
476                     sb.append(sb.isEmpty() ? "" : "&").append(urlEncode(k)).append('=').append(urlEncode(v2));
477            });
478            queryString = sb.toString();
479         }
480      }
481      return isEmpty(queryString) ? null : queryString;
482   }
483
484   @Override /* Overridden from HttpServletRequest */
485   public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream(), characterEncoding)); }
486
487   @Override /* Overridden from HttpServletRequest */
488   public String getRemoteAddr() { return remoteAddr; }
489
490   @Override /* Overridden from HttpServletRequest */
491   public String getRemoteHost() { return remoteHost; }
492
493   @Override /* Overridden from HttpServletRequest */
494   public int getRemotePort() { return remotePort; }
495
496   @Override /* Overridden from HttpServletRequest */
497   public String getRemoteUser() { return remoteUser; }
498
499   @Override /* Overridden from HttpServletRequest */
500   public RequestDispatcher getRequestDispatcher(String path) {
501      return requestDispatcher.get(path);
502   }
503
504   @Override /* Overridden from HttpServletRequest */
505   public String getRequestedSessionId() { return requestedSessionId; }
506
507   @Override
508   public String getRequestId() { return null; }
509
510   @Override /* Overridden from HttpServletRequest */
511   public String getRequestURI() {
512      if (requestURI == null) {
513         requestURI = uri;
514         requestURI = requestURI.replaceAll("^\\w+\\:\\/\\/[^\\/]+", "").replaceAll("\\?.*$", "");
515      }
516      return requestURI;
517   }
518
519   @Override /* Overridden from HttpServletRequest */
520   public StringBuffer getRequestURL() { return new StringBuffer(uri.replaceAll("\\?.*$", "")); }
521
522   @Override /* Overridden from HttpServletRequest */
523   public String getScheme() { return scheme; }
524
525   @Override /* Overridden from HttpServletRequest */
526   public String getServerName() { return serverName; }
527
528   @Override /* Overridden from HttpServletRequest */
529   public int getServerPort() { return serverPort; }
530
531   @Override
532   public ServletConnection getServletConnection() { return null; }
533
534   @Override /* Overridden from HttpServletRequest */
535   public ServletContext getServletContext() { return servletContext; }
536
537   @Override /* Overridden from HttpServletRequest */
538   public String getServletPath() { return servletPath; }
539
540   @Override /* Overridden from HttpServletRequest */
541   public HttpSession getSession() { return httpSession; }
542
543   @Override /* Overridden from HttpServletRequest */
544   public HttpSession getSession(boolean create) {
545      return httpSession;
546   }
547
548   @Override /* Overridden from HttpServletRequest */
549   public Principal getUserPrincipal() { return userPrincipal; }
550
551   /**
552    * Fluent setter.
553    *
554    * @param name Header name.
555    * @param value
556    *    Header value.
557    *    <br>The value is converted to a simple string using {@link Object#toString()}.
558    * @return This object.
559    */
560   public MockServletRequest header(String name, Object value) {
561      if (nn(value)) {
562         var v1 = (value instanceof String[] stringArray) ? stringArray : a(value.toString());
563         var v2 = headerMap.get(name);
564         var v3 = combine(v2, v1);
565         headerMap.put(name, v3);
566      }
567      return this;
568   }
569
570   /**
571    * Fluent setter.
572    *
573    * @param value The HTTP session.
574    * @return This object.
575    */
576   public MockServletRequest httpSession(HttpSession value) {
577      httpSession = value;
578      return this;
579   }
580
581   @Override /* Overridden from HttpServletRequest */
582   public boolean isAsyncStarted() { return false; }
583
584   @Override /* Overridden from HttpServletRequest */
585   public boolean isAsyncSupported() { return false; }
586
587   @Override /* Overridden from HttpServletRequest */
588   public boolean isRequestedSessionIdFromCookie() { return false; }
589
590   @Override /* Overridden from HttpServletRequest */
591   public boolean isRequestedSessionIdFromURL() { return false; }
592
593   @Override /* Overridden from HttpServletRequest */
594   public boolean isRequestedSessionIdValid() { return false; }
595
596   @Override /* Overridden from HttpServletRequest */
597   public boolean isSecure() { return false; }
598
599   @Override /* Overridden from HttpServletRequest */
600   public boolean isUserInRole(String role) {
601      return roles.contains(role);
602   }
603
604   /**
605    * Fluent setter.
606    *
607    * @param value The local address.
608    * @return This object.
609    */
610   public MockServletRequest localAddr(String value) {
611      localAddr = value;
612      return this;
613   }
614
615   /**
616    * Fluent setter.
617    *
618    * @param value The locale.
619    * @return This object.
620    */
621   public MockServletRequest locale(Locale value) {
622      locale = value;
623      return this;
624   }
625
626   /**
627    * Fluent setter.
628    *
629    * @param value The local name.
630    * @return This object.
631    */
632   public MockServletRequest localName(String value) {
633      localName = value;
634      return this;
635   }
636
637   /**
638    * Fluent setter.
639    *
640    * @param value The local port.
641    * @return This object.
642    */
643   public MockServletRequest localPort(int value) {
644      localPort = value;
645      return this;
646   }
647
648   @Override /* Overridden from HttpServletRequest */
649   public void login(String username, String password) throws ServletException {
650      // No-op: Mock implementation - full functionality not required
651   }
652
653   @Override /* Overridden from HttpServletRequest */
654   public void logout() throws ServletException {
655      // No-op: Mock implementation - full functionality not required
656   }
657
658   /**
659    * Fluent setter.
660    *
661    * @param value The method name for this request.
662    * @return This object.
663    */
664   public MockServletRequest method(String value) {
665      method = value;
666      return this;
667   }
668
669   /**
670    * Enabled debug mode on this request.
671    *
672    * <p>
673    * Prevents errors from being logged on the server side if no-trace per-request is enabled.
674    *
675    * @param value The enable flag value.
676    * @return This object.
677    */
678   public MockServletRequest noTrace(boolean value) {
679      if (value)
680         header("No-Trace", "true");
681      return this;
682   }
683
684   /**
685    * Fluent setter.
686    *
687    * @param value The path info.
688    * @return This object.
689    */
690   public MockServletRequest pathInfo(String value) {
691      pathInfo = value;
692      return this;
693   }
694
695   /**
696    * Fluent setter.
697    *
698    * @param value The path translated.
699    * @return This object.
700    */
701   public MockServletRequest pathTranslated(String value) {
702      pathTranslated = value;
703      return this;
704   }
705
706   /**
707    * Adds the specified parent path variables to this servlet request.
708    *
709    * <p>
710    * See {@link org.apache.juneau.rest.mock.classic.MockRestClient.Builder#pathVars(Map)} for an example.
711    *
712    * @param pathVars The parent path variables to add. Can be <jk>null</jk> (no-op).
713    * @return This object.
714    * @see org.apache.juneau.rest.mock.classic.MockRestClient.Builder#pathVars(Map)
715    */
716   public MockServletRequest pathVars(Map<String,String> pathVars) {
717      if (nn(pathVars))
718         this.attributeMap.put("juneau.pathVars", new TreeMap<>(pathVars));
719      return this;
720   }
721
722   /**
723    * Add resolved path variables to this client.
724    *
725    * <p>
726    * Identical to {@link #pathVars(Map)} but allows you to specify as a list of key/value pairs.
727    *
728    * @param pairs The key/value pairs.  Must be an even number of parameters.
729    * @return This object.
730    */
731   public MockServletRequest pathVars(String...pairs) {
732      return pathVars(mb(String.class, String.class).addPairs((Object[])pairs).build());
733   }
734
735   /**
736    * Fluent setter.
737    *
738    * @param value The protocol.
739    * @return This object.
740    */
741   public MockServletRequest protocol(String value) {
742      protocol = value;
743      return this;
744   }
745
746   /**
747    * Fluent setter.
748    *
749    * @param value The query string.
750    * @return This object.
751    */
752   public MockServletRequest queryString(String value) {
753      queryString = value;
754      return this;
755   }
756
757   /**
758    * Fluent setter.
759    *
760    * @param value The remote address.
761    * @return This object.
762    */
763   public MockServletRequest remoteAddr(String value) {
764      remoteAddr = value;
765      return this;
766   }
767
768   /**
769    * Fluent setter.
770    *
771    * @param value The remote port.
772    * @return This object.
773    */
774   public MockServletRequest remoteHost(String value) {
775      remoteHost = value;
776      return this;
777   }
778
779   /**
780    * Fluent setter.
781    *
782    * @param value The remote port.
783    * @return This object.
784    */
785   public MockServletRequest remotePort(int value) {
786      remotePort = value;
787      return this;
788   }
789
790   /**
791    * Fluent setter.
792    *
793    * @param value The remote user.
794    * @return This object.
795    */
796   public MockServletRequest remoteUser(String value) {
797      remoteUser = value;
798      return this;
799   }
800
801   @Override /* Overridden from HttpServletRequest */
802   public void removeAttribute(String name) {
803      this.attributeMap.remove(name);
804   }
805
806   /**
807    * Fluent setter.
808    *
809    * @param name The path to resolve.
810    * @param value The request dispatcher.
811    * @return This object.
812    */
813   public MockServletRequest requestDispatcher(String name, RequestDispatcher value) {
814      this.requestDispatcher.put(name, value);
815      return this;
816   }
817
818   /**
819    * Fluent setter.
820    *
821    * @param value The requested session ID.
822    * @return This object.
823    */
824   public MockServletRequest requestedSessionId(String value) {
825      requestedSessionId = value;
826      return this;
827   }
828
829   /**
830    * Fluent setter.
831    *
832    * @param value The request URI.
833    * @return This object.
834    */
835   public MockServletRequest requestURI(String value) {
836      requestURI = value;
837      return this;
838   }
839
840   /**
841    * Sets the specified role on this request.
842    *
843    * @param role The role to add to this request (e.g. <js>"ROLE_ADMIN"</js>).
844    * @return This object.
845    */
846   public MockServletRequest role(String role) {
847      this.roles = set(role);
848      return this;
849   }
850
851   /**
852    * Sets the specified roles on this request.
853    *
854    * @param roles The roles to add to this request (e.g. <js>"ROLE_ADMIN"</js>).
855    * @return This object.
856    */
857   public MockServletRequest roles(String...roles) {
858      this.roles = set(roles);
859      return this;
860   }
861
862   /**
863    * Fluent setter.
864    *
865    * @param value The scheme.
866    * @return This object.
867    */
868   public MockServletRequest scheme(String value) {
869      scheme = value;
870      return this;
871   }
872
873   /**
874    * Fluent setter.
875    *
876    * @param value The server name.
877    * @return This object.
878    */
879   public MockServletRequest serverName(String value) {
880      serverName = value;
881      return this;
882   }
883
884   /**
885    * Fluent setter.
886    *
887    * @param value The server port.
888    * @return This object.
889    */
890   public MockServletRequest serverPort(int value) {
891      serverPort = value;
892      return this;
893   }
894
895   /**
896    * Fluent setter.
897    *
898    * @param value The servlet context.
899    * @return This object.
900    */
901   public MockServletRequest servletContext(ServletContext value) {
902      servletContext = value;
903      return this;
904   }
905
906   /**
907    * Fluent setter.
908    *
909    * @param value The servlet path.
910    * @return This object.
911    */
912   public MockServletRequest servletPath(String value) {
913      servletPath = value;
914      return this;
915   }
916
917   @Override /* Overridden from HttpServletRequest */
918   public void setAttribute(String name, Object o) {
919      this.attributeMap.put(name, o);
920   }
921
922   @Override /* Overridden from HttpServletRequest */
923   public void setCharacterEncoding(String characterEncoding) throws UnsupportedEncodingException { this.characterEncoding = characterEncoding; }
924
925   @Override /* Overridden from HttpServletRequest */
926   public AsyncContext startAsync() throws IllegalStateException {
927      return null;
928   }
929
930   @Override /* Overridden from HttpServletRequest */
931   public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
932      return null;
933   }
934
935   @Override /* Overridden from HttpServletRequest */
936   public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException {
937      return null;
938   }
939
940   /**
941    * Fluent setter.
942    *
943    * @param uri The URI of the request.
944    * @return This object.
945    */
946   public MockServletRequest uri(String uri) {
947      uri = emptyIfNull(uri);
948      this.uri = uri;
949
950      if (uri.indexOf('?') != -1) {
951         var qs = uri.substring(uri.indexOf('?') + 1);
952         if (qs.indexOf('#') != -1)
953            qs = qs.substring(0, qs.indexOf('#'));
954         queryString = qs;
955         var listMap = RestUtils.parseQuery(qs);
956         for (var e : listMap.entrySet()) {
957            if (e.getValue() == null)
958               queryDataMap.put(e.getKey(), null);
959            else
960               queryDataMap.put(e.getKey(), array(e.getValue(), String.class));
961         }
962      }
963
964      return this;
965   }
966
967   /**
968    * Fluent setter.
969    *
970    * @param value The user principal.
971    * @return This object.
972    */
973   public MockServletRequest userPrincipal(Principal value) {
974      userPrincipal = value;
975      return this;
976   }
977
978   /**
979    * Sets the JUL log level to transiently apply to the target resource's logger for the duration of this
980    * request's dispatch.
981    *
982    * <p>
983    * This is a pure data holder read by the mock transport at the actual dispatch call site, which is responsible
984    * for saving the resource logger's current level, applying this value, invoking {@code RestContext.execute(...)},
985    * and restoring the prior level in a {@code finally} block. The mutation is therefore transient and never leaks
986    * across requests or tests. Replaces the removed {@code debug(boolean)} flag, which set a now-meaningless
987    * {@code Debug} request header.
988    *
989    * @param value The log level to apply during dispatch, or <jk>null</jk> for no override.
990    * @return This object.
991    */
992   public MockServletRequest logLevel(Level value) {
993      logLevel = value;
994      return this;
995   }
996
997   /**
998    * Returns the log level set via {@link #logLevel(Level)}.
999    *
1000    * @return The log level to apply during dispatch, or <jk>null</jk> if not set.
1001    */
1002   public Level getLogLevel() {
1003      return logLevel;
1004   }
1005}