Permalink
Browse files

support for new "bindable" constraint

  • Loading branch information...
jeffbrown committed Mar 8, 2012
1 parent 13261ca commit 313206292a7db9dba7c969294d6f0e465b5a713f
Showing with 811 additions and 64 deletions.
  1. +51 −0 grails-core/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/GrailsASTUtils.java
  2. +30 −26 ...trollers/src/main/groovy/org/codehaus/groovy/grails/compiler/web/ControllerActionTransformer.java
  3. +10 −0 ...trollers/src/main/groovy/org/codehaus/groovy/grails/compiler/web/ControllerDomainTransformer.java
  4. +8 −4 ...llers/src/main/groovy/org/codehaus/groovy/grails/plugins/web/api/ControllersDomainBindingApi.java
  5. +20 −0 ...roovy/org/codehaus/groovy/grails/compiler/web/ControllerActionTransformerCommandObjectSpec.groovy
  6. +4 −2 ...-persistence/src/test/groovy/org/codehaus/groovy/grails/orm/hibernate/JoinAssociationTests.groovy
  7. +3 −1 ...sistence/src/test/groovy/org/codehaus/groovy/grails/orm/hibernate/TreeListAssociationTests.groovy
  8. +3 −2 ...stence/src/test/groovy/org/codehaus/groovy/grails/web/binding/DataBindingWithEmbeddedTests.groovy
  9. +28 −26 grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/binding/DataBindingTests.groovy
  10. +9 −0 grails-web/src/main/groovy/org/codehaus/groovy/grails/web/binding/ASTDatabindingHelper.java
  11. +36 −3 grails-web/src/main/groovy/org/codehaus/groovy/grails/web/binding/DataBindingUtils.java
  12. +28 −0 grails-web/src/main/groovy/org/codehaus/groovy/grails/web/binding/DatabindingApi.java
  13. +246 −0 grails-web/src/main/groovy/org/codehaus/groovy/grails/web/binding/DefaultASTDatabindingHelper.java
  14. +335 −0 ...web/src/test/groovy/org/codehaus/groovy/grails/web/binding/DefaultASTDatabindingHelperSpec.groovy
@@ -27,7 +27,9 @@
import java.lang.annotation.Target;
import java.lang.reflect.Modifier;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
@@ -44,11 +46,15 @@
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.NamedArgumentListExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.CatchStatement;
@@ -687,6 +693,51 @@ public static void wrapMethodBodyInTryCatchDebugStatements(MethodNode methodNode
catchBlock.addStatement(new ExpressionStatement(new MethodCallExpression(new VariableExpression("log"), "error", logArguments)));
tryCatchStatement.addCatch(new CatchStatement(new Parameter(new ClassNode(Throwable.class), "e"),catchBlock));
}
+
+ /**
+ * Evaluates a constraints closure and returns metadata about the constraints configured in the closure. The
+ * Map returned has property names as keys and the value associated with each of those property names is
+ * a Map<String, Expression> which has constraint names as keys and the Expression associated with that constraint
+ * as values
+ *
+ * @param closureExpression the closure expression to evaluate
+ * @return the Map as described above
+ */
+ public static Map<String, Map<String, Expression>> getConstraintMetadata(final ClosureExpression closureExpression) {
+ final Map<String, Map<String, Expression>> results = new LinkedHashMap<String, Map<String, Expression>>();
+ final Statement closureCode = closureExpression.getCode();
+ if(closureCode instanceof BlockStatement) {
+ final List<Statement> closureStatements = ((BlockStatement) closureCode).getStatements();
+ for(Statement closureStatement : closureStatements) {
+ if(closureStatement instanceof ExpressionStatement) {
+ final Expression expression = ((ExpressionStatement) closureStatement).getExpression();
+ if(expression instanceof MethodCallExpression) {
+ final MethodCallExpression methodCallExpression = (MethodCallExpression) expression;
+ final Expression methodCallArguments = methodCallExpression.getArguments();
+ if(methodCallArguments instanceof TupleExpression) {
+ final List<Expression> methodCallArgumentExpressions = ((TupleExpression) methodCallArguments).getExpressions();
+ if(methodCallArgumentExpressions != null && methodCallArgumentExpressions.size() == 1 && methodCallArgumentExpressions.get(0) instanceof NamedArgumentListExpression) {
+ final Map<String, Expression> constraintNameToExpression = new LinkedHashMap<String, Expression>();
+ final List<MapEntryExpression> mapEntryExpressions = ((NamedArgumentListExpression) methodCallArgumentExpressions.get(0)).getMapEntryExpressions();
+ for (MapEntryExpression mapEntryExpression : mapEntryExpressions) {
+ final Expression keyExpression = mapEntryExpression.getKeyExpression();
+ if(keyExpression instanceof ConstantExpression) {
+ final Object value = ((ConstantExpression) keyExpression).getValue();
+ if(value instanceof String) {
+ constraintNameToExpression.put((String)value, mapEntryExpression.getValueExpression());
+ }
+ }
+ }
+ results.put(methodCallExpression.getMethodAsString(), constraintNameToExpression);
+ }
+ }
+ }
+ }
+ }
+ }
+ return results;
+ }
+
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.SOURCE)
@@ -33,6 +33,7 @@
import org.codehaus.groovy.grails.compiler.injection.AstTransformer;
import org.codehaus.groovy.grails.compiler.injection.GrailsASTUtils;
import org.codehaus.groovy.grails.compiler.injection.GrailsArtefactClassInjector;
+import org.codehaus.groovy.grails.web.binding.DefaultASTDatabindingHelper;
import org.codehaus.groovy.grails.web.plugins.support.WebMetaUtils;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
@@ -142,12 +143,12 @@ public ControllerActionTransformer() {
public void performInjection(SourceUnit source, GeneratorContext context, ClassNode classNode) {
final String className = classNode.getName();
if(className.endsWith(ControllerArtefactHandler.TYPE)) {
- annotateCandidateActionMethods(classNode, source);
- processClosures(classNode, source);
+ annotateCandidateActionMethods(classNode, source, context);
+ processClosures(classNode, source, context);
}
}
- private void annotateCandidateActionMethods(ClassNode classNode, SourceUnit source) {
+ private void annotateCandidateActionMethods(ClassNode classNode, SourceUnit source, GeneratorContext context) {
List<MethodNode> deferredNewMethods = new ArrayList<MethodNode>();
for (MethodNode method : classNode.getMethods()) {
if (!method.isStatic() && method.isPublic() &&
@@ -164,7 +165,7 @@ private void annotateCandidateActionMethods(ClassNode classNode, SourceUnit sour
"].";
error(source, message);
}
- MethodNode wrapperMethod = convertToMethodAction(classNode, method, source);
+ MethodNode wrapperMethod = convertToMethodAction(classNode, method, source, context);
if (wrapperMethod != null) {
deferredNewMethods.add(wrapperMethod);
}
@@ -184,7 +185,7 @@ private void annotateCandidateActionMethods(ClassNode classNode, SourceUnit sour
* @param _method The method to be converted
* @return The no-arg wrapper method, or null if none was created.
*/
- private MethodNode convertToMethodAction(ClassNode classNode, MethodNode _method, SourceUnit source) {
+ private MethodNode convertToMethodAction(ClassNode classNode, MethodNode _method, SourceUnit source, GeneratorContext context) {
final ClassNode returnType = _method.getReturnType();
Parameter[] parameters = _method.getParameters();
@@ -206,7 +207,7 @@ private MethodNode convertToMethodAction(ClassNode classNode, MethodNode _method
Modifier.PUBLIC, returnType,
ZERO_PARAMETERS,
EMPTY_CLASS_ARRAY,
- addOriginalMethodCall(_method, initializeActionParameters(classNode, _method, _method.getName(), parameters, source)));
+ addOriginalMethodCall(_method, initializeActionParameters(classNode, _method, _method.getName(), parameters, source, context)));
annotateActionMethod(parameters, method);
} else {
annotateActionMethod(parameters, _method);
@@ -241,7 +242,7 @@ private boolean isCommandObjectAction(Parameter[] params) {
&& params[0].getType() != new ClassNode(Object.class);
}
- private void processClosures(ClassNode classNode, SourceUnit source) {
+ private void processClosures(ClassNode classNode, SourceUnit source, GeneratorContext context) {
List<PropertyNode> propertyNodes = new ArrayList<PropertyNode>(classNode.getProperties());
Expression initialExpression;
@@ -253,16 +254,16 @@ private void processClosures(ClassNode classNode, SourceUnit source) {
initialExpression != null && initialExpression.getClass().equals(ClosureExpression.class)) {
closureAction = (ClosureExpression) initialExpression;
if (converterEnabled) {
- transformClosureToMethod(classNode, closureAction, property, source);
+ transformClosureToMethod(classNode, closureAction, property, source, context);
} else {
- addMethodToInvokeClosure(classNode, property, source);
+ addMethodToInvokeClosure(classNode, property, source, context);
}
}
}
}
protected void addMethodToInvokeClosure(ClassNode controllerClassNode,
- PropertyNode closureProperty, SourceUnit source) {
+ PropertyNode closureProperty, SourceUnit source, GeneratorContext context) {
MethodNode method = controllerClassNode.getMethod(closureProperty.getName(), ZERO_PARAMETERS);
if (method == null || !method.getDeclaringClass().equals(controllerClassNode)) {
ClosureExpression closureExpression = (ClosureExpression) closureProperty.getInitialExpression();
@@ -276,7 +277,7 @@ protected void addMethodToInvokeClosure(ClassNode controllerClassNode,
}
final MethodCallExpression methodCallExpression = new MethodCallExpression(closureExpression, "call", closureInvocationArguments);
- final BlockStatement newMethodCode = initializeActionParameters(controllerClassNode, closureProperty, closureProperty.getName(), parameters, source);
+ final BlockStatement newMethodCode = initializeActionParameters(controllerClassNode, closureProperty, closureProperty.getName(), parameters, source, context);
newMethodCode.addStatement(new ExpressionStatement(methodCallExpression));
@@ -304,14 +305,14 @@ protected void annotateActionMethod(final Parameter[] parameters,
}
protected void transformClosureToMethod(ClassNode classNode,
- ClosureExpression closureAction, PropertyNode property, SourceUnit source) {
+ ClosureExpression closureAction, PropertyNode property, SourceUnit source, GeneratorContext context) {
final MethodNode actionMethod = new MethodNode(property.getName(),
Modifier.PUBLIC, property.getType(),
closureAction.getParameters(), EMPTY_CLASS_ARRAY,
closureAction.getCode());
MethodNode convertedMethod = convertToMethodAction(classNode,
- actionMethod, source);
+ actionMethod, source, context);
if (convertedMethod != null) {
classNode.addMethod(convertedMethod);
}
@@ -324,7 +325,8 @@ protected BlockStatement initializeActionParameters(ClassNode classNode,
ASTNode actionNode,
String actionName,
Parameter[] actionParameters,
- SourceUnit source) {
+ SourceUnit source,
+ GeneratorContext context) {
BlockStatement wrapper = new BlockStatement();
ArgumentListExpression mapBindingResultConstructorArgs = new ArgumentListExpression();
@@ -341,13 +343,13 @@ protected BlockStatement initializeActionParameters(ClassNode classNode,
if (actionParameters != null) {
for (Parameter param : actionParameters) {
- initializeMethodParameter(classNode, wrapper, actionNode, actionName, param, source);
+ initializeMethodParameter(classNode, wrapper, actionNode, actionName, param, source, context);
}
}
return wrapper;
}
- protected void initializeMethodParameter(final ClassNode classNode, final BlockStatement wrapper, final ASTNode actionNode, final String actionName, final Parameter param, SourceUnit source) {
+ protected void initializeMethodParameter(final ClassNode classNode, final BlockStatement wrapper, final ASTNode actionNode, final String actionName, final Parameter param, final SourceUnit source, final GeneratorContext context) {
final ClassNode paramTypeClassNode = param.getType();
final String paramName = param.getName();
String requestParameterName = paramName;
@@ -362,7 +364,7 @@ protected void initializeMethodParameter(final ClassNode classNode, final BlockS
} else if (paramTypeClassNode.equals(new ClassNode(String.class))) {
initializeStringParameter(wrapper, param, requestParameterName);
} else if (!paramTypeClassNode.equals(OBJECT_CLASS)) {
- initializeCommandObjectParameter(wrapper, classNode, paramTypeClassNode, actionNode, actionName, paramName, source);
+ initializeCommandObjectParameter(wrapper, classNode, paramTypeClassNode, actionNode, actionName, paramName, source, context);
}
}
@@ -372,9 +374,9 @@ protected void initializeCommandObjectParameter(final BlockStatement wrapper,
final ASTNode actionNode,
final String actionName,
final String paramName,
- SourceUnit source) {
- enhanceCommandObjectClass(commandObjectTypeClassNode, actionName, source);
-
+ final SourceUnit source,
+ final GeneratorContext context) {
+ enhanceCommandObjectClass(commandObjectTypeClassNode, actionName, source, context);
final Expression constructorCallExpression = new ConstructorCallExpression(
commandObjectTypeClassNode, EMPTY_TUPLE);
@@ -385,12 +387,13 @@ protected void initializeCommandObjectParameter(final BlockStatement wrapper,
wrapper.addStatement(newCommandCode);
- final Statement autoWireCommandObjectStatement = getAutoWireCommandObjectStatement(paramName);
- wrapper.addStatement(autoWireCommandObjectStatement);
-
final Statement statement = getCommandObjectDataBindingStatement(
classNode, paramName, commandObjectTypeClassNode);
wrapper.addStatement(statement);
+
+ final Statement autoWireCommandObjectStatement = getAutoWireCommandObjectStatement(paramName);
+ wrapper.addStatement(autoWireCommandObjectStatement);
+
final MethodCallExpression validateMethodCallExpression = new MethodCallExpression(
new VariableExpression(paramName), "validate", EMPTY_TUPLE);
MethodNode validateMethod =
@@ -432,7 +435,7 @@ protected void initializeCommandObjectParameter(final BlockStatement wrapper,
}
protected void enhanceCommandObjectClass(
- final ClassNode commandObjectTypeClassNode, final String actionName, final SourceUnit source) {
+ final ClassNode commandObjectTypeClassNode, final String actionName, final SourceUnit source, final GeneratorContext context) {
if(!commandObjectTypeClassNode.isPrimaryClassNode()) {
final List<MethodNode> validateMethods = commandObjectTypeClassNode.getMethods("validate");
if(validateMethods.size() == 0) {
@@ -446,6 +449,7 @@ protected void enhanceCommandObjectClass(
}
final ASTValidateableHelper h = new DefaultASTValidateableHelper();
h.injectValidateableCode(commandObjectTypeClassNode);
+ new DefaultASTDatabindingHelper().injectDatabindingCode(source, context, commandObjectTypeClassNode);
}
/**
@@ -478,15 +482,15 @@ protected Statement getCommandObjectDataBindingStatement(
Expression invokeGetCommandObjectBindingParamsExpression = new StaticMethodCallExpression(new ClassNode(WebMetaUtils.class), "getCommandObjectBindingParams", getCommandObjectBindingParamsArgs);
final Statement intializeCommandObjectParams = new ExpressionStatement(
new DeclarationExpression(new VariableExpression(
- "commandObjectParams", new ClassNode(Map.class)),
+ "$commandObjectParams", new ClassNode(Map.class)),
Token.newSymbol(Types.EQUALS, 0, 0),
invokeGetCommandObjectBindingParamsExpression));
bindingStatement.addStatement(intializeCommandObjectParams);
final ArgumentListExpression arguments = new ArgumentListExpression();
arguments.addExpression(new VariableExpression(paramName));
- arguments.addExpression(new VariableExpression(new VariableExpression("commandObjectParams")));
+ arguments.addExpression(new VariableExpression(new VariableExpression("$commandObjectParams")));
final MethodCallExpression bindDataMethodCallExpression = new MethodCallExpression(
THIS_EXPRESSION, "bindData", arguments);
@@ -1,14 +1,18 @@
package org.codehaus.groovy.grails.compiler.web;
+
import java.net.URL;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.classgen.GeneratorContext;
+import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.grails.commons.DomainClassArtefactHandler;
import org.codehaus.groovy.grails.compiler.injection.AbstractGrailsArtefactTransformer;
import org.codehaus.groovy.grails.compiler.injection.AstTransformer;
import org.codehaus.groovy.grails.io.support.GrailsResourceUtils;
import org.codehaus.groovy.grails.plugins.web.api.ControllersDomainBindingApi;
+import org.codehaus.groovy.grails.web.binding.DefaultASTDatabindingHelper;
/**
* Adds binding methods to domain classes.
@@ -47,4 +51,10 @@ protected boolean requiresAutowiring() {
public boolean shouldInject(URL url) {
return GrailsResourceUtils.isDomainClass(url);
}
+
+ @Override
+ public void performInjection(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) {
+ super.performInjection(source, context, classNode);
+ new DefaultASTDatabindingHelper().injectDatabindingCode(source, context, classNode);
+ }
}
Oops, something went wrong.

0 comments on commit 3132062

Please sign in to comment.