Skip to content

Commit

Permalink
Major GSP performance patch . 1) Optimizes GSP -> groovy code generat…
Browse files Browse the repository at this point in the history
…ion (less generated Groovy code, more in the Java base class GroovyPage.).

Generates less bytecode -> reduces permgen memory usage. Some performance improvements.
2) Uses StreamCharBuffer for buffering taglib body closure's content and makes it possible to pass the body on to the "upper level"
without transforming the output to a java.lang.String in between. "out" reference lookup was improved. "out" is a single proxy to the correct Writer instance.
There is a stack for handling the "out" Writer replacement. "body()" in taglib returns a StreamCharBuffer instance instead of a java.lang.String. This might break some plugins.
3) Initial changes for adding support for request or session scoped taglibs. A scope property should be defined when the Taglib bean in created in the app context. This feature isn't active yet.
Session scoped taglibs could be used for shopping carts etc. The state can be kept directly in the taglib bean's properties.
4) Taglib can return any object value. Not just a java.lang.String.
  • Loading branch information
lhotari committed Aug 28, 2009
1 parent b4ba5e2 commit a2c53f8
Show file tree
Hide file tree
Showing 48 changed files with 975 additions and 530 deletions.
5 changes: 3 additions & 2 deletions grails/src/java/grails/test/GroovyPagesTestCase.groovy
Expand Up @@ -16,6 +16,7 @@ package grails.test

import org.codehaus.groovy.grails.web.pages.GroovyPagesTemplateEngine
import org.springframework.web.context.request.RequestContextHolder
import org.codehaus.groovy.grails.web.util.GrailsPrintWriter

/**
* A test harness that eases testing of GSP and tag libraries for Grails
Expand Down Expand Up @@ -53,7 +54,7 @@ class GroovyPagesTestCase extends GroovyTestCase {
def w = t.make(params)

def sw = new StringWriter()
def out = new PrintWriter(sw)
def out = new GrailsPrintWriter(sw)
webRequest.out = out
w.writeTo(out)

Expand All @@ -76,7 +77,7 @@ class GroovyPagesTestCase extends GroovyTestCase {
def w = t.make(params)

def sw = new StringWriter()
def out = new PrintWriter(sw)
def out = new GrailsPrintWriter(sw)
webRequest.out = out
w.writeTo(out)

Expand Down
81 changes: 46 additions & 35 deletions grails/src/java/grails/util/GrailsUtil.java
Expand Up @@ -18,6 +18,17 @@
import groovy.lang.GroovyShell;
import groovy.lang.Writable;
import groovy.util.slurpersupport.GPathResult;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.grails.commons.ApplicationAttributes;
Expand All @@ -34,14 +45,6 @@
import org.springframework.mock.web.MockServletContext;
import org.springframework.util.Assert;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

/**
*
* Grails utility methods for command line and GUI applications
Expand Down Expand Up @@ -77,34 +80,42 @@ public class GrailsUtil {
};

static {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
String version = null;
try {
Resource[] manifests = resolver.getResources("classpath*:META-INF/MANIFEST.MF");
Manifest grailsManifest = null;
for (int i = 0; i < manifests.length; i++) {
Resource r = manifests[i];
Manifest mf = new Manifest(r.getInputStream());
String implTitle = mf.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_TITLE);
if(!isBlank(implTitle) && implTitle.equals(GRAILS_IMPLEMENTATION_TITLE)) {
grailsManifest = mf;
break;
}
}

if(grailsManifest != null) {
version = grailsManifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
}

if(isBlank(version)) {
LOG.error("Unable to read Grails version from MANIFEST.MF. Are you sure the grails-core jar is on the classpath? " );
version = "Unknown";
}
} catch (IOException e) {
version = "Unknown";
LOG.error("Unable to read Grails version from MANIFEST.MF. Are you sure it the grails-core jar is on the classpath? " + e.getMessage(), e);
String version = GrailsUtil.class.getPackage().getImplementationVersion();
if(version==null || isBlank(version)) {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
Resource[] manifests = resolver.getResources("classpath*:META-INF/MANIFEST.MF");
Manifest grailsManifest = null;
for (int i = 0; i < manifests.length; i++) {
Resource r = manifests[i];
InputStream inputStream = null;
Manifest mf = null;
try {
inputStream = r.getInputStream();
mf = new Manifest(inputStream);
} finally {
IOUtils.closeQuietly(inputStream);
}
String implTitle = mf.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_TITLE);
if(!isBlank(implTitle) && implTitle.equals(GRAILS_IMPLEMENTATION_TITLE)) {
grailsManifest = mf;
break;
}
}

if(grailsManifest != null) {
version = grailsManifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
}

if(isBlank(version)) {
LOG.error("Unable to read Grails version from MANIFEST.MF. Are you sure the grails-core jar is on the classpath? " );
version = "Unknown";
}
} catch (IOException e) {
version = "Unknown";
LOG.error("Unable to read Grails version from MANIFEST.MF. Are you sure it the grails-core jar is on the classpath? " + e.getMessage(), e);
}
}

GRAILS_VERSION = version;
}

Expand Down
Expand Up @@ -103,11 +103,11 @@ public Map getGrailsClassesByName() {
return this.grailsClassesByName;
}

public synchronized GrailsClass getGrailsClass(String name) {
public GrailsClass getGrailsClass(String name) {
return this.grailsClassesByName.get(name);
}

public synchronized GrailsClass getGrailsClassByLogicalPropertyName(String logicalName) {
public GrailsClass getGrailsClassByLogicalPropertyName(String logicalName) {
return logicalPropertyNameToClassMap.get(logicalName);
}

Expand Down
Expand Up @@ -20,6 +20,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* A generic dyanmic property for any type
Expand All @@ -31,7 +32,7 @@ public class GenericDynamicProperty extends AbstractDynamicProperty {

private Class type;
private boolean readyOnly;
private Map propertyToInstanceMap = Collections.synchronizedMap(new HashMap());
private Map propertyToInstanceMap = new ConcurrentHashMap();
private Object initialValue;
private FunctionCallback initialValueGenerator;

Expand Down
Expand Up @@ -18,8 +18,10 @@
import groovy.lang.MissingPropertyException;
import org.apache.commons.collections.map.ReferenceMap;

import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* A generic dyanmic property for any type used a soft hashmap implementation for generic properties
Expand All @@ -32,7 +34,7 @@ public class WeakGenericDynamicProperty extends AbstractDynamicProperty {

private Class type;
private boolean readyOnly;
private Map propertyToInstanceMap = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.SOFT,ReferenceMap.SOFT,true));
private Map<String, SoftReference<Object>> propertyToInstanceMap = new ConcurrentHashMap<String, SoftReference<Object>>();
private Object initialValue;
private FunctionCallback initialValueGenerator;

Expand Down Expand Up @@ -82,16 +84,19 @@ public WeakGenericDynamicProperty(String propertyName, Class type, FunctionCallb

public Object get(Object object) {
String propertyKey = System.identityHashCode(object) + getPropertyName();
if(propertyToInstanceMap.containsKey(propertyKey)) {
return propertyToInstanceMap.get(propertyKey);

SoftReference<Object> ref=propertyToInstanceMap.get(propertyKey);
Object val=(ref != null)?ref.get():null;
if(val != null) {
return val;
}
else if (this.initialValueGenerator != null) {
final Object value = this.initialValueGenerator.execute(object);
propertyToInstanceMap.put(propertyKey, value);
propertyToInstanceMap.put(propertyKey, new SoftReference<Object>(value));
return value;
}
else if(this.initialValue != null) {
propertyToInstanceMap.put(propertyKey, this.initialValue);
propertyToInstanceMap.put(propertyKey, new SoftReference<Object>(this.initialValue));
return this.initialValue;
}
return null;
Expand All @@ -100,7 +105,7 @@ else if(this.initialValue != null) {
public void set(Object object, Object newValue) {
if(!readyOnly) {
if(this.type.isInstance(newValue))
propertyToInstanceMap.put(String.valueOf(System.identityHashCode(object)) + getPropertyName(), newValue );
propertyToInstanceMap.put(String.valueOf(System.identityHashCode(object)) + getPropertyName(), new SoftReference<Object>(newValue) );
else if(newValue != null)
throw new MissingPropertyException("Property '"+this.getPropertyName()+"' for object '"+object.getClass()+"' cannot be set with value '"+newValue+"'. Incorrect type.",object.getClass());
}
Expand Down
Expand Up @@ -17,11 +17,16 @@
package org.codehaus.groovy.grails.plugins.web


import groovy.lang.MetaClass;

import org.codehaus.groovy.grails.web.pages.TagLibraryLookup;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.request.RequestContextHolder as RCH
import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU

import org.codehaus.groovy.grails.plugins.web.taglib.*
import org.springframework.context.ApplicationContext
import org.codehaus.groovy.grails.web.pages.GroovyPageBinding;
import org.codehaus.groovy.grails.web.pages.GroovyPagesTemplateEngine
import org.codehaus.groovy.grails.web.pages.ext.jsp.TagLibraryResolver
import org.codehaus.groovy.grails.web.pages.TagLibraryLookup
Expand Down Expand Up @@ -163,6 +168,8 @@ public class GroovyPagesGrailsPlugin {
for(taglib in application.tagLibClasses) {
"${taglib.fullName}"(taglib.clazz) { bean ->
bean.autowire = true
// Taglib scoping support could be easily added here. Scope could be based on a static field in the taglib class.
//bean.scope = 'request'
}
}

Expand Down Expand Up @@ -230,8 +237,7 @@ public class GroovyPagesGrailsPlugin {
WebMetaUtils.registerCommonWebProperties(mc, application)

for(tag in taglib.tagNames) {
def tagLibrary = gspTagLibraryLookup.lookupTagLibrary(namespace, tag)
WebMetaUtils.registerMethodMissingForTags(mc, tagLibrary, tag)
WebMetaUtils.registerMethodMissingForTags(mc, gspTagLibraryLookup, namespace, tag)
}

mc.throwTagError = {String message ->
Expand All @@ -245,19 +251,19 @@ public class GroovyPagesGrailsPlugin {

mc.getPageScope = {->
def request = RequestContextHolder.currentRequestAttributes().currentRequest
def binding = request[GrailsApplicationAttributes.PAGE_SCOPE]
def binding = request.getAttribute(GrailsApplicationAttributes.PAGE_SCOPE)
if (!binding) {
binding = new Binding()
request[GrailsApplicationAttributes.PAGE_SCOPE] = binding
binding = new GroovyPageBinding()
request.setAttribute(GrailsApplicationAttributes.PAGE_SCOPE, binding)
}
binding
}

mc.getOut = {->
RequestContextHolder.currentRequestAttributes().out
org.codehaus.groovy.grails.web.pages.GroovyPageOutputStack.currentWriter()
}
mc.setOut = {Writer newOut ->
RequestContextHolder.currentRequestAttributes().out = newOut
org.codehaus.groovy.grails.web.pages.GroovyPageOutputStack.currentStack().push(newOut,true)
}
mc.propertyMissing = {String name ->
def result = gspTagLibraryLookup.lookupNamespaceDispatcher(name)
Expand All @@ -279,10 +285,15 @@ public class GroovyPagesGrailsPlugin {

}
mc.methodMissing = {String name, args ->
def usednamespace = namespace
def tagLibrary = gspTagLibraryLookup.lookupTagLibrary(namespace, name)
if(!tagLibrary) tagLibrary = gspTagLibraryLookup.lookupTagLibrary(GroovyPage.DEFAULT_NAMESPACE, name)
if(!tagLibrary) {
tagLibrary = gspTagLibraryLookup.lookupTagLibrary(GroovyPage.DEFAULT_NAMESPACE, name)
usednamespace = GroovyPage.DEFAULT_NAMESPACE
}
if(tagLibrary) {
WebMetaUtils.registerMethodMissingForTags(mc, tagLibrary, name)
WebMetaUtils.registerMethodMissingForTags(mc, gspTagLibraryLookup, usednamespace, name)
//WebMetaUtils.registerMethodMissingForTags(mc, tagLibrary, name)
}
if (mc.respondsTo(delegate, name, args)) {
return mc.invokeMethod(delegate, name, args)
Expand All @@ -291,7 +302,6 @@ public class GroovyPagesGrailsPlugin {
throw new MissingMethodException(name, delegate.class, args)
}
}
ctx.getBean(taglib.fullName).metaClass = mc
}

}
Expand All @@ -305,7 +315,7 @@ public class GroovyPagesGrailsPlugin {
def tagLibrary = lookup.lookupTagLibrary(GroovyPage.DEFAULT_NAMESPACE, name)
if (tagLibrary) {
MetaClass controllerMc = delegate.class.metaClass
WebMetaUtils.registerMethodMissingForTags(controllerMc,tagLibrary, name)
WebMetaUtils.registerMethodMissingForTags(controllerMc, lookup, GroovyPage.DEFAULT_NAMESPACE, name)
if(controllerMc.respondsTo(delegate, name, args)) {
return controllerMc.invokeMethod(delegate, name, args)
}
Expand All @@ -330,6 +340,7 @@ public class GroovyPagesGrailsPlugin {
def beans = beans {
"$beanName"(taglibClass.getClazz()) {bean ->
bean.autowire = true
//bean.scope = 'request'
}
}
beans.registerBeans(event.ctx)
Expand Down
Expand Up @@ -404,10 +404,10 @@ class PrototypeProvider implements JavascriptProvider {
}

if(attrs.url) {
url = taglib.createLink(attrs.url)
url = taglib.createLink(attrs.url)?.toString()
}
else {
url = taglib.createLink(attrs)
url = taglib.createLink(attrs)?.toString()
}

if(!attrs.params) attrs.params = [:]
Expand Down
Expand Up @@ -148,6 +148,10 @@ class ValidationTagLib {
* Loops through each error for either field or global errors
*/
def eachError = { attrs, body ->
eachErrorInternal(attrs,body)
}

def eachErrorInternal(attrs, body) {
def errorsList = extractErrors(attrs)
def var = attrs.var
def field = attrs['field']
Expand All @@ -167,9 +171,11 @@ class ValidationTagLib {
if(var) {
out << body([(var):error])
} else {
out << body(error)
out << body(error)
}
}

null
}

/**
Expand All @@ -184,19 +190,19 @@ class ValidationTagLib {
if (codec=='none') codec = ''

out << "<ul>"
out << eachError(attrs, {
out << eachErrorInternal(attrs, {
out << "<li>${message(error:it, encodeAs:codec)}</li>"
}
)
out << "</ul>"
}
else if(renderAs.equalsIgnoreCase("xml")) {
def mkp = new MarkupBuilder(out)
mkp.errors {
eachError(attrs, {
mkp.errors() {
eachErrorInternal(attrs, {
error(object:it.objectName,
field:it.field,
message:message(error:it),
message:message(error:it)?.toString(),
'rejected-value':StringEscapeUtils.escapeXml(it.rejectedValue))
})
}
Expand Down
Expand Up @@ -23,6 +23,7 @@
import org.codehaus.groovy.grails.commons.*;
import org.codehaus.groovy.grails.exceptions.GrailsException;
import org.codehaus.groovy.grails.exceptions.SourceCodeAware;
import org.codehaus.groovy.grails.web.pages.FastStringWriter;
import org.codehaus.groovy.grails.web.pages.GroovyPagesTemplateEngine;
import org.codehaus.groovy.grails.web.servlet.DefaultGrailsApplicationAttributes;
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes;
Expand Down Expand Up @@ -68,10 +69,9 @@ public class GrailsWrappedRuntimeException extends GrailsException {
public GrailsWrappedRuntimeException(ServletContext servletContext, Throwable t) {
super(t.getMessage(), t);
this.cause = t;
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
FastStringWriter pw = new FastStringWriter();
cause.printStackTrace(pw);
this.stackTrace = sw.toString();
this.stackTrace = pw.toString();

while(cause.getCause()!=cause) {
if(cause.getCause() == null) {
Expand Down

0 comments on commit a2c53f8

Please sign in to comment.