Automatic reset of instance variables in Stateless WOComponent
subclasses
I typically use regular ("stateful") components
for pages and I use all stateless
non-synchronizing components for subcomponents. This works well since
often the ivars in the page are pulled into subcomponents lazily on demand
through bindings. A common problem is that I may forget to add a newly created
ivar to the reset method which may manifest itself as a unusual bug (sharing of
the ivar value across different users' pages for example). In any case not
setting ivars to null in stateless components' reset() method can have serious
information security implications aswell as buggy behaviour. So I created a
class that automatically manages the reset responsibility for stateless
components and it only requires you to add one generic line of code to your
stateless component, or better again, write the reset method once in a subclass
which all stateless components extend and thus you can forget about maintaining
the reset() method forever. Performance is good too since the names of the ivars
to be reset for each component are cached the first time they are
evaluated.
Revision 3/23/2007: Now that I have
started using Eclipse and packaging my components, listing #2 required one more
line of code to allow the reset manager access to protected fields in packaged
classes at runtime.Pretty
self-explanatory. You can change the logic for deciding which types of fields
are added to the reset list yourself by choosing a convention. For example, I
reset all protected ivars beginning with an underscore character that are not
primitive, final, static or abstract. Listing 2 shows the code for the reset
manager class, WKResetManager, and listing 1 shows the code for my generic
reusable component subclass, WKRCComponent, which all stateless components
extend. By subclassing this your component will be stateless and
non-synchronizing aswell as having automatic ivar reset. Feedback, criticism
and/or questions are welcome to kieran_lists #at# mac dot
com.Listing 1.
//
// WKRCComponent.java
//
//
import com.webobjects.foundation.*;
import com.webobjects.appserver.*;
import com.webobjects.eocontrol.*;
import com.webobjects.eoaccess.*;
/** A subclass of WOComponent that implements stateless, reusable
components requiring manual binding synchronization */
public class WKRCComponent extends WOComponent {
public WKRCComponent(WOContext context) {
super(context);
}
/** Returns false to turn off automatic binding synchronization */
public boolean synchronizeVariablesWithBindings() {
return false;
}
/** Returns true to make this a stateless component */
public boolean isStateless() {
return true;
}
/** Called automatically by WO to reset this stateless component
in between shared usage across the application. */
public void reset(){
WKResetManager.reset( this );
}
}
Listing 2.
//
// WKResetManager.java
// No warranties expressed or implied. Free to use and distribute at your own risk.
//
import com.webobjects.foundation.*;
import com.webobjects.appserver.*;
import org.apache.log4j.Logger;
import java.lang.reflect.*;
/** This class provides functionality to enable the
automatic reset of ivars in stateless components.
Usage: Simply make sure all your stateless component
ivars that you wish to reset are protected and start
with underscore character. */
public class WKResetManager {
private static org.apache.log4j.Logger log = WKLoggerFactory.getLogger( WKResetManager.class );
/** A dictionary where each entry key is the name of a stateless
component class and the value is an array of type Field
that should be reset automatically. */
protected static final NSMutableDictionary _statelessComponentFieldLibrary = new NSMutableDictionary();
protected static NSMutableDictionary statelessComponentFieldLibrary() {
return _statelessComponentFieldLibrary;
}
/** @return NSArray of Field for the component. */
protected static NSArray fieldsForComponent( WOComponent component ) {
NSArray array = (NSArray)_statelessComponentFieldLibrary.valueForKey( component.getClass().getName() );
if ( array == null ) {
// Calculate the Field array
array = fieldsToReset( component );
// Cache it
setFieldsForComponent( array, component );
}
return array;
}
/** Sets the NSArray of Field for the component. */
protected static void setFieldsForComponent( NSArray fields, WOComponent component ) {
_statelessComponentFieldLibrary.setObjectForKey( fields, component.getClass().getName() );
}
/** Implements the standard reset functionality for stateless reusable components. */
public static void reset( WOComponent component ) {
Field aField = null;
NSArray resettableFields = WKResetManager.fieldsForComponent( component );
if ( log.isDebugEnabled() ) log.debug("Resetting component '"
+ component.getClass().getName()
+ "' with resettable fields = "
+ ( resettableFields == null ? "null" : resettableFields.toString() ));
if ( resettableFields != null ) {
for ( java.util.Enumeration e = resettableFields.objectEnumerator(); e.hasMoreElements() ; ) {
aField = (Field)e.nextElement();
try {
if ( aField.get( component ) != null ) {
aField.set( component, null );
if ( log.isDebugEnabled() ) log.debug("Automatically reset " + aField.toString() );
}
} catch ( IllegalAccessException exception) {
log.fatal( "Cannot access field '"
+ aField.toString()
+ "' in component '"
+ component.getClass().getName() + "'.", exception );
}
}
}
}
/** @return a list of field names that are deemed candidates for automatic stateless component reset.
This is only called once (lazily) per concrete component class.
Fields are candidates if the following criteria applies
1. the field name begins with underscore (_)
2. the field is a protected field
3. the field is not static
4. the field is not abstract
5. the field is not final
6. the field is not a primitive type
The subclass hierarchy of WOComponent are examined for resettable fields.
WOComponent itself is not searched of course.
*/
protected static NSArray fieldsToReset( WOComponent component ) {
NSMutableArray fieldsToReset = new NSMutableArray();
// Need this in case superclasses have the same fields
// that may have been over-ridden by inheritance
// and we don't need to check the same field twice
NSMutableArray fieldsCheckedAlready = new NSMutableArray();
// Get the class representing the subclass of the component
Class classDef = component.getClass();
if ( log.isDebugEnabled() ) log.debug("Starting classDef = " + ( classDef == null ? "null" : classDef.getName() ) );
// We want to crawl up the class hierarchy and examine
// all classes that are subclasses of WOComponent
// to find fields that should be reset.
while ( !classDef.getName().equals( "com.webobjects.appserver.WOComponent" ) ) {
Field[] fields = null;
// Get the list of Field objects for the class
try {
fields = classDef.getDeclaredFields();
} catch (SecurityException e) {
log.fatal( "error accessing declared fields", e );
}
for ( int i = 0 ; i < fields.length ; i++ ) {
Field aField = fields[i];
String fieldName = aField.getName();
// If checked already, ignore it (it could be an over-ridden field)
if ( !fieldsCheckedAlready.containsObject( fieldName ) ) {
// Add it to the checked already list
fieldsCheckedAlready.addObject( fieldName );
// Get the field modifiers (private, public, protected, static, etc.)
int modifiers = aField.getModifiers();
if ( log.isDebugEnabled() ) log.debug("Primitive check: aField.getType().isPrimitive() = "
+ aField.getType().isPrimitive()
+ " aField.getType().toString() = "
+ aField.getType().toString() );
// We want to reset the field automatically if
// it begins with underscore, is protected, is not primitive and is not static/abstract/final
if ( fieldName.charAt( 0 ) == '_'
&& Modifier.isProtected( modifiers )
&& !Modifier.isStatic( modifiers )
&& !Modifier.isAbstract( modifiers )
&& !Modifier.isFinal( modifiers )
&& !aField.getType().isPrimitive() ) {
// Candidate for resetting
// Remove package level protected so this reflection based utility can do it's job at runtime
// this does not affect compile time static checking
aField.setAccessible(true);
fieldsToReset.addObject( aField );
if ( log.isDebugEnabled() ) log.debug("Initializing resettable fields list: Added '" + fieldName + "' ("
+ aField.toString()
+ ") to reset list of fields.");
} // end if ( fieldName.charAt....
} // end if ( !fieldsCheckedAlready ....
} // end for ( int i ....
// Crawl up to the superclass to check it's fields.
classDef = classDef.getSuperclass();
if ( log.isDebugEnabled() ) log.debug("Next superclass classDef = "
+ ( classDef == null ? "null" : classDef.getName() ) );
} // end while ( !class...
return fieldsToReset.immutableClone();
}
}
Posted: Friday - July 22, 2005 at 01:41 PM
|
Quick Links
Statistics
Total entries in this blog:
Total entries in this category:
Published On: Dec 29, 2008 02:27 PM
|