Skip to content

java.lang.UnsupportedOperationException when struts2 is running in same app. #623

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
lalo-mx opened this issue Jul 10, 2017 · 5 comments
Closed

Comments

@lalo-mx
Copy link

lalo-mx commented Jul 10, 2017

Hello,

I have a application with thymeleaf 2.1.4.RELEASE and while i was trying to update to the 3.0.6.RELEASE version i found an issue with ognl.

org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "/WEB-INF/templates/report.html")
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:241)
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parseStandalone(AbstractMarkupTemplateParser.java:100)
at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:666)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1059)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1048)
at app.FormAction.execute(FormAction.java:29)
at sun.reflect.GeneratedMethodAccessor1686.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java)
at java.lang.reflect.Method.invoke(Method.java:498)
at ognl.OgnlRuntime.invokeMethod(OgnlRuntime.java:873)
at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1539)
at ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:68)
at com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethodWithDebugInfo(XWorkMethodAccessor.java:96)
at com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethod(XWorkMethodAccessor.java:88)
at ognl.OgnlRuntime.callMethod(OgnlRuntime.java:1615)
at ognl.ASTMethod.getValueBody(ASTMethod.java:91)
at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
at ognl.SimpleNode.getValue(SimpleNode.java:258)
at ognl.Ognl.getValue(Ognl.java:467)
at ognl.Ognl.getValue(Ognl.java:431)
at com.opensymphony.xwork2.ognl.OgnlUtil$3.execute(OgnlUtil.java:351)
at com.opensymphony.xwork2.ognl.OgnlUtil.compileAndExecuteMethod(OgnlUtil.java:403)
at com.opensymphony.xwork2.ognl.OgnlUtil.callMethod(OgnlUtil.java:349)
at com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:436)
at com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:291)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:252)
at org.apache.struts2.interceptor.debugging.DebuggingInterceptor.intercept(DebuggingInterceptor.java:253)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:177)
at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.validator.ValidationInterceptor.doIntercept(ValidationInterceptor.java:260)
at org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor.doIntercept(AnnotationValidationInterceptor.java:73)
at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.doIntercept(ConversionErrorInterceptor.java:139)
at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:133)
at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:133)
at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.StaticParametersInterceptor.intercept(StaticParametersInterceptor.java:192)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at org.apache.struts2.interceptor.MultiselectInterceptor.intercept(MultiselectInterceptor.java:69)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at org.apache.struts2.interceptor.DateTextFieldInterceptor.intercept(DateTextFieldInterceptor.java:115)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at org.apache.struts2.interceptor.CheckboxInterceptor.intercept(CheckboxInterceptor.java:88)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at org.apache.struts2.interceptor.FileUploadInterceptor.intercept(FileUploadInterceptor.java:246)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor.intercept(ModelDrivenInterceptor.java:99)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor.intercept(ScopedModelDrivenInterceptor.java:139)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.ChainingInterceptor.intercept(ChainingInterceptor.java:155)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:174)
at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at org.apache.struts2.interceptor.I18nInterceptor.intercept(I18nInterceptor.java:120)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:171)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.AliasInterceptor.intercept(AliasInterceptor.java:195)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.intercept(ExceptionMappingInterceptor.java:193)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247)
at org.apache.struts2.factory.StrutsActionProxy.execute(StrutsActionProxy.java:54)
at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:564)
at org.apache.struts2.dispatcher.ExecuteOperations.executeAction(ExecuteOperations.java:81)
at org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:143)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.__invoke(StandardContextValve.java:96)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.attoparser.ParseException: Exception evaluating OGNL expression: "boss.name" (template: "report.html" - line 5, col 21)
at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
at org.attoparser.MarkupParser.parse(MarkupParser.java:257)
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
... 95 more
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating OGNL expression: "boss.name" (template: "report.html" - line 5, col 21)
at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:191)
at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:95)
at org.thymeleaf.standard.expression.VariableExpression.executeVariableExpression(VariableExpression.java:165)
at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:66)
at org.thymeleaf.standard.expression.Expression.execute(Expression.java:109)
at org.thymeleaf.standard.expression.Expression.execute(Expression.java:138)
at org.thymeleaf.standard.expression.Expression.execute(Expression.java:125)
at org.thymeleaf.standard.processor.AbstractStandardExpressionAttributeTagProcessor.doProcess(AbstractStandardExpressionAttributeTagProcessor.java:82)
at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74)
at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95)
at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633)
at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1314)
at org.thymeleaf.engine.TemplateHandlerAdapterMarkupHandler.handleOpenElementEnd(TemplateHandlerAdapterMarkupHandler.java:304)
at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler$InlineMarkupAdapterPreProcessorHandler.handleOpenElementEnd(InlinedOutputExpressionMarkupHandler.java:278)
at org.thymeleaf.standard.inline.OutputExpressionInlinePreProcessorHandler.handleOpenElementEnd(OutputExpressionInlinePreProcessorHandler.java:186)
at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler.handleOpenElementEnd(InlinedOutputExpressionMarkupHandler.java:124)
at org.attoparser.HtmlElement.handleOpenElementEnd(HtmlElement.java:109)
at org.attoparser.HtmlMarkupHandler.handleOpenElementEnd(HtmlMarkupHandler.java:297)
at org.attoparser.MarkupEventProcessorHandler.handleOpenElementEnd(MarkupEventProcessorHandler.java:402)
at org.attoparser.ParsingElementMarkupUtil.parseOpenElement(ParsingElementMarkupUtil.java:159)
at org.attoparser.MarkupParser.parseBuffer(MarkupParser.java:710)
at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:301)
... 97 more
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating OGNL expression: "boss.name"
at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:191)
at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:177)
... 118 more
Caused by: java.lang.UnsupportedOperationException
at java.util.AbstractMap.put(AbstractMap.java:209)
at ognl.OgnlContext.put(OgnlContext.java:579)
at com.opensymphony.xwork2.ognl.accessor.ObjectAccessor.getProperty(ObjectAccessor.java:19)
2017-07-10 09:17:32,827 DEBUG [http-nio-9381-exec-8] dispatcher.DefaultDispatcherErrorHandler (DefaultDispatcherErrorHandler.java:86) - Exception occurred during processing request: org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "/WEB-INF/templates/report.html")
at ognl.OgnlRuntime.getProperty(OgnlRuntime.java:2666)
at ognl.ASTProperty.getValueBody(ASTProperty.java:114)
at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
at ognl.SimpleNode.getValue(SimpleNode.java:258)
at ognl.ASTChain.getValueBody(ASTChain.java:141)
at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
at ognl.SimpleNode.getValue(SimpleNode.java:258)
at ognl.Ognl.getValue(Ognl.java:467)
at ognl.Ognl.getValue(Ognl.java:431)
at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.executeExpression(OGNLVariableExpressionEvaluator.java:316)
at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:170)

VARIABLE
`public class Boss {

public Boss(String name) {
    this.name = name;
}

private String name;

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

}
`

TEMPLATE

<div>
<h1>Thymeleaf 3 + Strut 2 example</h1>
<p>
Hello <span th:text="${boss.name}">Boss</span>!!!
</p>
</div>

In the same app i have a dependency with Struts 2.

It looks like Struts is replacing ognl.ObjectMethodAccessor
with com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.

Is that an error o is it thymeleaf and Struts2 fully incompatibles?

NOTE: Without Struts2 it works as expected.

app-example.zip

@lalo-mx
Copy link
Author

lalo-mx commented Jul 13, 2017

I found a solution for the issue, in the xwork-core library(XWorkObjectPropertyAccessor) they set some values in the context.

`

public class XWorkObjectPropertyAccessor extends ObjectPropertyAccessor {

@Override
public Object getProperty(Map context, Object target, Object oname)
        throws OgnlException {

    //set the last set objects in the context
    //so if the next objects accessed are
    //Maps or Collections they can use the information
    //to determine conversion types

    context.put(XWorkConverter.LAST_BEAN_CLASS_ACCESSED, target.getClass());
    context.put(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED, oname.toString());
    ReflectionContextState.updateCurrentPropertyPath(context, oname);
    return super.getProperty(context, target, oname);

}`

Then i modified the class OGNLVariableExpressionEvaluator(thymeleaf) in the method "evaluate" in the part:

`

            if (expContext.getRestrictVariableAccess()) {
                contextVariablesMap = CONTEXT_VARIABLES_MAP_NOEXPOBJECTS_RESTRICTIONS;
            } else {
                contextVariablesMap = Collections.EMPTY_MAP;
            }

`

Changing the immutable map with a HashMap the issue was solved for my particular case.
`

            if (expContext.getRestrictVariableAccess()) {
                contextVariablesMap = new HashMap<String, Object>(1);
                contextVariablesMap.put(OGNLContextPropertyAccessor.RESTRICT_REQUEST_PARAMETERS, OGNLContextPropertyAccessor.RESTRICT_REQUEST_PARAMETERS);
            } else {
                contextVariablesMap = new HashMap<String, Object>(0);
            }

`

@danielfernandez
Copy link
Member

Hmmm… I'm not sure this actually has a solution.

The main problem here is that OGNL is not really prepared to have more than one tenant executing it in the same application, a large part of its configuration being made by means of static methods and so. So Struts2 and Thymeleaf making use of OGNL leads to a conflict here. I think we can perfectly call this an OGNL limitation.

The conflict consists on Struts2 adding a PropertyAccesor (which, being the configuration global, affects Thymeleaf) which not only does access the needed object property, but actually performs modifications on the context while doing that.

I understand this might be of some use for Struts2, but it definitely is an unexpected and undesired side effect from the Thymeleaf standpoint.

The problem is that in this case these constant, unmodifiable context maps you've changed code for are very used. Actually, they are used for most expressions in a template because most expressions do not make use of expression objects. So a lot of map instantiations are saved (note that this code is executed for every expression in every template). The problem with changing this to a new HashMap<>() is, therefore, that we would be adding a huge amount of HashMap instantiations for each template, even if Struts2 is not used. So I'm not sure on how to approach this, to be honest, but at first thought it seems like a high price to pay for a side effect from a third-party library…

@lalo-mx
Copy link
Author

lalo-mx commented Jul 20, 2017

I had not thought about the performance issue, what do you think of a wrapper for the immutable map that does not throw errors and does not allow adding new elements?



/**
 *
 * @param <K> the type of keys maintained by this map
 * @param <V> the type of mapped values
 * @author Eduardo Guadalupe Quintanilla Flores
 */
public class ImmutableMap<K,V> implements Map<K,V>, Cloneable, Serializable {

    private final Map<K,V> map;

    public ImmutableMap(Map<K,V> map) {
        this.map = map;
    }
        
    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }

    @Override
    public V get(Object key) {
        return map.get(key);
    }

    @Override
    public V put(K key, V value) {
        return null;
    }

    @Override
    public V remove(Object key) {
        return null;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
    }

    @Override
    public void clear() {
    }

    @Override
    public Set<K> keySet() {
        return map.keySet();
    }

    @Override
    public Collection<V> values() {
        return map.values();
    }

    @Override
    public Set<Map.Entry<K,V>> entrySet() {
        return map.entrySet();
    }

}

And then use it like this:

	if (expContext.getRestrictVariableAccess()) {
	    contextVariablesMap = new ImmutableMap(CONTEXT_VARIABLES_MAP_NOEXPOBJECTS_RESTRICTIONS);
	} else {
	    contextVariablesMap = new ImmutableMap(Collections.EMPTY_MAP);
    }

@danielfernandez
Copy link
Member

I've had a look at the Struts 2 code, and I'm afraid I think the solution you propose would not be adequate. First, because it would be quite intrusive, adding to the Thymeleaf core a feature that would be merely a workaround –not really a fix– for a specific scenario in Struts 2; and Second, because such workaround in itself would in fact provoke an unexpected behaviour by swallowing errors. I mean, this could be OK in a very specific OGNL evaluator created for a very specific scenario… but not at the default OGNL evaluator at the Thymeleaf core.

So given that the StandardDialect does allow users to specify their own implementations of the IStandardVariableExpressionEvaluator, and therefore it would be relative easy to create an evaluator implementation equivalent to the already existing (and used by default) org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator. Then this application- or framework-specific implementation could use a context like the one you suggest to work around this behaviour in Struts 2.

Alternatively, and given this behaviour seems to only appear in Struts 2 when ObjectProxy objects are added to the context (given it is the ObjectProxyPropertyAccessor who tries to do these modifications, maybe using this kind of objects could be avoided in Struts 2 applications somehow. I don't have much modern Struts knowledge, so I don't know if this is even possible…

So I'm going to close this ticket as a declined feature, given there is a valid workaround for user applications/libraries (create a custom evaluator), and applying such workarounds to the Thymeleaf core would IMHO create more issues than it would solve.

@miao1007
Copy link

Hi , i met the same error, and patched by replace the singleton map with hashmap.

here is the code, but it may be only for my small project. and it will be slow and high memory cost.

public static IDialect getDialect() {
    StandardDialect dialect = new StandardDialect();
    try {
        Class.forName("com.opensymphony.xwork2.ognl.accessor.ObjectAccessor") //check has struts
    } catch (ClassNotFoundException e) {
        return dialect;
    }
    try {//replace the singleton map with hashmap
        Field field = OGNLVariableExpressionEvaluator.class.getDeclaredField("CONTEXT_VARIABLES_MAP_NOEXPOBJECTS_RESTRICTIONS");
        field.setAccessible(true);
        field.set(null, new HashMap<String, Object>())
    } catch (Exception e) {
        throw new RuntimeException("Thymleaf can't work with struts.", e)
    }
    OGNLVariableExpressionEvaluator evaluator = new OGNLVariableExpressionEvaluator(true);
    dialect.setVariableExpressionEvaluator(new IStandardVariableExpressionEvaluator() {
        Object evaluate(IExpressionContext context, IStandardVariableExpression expression, StandardExpressionExecutionContext expContext) {
            expContext = StandardExpressionExecutionContext.RESTRICTED; //false, false -> true, false
            return evaluator.evaluate(context, expression, expContext);
        }
    })
    return dialect
}

It's just a transitional patch, i will later replace all struts with thymleaf/SpringMVC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants