Struts: Frequently asked questions
Here you can find answers to questions on Jive's use of Struts, an open framework for supporting a model-view-controller architecture.
Why does Jive use Struts?
Jive has a long history with the WebWork framework, which became Struts 2.0. In fact, Patrick Lightbody, the creator of WebWork is a former Jive employee. It's served us well as our MVC web framework.
With Jive 9.3, Struts is upgraded to version 2.5.
Is it difficult to migrate from WebWork 2 to Struts 2?
No. Essentially, Struts 2.0 is the technical equivalent of WebWork 2.3. Aside from the package and property renaming, it isn't much different from, for example, migrating from WebWork 2.1 to 2.2.
What does the .jspa file extension stand for?
A .jspa
web extension, such as search.jspa?communityID=1
, represents a servlet alias; therefore, .jspa files do not exist on the file system. Jive Forums and
Jive Knowledge Base both use a web framework called Struts and the .jspa extension maps to the Struts servlet controller. So when a request is made to
search.jspa?communityID=1
, the following actions occur:
- The application server knows to pass control to the dispatcher servlet because of the .jspa mapping declared in the web.xml file.
- The dispatcher servlet knows which action to execute based on the alias ( search in this case). An action is a Java bean which handles logic for the view. In this case, a forum is requested so the forum action is loaded and other things like permissions and authentication are handled.
- The action executes and returns a response code. Based on that code, the dispatcher servlet redirects to a FTL template. The template is executed and uses objects loaded by the action class.
To determine what FTLs are executed when a .jspa is called, view the struts-actions.xml
file. If you are using Jive SBS or Jive Forums 5.5 or later, the file is called
xwork-community.xml
and is in the JAR file.
How does Jive implement pretty URLs? When/how does Jive use the URLMapper? How can I customize this?
In struts.properties
, there is a key called struts.mapper.class
where we provide our own custom implementation of ActionMapper
, which is how
Struts figures out how to map a request to an action. The default is to map actions to the <action name> + .jspa
. For example, when you map the LoginAction.java to the URL
login you would access that via the web by login.jspa. We've extended that concept to support certain friendly URLs, such as /blogs/people/aaron
. These URLs are implemented
through a number of subclasses of URLMapping that manually associate an action with a request path.
For more information, see Clearspace and Meaningful URLs on Worx.
How do we use Struts type converters?
Starting in version 2.0, Jive implemented the type conversion Struts 2.0 facility to populate actions with the correct objects. For example if you're setting the community with a community ID
for the URL /community/feeds/allcontent?community=2001
, Struts would find the com.jivesoftware.community.web.struts.converter.CommunityConverter
, look up the
correct community object based on the ID, and set it in the action.
The mapping file is xwork-conversion.properties
.
There is not currently a way to modify this from within a plugin.
How do we use Struts interceptors?
Jive uses interceptors extensively to provide functionality across a large number of actions. Examples include the ReferrerInterceptor which maintains the previous URL in the session, and the ViewCountInterceptor which updates content read count numbers. This is good if you need to do some pre- or post-processing of the action event from the Struts layer.
For more information, see Introducing Interceptors at Apache Struts portal https://struts.apache.org/getting-started/introducing-interceptors.html.
These aren't currently modifiable from a plugin.
Historically, Jive used an IOCInterceptor to achieve dependency injection and security-related interceptors; those no longer apply in version 2.0, as we use the Spring framework for dependency injection and Acegi for security (now invoked from the Servlet Filter layer).
Does Jive SBS use auto wiring in Struts? Do we use Spring with Struts?
Yes. In the struts.properties
file we set this to use the Spring framework:
struts.objectFactory=spring
CS uses the Spring framework to inject Spring beans into our Struts actions. The actions are autowired by the name of the defined Spring bean:
struts.objectFactory.spring.autowire=name
. If you're writing a plugin, this means that you can access any Spring bean from your action by writing a setter method using the
JavaBean convention. For example, if you need the CommunityManager you add the method setCommunityManager
to your action. If you're writing your own Spring bean, you declare
it in your plugin and follow the same convention.
public void setCommunityManager(CommunityManager communityManager) {
this.communityManager = communityManager;
}
What is the struts-community*.xml file?
This file contains all the struts action definitions.
The most important files are:
struts-community.xml
: The main set of actions.struts-community-admin.xml
: The Admin Console actions.struts.community-custom.xml
: If you have to overlay an action outside of a plugin.
Other interesting files (not usually customized):
struts-community-upgrade.xml
struts-community-setup.xml
What are some best practices for writing forms?
- By default when the action loads go to the input method of your action, such as
/myform!input.jspa
. This maps to theinput()
method on your action class. By default Struts executes theexecute()
method of your action, but you can set it to go to input by default in the action declaration, setdefault="input"
. - Make your form parameter names getters and setters on your action and let Struts auto-write the request parameters.
- Implement the
Preparable
interface to do pre-population of forms.There are 3 ways to add errors and information to your form:
addActionError()
: This generally displays the message at the top of the page.addFieldError()
: This message should appear next to the input field.addActionMessage()
: This message will appear at the top of the page.
- If you implement the
Validateable
interface (or override thevalidate()
method inDefaultActionSupport
) then you can handle standard error flows by making use of the methods above.
I don't want my action to be wrapped by SiteMesh. How do I do that?
The best way is to set the following annotation on your action class @Decorate(false)
.
You can also modify the templates.xml file by adding your URL to the excludes section, if declaring it globally at the action level doesn't work for you. For example, you want to decorate an
input form /form!input.jspa
but not the result, /form.jspa
:
<excludes>
<pattern>/admin/system-log.js*</pattern>
...
</excludes>
How do I get the application to ignore my action when redirecting after a login?
Set the following annotation on your Action class: SetReferer(false)
.
How do Struts and SiteMesh work together?
SiteMesh actually wraps every servlet request from a servlet filter, buffers the output of every request passed in and out of Struts, wrapping the result in a header and footer. If you ever choose not to decorate your request, disable it using one of the methods described above.
When/why would I use the ActionContext
?
You can access the ActionContext
from your action class via the public static ActionContext getContext()
method. This object allows you to access various
lower-level objects, such as the current Request
, Session
, ActionInvocation
.
How to do non FTL results?
You can invoke one of the standard Struts or custom result types in your action declaration. For example:
<action name="spellcheck" class="com.jivesoftware.community.action.SpellCheckAction">
<result name="success" type="stream">
<param name="parse">false</param>
<param name="contentType">text/json</param>
<param name="inputName">jsonStream</param>
<param name="bufferSize">1024</param>
</result>
</action>
When should we use sessions?
Never, ideally. If you use sessions, you are forced to use sticky sessions on your load balancer when you deploy to a cluster.
What are the *ActionSupport classes?
We've grouped convenience methods for your actions under the following classes:
CommunityActionSupport
SocialGroupActionSupport
ProjectActionSupport
BlogActionSupport
What are the steps you go through when debugging a problem with Struts?
If the request URL ends with .jspa, you can look up the action class using struts-community.xml.
If the request URL doesn't end with .jspa, you need to find the URLMapping that maps the URL to the action name. The following URLMapper classes exist in version 2.5:
BlogURLMapping
CommunityURLMapping
DocURLMapping
DummyURLMapping
GlobalFeedURLMapping
MessageURLMapping
PeopleURLMapping
ProjectURLMapping
SearchURLMapping
SocialGroupURLMapping
TagURLMapping
TaskURLMapping
ThreadURLMapping
TreeURLMapping
The line that you are looking for looks something like this:
mapping.setName("view-community-stats-feed");
Given the name of the action from the URLMapping class, you can then find the action class from struts-community.xml and you should also be able to find the FreeMarker template associated with the action name.
Now that you have the action class, you can set a breakpoint in the action's execute
or other method.
Any gotchas doing substitutions in your struts.xml?
When referencing action properties in your struts-community-custom.xml (struts.xml for plugins) file, don't use any FreeMarker built-ins (directives). This can be tempting to do because the syntax is very similar, but Struts isn't actually using FreeMarker substitution here, it's using pure OGNL syntax. FreeMarker is a super-set of OGNL and the full feature set won't work here.
Wrong:
<result name="success" type="redirect">my-new-action.jspa?docID=${document.ID?C}
Notice the use of the freemarker directive ?C
. This will break.
Correct:
<result name="success" type="redirect">my-new-action.jspa?docID=${document.ID}</result>
What are the main technical considerations due to the upgrade to Struts 2.5?
The upgrade to Struts 2.5 brought changes to Jive action methods.
First, before Jive 9.3, Jive relied on a Struts defect where if an action method did not exist, it tried to call do{actionMethodName}
. Such behavior was dropped in Struts 2.5
(apache/struts@586f770). From Jive 9.3 onwards, we made
sure that every action method starting with the keyword do
has an equivalent starting without do
. This ensures backward compatibility within Jive.
doDefault
methods were not renamed because default
is a keyword in Java. Instead, all calls to default
action were adjusted to
doDefault
. Second, the ActionSupport
class introduced a protected method called getContainer
which clashed with JiveContainerAware.getContainer
. To
avoid the name clash, Jive subclasses JiveBaseActionSupport
which delegates to ActionSupport
.