This page provides info relevant to the Roller struts2 migration effort which is starting as of Roller 4.0 and will end ??
This section tries to breakdown the various ways in which Roller uses struts2 and should give you an idea of what tools and code are at your disposal for working with struts2 in Roller.
A number of files are changing for with the transition from struts1 -> struts2 and this is a simple list of what files are important in the struts2 configuration and what they are used for.
One of the goals of the struts2 migration is to make our actions simpler and easier to work on by having all the code and features which are common to all (or most) actions take place automatically and the UIAction class is how we do that. The UIAction class is a base class for actions to extend and provides support for all the things that we expect most of our actions will need to do, such as looking up the authenticated user, applying security checks on weblog permissions, setting action messages and errors, defining a page title, setting the tabbed menu preferences, etc, etc. Many of these things which were handled in custom or inconsistent ways in our struts1 forms are now handled in a simple and consolidated manner now and are provided to action developers for free.
Feel free to take a look at the UIAction class when you first start working with struts2 actions in Roller, but i'll talk about some of it's features in the coming sections. The main thing to know is that the UIAction class is meant to serve as a solid base for all Roller struts2 actions.
All struts2 actions should extend the UIAction class unless you have a very specific reason not too.
One of the main features of the UIAction class is that it provides free access to some of the things that were handled in custom ways in the old struts1 actions. The best examples of this are how to access the UserData object representing the authenticated user, and the WebsiteData object representing the weblog being dealt with by the action. Pretty much all of Roller's actions require these 2 objects to work properly and so the UIAction has taken care of them for you by providing you getAuthenticatedUser() and getActionWeblog() methods.
These objects are automatically populated for all actions which extend UIAction so that the writer of the action can simply expect them to be there and focus on the action logic rather than filling their action with lots of boiler plate code for checking for these objects and looking them up. These objects are populated by a special struts2 interceptor which is applied to all actions which extend UIAction, so effectively what you get as an action developer is a freebie =)
Currently these are the only 2 objects which are extracted from the request and made available via UIAction, all other objects should be loaded by the action itself since they are more action specific. Hint: read about the myPrepare() method below.
One of the major things that the old struts1 code did poorly was action security enforcement. In specific, in all of the old struts1 code you would see a bunch of boilerplate code which basically did this ...
RollerSession rses = RollerSession.getRollerSession(request);
RollerRequest rreq = RollerRequest.getRollerRequest(request);
UserData user = rses.getAuthenticatedUser();
WebsiteData weblog = rreq.getWebsite();
if(user.hasPermissions(weblog, PermissionsData.AUTHOR)) {
// do action logic
} else {
return mapping.findForward("access-denied");
}
|
Now that's a lot of code to be duplicating in every action and lucky for you, with the new struts2 actions it's no longer necessary. Instead of having all that security enforcement code inside of the action methods its now been pushed higher up the execution chain and it happens in a custom interceptor before the action method ever gets executed, so all you need to know is that if the request actually gets to your action method then all the right permission checking has been done already.
Now, obviously each action will have different security constraints and we still need a way to specify those constraints, so your job isn't completely done. All you need to do to ensure that the proper security rules are applied for your action is to override some security methods. The UIAction base class implements an interface called UISecurityEnforced which looks like this ...
public boolean isUserRequired();
public boolean isWeblogRequired();
public String requiredUserRole();
public short requiredWeblogPermissions();
|
These methods provide a very simple way for action writers to control the security enforcement on their action. By overriding any of these methods you can configure your action to not enforce any security rules at all or to enforce very specific security options, it's up to you. To set a security preference just implement the method as you would expect. So to enforce that users must have the ADMIN permission on the action weblog to use your action you would do this ...
public boolean isUserRequired() { return true; }
public boolean isWeblogRequired() { return true; }
public String requiredUserRole() { return "editor"; }
public short requiredWeblogPermissions() { return PermissionsData.ADMIN; }
|
That's it! If you put the code above in your action and are extending UIAction then your action will ensure that the logged in user has ADMIN permissions on the action weblog before executing. And, to make things even easier, the UIAction class is defined with some reasonable defaults so that you don't have to implement all of those methods in every action. By default the UIAction class enforces these rules ...
public boolean isUserRequired() { return true; }
public boolean isWeblogRequired() { return true; }
public String requiredUserRole() { return "editor"; }
public short requiredWeblogPermissions() { return -1; }
|
This means that by default, all actions require that a user and weblog is properly specified and that the user has the "editor" role. So now all you really need to do for the example we gave above is put this in your action to enforce the ADMIN permission ...
public short requiredWeblogPermissions() { return PermissionsData.ADMIN; }
|
Pretty slick eh? Note: the reason why there is no default required weblog permission is mostly to alleviate any confusion about required permissions for actions. Basically, we want the action classes to define the requiredWeblogPermissions() method as shown above so that anyone working on the action can see directly in that class what the enforced permissions are.
So, now that we have some nice action freebies already set for us and we've defined the security for our action, lets look at the myPrepare() method to see how we can use that to easily do common preparation code that our action uses.
The myPrepare() method is basically a way for actions to do any setup work that applies to their action before the actual action method is called. This is particularly great for loading things from the database that you know your action methods will need. A good example of this from the ThemeEdit action is how the myPrepare() method loads the list of enabled themes for the action to use.
public void myPrepare() {
ThemeManager themeMgr = RollerFactory.getRoller().getThemeManager();
setThemes(themeMgr.getEnabledThemesList());
}
|
That's it. Now I know that my action has already looked up the list of themes that are used by all the action methods defined in my action and the code is in a nice and reusable place. So if you have any work that you want to do just before your action method executes, then implement the myPrepare() method.
One of the page elements that is required for pretty much all actions is the tabbed menu which displays near the top of the page and show the current location of the user. The old Roller code used to render that menu using a custom jsp taglib which was hard to understand and effectively used lots of custom request parsing code to figure out what page the request was on and what permissions to enforce, etc. All of that has been updated for the struts2 actions and design has changed to use a simple tiles jsp for rendering the tabbed menu and the logic for forming the menu has been pushed to the actions where it is most appropriate. Basically, with the new struts2 code it is an actions job to properly provide a menu to be rendered.
Now, as you would expect we have made the process for actions to provide a menu extremely simple. The UIAction class defines a few methods which deal with controlling and accessing the menu for that action via a getMenu() method. To define how to render a standard Roller tabbed menu you simply need to set 2 attributes on your action which are inherited from the UIAction class, the actionName and desiredMenu attributes. These 2 attributes tell the menuing code what action is currently selected and what menu should be rendered, the rest is handled for you by using a MenuHelper class to construct a Menu which is appropriate.
So, to specify these attributes you will simply want to define their values in the default constructor for your action like so ...
public MyAction() {
this.actionName = "myAction";
this.desiredMenu = "editor"; // choose editor menu
}
|
NOTE: the actionName value must specify the name of your action as defined in the struts.xml action mapping, not necessarily your action class name. the desiredMenu attribute is just the name of the menu, which currently only has options for "editor" and "admin".
One of the biggest changes with the struts2 migration is going to be how Roller handles errors and exceptions. The old struts1 code did a pretty poor job of properly handling exceptions and with the struts2 migration that is changing. There is really only one significant change happening, but it's a big one, with the new struts2 actions all actions must catch their own exceptions and deal with them somehow. It is no longer acceptable to define an action method as "public String execute() throws Exception".
Luckily, now that we are using struts2 it's a little bit easier to report errors on your action so hopefully that will make it easier and more encourageable for action writers to properly handle errors. To define an error on a struts2 action all you need to do is call the addError() method and you are done. The addError() method has 2 forms, one which simply accepts a bundle key, the other which accepts a bundle key and an optional parameter ...
public void addError(String errorKey);
public void addError(String errorKey, String param);
|
Same goes for message notifications as well ...
public void addMessage(String msgKey);
public void addMessage(String msgKey, String param);
|
So setting errors/messages to display on the UI is now as easy as it's every going to be and there are no more excuses for being lazy and simply throwing your exceptions out of your action method for something else to handle.
Assuming that you have read through and understand the basic design of how Roller uses struts2 and you are ready to try migrating some of the code, this walkthrough can serve as a decent starting point and helper to show you what you are getting into. Of course, this is only a best effort guide and each action will have it's own quirks and gotchas which may not be covered here. If that's the case then try to sort it out and if you can't then ask about it on the Roller dev list.
Here are some notes on the series of steps that you might run through to migrate an existing Roller struts1 action to a new struts2 action. The specific details of the work will vary depending on the situation.
Migrating JSPs