Extending Seam Components
October 14th, 2007 Radim Marek Posted in seam |
An important characteristic of every framework is a level of possible extensibility. JBoss Seam scores top marks in this perspective. Reason for it is simple. The main goal, to introduce consistent programming model that will make various frameworks to work together, is not just promoted for application developer, but also used through-out Seam itself. So with knowledge how to use component model and a quick insight behind the scene, you can easily start extending built-in components.
Mechanism you need to understand is component deployment process. Seam application start-up is triggered by the context listener which instantiates the Initialization object responsible for building configuration metadata. That is accomplished by collection of data from standard configuration files (WEB-INF/components.xml and deprecated WEB-INF/events.xml), configuration properties (supplied both from servlet context and file name seam.properties in the root of the classpath) and eventually by scanning archives in the application classpath for classes annotated with @Name which implies the Seam component.
Dependency Manager is then responsible for evaluation of component dependency to determine which components are to be installed.
And that’s the place where Seam utilizes another annotation - @Install. It does not only specify whether or not the component should be installed (this might be overridden by configuration files) and its dependencies, but also specifies precedence of the component. If two components with the same name are present, Dependency Manager will offer for installation only the one with higher precedence.
The precedence is specified integer value and following pre-defined constants are available:
- BUILT_IN [0] used for built-in components
- FRAMEWORK [10] to be used by frameworks which extend Seam
- APPLICATION [20] default precedence, to be used by application components
- DEPLOYMENT [30] used for overriding components for particular deployment
- MOCK [40] for objects used in testing
Further control over the components deployment process is provided by following two annotations:
- @BypassInterceptors which disables all Seam interceptors on particular class or method.
- @Startup to control initialization of component depending of scope its available in
With this overview you’re ready to extend any provided component, either Seam built-in or available within 3rd party framework or application.
Extending Identity object
As example I’ll show you how to extend built-in identity object to support custom JAAS Login Module that requires custom callback handler to be used to obtain/provide further information. This particular implementation adds functionality to support NTLM as other authentication method (together with customized jCIFS library) and NTLM support iself is not part of this code snapshot.
@Name("org.jboss.seam.security.identity")
@Scope(SESSION)
@Install(precedence = APPLICATION)
@BypassInterceptors
@Startup
public class NTLMIdentity extends Identity {
//
// custom code
//
@Override
public String login() {
try {
retrieveNTLMAuthenticationDetails();
} catch (LoginException le) {
log.error("Unable to retrieve NTLM authentication details!", le);
return null;
}
}
@Override
protected CallbackHandler getDefaultCallbackHandler() {
return new CallbackHandler() {
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName(getUsername());
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword(getPassword() != null ?
getPassword().toCharArray() : null);
} else if (callbacks[i] instanceof SFSIdentityCallbackHandler) {
processSFSCallbackHandler((SFSIdentityCallbackHandler) callbacks[i]);
} else {
throw new UnsupportedCallbackException(callbacks[i], "Unsupported callback");
}
}
}
};
}
//
// custom code
//
}
Where @Name is specified to override Seam built-in Identity component, and precedence set higher than BUILT_IN. If you run application, the result of #{identity} and Identity.instance() will always be NTLMIdentity object.
February 22nd, 2008 at 5:53 pm
Thank you for this great article.
I’ve used this mechanism on my new application and it worked just fine.
In my case, I used it not to implement another login mechanism, but to add more fields to the authentication process. On my application, the user must authenticate with username, password and location id (which will be automatically filled in the future).
This is my version of the extended class:
@Name(”org.jboss.seam.security.identity”)
@Scope(ScopeType.SESSION)
@Install(precedence=Install.APPLICATION)
@BypassInterceptors
@Startup
public class MyExtendedIdentity extends RuleBasedIdentity {
private Integer posId;
public MyExtendedIdentity() {
super();
}
public Integer getPosId() {
return posId;
}
public void setPosId(Integer posId) {
this.posId = posId;
}
}
Newbies, don’t forget to add the default constructor to your extended class.
Another remark is that I am using Drools for the authorization process. So, I had to extend my class from org.jboss.seam.security.RuleBasedIdentity and not org.jboss.seam.security.Identity.
February 27th, 2008 at 5:24 am
Hello,
I am trying to do something similar to what Nuno did. I extended Identity so that I can add a new attribute that I need to use in Authenticator.authenticate. The code looks pretty much the same, except that I am extending identity. The issue am having is that the getNewProp (below) is not recognized as part of Identity, which I thought it would:
String sUsername = Identity.instance().getUsername();
String sPassword = Identity.instance().getPassword();
String sNewProp = Identity.instance().getNewProp();
Do I need to make other changes for this to happen?
Thanks
March 24th, 2008 at 3:49 pm
Perhaps Identity.instance() is returning the original Seam class, in which newProp is not defined. Have you tried adding an instance method to your class and using “YourClass.instance()” instead? I’m a newbie myself, but if this helps, I’m glad.
May 6th, 2008 at 9:31 am
Hi!
Nice article.
Leo: You can access new method by cast the Identity.instance() down to CustomIdentity.
But i still face a problem accessing the method of CustomIdenity in pages.xml like this
How can i tell seam to properly cast identity to CustomIdentiy. Can anyone help.
thanks
November 16th, 2008 at 7:46 pm
[...] is quite straightforward, following the same principles as I’ve shown in the post Extending Seam components. It’s important to keep this component stateless (or at least bound them to EVENT scope) to [...]