/*
 * Copyright 2007 Google Inc.
 * 
 * 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.google.gwt.user.server.rpc;

import com.google.gwt.user.client.rpc.IsSerializable;
import com.google.gwt.user.client.rpc.SerializationException;

import junit.framework.TestCase;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Set;

import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

/**
 * Test some of the failure modes associated with
 * {@link RemoteServiceServlet#doGetSerializationPolicy(HttpServletRequest, String, String)}.
 * 
 * TODO: test caching of policies?
 */
public class RemoteServiceServletTest extends TestCase {

  private static class Bar implements Serializable {
  }

  private static class Baz {
  }

  private static class Foo implements IsSerializable {
  }

  private class MockHttpServletRequestContextPath extends
      MockHttpServletRequest {
    private String contextPath;

    @Override
    public String getContextPath() {
      return contextPath;
    }
  }

  private class MockServletConfig implements ServletConfig {
    private ServletContext context;

    public MockServletConfig(ServletContext context) {
      this.context = context;
    }

    public String getInitParameter(String arg0) {
      throw new UnsupportedOperationException();
    }

    public Enumeration<String> getInitParameterNames() {
      throw new UnsupportedOperationException();
    }

    public ServletContext getServletContext() {
      return context;
    }

    public String getServletName() {
      return "MockServlet";
    }
  }

  private class MockServletContext implements ServletContext {
    private String messageLogged;

    public MockServletContext() {
    }

    public Object getAttribute(String arg0) {
      throw new UnsupportedOperationException();
    }

    public Enumeration<String> getAttributeNames() {
      throw new UnsupportedOperationException();
    }

    public ServletContext getContext(String arg0) {
      throw new UnsupportedOperationException();
    }

    public String getInitParameter(String arg0) {
      throw new UnsupportedOperationException();
    }

    public Enumeration<String> getInitParameterNames() {
      throw new UnsupportedOperationException();
    }

    public int getMajorVersion() {
      throw new UnsupportedOperationException();
    }

    public String getMimeType(String arg0) {
      throw new UnsupportedOperationException();
    }

    public int getMinorVersion() {
      throw new UnsupportedOperationException();
    }

    public RequestDispatcher getNamedDispatcher(String arg0) {
      throw new UnsupportedOperationException();
    }

    public String getRealPath(String arg0) {
      throw new UnsupportedOperationException();
    }

    public RequestDispatcher getRequestDispatcher(String arg0) {
      throw new UnsupportedOperationException();
    }

    public URL getResource(String arg0) throws MalformedURLException {
      throw new UnsupportedOperationException();
    }

    public InputStream getResourceAsStream(String arg0) {
      throw new UnsupportedOperationException();
    }

    public Set<String> getResourcePaths(String arg0) {
      throw new UnsupportedOperationException();
    }

    public String getServerInfo() {
      throw new UnsupportedOperationException();
    }

    public Servlet getServlet(String arg0) throws ServletException {
      throw new UnsupportedOperationException();
    }

    public String getServletContextName() {
      throw new UnsupportedOperationException();
    }

    public Enumeration<String> getServletNames() {
      throw new UnsupportedOperationException();
    }

    public Enumeration<String> getServlets() {
      throw new UnsupportedOperationException();
    }

    public void log(Exception arg0, String arg1) {
      log(arg1, arg0);
    }

    public void log(String arg0) {
      log(arg0, null);
    }

    public void log(String arg0, Throwable arg1) {
      messageLogged = arg0;
    }

    public void removeAttribute(String arg0) {
    }

    public void setAttribute(String arg0, Object arg1) {
      throw new UnsupportedOperationException();
    }
  }

  public void testDoGetSerializationPolicy_FailToOpenMD5Resource()
      throws ServletException {
    MockServletContext mockContext = new MockServletContext() {
      public InputStream getResourceAsStream(String resource) {
        return null;
      }
    };
    MockServletConfig mockConfig = new MockServletConfig(mockContext);

    RemoteServiceServlet rss = new RemoteServiceServlet();

    MockHttpServletRequestContextPath mockRequest = new MockHttpServletRequestContextPath();
    rss.init(mockConfig);

    mockRequest.contextPath = "/MyModule";

    SerializationPolicy serializationPolicy = rss.doGetSerializationPolicy(
        mockRequest, "http://www.google.com/MyModule", "12345");
    assertNull(serializationPolicy);
    assertNotNull(mockContext.messageLogged);
  }

  /**
   * Test method for
   * {@link com.google.gwt.user.server.rpc.RemoteServiceServlet#doGetSerializationPolicy(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String)}.
   * 
   * This method tests that if the module path is in a different context than
   * the RemoteServiceServlet which is processing the request, a message will be
   * logged and null is returned for the SerializationPolicy.
   */
  public void testDoGetSerializationPolicy_ModuleInSeparateServlet()
      throws ServletException {
    MockServletContext mockContext = new MockServletContext();
    MockServletConfig mockConfig = new MockServletConfig(mockContext);

    RemoteServiceServlet rss = new RemoteServiceServlet();

    MockHttpServletRequestContextPath mockRequest = new MockHttpServletRequestContextPath();
    rss.init(mockConfig);

    mockRequest.contextPath = "/foo";
    SerializationPolicy serializationPolicy = rss.doGetSerializationPolicy(
        mockRequest, "http://www.google.com/MyModule", "");
    assertNotNull(mockContext.messageLogged);
    assertNull(serializationPolicy);
  }

  /**
   * Test method for
   * {@link com.google.gwt.user.server.rpc.RemoteServiceServlet#doGetSerializationPolicy(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String)}.
   * 
   * This method tests the success case. The resource exists and is in the same
   * path at the web application.
   */
  public void testDoGetSerializationPolicy_Success() throws ServletException,
      SerializationException {
    final String resourceHash = "12345";
    final String resourcePath = SerializationPolicyLoader.getSerializationPolicyFileName(resourceHash);
    MockServletContext mockContext = new MockServletContext() {
      public InputStream getResourceAsStream(String resource) {
        if (resourcePath.equals(resource)) {
          try {
            String payLoad = Foo.class.getName() + ",true\n"
                + Bar.class.getName() + ",false\n";
            return new ByteArrayInputStream(
                payLoad.getBytes(SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING));
          } catch (UnsupportedEncodingException e) {
            return null;
          }
        }

        return null;
      }
    };
    MockServletConfig mockConfig = new MockServletConfig(mockContext);

    RemoteServiceServlet rss = new RemoteServiceServlet();

    MockHttpServletRequestContextPath mockRequest = new MockHttpServletRequestContextPath();
    rss.init(mockConfig);

    mockRequest.contextPath = "/MyModule";

    SerializationPolicy serializationPolicy = rss.doGetSerializationPolicy(
        mockRequest, "http://www.google.com/MyModule", resourceHash);
    assertNotNull(serializationPolicy);

    assertDeserializeFields(serializationPolicy, Foo.class);
    assertValidDeserialize(serializationPolicy, Foo.class);

    assertDeserializeFields(serializationPolicy, Bar.class);
    assertNotValidDeserialize(serializationPolicy, Bar.class);

    assertNotDeserializeFields(serializationPolicy, Baz.class);
    assertNotValidDeserialize(serializationPolicy, Baz.class);
  }

  private void assertDeserializeFields(SerializationPolicy policy,
      Class<?> clazz) {
    assertTrue(policy.shouldDeserializeFields(clazz));
  }

  private void assertNotDeserializeFields(SerializationPolicy policy,
      Class<?> clazz) {
    assertFalse(policy.shouldDeserializeFields(clazz));
  }

  private void assertNotValidDeserialize(SerializationPolicy policy,
      Class<?> clazz) {
    try {
      policy.validateDeserialize(clazz);
      fail("assertNotValidDeserialize: " + clazz.getName()
          + " failed to throw an exception");
    } catch (SerializationException e) {
      // expected
    }
  }

  private void assertValidDeserialize(SerializationPolicy policy, Class<?> clazz)
      throws SerializationException {
    policy.validateDeserialize(clazz);
  }
}
