Struts Version: 2.5.2
Struts Dependencies in POM
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>${org.strutsframework-version}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-convention-plugin</artifactId>
<version>${org.strutsframework-version}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-rest-plugin</artifactId>
<version>${org.strutsframework-version}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-spring-plugin</artifactId>
<version>${org.strutsframework-version}</version>
</dependency>
Struts xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<!-- Tell jinjava where the templates are -->
<constant name="struts.jinjava.basepath" value="WEB-INF/jinjava" />
<!-- custom jinjava tags specific to iws -->
<constant name="struts.jinjava.scan.tagPackage" value="com.hs.iws.jinjava.tag" />
<constant name="struts.jinjava.scan.functionPackage" value="com.hs.iws.jinjava.function" />
<!--Tell struts to use the REST action Mapper-->
<!--<constant name="struts.mapper.class" value="rest"/>-->
<!-- allow rest and non rest actions to live together -->
<constant name="struts.mapper.class" value="org.apache.struts2.dispatcher.mapper.PrefixBasedActionMapper" />
<constant name="struts.mapper.prefixMapping" value=":rest,/grid:struts"/>
<constant name="struts.rest.namespace" value="/" />
<constant name="struts.convention.action.suffix" value="Action"/>
<constant name="struts.convention.action.mapAllMatches" value="true"/>
<constant name="struts.convention.package.locators.basePackage" value="com.hs.iws.actions" />
<!--re-assert the extensions for struts that have been over written by the rest plugin-->
<constant name="struts.action.extension" value="xhtml,,json,action"/>
<constant name="struts.rest.content.restrictToGET" value="false" />
<!--configure Convention Plugin to find our controllers-->
<constant name="struts.convention.default.parent.package" value="iws-default"/>
<!-- Spring Configuration -->
<!-- <constant name="struts.objectFactory" value="spring" /> -->
<constant name="struts.objectFactory.spring.autoWire" value="type" />
<!-- all grid actions should fall under this package -->
<package name="iws-grid" namespace="/grid" extends="struts-default,jweb-struts-gson-json,jinjava,datatables">
<interceptors>
<interceptor-stack name="iws-datatable-stack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="datetime"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params"/>
<interceptor-ref name="gson-json" />
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="debugging"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="iws-datatable-stack" />
</package>
<package name="iws-default" extends="rest-default, struts-default, jinjava, jweb-struts-gson-json" namespace="/">
</package>
</struts>
Action Class
package com.hs.iws.actions;
import com.hs.datatables.DataTable10CriteriaQuery;
import com.hs.datatables.DataTable10Helper;
import com.hs.iws.model.Users;
import org.apache.struts2.convention.annotation.*;
/**
* Created by Paul on 9/14/2016.
*/
@InterceptorRef(value = "iws-datatable-stack")
@ParentPackage(value = "iws-grid")
public class TestGridAction extends DataTable10CriteriaQuery{
@Action(value="/test-grid-json",
results={
@Result(name = "success", type = "datatable")
}
)
public String execute() {
return super.execute();
}
@Override
protected Class<?> getHibernateClass() {
return Users.class;
}
}
I am working with the DataTables JS library and am trying to write actions for the grid. I have a api in place that creates the json for me already and I just need to stream it back. I created a custom result to handle this, but the result mapped in the action never runs. No matter what the result type, the REST mapper tries to handle it as soon as it sees that application/json has been requested from the client. I have used the prefix mapping in the configuration to have all url's using /grid to bypass the rest mapper. It seems to be working in some capacity because it runs the correct interceptor stack and is using the @Action annotation information to map the url. However, the result specified is NOT running and is instead being provided by the rest mapper based on the stack trace I am receiving. I would like to completely bypass the rest mapper for any actions in the /grid namespace. Have I done something wrong in the configuration that is still causing rest to be involved in the request to those actions?
Stack Trace
ERROR RestActionInvocation Exception processing the result.
net.sf.json.JSONException: java.lang.reflect.InvocationTargetException
at net.sf.json.JSONObject._fromBean(JSONObject.java:987)
at net.sf.json.JSONObject.fromObject(JSONObject.java:168)
at net.sf.json.AbstractJSON._processValue(AbstractJSON.java:265)
at net.sf.json.JSONArray._processValue(JSONArray.java:2514)
at net.sf.json.JSONArray.processValue(JSONArray.java:2539)
at net.sf.json.JSONArray.addValue(JSONArray.java:2526)
at net.sf.json.JSONArray._fromCollection(JSONArray.java:1057)
at net.sf.json.JSONArray.fromObject(JSONArray.java:123)
at net.sf.json.AbstractJSON._processValue(AbstractJSON.java:237)
at net.sf.json.JSONObject._processValue(JSONObject.java:2808)
at net.sf.json.JSONObject.processValue(JSONObject.java:2874)
at net.sf.json.JSONObject.setInternal(JSONObject.java:2889)
at net.sf.json.JSONObject.setValue(JSONObject.java:1577)
at net.sf.json.JSONObject._fromBean(JSONObject.java:934)
at net.sf.json.JSONObject.fromObject(JSONObject.java:168)
at net.sf.json.AbstractJSON._processValue(AbstractJSON.java:265)
at net.sf.json.JSONObject._processValue(JSONObject.java:2808)
at net.sf.json.JSONObject.processValue(JSONObject.java:2874)
at net.sf.json.JSONObject.setInternal(JSONObject.java:2889)
at net.sf.json.JSONObject.setValue(JSONObject.java:1577)
at net.sf.json.JSONObject._fromBean(JSONObject.java:934)
at net.sf.json.JSONObject.fromObject(JSONObject.java:168)
at net.sf.json.JSONObject.fromObject(JSONObject.java:130)
at org.apache.struts2.rest.handler.JsonLibHandler.fromObject(JsonLibHandler.java:72)
at org.apache.struts2.rest.DefaultContentTypeHandlerManager.handleResult(DefaultContentTypeHandlerManager.java:181)
at org.apache.struts2.rest.RestActionInvocation.executeResult(RestActionInvocation.java:227)
at org.apache.struts2.rest.RestActionInvocation.processResult(RestActionInvocation.java:194)
at org.apache.struts2.rest.RestActionInvocation.invoke(RestActionInvocation.java:142)
at com.opensymphony.xwork2.DefaultActionProxy.execute(DefaultActionProxy.java:154)
at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:556)
at org.apache.struts2.dispatcher.ExecuteOperations.executeAction(ExecuteOperations.java:81)
at org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:113)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at com.hs.security.SecurityScanner.doFilter(SecurityScanner.java:95)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:105)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:506)
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:620)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:1078)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:760)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1524)
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:745)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:2116)
at org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(PropertyUtilsBean.java:1267)
at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:808)
at org.apache.commons.beanutils.PropertyUtilsBean.getProperty(PropertyUtilsBean.java:884)
at org.apache.commons.beanutils.PropertyUtils.getProperty(PropertyUtils.java:464)
at net.sf.json.JSONObject._fromBean(JSONObject.java:918)
... 52 more
Caused by: java.lang.UnsupportedOperationException: JsonObject
at com.google.gson.JsonElement.getAsByte(JsonElement.java:257)
... 62 more
I've run into a similar issue. First, as you probably already know the struts2-rest-plugin can return more than just json, it can return xml, and xhtml as well (by switching on the file extension in the url). The annoying reason that your result does not work is that the strtus2-rest-plugin doesn't use results but uses ContentTypeHandlers which supersede any attempts to use results.
I think in your case, the .action suffix disrupts the rest-plugin and so it goes looking for another matching action, probably using plain old conventions.
In my own code I created a restful and non-restful packages to side step this issue. I also needed to create my own content-type handler to replace their default. It is possible to turn a custom result into a custom-content-type handler, but unless it is of type "xml, json, or xhtml" I think creating a non-restful package, and using that to house those actions makes more sense.
If I have time tonight I'll provide a copy of the struts.xml used to create two sets of packages. This will be useful because I found the struts2-rest-plugin's configuration to be brittle (not very intuitive, and require lines that didn't entirely make sense to me which were added more by guessing than logic).