content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   *
   * For further information about Alkacon Software GmbH & Co. KG, please see the
   * company website: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://www.opencms.org
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  package org.opencms.flex;
  
  import org.opencms.file.CmsObject;
  import org.opencms.main.OpenCms;
  import org.opencms.test.OpenCmsTestCase;
  import org.opencms.test.OpenCmsTestProperties;
  import org.opencms.util.CmsRequestUtil;
  
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.Method;
  import java.lang.reflect.Proxy;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  import junit.extensions.TestSetup;
  import junit.framework.TestSuite;
  
  /**
   * Unit tests for the {@link CmsFlexResponse}.<p>
   *
   * This test suite performs way more set-up than is required for the amount of testing that is done.
   * However, there is probably value in demonstrating how to set up a test case to access flex cache resources
   * so that more robust unit tests can be developed here.<p>
   *
   * @since 6.0.1
   */
  public class TestCmsFlexResponse extends OpenCmsTestCase {
  
      /**
       * An InvocationHandler which simply records the arguments for each method that was called.<p>
       *
       * If a 'stub' object was passed in the contructor, and the stub object has a method of the
       * same signature as the one that is being called, that method will be invoked.<p>
       */
      public static class RecordingMock implements InvocationHandler {
  
          /** Maps {@link Method} to a {@link List} of arguments passed to each invocation of that method handled by this object. */
          HashMap m_invocations = new HashMap();
  
          /** If non-null, delegate method invocations to this object when possible. */
          Object m_stub;
  
          /**
           * Default empty construtor.<p>
           */
          public RecordingMock() {
  
              // noop
          }
  
          /**
           * Construtor with a 'stub' Object.<p>
           *
           * @param stub the stub Object to use
           */
          public RecordingMock(Object stub) {
  
              m_stub = stub;
          }
  
          /**
           * Returns a list of all recorded calls to the given method.<p>
           *
           * @param method the method to get the recorded calls for
           *
           * @return a list of all recorded calls to the given method
           */
          public List getCalls(Method method) {
  
              ArrayList calls = (ArrayList)m_invocations.get(method);
              if (calls == null) {
                  calls = new ArrayList();
                  m_invocations.put(method, calls);
              }
              return calls;
          }
  
          /**
           * Notice that the given method has been invoked.<p>
           *
           * Two actions are taken:
           * <ol>
           * <li>The invocation is recorded in <code>{@link #getCalls getCalls(method)}</code>.</li>
           * <li>If {@link #m_stub} is not null, the requested method is invoked on it</li>
           * </ol>
           *
           * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
           */
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  
              // record the invocation
              getCalls(method).add(args);
  
              // check to see if our stub object supports this method.
              if (m_stub != null) {
                  Method stubMethod = null;
                  try {
                      stubMethod = m_stub.getClass().getMethod(method.getName(), method.getParameterTypes());
                      if (stubMethod != null) {
                          if (stubMethod.getReturnType() == null) {
                              if (method.getReturnType() != null) {
                                  stubMethod = null;
                              }
                          } else if (!stubMethod.getReturnType().equals(method.getReturnType())) {
                              stubMethod = null;
                          }
                      }
                  } catch (NoSuchMethodException e) {
                      // ignore
                  } catch (SecurityException e) {
                      // ignore
                  }
  
                  // if stubMethod is not null, then stub object has a public method with the requested signature
                  if (stubMethod != null) {
                      return stubMethod.invoke(m_stub, args);
                  }
              }
  
              return null;
          }
      }
  
      /**
       * A partial implementation of {@link HttpServletRequest} which allows for the setting and getting of request attributes.<p>
       */
      public static class RequestStub {
  
          /** Attribute map. */
          HashMap m_attributes = new HashMap();
  
          /**
           * Returns the named attribute value.<p>
           *
           * @param name the name of the attribute to return
           * @return the value of the attribute
           */
          public Object getAttribute(String name) {
  
              return m_attributes.get(name);
          }
  
          /**
           * Removes the named attribute.<p>
           *
           * @param name the name of the attribute to remove
           */
          public void removeAttribute(String name) {
  
              m_attributes.remove(name);
          }
  
          /**
           * Sets the named attribute to the given value.<p>
           *
           * @param name the name of the attribute to set
           * @param value the value to set
           */
          public void setAttribute(String name, Object value) {
  
              m_attributes.put(name, value);
          }
      }
  
      /** Method for setContentType(String) from the HttpServletResponse class. */
      static Method SET_CONTENT_TYPE;
  
      /** Flex controller to be used by the tests. */
      private CmsFlexController m_controller;
  
      /** Request mockup object. */
      private RecordingMock m_reqMock;
  
      /** Response mockup object. */
      private RecordingMock m_resMock;
  
      /** Servlet request to use with the tests. */
      private HttpServletRequest m_request;
  
      /** Servlet response to use with the tests. */
      private HttpServletResponse m_response;
  
      /**
       * Default JUnit constructor.<p>
       *
       * @param arg0 JUnit parameters
       */
      public TestCmsFlexResponse(String arg0) {
  
          super(arg0);
      }
  
      /**
       * Static initializer for this test case.<p>
       */
      static {
          try {
              SET_CONTENT_TYPE = HttpServletResponse.class.getMethod("setContentType", new Class[] {String.class});
          } catch (NoSuchMethodException e) {
              throw new RuntimeException("HttpServletResponse linkage error", e);
          }
      }
  
      /**
       * Test suite for this test class.<p>
       *
       * @return the test suite
       */
      public static TestSetup suite() {
  
          OpenCmsTestProperties.initialize(org.opencms.test.AllTests.TEST_PROPERTIES_PATH);
  
          TestSuite suite = new TestSuite();
          suite.setName(TestCmsFlexResponse.class.getName());
  
          suite.addTest(new TestCmsFlexResponse("testContentTypeRules"));
  
          TestSetup wrapper = new TestSetup(suite) {
  
              @Override
              protected void setUp() {
  
                  setupOpenCms("simpletest", "/");
              }
  
              @Override
              protected void tearDown() {
  
                  removeOpenCms();
              }
          };
  
          return wrapper;
      }
  
      /**
       * Convenience method to create a mock {@link HttpServletRequest} backed by the given invocation handler.<p>
       *
       * @param recorder the mock recorder
       *
       * @return the new created request
       */
      private static HttpServletRequest createMockRequest(RecordingMock recorder) {
  
          return (HttpServletRequest)createProxy(HttpServletRequest.class, recorder);
      }
  
      /**
       * Convenience method to create a mock {@link HttpServletResponse} backed by the given invocation handler.<p>
       *
       * @param recorder the mock recorder
       *
       * @return the new created response
       */
      private static HttpServletResponse createMockResponse(RecordingMock recorder) {
  
          return (HttpServletResponse)createProxy(HttpServletResponse.class, recorder);
      }
  
      private static Object createProxy(Class interfaceClass, InvocationHandler handler) {
  
          return Proxy.newProxyInstance(
              Thread.currentThread().getContextClassLoader(),
              new Class[] {interfaceClass},
              handler);
      }
  
      /**
       * Test semantics for Content-Type header on
       * {@link CmsFlexResponse#setContentType(String)} and {@link CmsFlexResponse#setHeader(String, String)}.<p>
       *
       * @throws Exception if the test fails
       */
      public void testContentTypeRules() throws Exception {
  
          // test that non-top elements won't try to set the content type.
          CmsFlexResponse f_res = new CmsFlexResponse(m_response, m_controller, false, false);
          f_res.setHeader(CmsRequestUtil.HEADER_CONTENT_TYPE, "application/borked");
          assertTrue("non-top request does not set content type header", m_resMock.m_invocations.isEmpty());
  
          f_res.setContentType("application/stillborked");
          assertTrue("non-top request does not set content type header", m_resMock.m_invocations.isEmpty());
  
          // test that top elements only set content type once
          // first, try with a call to setContentType()
          f_res = new CmsFlexResponse(m_response, m_controller, false, true);
          f_res.setContentType("text/foo");
  
          assertEquals("one method has been invoked on the actual response", 1, m_resMock.m_invocations.size());
          List setCalls = m_resMock.getCalls(SET_CONTENT_TYPE);
          assertEquals("top element has called setContentType() on the actual servlet response", 1, setCalls.size());
          assertEquals("correct content type value passed", "text/foo", ((Object[])setCalls.get(0))[0]);
  
          // subsequent attempts to set the content type on the same response should have no affect
          f_res.setContentType("text/bar");
          assertEquals("top element did NOT call content type method again", 1, setCalls.size());
  
          f_res.setHeader(CmsRequestUtil.HEADER_CONTENT_TYPE, "text/baz");
          assertEquals("still no more calls to setContentType() method", 1, setCalls.size());
          assertEquals("no other methods called on request", 1, m_resMock.m_invocations.size());
  
          // now, try with a call to setHeader on a new top response
          f_res = new CmsFlexResponse(m_response, m_controller, false, true);
          f_res.setHeader(CmsRequestUtil.HEADER_CONTENT_TYPE, "text/qux");
          assertEquals("setContentType() was called from setHeader", 2, setCalls.size());
          assertEquals("correct content type value passed", "text/qux", ((Object[])setCalls.get(1))[0]);
  
          // subsequent attempts to set the content type on the same response should have no affect
          f_res.setContentType("text/quux");
          assertEquals("no further calls to setContentType", 2, setCalls.size());
          assertEquals("no other methods called", 1, m_resMock.m_invocations.size());
  
          f_res.setHeader(CmsRequestUtil.HEADER_CONTENT_TYPE, "text/arg");
          assertEquals("no further calls to setContentType", 2, setCalls.size());
          assertEquals("no other methods called", 1, m_resMock.m_invocations.size());
      }
  
      /**
       * Initializes a flex cache controller and mock servlet request and response objects to be
       * used by this unit tests.<p>
       *
       * @throws Exception if the setup fails
       *
       * @see junit.framework.TestCase#setUp()
       */
      @Override
      protected void setUp() throws Exception {
  
          super.setUp();
          CmsObject cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserGuest());
          if (!OpenCms.getDefaultUsers().isUserGuest(cms.getRequestContext().getCurrentUser().getName())) {
              fail("'Guest' user could not be properly initialized!");
          }
  
          m_reqMock = new RecordingMock(new RequestStub());
          m_request = createMockRequest(m_reqMock);
          m_resMock = new RecordingMock();
          m_response = createMockResponse(m_resMock);
  
          m_controller = new CmsFlexController(
              cms,
              null,
              CmsFlexDummyLoader.getFlexCache(),
              m_request,
              m_response,
              false,
              true);
          CmsFlexController.setController(m_request, m_controller);
      }
  
      /**
       * @see junit.framework.TestCase#tearDown()
       */
      @Override
      protected void tearDown() throws Exception {
  
          super.tearDown();
          m_reqMock = null;
          m_resMock = null;
          m_request = null;
          m_response = null;
          m_controller = null;
      }
  }