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