Skip to content

Commit 3827c7a

Browse files
Change how command objects are initialized
This change will cause the compiler to turn this: class SomeController { @transactional def someAction(SomeDomain sd) { // application code here } } Into this: class SomeController { def someAction() { someAction(null) } def someAction(SomeDomain sd) { withTransaction { status -> $tt__someAction(sd, status) } } def $tt_someAction(SomeDomain sd, TransactionStatus status) { if(sd == null) { sd = // initialize sd } // original application code here } } The initialization code has been moved into the transaction handling method and the null check is there to accommodate unit tests which explicitly pass a parameter in to the action method. This is related to apache/grails-data-mapping#580.
1 parent 623b5e5 commit 3827c7a

File tree

3 files changed

+86
-9
lines changed

3 files changed

+86
-9
lines changed

grails-core/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/GrailsASTUtils.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,16 @@ public static boolean hasAnnotation(final ClassNode classNode, final Class<? ext
873873
return !classNode.getAnnotations(new ClassNode(annotationClass)).isEmpty();
874874
}
875875

876+
/**
877+
* Returns true if classNode is marked with annotationClass
878+
* @param methodNode A MethodNode to inspect
879+
* @param annotationClass an annotation to look for
880+
* @return true if methodNode is marked with annotationClass, otherwise false
881+
*/
882+
public static boolean hasAnnotation(final MethodNode methodNode, final Class<? extends Annotation> annotationClass) {
883+
return !methodNode.getAnnotations(new ClassNode(annotationClass)).isEmpty();
884+
}
885+
876886
/**
877887
* @param classNode a ClassNode to search
878888
* @param annotationsToLookFor Annotations to look for

grails-core/src/main/groovy/org/codehaus/groovy/grails/transaction/transform/TransactionalTransform.groovy

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ class TransactionalTransform implements ASTTransformation{
259259
}
260260

261261
protected MethodCallExpression moveOriginalCodeToNewMethod(SourceUnit source, ClassNode classNode, MethodNode methodNode) {
262-
String renamedMethodName = '$tt__' + methodNode.getName()
262+
String renamedMethodName = getTransactionHandlingMethodName(methodNode)
263263
final transactionStatusParameter = new Parameter(ClassHelper.make(TransactionStatus), "transactionStatus")
264264
def newParameters = methodNode.getParameters() ? (copyParameters(((methodNode.getParameters() as List) + [transactionStatusParameter]) as Parameter[])) : [transactionStatusParameter] as Parameter[]
265265

@@ -289,6 +289,10 @@ class TransactionalTransform implements ASTTransformation{
289289
originalMethodCall
290290
}
291291

292+
static String getTransactionHandlingMethodName(MethodNode methodNode) {
293+
'$tt__' + methodNode.getName()
294+
}
295+
292296
protected void weaveTransactionManagerAware(SourceUnit source, ClassNode declaringClassNode) {
293297
ClassNode transactionManagerAwareInterface = ClassHelper.make(TransactionManagerAware)
294298

grails-plugin-controllers/src/main/groovy/org/codehaus/groovy/grails/compiler/web/ControllerActionTransformer.java

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.codehaus.groovy.grails.compiler.injection.GrailsASTUtils.buildGetPropertyExpression;
2222
import static org.codehaus.groovy.grails.compiler.injection.GrailsASTUtils.buildSetPropertyExpression;
2323
import grails.artefact.Artefact;
24+
import grails.transaction.Transactional;
2425
import grails.util.BuildSettings;
2526
import grails.util.CollectionUtils;
2627
import grails.validation.ASTValidateableHelper;
@@ -86,12 +87,14 @@
8687
import org.codehaus.groovy.grails.compiler.injection.AstTransformer;
8788
import org.codehaus.groovy.grails.compiler.injection.GrailsASTUtils;
8889
import org.codehaus.groovy.grails.compiler.injection.GrailsArtefactClassInjector;
90+
import org.codehaus.groovy.grails.transaction.transform.TransactionalTransform;
8991
import org.codehaus.groovy.grails.web.binding.DefaultASTDatabindingHelper;
9092
import org.codehaus.groovy.grails.web.controllers.DefaultControllerExceptionHandlerMetaData;
9193
import org.codehaus.groovy.grails.web.util.TypeConvertingMap;
9294
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
9395
import org.codehaus.groovy.syntax.Token;
9496
import org.codehaus.groovy.syntax.Types;
97+
import org.springframework.transaction.TransactionStatus;
9598
import org.springframework.validation.Errors;
9699
import org.springframework.validation.MapBindingResult;
97100

@@ -357,6 +360,7 @@ private MethodNode convertToMethodAction(ClassNode classNode, MethodNode methodN
357360

358361
MethodNode method = null;
359362
if (methodNode.getParameters().length > 0) {
363+
populateMethodWithCommandObjectInitializationCode(classNode, methodNode, source, context);
360364
final BlockStatement methodCode = new BlockStatement();
361365

362366
final BlockStatement codeToHandleAllowedMethods = getCodeToHandleAllowedMethods(classNode, methodNode.getName());
@@ -385,6 +389,54 @@ private MethodNode convertToMethodAction(ClassNode classNode, MethodNode methodN
385389
return method;
386390
}
387391

392+
protected void populateMethodWithCommandObjectInitializationCode(final ClassNode classNode,
393+
final MethodNode methodNode,
394+
final SourceUnit source,
395+
final GeneratorContext context) {
396+
final MethodNode methodToUse = getMethodToIncludeCommandObjectInitializationCode(methodNode);
397+
final BlockStatement newCodeForExistingMethod = new BlockStatement();
398+
final BlockStatement codeToInitializeCommandObjects = getCodeToInitializeCommandObjects(methodToUse, methodToUse.getName(), methodToUse.getParameters(), classNode, source, context);
399+
newCodeForExistingMethod.addStatement(codeToInitializeCommandObjects);
400+
newCodeForExistingMethod.addStatement(methodToUse.getCode());
401+
methodToUse.setCode(newCodeForExistingMethod);
402+
}
403+
404+
protected MethodNode getMethodToIncludeCommandObjectInitializationCode(final MethodNode methodNode) {
405+
MethodNode node = methodNode;
406+
if(GrailsASTUtils.hasAnnotation(methodNode, Transactional.class)) {
407+
final ClassNode declaringClass = methodNode.getDeclaringClass();
408+
final String txHandlingMethodName = TransactionalTransform.getTransactionHandlingMethodName(methodNode);
409+
final Parameter[] originalMethodParameters = methodNode.getParameters();
410+
final Parameter[] txHandlingMethodParams = new Parameter[originalMethodParameters.length + 1];
411+
System.arraycopy(originalMethodParameters, 0, txHandlingMethodParams, 0, originalMethodParameters.length);
412+
txHandlingMethodParams[txHandlingMethodParams.length-1] = new Parameter(ClassHelper.make(TransactionStatus.class), "transactionStatus");
413+
if(declaringClass.hasMethod(txHandlingMethodName, txHandlingMethodParams)) {
414+
node = declaringClass.getMethod(txHandlingMethodName, txHandlingMethodParams);
415+
}
416+
}
417+
return node;
418+
}
419+
420+
protected BlockStatement getCodeToInitializeCommandObjects(final ASTNode actionNode,
421+
final String actionName,
422+
final Parameter[] parameters,
423+
final ClassNode controllerNode,
424+
final SourceUnit source,
425+
final GeneratorContext context) {
426+
final BlockStatement body = new BlockStatement();
427+
for(Parameter p : parameters) {
428+
final String paramName = p.getName();
429+
final ClassNode paramTypeClassNode = p.getType();
430+
if (!(PRIMITIVE_CLASS_NODES.contains(paramTypeClassNode) ||
431+
TYPE_WRAPPER_CLASS_TO_CONVERSION_METHOD_NAME.containsKey(paramTypeClassNode)) &&
432+
!paramTypeClassNode.equals(new ClassNode(String.class)) &&
433+
!paramTypeClassNode.equals(OBJECT_CLASS)) {
434+
initializeAndValidateCommandObjectParameter(body, controllerNode, paramTypeClassNode, actionNode, actionName, paramName, source, context);
435+
}
436+
}
437+
return body;
438+
}
439+
388440
private Statement addOriginalMethodCall(MethodNode methodNode, BlockStatement blockStatement) {
389441

390442
if (blockStatement == null) {
@@ -438,7 +490,16 @@ protected void addMethodToInvokeClosure(ClassNode controllerClassNode,
438490

439491
MethodNode method = controllerClassNode.getMethod(closureProperty.getName(), ZERO_PARAMETERS);
440492
if (method == null || !method.getDeclaringClass().equals(controllerClassNode)) {
441-
ClosureExpression closureExpression = (ClosureExpression) closureProperty.getInitialExpression();
493+
final ClosureExpression closureExpression = (ClosureExpression) closureProperty.getInitialExpression();
494+
final BlockStatement newClosureCode = new BlockStatement();
495+
final Statement originalClosureCode = closureExpression.getCode();
496+
final BlockStatement codeToInitializeCommandObjects = getCodeToInitializeCommandObjects(closureProperty, closureProperty.getName(), closureExpression.getParameters(), controllerClassNode, source, context);
497+
498+
newClosureCode.addStatement(codeToInitializeCommandObjects);
499+
newClosureCode.addStatement(originalClosureCode);
500+
501+
closureExpression.setCode(newClosureCode);
502+
442503
final Parameter[] parameters = closureExpression.getParameters();
443504
final BlockStatement newMethodCode = initializeActionParameters(
444505
controllerClassNode, closureProperty, closureProperty.getName(),
@@ -705,18 +766,16 @@ protected void initializeMethodParameter(final ClassNode classNode, final BlockS
705766
} else if (paramTypeClassNode.equals(new ClassNode(String.class))) {
706767
initializeStringParameter(classNode, wrapper, param, requestParameterName);
707768
} else if (!paramTypeClassNode.equals(OBJECT_CLASS)) {
708-
initializeAndValidateCommandObjectParameter(wrapper, classNode, paramTypeClassNode,
709-
actionNode, actionName, paramName, source, context);
769+
final DeclarationExpression declareCoExpression = new DeclarationExpression(
770+
new VariableExpression(paramName, paramTypeClassNode), Token.newSymbol(Types.EQUALS, 0, 0), new ConstantExpression(null));
771+
wrapper.addStatement(new ExpressionStatement(declareCoExpression));
710772
}
711773
}
712774

713775
protected void initializeAndValidateCommandObjectParameter(final BlockStatement wrapper,
714776
final ClassNode controllerNode, final ClassNode commandObjectNode,
715777
final ASTNode actionNode, final String actionName, final String paramName,
716778
final SourceUnit source, final GeneratorContext context) {
717-
final DeclarationExpression declareCoExpression = new DeclarationExpression(
718-
new VariableExpression(paramName, commandObjectNode), Token.newSymbol(Types.EQUALS, 0, 0), new EmptyExpression());
719-
wrapper.addStatement(new ExpressionStatement(declareCoExpression));
720779

721780
if(commandObjectNode.isInterface() || Modifier.isAbstract(commandObjectNode.getModifiers())) {
722781
final String warningMessage = "The [" + actionName + "] action in [" +
@@ -754,10 +813,12 @@ protected void initializeAndValidateCommandObjectParameter(final BlockStatement
754813
if (argumentIsValidateable) {
755814
final MethodCallExpression validateMethodCallExpression =
756815
new MethodCallExpression(new VariableExpression(paramName), "validate", EMPTY_TUPLE);
816+
757817
final MethodNode validateMethod =
758818
commandObjectNode.getMethod("validate", new Parameter[0]);
759819
if (validateMethod != null) {
760820
validateMethodCallExpression.setMethodTarget(validateMethod);
821+
validateMethodCallExpression.setImplicitThis(false);
761822
}
762823
final Statement ifCommandObjectIsNotNullThenValidate = new IfStatement(new BooleanExpression(new VariableExpression(paramName)), new ExpressionStatement(validateMethodCallExpression), new ExpressionStatement(new EmptyExpression()));
763824
wrapper.addStatement(ifCommandObjectIsNotNullThenValidate);
@@ -804,8 +865,10 @@ protected void initializeCommandObjectParameter(final BlockStatement wrapper,
804865
applyDefaultMethodTarget(initializeCommandObjectMethodCall, commandObjectNode);
805866

806867
final Expression assignCommandObjectToParameter = new BinaryExpression(new VariableExpression(paramName), Token.newSymbol(Types.EQUALS, 0, 0), initializeCommandObjectMethodCall);
807-
808-
wrapper.addStatement(new ExpressionStatement(assignCommandObjectToParameter));
868+
869+
final BinaryExpression isParamNullExression = new BinaryExpression(new VariableExpression(paramName), Token.newSymbol(Types.COMPARE_EQUAL, 0, 0), new ConstantExpression(null));
870+
final Statement initializeCommandObjectIfNull = new IfStatement(new BooleanExpression(isParamNullExression), new ExpressionStatement(assignCommandObjectToParameter), new EmptyStatement());
871+
wrapper.addStatement(initializeCommandObjectIfNull);
809872
}
810873

811874
/**

0 commit comments

Comments
 (0)