/*
 * $Id$
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.struts2;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.util.TextParser;
import com.opensymphony.xwork2.util.OgnlTextParser;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate;
import com.opensymphony.xwork2.util.fs.DefaultFileManager;
import junit.framework.TestCase;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.views.util.DefaultUrlHelper;
import org.apache.struts2.views.util.UrlHelper;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class EmbeddedJSPResultTest extends TestCase {
    private HttpServletRequest request;
    private MockHttpServletResponse response;
    private MockServletContext context;
    private EmbeddedJSPResult result;

    public void testScriptlet() throws Exception {
        result.setLocation("org/apache/struts2/scriptlet.jsp");
        result.execute(null);

        assertEquals("Saynotoscriptlets", StringUtils.deleteWhitespace(response.getContentAsString()));
    }

    public void testEmbedded() throws Exception {
        //the jsp is inside jsps.jar
        result.setLocation("dir/all.jsp");
        result.execute(null);

        assertEquals("helloJGWhoamI?XXXXXXXXXXXYThissessionisnotsecure.", StringUtils.deleteWhitespace(response.getContentAsString()));
    }

    public void testFilesAreReadOnlyOnce() throws Exception {
        //make sure that files are not read multiple times
        String jsp = "org/apache/struts2/dont-use.jsp";

        CountingClassLoaderInterface classLoaderInterface = new CountingClassLoaderInterface(this.getClass().getClassLoader());
        context.setAttribute(ClassLoaderInterface.CLASS_LOADER_INTERFACE, classLoaderInterface);
        result.setLocation(jsp);

        result.execute(null);
        Integer counter0 = classLoaderInterface.counters.get(jsp);
        assertNotNull(counter0);

        result.execute(null);
        Integer counter1 = classLoaderInterface.counters.get(jsp);
        assertNotNull(counter1);

        assertEquals(counter0, counter1);
    }

    public void testEmbeddedAbsolutePath() throws Exception {
        //the jsp is inside jsps.jar
        result.setLocation("/dir/all.jsp");
        result.execute(null);

        assertEquals("helloJGWhoamI?XXXXXXXXXXXYThissessionisnotsecure.", StringUtils.deleteWhitespace(response.getContentAsString()));
    }

    public void testSimple() throws Exception {
        result.setLocation("org/apache/struts2/simple0.jsp");
        result.execute(null);

        assertEquals("hello", response.getContentAsString().trim());
    }

    //ok i give up..i don't know why this doesn't work from maven
   /* public void testKeyInContext() throws Exception {
        result.setLocation("org/apache/struts2/scriptlet.jsp");
        result.execute(null);

        String key = JspApplicationContextImpl.class.getName() + "@" + this.getClass().getClassLoader().hashCode();
        assertNotNull(context.getAttribute(key));
    }*/

    public void testEL() throws Exception {
        result.setLocation("org/apache/struts2/el.jsp");
        result.execute(null);

        assertEquals("somethingelseText", response.getContentAsString().trim());
    }

    public void testAbsolutePath() throws Exception {
        result.setLocation("/org/apache/struts2/simple0.jsp");
        result.execute(null);

        assertEquals("hello", response.getContentAsString().trim());
    }

    public void testTag0() throws Exception {
        result.setLocation("org/apache/struts2/tag0.jsp");
        result.execute(null);

        assertEquals("Thissessionisnotsecure.OtherText", StringUtils.deleteWhitespace(response.getContentAsString()));
    }

    public void testIncludeSimple() throws Exception {
        result.setLocation("org/apache/struts2/includes0.jsp");
        result.execute(null);

        assertEquals("helloTest", StringUtils.deleteWhitespace(response.getContentAsString()));
    }

    public void testIncludeSimpleWithDirective() throws Exception {
        result.setLocation("org/apache/struts2/includes3.jsp");
        result.execute(null);

        assertEquals("helloTest", StringUtils.deleteWhitespace(response.getContentAsString()));
    }

    public void testIncludeWithSubdir() throws Exception {
        result.setLocation("org/apache/struts2/includes1.jsp");
        result.execute(null);

        assertEquals("subTest", StringUtils.deleteWhitespace(response.getContentAsString()));
    }

    public void testIncludeWithParam() throws Exception {
        result.setLocation("org/apache/struts2/includes2.jsp");
        result.execute(null);

        assertEquals("JGTest", StringUtils.deleteWhitespace(response.getContentAsString()));
    }

    public void testBroken0() throws Exception {
        try {
            result.setLocation("org/apache/struts2/broken0.jsp");
            result.execute(null);
            fail("should have failed with broken jsp");
        } catch (IllegalStateException ex) {
            //ok
        }
    }

    public void testJSTL() throws Exception {
        result.setLocation("org/apache/struts2/jstl.jsp");
        result.execute(null);

        assertEquals("XXXXXXXXXXXY", StringUtils.deleteWhitespace(response.getContentAsString()));
    }


    public void testCachedInstances() throws InterruptedException {
        ServletCache cache = new ServletCache();
        Servlet servlet1 = cache.get("org/apache/struts2/simple0.jsp");
        Servlet servlet2 = cache.get("org/apache/struts2/simple0.jsp");

        assertSame(servlet1, servlet2);
    }

    public void testCacheInstanceWithManyThreads() throws BrokenBarrierException, InterruptedException {
        //start a bunch of thread at the same time using CyclicBarrier and hit the cache
        //then wait for all the threads to end and check that they all got a reference to the same object
        //and the cache size should be 1

        DummyServletCache cache = new DummyServletCache();
        int numThreads = 70;

        CyclicBarrier startBarrier = new CyclicBarrier(numThreads + 1);
        CyclicBarrier endBarrier = new CyclicBarrier(numThreads + 1);

        List<ServletGetRunnable> runnables = new ArrayList<ServletGetRunnable>(numThreads);

        //create the threads
        for (int i = 0; i < numThreads; i++) {
            ServletGetRunnable runnable = new ServletGetRunnable(cache, startBarrier, endBarrier, ActionContext.getContext());
            Thread thread = new Thread(runnable);
            runnables.add(runnable);
            thread.start();
        }

        startBarrier.await();
        endBarrier.await();
        Object servlet = cache.get("org/apache/struts2/simple0.jsp");
        assertEquals(1, cache.size());

        for (ServletGetRunnable runnable : runnables) {
            assertSame(servlet, runnable.getObject());
        }
    }

    public void testBeans() throws Exception {
        result.setLocation("org/apache/struts2/beans.jsp");
        result.execute(null);

        assertEquals("WhoamI?", StringUtils.deleteWhitespace(response.getContentAsString()));
    }

    public void testNotURLClassLoader() throws Exception {
        ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
        NotURLClassLoader loader = new NotURLClassLoader(parentClassLoader);
        Thread.currentThread().setContextClassLoader(loader);

        try {
            result.setLocation("org/apache/struts2/tag0.jsp");
            result.execute(null);

            assertEquals("Thissessionisnotsecure.OtherText", StringUtils.deleteWhitespace(response.getContentAsString()));
        } finally {
            Thread.currentThread().setContextClassLoader(parentClassLoader);
        }
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        result = new EmbeddedJSPResult();

        request = EasyMock.createNiceMock(HttpServletRequest.class);
        response = new MockHttpServletResponse();
        context = new MockServletContext();

        final Map params = new HashMap();

        HttpSession session = EasyMock.createNiceMock(HttpSession.class);
        EasyMock.replay(session);

        EasyMock.expect(request.getSession()).andReturn(session).anyTimes();
        EasyMock.expect(request.getParameterMap()).andReturn(params).anyTimes();
        EasyMock.expect(request.getParameter("username")).andAnswer(new IAnswer<String>() {
            public String answer() throws Throwable {
                return ActionContext.getContext().getParameters().get("username").getValue();
            }
        });
        EasyMock.expect(request.getAttribute("something")).andReturn("somethingelse").anyTimes();

        EasyMock.replay(request);

        ActionContext actionContext = new ActionContext(new HashMap<String, Object>());
        ActionContext.setContext(actionContext);
        actionContext.setParameters(HttpParameters.create(params).build());
        ServletActionContext.setRequest(request);
        ServletActionContext.setResponse(response);
        ServletActionContext.setServletContext(context);

        //mock value stack
        Map stackContext = new HashMap();
        ValueStack valueStack = EasyMock.createNiceMock(ValueStack.class);
        EasyMock.expect(valueStack.getContext()).andReturn(stackContext).anyTimes();
        EasyMock.replay(valueStack);

        //mock converter
        XWorkConverter converter = new DummyConverter();

        DefaultFileManager fileManager = new DefaultFileManager();
        fileManager.setReloadingConfigs(false);

        //mock container
        Container container = EasyMock.createNiceMock(Container.class);
        EasyMock.expect(container.getInstance(XWorkConverter.class)).andReturn(converter).anyTimes();
        TextParser parser = new OgnlTextParser();
        EasyMock.expect(container.getInstance(TextParser.class)).andReturn(parser).anyTimes();
        EasyMock.expect(container.getInstanceNames(FileManager.class)).andReturn(new HashSet<String>()).anyTimes();
        EasyMock.expect(container.getInstance(FileManager.class)).andReturn(fileManager).anyTimes();

        UrlHelper urlHelper = new DefaultUrlHelper();
        EasyMock.expect(container.getInstance(UrlHelper.class)).andReturn(urlHelper).anyTimes();
        FileManagerFactory fileManagerFactory = new DummyFileManagerFactory();
        EasyMock.expect(container.getInstance(FileManagerFactory.class)).andReturn(fileManagerFactory).anyTimes();

        EasyMock.replay(container);
        stackContext.put(ActionContext.CONTAINER, container);
        actionContext.setContainer(container);

        actionContext.setValueStack(valueStack);
    }

}

//converter has a protected default constructor...meh
class DummyConverter extends XWorkConverter {

}

class DummyFileManagerFactory implements FileManagerFactory {

    public void setReloadingConfigs(String reloadingConfigs) {
    }

    public FileManager getFileManager() {
        return new DefaultFileManager();
    }

}

class DummyServletCache extends ServletCache {
    public int size() {
        return cache.size();
    }
}

class ServletGetRunnable implements Runnable {
    private ServletCache servletCache;
    private Object object;
    private CyclicBarrier startBarrier;
    private ActionContext actionContext;
    private CyclicBarrier endBarrier;

    ServletGetRunnable(ServletCache servletCache, CyclicBarrier startBarrier, CyclicBarrier endBarrier, ActionContext actionContext) {
        this.servletCache = servletCache;
        this.startBarrier = startBarrier;
        this.endBarrier = endBarrier;
        this.actionContext = actionContext;
    }

    public void run() {
        ActionContext.setContext(actionContext);
        //wait to start all threads at once..or try at least
        try {
            startBarrier.await();
            object = servletCache.get("org/apache/struts2/simple0.jsp");

            for (int i = 0; i < 10; i++) {
                Object object2 = servletCache.get("org/apache/struts2/simple0.jsp");
                if (object2 != object)
                    throw new RuntimeException("got different object from cache");
            }

            endBarrier.await();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object getObject() {
        return object;
    }
}

class CountingClassLoaderInterface extends ClassLoaderInterfaceDelegate {
    public Map<String, Integer> counters = new HashMap<String, Integer>();

    public CountingClassLoaderInterface(ClassLoader classLoader) {
        super(classLoader);
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        Integer counter = counters.get(name);
        counter = counter == null ? 1 : counter + 1;
        counters.put(name, counter);

        return super.getResourceAsStream(name);
    }
}

class NotURLClassLoader extends ClassLoader {

    NotURLClassLoader(ClassLoader parentClassLoader) {
        super(parentClassLoader);
    }
}
