/*
 * Copyright 2002-2006,2009 The Apache Software Foundation.
 * 
 * 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.opensymphony.xwork2.interceptor;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ModelDrivenAction;
import com.opensymphony.xwork2.SimpleAction;
import com.opensymphony.xwork2.TestBean;
import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.XWorkTestCase;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.providers.MockConfigurationProvider;
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import com.opensymphony.xwork2.ognl.OgnlValueStack;
import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import ognl.PropertyAccessor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


/**
 * Unit test for {@link ParametersInterceptor}.
 *
 * @author Jason Carreira
 */
public class ParametersInterceptorTest extends XWorkTestCase {

    public void testParameterNameAware() {
        ParametersInterceptor pi = createParametersInterceptor();
        final Map actual = injectValueStackFactory(pi);
        ValueStack stack = createStubValueStack(actual);
        final Map expected = new HashMap() {
            {
                put("fooKey", "fooValue");
                put("barKey", "barValue");
            }
        };
        Object a = new ParameterNameAware() {
            public boolean acceptableParameterName(String parameterName) {
                return expected.containsKey(parameterName);
            }
        };
        Map parameters = new HashMap() {
            {
                put("fooKey", "fooValue");
                put("barKey", "barValue");
                put("error", "error");
            }
        };
        pi.setParameters(a, stack, parameters);
        assertEquals(expected, actual);
    }

    public void testDoesNotAllowMethodInvocations() throws Exception {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("@java.lang.System@exit(1).dummy", "dumb value");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.MODEL_DRIVEN_PARAM_TEST, extraContext);
        assertEquals(Action.SUCCESS, proxy.execute());

        ModelDrivenAction action = (ModelDrivenAction) proxy.getAction();
        TestBean model = (TestBean) action.getModel();

        String property = System.getProperty("xwork.security.test");
        assertNull(property);
    }

    public void testModelDrivenParameters() throws Exception {
        Map<String, Object> params = new HashMap<String, Object>();
        final String fooVal = "com.opensymphony.xwork2.interceptor.ParametersInterceptorTest.foo";
        params.put("foo", fooVal);

        final String nameVal = "com.opensymphony.xwork2.interceptor.ParametersInterceptorTest.name";
        params.put("name", nameVal);
        params.put("count", "15");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.MODEL_DRIVEN_PARAM_TEST, extraContext);
        assertEquals(Action.SUCCESS, proxy.execute());

        ModelDrivenAction action = (ModelDrivenAction) proxy.getAction();
        TestBean model = (TestBean) action.getModel();
        assertEquals(nameVal, model.getName());
        assertEquals(15, model.getCount());
        assertEquals(fooVal, action.getFoo());
    }

    public void testParametersDoesNotAffectSession() throws Exception {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("blah", "This is blah");
        params.put("#session.foo", "Foo");
        params.put("\u0023session[\'user\']", "0wn3d");
        params.put("\\u0023session[\'user\']", "0wn3d");
        params.put("\u0023session.user2", "0wn3d");
        params.put("\\u0023session.user2", "0wn3d");
        params.put("('\u0023'%20%2b%20'session[\'user3\']')(unused)", "0wn3d");
        params.put("('\\u0023' + 'session[\\'user4\\']')(unused)", "0wn3d");
        params.put("('\u0023'%2b'session[\'user5\']')(unused)", "0wn3d");
        params.put("('\\u0023'%2b'session[\'user5\']')(unused)", "0wn3d");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);
        ValueStack stack = proxy.getInvocation().getStack();
        HashMap<String, Object> session = new HashMap<String, Object>();
        stack.getContext().put("session", session);
        proxy.execute();
        assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getBlah());
        assertNull(session.get("foo"));
        assertNull(session.get("user"));
        assertNull(session.get("user2"));
        assertNull(session.get("user3"));
        assertNull(session.get("user4"));
        assertNull(session.get("user5"));
    }

    public void testAccessToOgnlInternals() throws Exception {
        // given
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("blah", "This is blah");
        params.put("('\\u0023_memberAccess[\\'allowStaticMethodAccess\\']')(meh)", "true");
        params.put("('(aaa)(('\\u0023context[\\'xwork.MethodAccessor.denyMethodExecution\\']\\u003d\\u0023foo')(\\u0023foo\\u003dnew java.lang.Boolean(\"false\")))", "");
        params.put("(asdf)(('\\u0023rt.exit(1)')(\\u0023rt\\u003d@java.lang.Runtime@getRuntime()))", "1");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, "", extraContext);
        ValueStack stack = proxy.getInvocation().getStack();

        // when
        proxy.execute();
        proxy.getAction();

        //then
        assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getBlah());
        Object allowMethodAccess = stack.findValue("\u0023_memberAccess['allowStaticMethodAccess']");
        assertNotNull(allowMethodAccess);
        assertEquals(Boolean.FALSE, allowMethodAccess);
    }

    public void testParameters() throws Exception {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("blah", "This is blah");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);
        proxy.execute();
        assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getBlah());
    }

     public void testParametersWithSpacesInTheName() throws Exception {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("theProtectedMap['p0 p1']", "test1");
        params.put("theProtectedMap['p0p1 ']", "test2");
        params.put("theProtectedMap[' p0p1 ']", "test3");
        params.put("theProtectedMap[' p0 p1 ']", "test4");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);
        proxy.execute();
        Map<String, String> existingMap =  ((SimpleAction) proxy.getAction()).getTheProtectedMap();
        assertEquals(4, existingMap.size());
        assertEquals("test1", existingMap.get("p0 p1"));
        assertEquals("test2", existingMap.get("p0p1 "));
        assertEquals("test3", existingMap.get(" p0p1 "));
        assertEquals("test4", existingMap.get(" p0 p1 "));
    }

    public void testExcludedTrickyParameters() throws Exception {
        Map<String, Object> params = new HashMap<String, Object>() {
            {
                put("blah", "This is blah");
                put("name", "try_1");
                put("(name)", "try_2");
                put("['name']", "try_3");
                put("['na' + 'me']", "try_4");
                put("{name}[0]", "try_5");
                put("(new string{'name'})[0]", "try_6");
                put("#{key: 'name'}.key", "try_7");

            }
        };

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);

        ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME);
        ParametersInterceptor pi =(ParametersInterceptor) config.getInterceptors().get(0).getInterceptor();
        pi.setExcludeParams("name");

        proxy.execute();

        SimpleAction action = (SimpleAction) proxy.getAction();
        assertNull(action.getName());
        assertEquals("This is blah", (action).getBlah());
    }

    public void testAcceptedTrickyParameters() throws Exception {
        Map<String, Object> params = new HashMap<String, Object>() {
            {
                put("blah", "This is blah");
                put("baz", "123");
                put("name", "try_1");
                put("(name)", "try_2");
                put("['name']", "try_3");
                put("['na' + 'me']", "try_4");
                put("{name}[0]", "try_5");
                put("(new string{'name'})[0]", "try_6");
                put("#{key: 'name'}.key", "try_7");
            }
        };

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);

        ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME);
        ParametersInterceptor pi =(ParametersInterceptor) config.getInterceptors().get(0).getInterceptor();
        pi.setAcceptParamNames("blah, baz");

        proxy.execute();

        SimpleAction action = (SimpleAction) proxy.getAction();
        assertNull(action.getName());
        assertEquals("This is blah", (action).getBlah());
        assertEquals(123, action.getBaz());
    }


    public void testParametersNotAccessPrivateVariables() throws Exception {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("protectedMap.foo", "This is blah");
        params.put("theProtectedMap.boo", "This is blah");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);
        proxy.execute();
        SimpleAction action = (SimpleAction) proxy.getAction();
        assertEquals(1, action.getTheProtectedMap().size());
        assertNotNull(action.getTheProtectedMap().get("boo"));
        assertNull(action.getTheProtectedMap().get("foo"));
    }

    public void testParametersNotAccessProtectedMethods() throws Exception {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("theSemiProtectedMap.foo", "This is blah");
        params.put("theProtectedMap.boo", "This is blah");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);
        proxy.execute();
        SimpleAction action = (SimpleAction) proxy.getAction();
        assertEquals(1, action.getTheProtectedMap().size());
        assertNotNull(action.getTheProtectedMap().get("boo"));
        assertNull(action.getTheProtectedMap().get("foo"));
    }

    public void testParametersOverwriteField() throws Exception {
        Map<String, Object> params = new LinkedHashMap<String, Object>();
        params.put("existingMap.boo", "This is blah");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);
        proxy.execute();
        SimpleAction action = (SimpleAction) proxy.getAction();
        assertEquals(1, action.getTheExistingMap().size());
        assertNotNull(action.getTheExistingMap().get("boo"));
        assertNull(action.getTheExistingMap().get("existingKey"));
    }

    public void testNonexistentParametersGetLoggedInDevMode() throws Exception {
        loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"),
                new MockConfigurationProvider(Collections.singletonMap("devMode", "true")));
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("not_a_property", "There is no action property named like this");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);
        ParametersInterceptor.setDevMode("true");

        ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME);
        container.inject(config.getInterceptors().get(0).getInterceptor());
        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);
        proxy.execute();
        final String actionMessage = "" + ((SimpleAction) proxy.getAction()).getActionMessages().toArray()[0];
        assertTrue(actionMessage.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'"));
    }

    public void testNonexistentParametersAreIgnoredInProductionMode() throws Exception {
        loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"),
                new MockConfigurationProvider(Collections.singletonMap("devMode", "false")));
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("not_a_property", "There is no action property named like this");

        HashMap<String, Object> extraContext = new HashMap<String, Object>();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME);
        container.inject(config.getInterceptors().get(0).getInterceptor());
        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);
        proxy.execute();
        assertTrue(((SimpleAction) proxy.getAction()).getActionMessages().isEmpty());
    }

    public void testNoParametersAction() throws Exception {
        ParametersInterceptor interceptor = new ParametersInterceptor();
        interceptor.init();

        MockActionInvocation mai = new MockActionInvocation();
        Action action = new NoParametersAction();
        mai.setAction(action);

        interceptor.doIntercept(mai);
        interceptor.destroy();
    }

    public void testNoOrdered() throws Exception {
        ParametersInterceptor pi = createParametersInterceptor();
        final Map<String, Object> actual = new LinkedHashMap<String, Object>();
        pi.setValueStackFactory(createValueStackFactory(actual));
        ValueStack stack = createStubValueStack(actual);

        Map<String, Object> parameters = new HashMap<String, Object>();
        parameters.put("user.address.city", "London");
        parameters.put("user.name", "Superman");

        Action action = new SimpleAction();
        pi.setParameters(action, stack, parameters);

        assertEquals("ordered should be false by default", false, pi.isOrdered());
        assertEquals(2, actual.size());
        assertEquals("London", actual.get("user.address.city"));
        assertEquals("Superman", actual.get("user.name"));

        // is not ordered
        List<Object> values = new ArrayList<Object>(actual.values());
        assertEquals("London", values.get(0));
        assertEquals("Superman", values.get(1));
    }

    public void testOrdered() throws Exception {
        ParametersInterceptor pi = new ParametersInterceptor();
        pi.setOrdered(true);
        container.inject(pi);
        final Map<String, Object> actual = new LinkedHashMap<String, Object>();
        pi.setValueStackFactory(createValueStackFactory(actual));
        ValueStack stack = createStubValueStack(actual);

        Map<String, Object> parameters = new HashMap<String, Object>();
        parameters.put("user.address.city", "London");
        parameters.put("user.name", "Superman");

        Action action = new SimpleAction();
        pi.setParameters(action, stack, parameters);

        assertEquals(true, pi.isOrdered());
        assertEquals(2, actual.size());
        assertEquals("London", actual.get("user.address.city"));
        assertEquals("Superman", actual.get("user.name"));

        // should be ordered so user.name should be first
        List<Object> values = new ArrayList<Object>(actual.values());
        assertEquals("Superman", values.get(0));
        assertEquals("London", values.get(1));
    }

    public void testSetOrdered() throws Exception {
        ParametersInterceptor pi = createParametersInterceptor();
        assertEquals("ordered should be false by default", false, pi.isOrdered());
        pi.setOrdered(true);
        assertEquals(true, pi.isOrdered());
    }

    public void testExcludedParametersAreIgnored() throws Exception {
        ParametersInterceptor pi = createParametersInterceptor();
        pi.setExcludeParams("dojo\\..*");
        final Map actual = injectValueStackFactory(pi);
        ValueStack stack = injectValueStack(actual);

        final Map<String, Object> expected = new HashMap<String, Object>() {
            {
                put("fooKey", "fooValue");
            }
        };

        Map<String, Object> parameters = new HashMap<String, Object>() {
            {
                put("dojo.test", "dojoValue");
                put("fooKey", "fooValue");
            }
        };
        pi.setParameters(new NoParametersAction(), stack, parameters);
        assertEquals(expected, actual);
    }

    public void testInternalParametersAreIgnored() throws Exception {
        // given
        ParametersInterceptor interceptor = createParametersInterceptor();
        final Map actual = injectValueStackFactory(interceptor);
        ValueStack stack = injectValueStack(actual);


        final Map<String, Object> expected = new HashMap<String, Object>() {
            {
                put("ordinary.bean", "value");
            }
        };

        Map<String, Object> parameters = new HashMap<String, Object>() {
            {
                put("ordinary.bean", "value");
                put("#some.internal.object", "true");
                put("(bla)#some.internal.object", "true");
                put("#some.internal.object(bla)#some.internal.object", "true");
                put("#_some.internal.object", "true");
                put("\u0023_some.internal.object", "true");
                put("\u0023_some.internal.object,[dfd],bla(\u0023_some.internal.object)", "true");
                put("\\u0023_some.internal.object", "true");
            }
        };

        // when
        interceptor.setParameters(new NoParametersAction(), stack, parameters);

        // then
        assertEquals(expected, actual);
    }

    private ValueStack injectValueStack(Map actual) {
        ValueStack stack = createStubValueStack(actual);
        container.inject(stack);
        return stack;
    }

    private Map injectValueStackFactory(ParametersInterceptor interceptor) {
        final Map actual = new HashMap();
        interceptor.setValueStackFactory(createValueStackFactory(actual));
        return actual;
    }

    private ParametersInterceptor createParametersInterceptor() {
        ParametersInterceptor pi = new ParametersInterceptor();
        container.inject(pi);
        return pi;
    }

    private ValueStackFactory createValueStackFactory(final Map<String, Object> context) {
        OgnlValueStackFactory factory = new OgnlValueStackFactory() {
            @Override
            public ValueStack createValueStack(ValueStack stack) {
                return createStubValueStack(context);
            }
        };
        container.inject(factory);
        return factory;
    }

    private ValueStack createStubValueStack(final Map<String, Object> actual) {
        ValueStack stack = new OgnlValueStack(
                container.getInstance(XWorkConverter.class),
                (CompoundRootAccessor)container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()),
                container.getInstance(TextProvider.class, "system"), true) {
            @Override
            public void setValue(String expr, Object value) {
                actual.put(expr, value);
            }
        };
        container.inject(stack);
        return stack;
    }

    /*
    public void testIndexedParameters() throws Exception {
        Map params = new HashMap();
        params.put("indexedProp[33]", "This is blah");

        HashMap extraContext = new HashMap();
        extraContext.put(ActionContext.PARAMETERS, params);

        ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext);
        proxy.execute();
        assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getIndexedProp(33));
    }
    */


    private class NoParametersAction implements Action, NoParameters {

        public String execute() throws Exception {
            return SUCCESS;
        }
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        loadConfigurationProviders(new XmlConfigurationProvider("xwork-test-beans.xml"), new MockConfigurationProvider());

        ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME);
        container.inject(config.getInterceptors().get(0).getInterceptor());
    }
}
