Showing posts with label struts2. Show all posts
Showing posts with label struts2. Show all posts

Tuesday, May 13, 2008

Playing with the Struts 2 Dispatcher


Due to the nature of our permission handling on a project at work I am in need of a way to pass URLs in to the Struts dispatcher and get back the class name and method of the action to invoke (possibly the results as well...). See we have our classes implement a "SecurityAware" interface that returns an array of [OR'd] permission names required to view a page. We also have a permission based menu system that sometimes requires different permissions than the page the menu item points to. I had run into a problem in trying to find all those needed permissions between the database and the source code which was a lot of manual work. So you can see why I'd want to just pass in the URLs from the menu and get the action, call the security method and display the needed permissions for the resulting page.


What I did was start taking a trip through the Struts 2 source and the test classes for an idea of what was going on. I started at the FilterDispatcher since that's the one class that I define and is the starting point of my (any?) Struts2 applications.


The filterDispatcher creates a Dispatcher object. This seems to be the core configuration beast of the system. This has an ActionMapper injected into it. This is where my problems begin.


How are things injected into the FilterDispatcher since I'm "creating" it in the web.xml? There's a ConfigurationManager that is created inside that loads the struts config files and I've had luck printing out parameters that I've set in my struts.xml so I think it's loading correctly. What I'm now having trouble with is the ActionMapper. I'm playing with this in jRuby and I have no idea how to deal with Java5 Generics (I guess I could Google it...) so I took the route of just injecting my created actionMapper.


actionMapper = DefaultActionMapper.new
dispatcher.configurationManager.configuration.container.inject(actionMapper)


This does have an effect in that I can set struts.enable.SlashesInActionNames to true and then call actionMapper.slashesInActionNames? and have the result be true (it defaults to false, so there is a change).


I should mention that I'm running this code in the struts blank example app's WEB-INF/classes directory. I can create a MockServletRequest and pass in URLs that should and shouldn't work. It errors out appropriately when the URL doesn't end with ".action" and I can get it to parse the namespace portion correctly, but I'm having no luck with the Method. For example /example/Login_input.action should result in an ActionMapping with:

  • Name: Login_input

  • Namespace: /example

  • Method: input


Or at least that's what I'm expecting, but I never get anything for the method.


Here's my code so far.


require 'java'

import 'org.apache.struts2.dispatcher.Dispatcher'
import 'org.apache.struts2.dispatcher.FilterDispatcher'
import 'org.apache.struts2.dispatcher.mapper.DefaultActionMapper'

import 'org.springframework.mock.web.MockServletContext'
import 'org.springframework.mock.web.MockFilterConfig'
import 'org.springframework.mock.web.MockHttpServletRequest'
import 'org.springframework.mock.web.MockHttpServletResponse'


filterConfig = MockFilterConfig.new
servletContext = MockServletContext.new


dispatcher = Dispatcher.new(servletContext, {})
dispatcher.init
Dispatcher.instance = dispatcher

actionMapper = DefaultActionMapper.new
dispatcher.configurationManager.configuration.container.inject(actionMapper)


request = MockHttpServletRequest.new(servletContext, 'GET', '/example/other.action');
response = MockHttpServletResponse.new;

actionMapping = actionMapper.getMapping(request,dispatcher.configurationManager)
puts "Name: #{actionMapping.name}"
puts "Namespace: #{actionMapping.namespace}"
puts "Method: #{actionMapping.method}"
puts "Result: #{actionMapping.result}"


Sunday, October 28, 2007

Struts 2 Dynamic Validators

We had a need for a dynamic, DB driven form with simple validation at work. The site was to be used by multiple clients and they would all have the same basic functionality except that each client would have a form they could customize and specify validation on any of the fields if necessary. Spec wise the validation was to be required or not. I figured that as time would move on they'd want other validations as well so I figured we could use the existing Struts2/XWork validators as to not recreate the wheel.

The plan was simple. Have a table that represented the form's fields with a client foreign key and a validator foreign key. The validator key linked to the validator table that would provide all the information needed by a Struts2/XWork field validator. This would actually be two tables, the first would be the "parent" storing the validator class name and the the second would be a mapping of parameter names and values. Basically everything that's in the validation xml but in the database.

When the action was gotten we would loop over the fields and render each one based on the type. When the form was posted we would then loop over the list of validators, instantiate each one, populate it's parameters and call .validate() on them.

Creating the form and validators with correctly populated parameters was easy. The problem came when it tried to validate. See the validation framework validates the action class, as it should. So when it came time to validate a field, a getFieldName() method would be called on the action class. Since the form is dynamic that method won't exist and validation will fail.

I tried implementing the class as a commons DynaBean. That didn't work (didn't expect it to really, but just trying anything).
I tried implementing the class in Groovy and using MetaExpandoClass to handle the calls to the missing methods, but that type of magic only works within Groovy and the XWork classes don't know about that.
I tried the java.lang.reflection.Proxy class and a ImplementationHandler but those aren't for handling dynamic methods.
The only think I didn't try is doing it like the old struts 1 map backed forms. Where the field name would be something like myfields[name] and the validating on the map. But I don't think that will work either. I don't really like that solution either because it forces you to be aware of the implementation and when entering validators in the db you'd have to be aware of that. Or if you used an ExpressionValidator with OGNL.
I'd hate to have to create an implementation of the XWork OGNL parser that looks for DynaBeans and treat them appropriately. But I think that might be the only solution.
Something I'd look into if work really need this functionality but for now I've spent way too much time on it.