Recently, I ran up against the performance limitations of this fix: A page with a few hundred links on it takes ~30 seconds to generate, 2/3 of which are spent in the patch code. So, I tweaked the patch to improve its performance, and wanted to share my results (below). Using this version, the page generation is well under one second. The performance improvement comes from in-place manipulation of the response's content, as opposed to replacing the content with a modified copy. This results in a much smaller memory footprint, which greatly reduces the time spent allocating and garbage-collecting throughout the application.
package wo.patch;
import com.webobjects.foundation.*;
import com.webobjects.appserver.*;
import com.webobjects.appserver._private.*;
import java.util.regex.Pattern;
public class WOHyperlinkPatch extends WOHyperlink {
/**
* A WOResponse sub-class that, if used, makes the patch more efficient.
*/
public static class AccessibleResponse extends WOResponse {
/**
* Provides direct access to the content string-buffer. This allows us to fix the response content more efficiently.
*/
public CharSequence _mutableContentString() {
return _content;
}
}
private static final String BOZO_AMPERSAND = "&";
private static final String SIMPLE_AMPERSAND = "&";
private static final Pattern PatchPattern = Pattern.compile( "\\Q" + BOZO_AMPERSAND + "\\E" );
private static final String PatchReplacement = SIMPLE_AMPERSAND;
public WOHyperlinkPatch( String aName, NSDictionary associations, WOElement template ) {
super( aName, associations, template );
}
/**
* Overridden to correct any bozo ampersands that were appended as part of the query string.
*/
protected void _appendQueryStringToResponse( WOResponse r, WOContext c, boolean flag ) {
boolean isAccessible = ( r instanceof AccessibleResponse );
CharSequence content0, content1;
int length0, length1;
// Get the content (and its length) before appending the query string:
content0 = ( isAccessible ? ((AccessibleResponse)r)._mutableContentString() : r.contentString() );
length0 = content0.length();
super._appendQueryStringToResponse( r, c, flag );
// Get the content (and its length) after appending the query string:
content1 = ( isAccessible ? content0 : r.contentString() );
length1 = content1.length();
if ( length1 > length0 ) {
String q = content1.subSequence( length0, length1 ).toString();
if ( q.indexOf( BOZO_AMPERSAND ) != -1 ) {
// Correct the bozo ampersands:
q = PatchPattern.matcher( q ).replaceAll( PatchReplacement );
if ( isAccessible ) {
((StringBuffer)content1).setLength( length0 );
((StringBuffer)content1).append( q );
} else {
NSLog.out.appendln( "********** WARNING: WOHyperlinkPatch is applying the inefficient patch **********" );
r.setContent( content0 + q );
}
} else if ( q.indexOf('=') != q.lastIndexOf('=') && q.indexOf( SIMPLE_AMPERSAND ) > 0 ) {
// If the query string contains multiple name-value pairs, but only simple ampersands, this bug might have been fixed...
NSLog.out.appendln( "********** NOTICE: WOHyperlinkPatch may be unnecessary now **********" );
}
}
}
}
My apologies if there are typos in the above code -- it was a copy-paste-clean-up job.