Struts 2 is one of the most popular web application frameworks which is developed with pull-MVC pattern as its base. Most of the web applications developed today uses struts as their building tool. Now, the latest release of struts was in the market and there is a demanding need to migrate the old applications developed with struts to new struts release in order to make use of the powerful features offered by new release.
It is assumed that readers of this article are aware of struts web application framework. As a primer, we will discuss the architecture of the new framework. We can look at struts as a front controller. This is the characteristic of struts in the beginning and now also as well. Since struts is nothing but the functional combination of servlets and JSPs, actions will be invoked through URLs and the input information for those actions will be supplied in the form of form parameters and request parameters itself and the servlet objects like session, request, response and others are still available for the actions in struts. This is the basic overview.
also read:
- Introduction to Struts Actions
- Struts 2.0 and JPA Integration
- Integrating Struts 2.0 applications with Hibernate
- Struts 2.0 and Validations using Annotations
Any action required by the struts 2 user will be executed by performing the sequence of steps as stated below
- User will make a request for a particular resource
- In order to identify what interceptors, action classes and results to be used to serve this request, framework first matches the request with a particular configuration. From this configuration, framework identifies which interceptors, action classes and results to be used.
- This request is passed through number of interceptors
- A request will be pre-processed in order to dynamically identify certain action execution criteria. To support pre-processing, interceptors and interceptor stacks are configured at different levels to scan the requests. This is something similar to that of RequestProcessor in older version of struts.
- Requested action will be determined and invoked
- In latest release of struts, we can configure the method of a particular class for invoking the action of that request. In this step, an action class will be instantiated and the method that represents the action will be invoked.
- Post action execution, result is invoked
- Once the action is completed, based on the result of the action, the result action will be instantiated and invoked. The output information that is to be returned to the user will be embedded into this result object and the furnished request is returned back.
- Results of the request will be returned to user through interceptors
- This request returned in above step will again passes through a set of interceptors in order to ensure the clean-up that is required if any.
- Final response reaches the user
- Passing through all of these steps, finally response in the form of html page will be returned to the user. There is no need for the action to return the html page but it can also return other Meta information and even it can result in invoking some other action.
In earlier struts, usually data will be present in the servlet object scopes like request scope, response scope and so on. But, in struts 2, data will be pulled as a result from executing a particular action. This is the reason why struts 2 is pull-MVC based framework.
Equipped with the above primer, we will proceed to discuss about migrating old struts application to new.
Configuration of deployment descriptor
The first and most important migration step is to modify the deployment descriptor file of the web application. Every web application developer knows about deployment descriptor which is nothing but web.xml. this is the first place where we will enable the struts web application framework in servlet containers. We will do this by specifying struts action class in web.xml as follows
<servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
But in struts 2, this dispatcher of the action is changed from servlet to a filter. So, this configuration change needs to be done as shown below.
<filter> <filter-name>myFilter</filter-name> <filter-class>org.apache.struts.action2.dispatcher.FilterDispatcher</filter-class> </filter> <filter-mapping> <filter-name>myFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Observe here that there is no extension specified in the URL pattern. In latest struts 2, .action is considered as the default extension. But be warned that in struts 2.3.14.3 version, this seems to be dropped (taking .action as by default) because of changes related to S2-015 and wild card action mapping. This is defined as struts.action.extension property in default.properties file. Generally this default.properties file will be the location where different properties are defined. It is recommended to make use of the extension .action explicitly to make the application run successfully with 2.3.14.3 version of struts. If you want to override the default properties of the struts application, then you can make it through define the struts.properties file and placing this file in the classpath. In this way, it is possible to override the default properties.
Struts 2 Configuration File
In order to configure struts such that the servlet container will be aware of it, In old struts, we used to specify the configuration files through init-param attribute under servlet tag in web.xml. with the latest struts, there is no need to specify the configuration files through deployment descriptor but it will have the separate default configuration file called struts.xml. we need to create the struts.xml with the required parameters for the application and need to place this file in classpath so that it will be available for the application.
It is important to remember that the actions will have the .do extension in earlier struts but in latest struts, it is with .action extension. In new release, it is not possible to have actions with both the extensions at same place. This feature helps us to distinguish the old and new applications very easily.
The above discussion explains the configuration related changes. To summarize,
- You need to modify the deployment descriptor (web.xml) so that it uses the filter
- Recommended to specify the extension of .action in Deployment Descriptor explicitly
- Create the struts.xml configuration file and place it in the classpath
Defining the action structure
In struts older version, we need to write the action such that it has to follow certain principle as stated below
- Each and every action class needs to extend Action base class. This removes the ability to extend the functionality of this particular action class further.
- Need to assure that only one action class instance is created at any period of time by making it thread safe.
- Always the method that needs to be invoked to execute the action is “Execute”. In some case, there is a method called DispatchAction which re-directs the action to some other method.
- The result of the action is returned in the form of ActionForward which is constructed by ActionMapping class.
Struts 2, even though makes use of the same steps as said above most of the time, it is made very simple so that one can become more efficient in making use of it. Below is the code that demonstrates this
Old Struts Action Example:
Public class SampleAction extends Action { public ActionForward execute(ActionMapping mapper, ActionForm details, HttpServletRequest req, HttpServletResponse res) { // business logic goes here return (mapper.findForward(“Success”)); } }
New Struts Action Example:
public class NewStrutsAction { public string execute() throws Exception { // Business logic goes here return “success”; } }
In new implementations, you no need to extend the Action Base Class and depend on the ActionMapping class to create the result. You can implement your business logic in execute method.
It is even not mandatory to specify the execute method in order to execute your action. You can specify any method as the execute method until it follows the guidelines of having the method signature in the configuration file. This means, you can specify any method in the configuration file with public string methodname() signature and that suffice for executing the execution of action. One more thing to remember is that there is no need of parameters to this execute method. You might be wondering how we will get access to the objects like ActionMapping and ActionForm and the request and response in order to execute the action. The information that is required to perform the action is supplied through the usage of “inversion of control” or “dependency injection” pattern. This is not being used in earlier struts releases.
Lets see how this dependency injection works. Now, consider the case where you need access to the request object. With the dependency injection, we need to implement an interface and the name of that interface is “ServletRequestAware”. The method defined in this interface is setServletRequest. Using this method, we will supply the request object to the application. The signature of this interface is as below
public interface ServletRequestAware { public void setServletRequest(HttpServcletRequest req); }
So, now to implement the execute functionality, below is the format to implement the action class
public class myActionClass implements ServletRequestAware { private HttpServletRequest myRequest; public void setServletRequest(HttpServletRequest req) { myRequest = req; } public String execute() throws Exception { return Action.SUCCESS; } }
This might look little bit complex, but this approach cleanly separates the business logic from minute level dependencies.
One more last step in this process is to attach the ServletConfigInterceptor with this action. The main goal of the interceptor is to obtain the request object and inject it into the action. Remember the importance of implementing the above said interface as the interceptor works with the interface very closely to incorporate the dependency injection pattern into the application. To remind, the use of this dependency injection pattern is to separate the business logic from minute details so that this logic is in separate POJO class separate from framework and can be used outside of the framework as well.
Migrating Struts Application to Struts 2
To explain the process better, we will focus on one simple application. We will explain how this application is implemented using old struts framework and then convert it into the new application that uses the new struts framework.
Consider the example of a weblog. So, implementation of this weblog feature involves the following steps.
- Adding a new weblog entry
- Viewing the weblog entry
- Editing the weblog entry
- Removing the weblog entry
- Able to see all the weblog entries
Apart from the above specified steps, there is also one more component which binds all the above steps together. This is nothing but the main back end application service.
Implementing the above steps in the specified order will improve the productivity to a great extent. Here we will follow the process of iteration where we will develop the application first using old struts framework and then convert it into the new framework.
The main business service that hosts all the above steps will look like this
public class wewebLogEntryService { private static List<webLogEntry> entries = new ArrayList<webLogEntry>(); public webLogEntry createEntry(webLogEntry entry){...} public void updateEntry(webLogEntry entry){...} public void deleteEntry(weblogEntry entry){...} public webLogEntry findEntry(String entry){...} }
In order to simply the process, we will create the instance of this service in action. In struts2 framework, spring framework is used as the default container. Because of this reason, adding a setter method to the action allows the struts2 framework to retrieve the correct service from the spring framework context and use that service information to apply to the action through the setter method.
The method signature of the setter method is as follows
public void setWebLogEntryService(wewebLogEntryService service) { this.wewebLogEntryService = service; }
While constructing the new struts 2 application, we need to specify the interceptor called “ActionAutoWritingInterceptor” in the interceptor stack. As we already know that interceptors can be used to perform the pre-processing, here by including the interceptor, we can inject the spring managed business objects can be included in struts2 framework. Later through the configuration files, we will specify how the match between the setter and the business object can be achieved.
Developing the application
For each and every step specified above, there will be one action class associated with it along with the action form. These action form classes can be used across all the actions available in the application.
Since the common component in the struts application is the action form which will be used across the actions in the application, we will focus on developing this first
public class WebLogEntryForm extends ActionForm { private String entryIdentifier; private String entryName; private String entryContent; public void setEntryIdentifier(String id) { entryIdentifier = id; } public String getEntryIdentifier() { return entryIdentifier; } // similarly set the setters and getters for other properties. }
Now we are ready with the ActionForm and this will be used by other actions like creating the entry, updating the entry and deleting the entry.
Developing the View Action
public class viewwebLogEntry extends Action { public ActionForward execute(ActionMapping mapper, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception { webLogEntryService service = new wewebLogEntryService(); String id = req.getParameter(“entryId”); req.setAttribute("webLogEntry",service.findEntry(id)); return (mapper.findForward("success")); } }
Developing the Create action
public class SaveWebLogEntryAction extends Action { public ActionForward execute(ActionMapping mapper, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception { webLogEntryService service = new wewebLogEntryService(); webLogEntryForm webLogForm = (webLogEntryForm) form; webLog webLog = new webLog(); BeanUtils.copyProperties( webLog, webLogForm); service.createEntry( webLog ); return (mapper.findForward("success")); } }
Developing the Update Action
public class UpdateWebLogEntryAction extends Action { public ActionForward execute(ActionMapping mapper, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception { webLogService service = new webLogService(); webLogForm webLogForm = (webLogForm) form; webLog webLog =service.findEntry(webLogForm.getEntryIdentifier()); BeanUtils.copyProperties(webLog, webLogForm); service.update(webLog); req.setAttribute("webLog ", webLog); return (mapper.findForward("success")); } }
When we carefully look at the above code, we can observe a specific pattern here.
- Business object is instantiated in the action class.
- Data is retrieved from the request in one of the two forms that is data is retrieved from the request object directly as well as data is retrieved from the ActionForm objects. Remember that request object and the actionForm objects are similar.
- Now the functionality of the business service is used to execute the action where if the parameter that is passed to the service function is simple it can be used directly by converting that parameter type into the appropriate type. But if the parameter type is complex, then ActionForm is required to convert the complex object into the correct type and then that is used further.
- Once the action is executed, we need to prepare the return data. So, in order to return the information back to the user, we need to mould the return data into an object and that object will be set as an attribute into the request object.
- Finally, we need to find the ActionForward object and that needs to be returned.
Usage of new struts framework in developing the action code
In this approach, instead of developing separate action classes for each of the actions like creation, updation, deletion and viewing of the information, we will develop a single class that implements all of this actions at once place.
In this approach, each activity is represented by a method in the action class. Here, we will have a single action class where we will have separate methods for each action. This action class will have to implement 3 interfaces that are ServletRequestAware, Prepareable and ModelDriven.
In this, we already discussed about ServletRequestAware interface which is used to inject the request object into the application. Using this ServletRequestAware interceptor we will inject the request object into the application which also can be used to inject the result data into the request which further can be used to render the result to the end user.
The next thing is, apart from supplying the request object to the application, there is a need to do some pre-action activities like configurations, set-ups and population of data. This can be achieved through the prepareInterceptor. Using the prepareInterceptor means we need to implement the Prepearable Interface whose signature is as below
public void prepare() throws Exception { // your prepare logic goes here }
When our action class implements the prepearable interface, it implements the prepare method and this method will be called before the execute method allowing us to do the configurations, setup and populating of required data to perform the action. So, applying this context to our application, we use the prepearable interface to check whether the entry we are trying to add is the new entry or the existing entry. If it is new entry, it is added to the list but if it is existing entry, then that entru will be retrieved and displayed to the user stating that the entry already exist.
In earlier struts applications, we need that the action should be thread safe. But in new struts release, there is no such restriction. Because of this freedom, we can make use of the class level attributes and methods to execute the action effectively. Having this concept in mind, ModelDriven interface is implemented in the following manner
- The list of attributes are retrieved from the request and are iterated over
- Next is to search for the setter methods of that particular property in the action class
- Then the value of this particular property is retrieved from the HttpServletRequest object
- This retrieved value is then converted into the appropriate type matching with that of the action setter method either by directly converting it to correct type of by using the ActionForm object to convert it into correct type
- This converted value is then applied to the action through using the setter method
The ParameterInterceptor is used to provide all the above functionalities. It is important to make sure that this interceptor is there on the interceptor stack. This is a good programming practice to follow since it allows to identify any problems in the application very easily because most of the times the issues in the applications are related to the interceptors. (business logic issues are programmers self-responsibility).
Now, since we are equipped with string based forms, we need to apply this form to the fields of the domain object or transfer object. Actually this is very simple step. This is the only thing that differentiates you from developers. All you have to do in this step is to implement the ModelDriven interface thus making ModelDrivenInterceptor available to the action. Once this is done, the dependency on setter method will vanish. All you have to do is top check whether there is any setter method available on the action and if one is available, you have to retrieve the model and from the model, check to see whether there is any setter method that matches the attribute. In this scenario if you find no method on the model but is there on the action, then the value will be set on the model. This gives flexibility for setting various fields with their values. For instance, this approach is used in the webLogEntryService to find the correct entry before even adding the entry to the list. This avoids unnecessary manipulation of the field values for each entry.
With the above information, implementing the methods that can be invoked on the action becomes very easy. Using this approach, the required business service can be invoked and the information that needs to be returned back to the user can be embedded into the request object.
public class WebLogEntryActivity extends ActionSupport implements ModelDriven, Preparable, ServletRequestAware { private String webLogId; private webLog webLog; private WebLogEntryService service = new WebLogEntryService(); private HttpServletRequest req; public void setServletRequest(HttpServletRequest httpServletRequest) { this.req = httpServletRequest; } public void setId(String blogId) { this.webLogId = blogId; } public void prepare() throws Exception { if( webLogId==null ) { webLog = new webLog(); } else { webLog = service.findEntry(blogId); } } public Object getModel() { return webLog; } public String saveEntry() { service.createEntry(webLog); return SUCCESS; } public String updateEntry() { service.updateEntry(webLog); req.setAttribute("webLog",webLog); return SUCCESS; } public String removeEntry() { service.deleteEntry(webLogId); return SUCCESS; } }
This above code configurations completes the discussion about the code changes that we need to adopt.
Action Configuration
Before we need to invoke any of the actions from the browsers, we need to configure them such that servlet containers should understand the actions requested. This is done by using the XML configuration files.
The main configuration file that is used for this type of configurations is struts-config.xml. This file will be located under WEB-INF folder in the web application structure. In this file, we need to configure two elements stated below
- Action
- Action Form
Apart from the above specified components, we also need to configure the interceptors. But we don’t configure interceptors in struts-config.XML but we configure them in struts.xml file. This file is located in classes directory. Be informed that configuring the struts.xml is little bit more complex than configuring the struts-config.xml.
The package of the action and the class name of the action will be represented by the form-beans node in struts-config.xml. It will be defined as follows.
<struts-config> <form-beans> <form-bean name="webLogForm" type="com.javabeat.articles.conversion.struts.webLogForm"/> </form-beans> ... </struts-config>
Note that type value should reflect full class file path of Form object. We can configure the actions in the example we discussed earlier in three different ways as stated below
- Configuring the Redirection
- Configuring the action
- Configuring the post-redirection.
Above configuration will be discussed in our next article.
The final things that we will discuss here as part of the migration is below
- How to configure the interceptors as well as interceptor stacks.
- To learn more about configuring the interceptors and interceptor stacks, you can refer struts-config.XML file which is distributed in the core Jar Files. The struts-config.XML files that are distributed with the core jar files will present lots of examples to configure the interceptors and interceptor stacks.
- Using the ParameterAware Interface to fully utilize UI property maps.
- Instead of having the separate mechanisms for specifying different properties or attributes, it is possible to specify all the attributes in one place using the maps in an action. This is the new feature in struts 2. This enables the dynamic nature of the struts application.
- Using WildCard patterns in configuration files.
- There is an option for you to save some time from typing. This can be achieved through the usage of wild cards and this option is made available to the users in new struts release.
This ends the discussion of the migration of old struts application into new struts application.