All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.phloc.web.mock.MockHttpServletResponse Maven / Gradle / Ivy

/**
 * Copyright (C) 2006-2015 phloc systems
 * http://www.phloc.com
 * office[at]phloc[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.phloc.web.mock;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.phloc.commons.IHasLocale;
import com.phloc.commons.ValueEnforcer;
import com.phloc.commons.annotations.Nonempty;
import com.phloc.commons.annotations.ReturnsMutableCopy;
import com.phloc.commons.charset.CCharset;
import com.phloc.commons.charset.CharsetManager;
import com.phloc.commons.collections.ArrayHelper;
import com.phloc.commons.collections.ContainerHelper;
import com.phloc.commons.collections.multimap.IMultiMapSetBased;
import com.phloc.commons.collections.multimap.MultiHashMapLinkedHashSetBased;
import com.phloc.commons.io.streams.NonBlockingByteArrayOutputStream;
import com.phloc.commons.io.streams.StreamUtils;
import com.phloc.commons.mime.IMimeType;
import com.phloc.commons.mime.MimeTypeParser;
import com.phloc.commons.mime.MimeTypeParserException;
import com.phloc.commons.mime.MimeTypeUtils;
import com.phloc.commons.string.StringHelper;
import com.phloc.commons.system.SystemHelper;
import com.phloc.web.CWeb;

// ESCA-JAVA0116:
/**
 * Mock implementation of {@link HttpServletResponse}.
 *
 * @author Philip Helger
 */
@NotThreadSafe
public class MockHttpServletResponse implements HttpServletResponse, IHasLocale
{
  public static final int DEFAULT_SERVER_PORT = CWeb.DEFAULT_PORT_HTTP;
  public static final String DEFAULT_CHARSET_NAME = CCharset.CHARSET_UTF_8;
  public static final Charset DEFAULT_CHARSET_OBJ = CCharset.CHARSET_UTF_8_OBJ;
  private static final int DEFAULT_BUFFER_SIZE = 4096;
  private static final Logger s_aLogger = LoggerFactory.getLogger (MockHttpServletResponse.class);

  private boolean m_bOutputStreamAccessAllowed = true;
  private boolean m_bWriterAccessAllowed = true;
  private Charset m_aCharacterEncoding = DEFAULT_CHARSET_OBJ;
  private final NonBlockingByteArrayOutputStream m_aContent = new NonBlockingByteArrayOutputStream ();
  private final ServletOutputStream m_aOS = new ServletOutputStream ()
  {
    @Override
    public void write (final int b) throws IOException
    {
      MockHttpServletResponse.this.m_aContent.write (b);
      super.flush ();
      _setCommittedIfBufferSizeExceeded ();
    }

    @Override
    public void flush () throws IOException
    {
      super.flush ();
      setCommitted (true);
    }

    @Override
    public boolean isReady ()
    {
      return false;
    }

    @Override
    public void setWriteListener (final WriteListener writeListener)
    {
      // not supported
    }
  };
  private PrintWriter m_aWriter;
  private long m_nContentLength = 0;
  private String m_sContentType;
  private int m_nBufferSize = DEFAULT_BUFFER_SIZE;
  private boolean m_bCommitted;
  private Locale m_aLocale = Locale.getDefault ();

  // HttpServletResponse properties
  private final List  m_aCookies = new ArrayList <> ();
  private final IMultiMapSetBased  m_aHeaders = new MultiHashMapLinkedHashSetBased <> ();
  private int m_nStatus = HttpServletResponse.SC_OK;
  private String m_sErrorMessage;
  private String m_sRedirectedUrl;
  private String m_sForwardedUrl;
  private String m_sIncludedUrl;
  private String m_sEncodeUrlSuffix;
  private String m_sEncodeRedirectUrlSuffix;

  public MockHttpServletResponse ()
  {}

  /**
   * Set whether {@link #getOutputStream()} access is allowed.
   * 

* Default is true. * * @param bOutputStreamAccessAllowed * Whether or not to allow it */ public void setOutputStreamAccessAllowed (final boolean bOutputStreamAccessAllowed) { this.m_bOutputStreamAccessAllowed = bOutputStreamAccessAllowed; } /** * @return whether {@link #getOutputStream()} access is allowed. */ public boolean isOutputStreamAccessAllowed () { return this.m_bOutputStreamAccessAllowed; } /** * Set whether {@link #getWriter()} access is allowed. * * @param bWriterAccessAllowed * Whether or not it is allowed. Default is true. */ public void setWriterAccessAllowed (final boolean bWriterAccessAllowed) { this.m_bWriterAccessAllowed = bWriterAccessAllowed; } /** * @return whether {@link #getOutputStream()} access is allowed. */ public boolean isWriterAccessAllowed () { return this.m_bWriterAccessAllowed; } @Override public void setCharacterEncoding (@Nullable final String sCharacterEncoding) { setCharacterEncoding (sCharacterEncoding == null ? null : CharsetManager.getCharsetFromName (sCharacterEncoding)); } public void setCharacterEncoding (@Nullable final Charset aCharacterEncoding) { this.m_aCharacterEncoding = aCharacterEncoding; } @Override @Nullable public String getCharacterEncoding () { return this.m_aCharacterEncoding == null ? null : this.m_aCharacterEncoding.name (); } @Nullable public Charset getCharacterEncodingObj () { return this.m_aCharacterEncoding; } @Nonnull @Nonempty @Deprecated public String getCharacterEncodingOrDefault () { String ret = getCharacterEncoding (); if (ret == null) ret = SystemHelper.getSystemCharsetName (); return ret; } @Nonnull public Charset getCharacterEncodingObjOrDefault () { Charset ret = getCharacterEncodingObj (); if (ret == null) ret = SystemHelper.getSystemCharset (); return ret; } @Override @Nonnull public ServletOutputStream getOutputStream () { if (!this.m_bOutputStreamAccessAllowed) throw new IllegalStateException ("OutputStream access not allowed"); //$NON-NLS-1$ return this.m_aOS; } @Override @Nonnull public PrintWriter getWriter () { if (!this.m_bWriterAccessAllowed) throw new IllegalStateException ("Writer access not allowed"); //$NON-NLS-1$ if (this.m_aWriter == null) { final Writer aWriter = StreamUtils.createWriter (this.m_aContent, getCharacterEncodingObjOrDefault ()); this.m_aWriter = new ResponsePrintWriter (aWriter); } return this.m_aWriter; } @Nonnull @ReturnsMutableCopy public byte [] getContentAsByteArray () { flushBuffer (); return this.m_aContent.toByteArray (); } @Nonnull public String getContentAsString () { return getContentAsString (getCharacterEncodingOrDefault ()); } @Nonnull @Deprecated public String getContentAsString (@Nonnull @Nonempty final String sCharset) { flushBuffer (); return this.m_aContent.getAsString (sCharset); } @Nonnull public String getContentAsString (@Nonnull final Charset aCharset) { flushBuffer (); return this.m_aContent.getAsString (aCharset); } @Override public void setContentLength (final int nContentLength) { this.m_nContentLength = nContentLength; } public int getContentLength () { return (int) this.m_nContentLength; } @Override public void setContentType (@Nullable final String sContentType) { this.m_sContentType = sContentType; if (sContentType != null) { try { final IMimeType aContentType = MimeTypeParser.parseMimeType (sContentType); final String sEncoding = MimeTypeUtils.getCharsetNameFromMimeType (aContentType); if (sEncoding != null) setCharacterEncoding (sEncoding); } catch (final MimeTypeParserException ex) { s_aLogger.warn ("Passed content type '" + sContentType + "' cannot be parsed as a MIME type"); //$NON-NLS-1$ //$NON-NLS-2$ } } } @Override @Nullable public String getContentType () { return this.m_sContentType; } @Override public void setBufferSize (final int nBufferSize) { this.m_nBufferSize = nBufferSize; } @Override public int getBufferSize () { return this.m_nBufferSize; } @Override public void flushBuffer () { setCommitted (true); } /* * Throws exception if committed! */ @Override public void resetBuffer () { if (isCommitted ()) throw new IllegalStateException ("Cannot reset buffer - response is already committed"); //$NON-NLS-1$ this.m_aContent.reset (); this.m_aWriter = null; } void _setCommittedIfBufferSizeExceeded () { final int nBufSize = getBufferSize (); if (nBufSize > 0 && this.m_aContent.size () > nBufSize) setCommitted (true); } public void setCommitted (final boolean bCommitted) { this.m_bCommitted = bCommitted; } @Override public boolean isCommitted () { return this.m_bCommitted; } /* * Throws exception if committed! */ @Override public void reset () { resetBuffer (); this.m_aCharacterEncoding = null; this.m_nContentLength = 0; this.m_sContentType = null; this.m_aLocale = null; this.m_aCookies.clear (); this.m_aHeaders.clear (); this.m_nStatus = HttpServletResponse.SC_OK; this.m_sErrorMessage = null; } @Override public void setLocale (@Nullable final Locale aLocale) { this.m_aLocale = aLocale; } @Override @Nullable public Locale getLocale () { return this.m_aLocale; } // --------------------------------------------------------------------- // HttpServletResponse interface // --------------------------------------------------------------------- @Override public void addCookie (@Nonnull final Cookie aCookie) { ValueEnforcer.notNull (aCookie, "Cookie"); //$NON-NLS-1$ this.m_aCookies.add (aCookie); } @Nonnull @ReturnsMutableCopy public Cookie [] getCookies () { return ArrayHelper.newArray (this.m_aCookies, Cookie.class); } @Nullable public Cookie getCookie (@Nonnull final String sName) { ValueEnforcer.notNull (sName, "Name"); //$NON-NLS-1$ for (final Cookie aCookie : this.m_aCookies) if (sName.equals (aCookie.getName ())) return aCookie; return null; } @Nullable private static String _unifyHeaderName (@Nullable final String sName) { // Same as in MockHttpServletRequest return sName == null ? null : sName.toLowerCase (Locale.US); } @Override public boolean containsHeader (@Nullable final String sName) { return this.m_aHeaders.containsKey (_unifyHeaderName (sName)); } /** * Return the names of all specified headers as a Set of Strings. * * @return the Set of header name Strings, or an * empty Set if none */ @Override @Nonnull @ReturnsMutableCopy public Set getHeaderNames () { return ContainerHelper.newSet (this.m_aHeaders.keySet ()); } /** * Return the primary value for the given header, if any. *

* Will return the first value in case of multiple values. * * @param sName * the name of the header * @return the associated header value, or null if none */ @Override @Nullable public String getHeader (@Nullable final String sName) { final List aList = getHeaders (sName); return ContainerHelper.getFirstElement (aList); } /** * Return all values for the given header as a List of value objects. * * @param sName * the name of the header * @return the associated header values, or an empty List if none */ @Override @Nonnull public List getHeaders (@Nullable final String sName) { final List aHeaders = ContainerHelper.newList (); for (final Object aHeader : this.m_aHeaders.get (_unifyHeaderName (sName))) { aHeaders.add (String.valueOf (aHeader)); } return aHeaders; } /** * The default implementation returns the given URL String as-is. Use * {@link #setEncodeUrlSuffix(String)} to define a suffix to be appended. * * @return the encoded URL */ @Override @Nullable public String encodeURL (@Nullable final String sUrl) { if (StringHelper.hasText (this.m_sEncodeUrlSuffix)) return StringHelper.getNotNull (sUrl) + this.m_sEncodeUrlSuffix; return sUrl; } /** * The default implementation returns the given URL String as-is. Use * {@link #setEncodeRedirectUrlSuffix(String)} to define a suffix to be * appended. * * @return the encoded URL */ @Override @Nullable public String encodeRedirectURL (@Nullable final String sUrl) { if (StringHelper.hasText (this.m_sEncodeRedirectUrlSuffix)) return StringHelper.getNotNull (sUrl) + this.m_sEncodeRedirectUrlSuffix; return sUrl; } @Override public void sendError (final int nStatus, @Nullable final String sErrorMessage) throws IOException { if (isCommitted ()) throw new IllegalStateException ("Cannot set error status - response is already committed"); //$NON-NLS-1$ this.m_nStatus = nStatus; this.m_sErrorMessage = sErrorMessage; setCommitted (true); } @Override public void sendError (final int nStatus) throws IOException { if (isCommitted ()) throw new IllegalStateException ("Cannot set error status - response is already committed"); //$NON-NLS-1$ this.m_nStatus = nStatus; setCommitted (true); } @Override public void sendRedirect (@Nonnull final String sUrl) throws IOException { if (isCommitted ()) throw new IllegalStateException ("Cannot send redirect - response is already committed"); //$NON-NLS-1$ ValueEnforcer.notNull (sUrl, "URL"); //$NON-NLS-1$ this.m_sRedirectedUrl = sUrl; setCommitted (true); } @Nullable public String getRedirectedUrl () { return this.m_sRedirectedUrl; } @Override public void setDateHeader (@Nullable final String sName, final long nValue) { _setHeaderValue (sName, Long.valueOf (nValue)); } @Override public void addDateHeader (@Nullable final String sName, final long nValue) { _addHeaderValue (sName, Long.valueOf (nValue)); } @Override public void setHeader (@Nullable final String sName, @Nullable final String sValue) { _setHeaderValue (sName, sValue); } @Override public void addHeader (@Nullable final String sName, @Nullable final String sValue) { _addHeaderValue (sName, sValue); } @Override public void setIntHeader (@Nullable final String sName, final int nValue) { _setHeaderValue (sName, Integer.valueOf (nValue)); } @Override public void addIntHeader (@Nullable final String sName, final int nValue) { _addHeaderValue (sName, Integer.valueOf (nValue)); } private void _setHeaderValue (@Nullable final String sName, @Nullable final Object aValue) { _doAddHeaderValue (sName, aValue, true); } private void _addHeaderValue (@Nullable final String sName, @Nullable final Object aValue) { _doAddHeaderValue (sName, aValue, false); } private void _doAddHeaderValue (@Nullable final String sName, @Nullable final Object aValue, final boolean bReplace) { if (bReplace || !this.m_aHeaders.containsSingle (_unifyHeaderName (sName), aValue)) this.m_aHeaders.putSingle (_unifyHeaderName (sName), aValue); } @Override public void setStatus (final int nStatus) { this.m_nStatus = nStatus; } @Override public int getStatus () { return this.m_nStatus; } @Nullable public String getErrorMessage () { return this.m_sErrorMessage; } // Methods for MockRequestDispatcher public void setForwardedUrl (@Nullable final String sForwardedUrl) { this.m_sForwardedUrl = sForwardedUrl; } @Nullable public String getForwardedUrl () { return this.m_sForwardedUrl; } public void setIncludedUrl (@Nullable final String sIncludedUrl) { this.m_sIncludedUrl = sIncludedUrl; } @Nullable public String getIncludedUrl () { return this.m_sIncludedUrl; } public void setEncodeUrlSuffix (@Nullable final String sEncodeUrlSuffix) { this.m_sEncodeUrlSuffix = sEncodeUrlSuffix; } @Nullable public String getEncodeUrlSuffix () { return this.m_sEncodeUrlSuffix; } public void setEncodeRedirectUrlSuffix (@Nullable final String sEncodeRedirectUrlSuffix) { this.m_sEncodeRedirectUrlSuffix = sEncodeRedirectUrlSuffix; } @Nullable public String getEncodeRedirectUrlSuffix () { return this.m_sEncodeRedirectUrlSuffix; } /** * Inner class that adapts the PrintWriter to mark the response as committed * once the buffer size is exceeded. */ private class ResponsePrintWriter extends PrintWriter { public ResponsePrintWriter (@Nonnull final Writer aOut) { super (aOut, true); } @Override public void write (final char aBuf[], final int nOff, final int nLen) { super.write (aBuf, nOff, nLen); super.flush (); _setCommittedIfBufferSizeExceeded (); } @Override public void write (final String sStr, final int nOff, final int nLen) { super.write (sStr, nOff, nLen); super.flush (); _setCommittedIfBufferSizeExceeded (); } @Override public void write (final int c) { super.write (c); super.flush (); _setCommittedIfBufferSizeExceeded (); } @Override public void flush () { super.flush (); setCommitted (true); } } @Override public void setContentLengthLong (final long len) { this.m_nContentLength = len; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy