Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fix and test for GRAILS-9044 FilterConfig.methodMissing gets called f…

…or every invocation when calling a method with several arguments

Fix and test for GRAILS-9050 Grails Filters's helper methods are shared between all Filters classes
  • Loading branch information...
commit 73b248a0ecb7ee0e9b00a9273ad4952bbb55fd8f 1 parent 0f2c77b
Lari Hotari authored April 23, 2012
133  ...ls-plugin-filters/src/main/groovy/org/codehaus/groovy/grails/plugins/web/filters/DelegateMetaMethod.java
... ...
@@ -0,0 +1,133 @@
  1
+package org.codehaus.groovy.grails.plugins.web.filters;
  2
+
  3
+import groovy.lang.MetaMethod;
  4
+
  5
+import org.codehaus.groovy.reflection.CachedClass;
  6
+
  7
+/**
  8
+ * MetaMethod implementation that delegates to real MetaMethod implementation
  9
+ * 
  10
+ * This can be used to efficiently proxy a metamethod from another metaClass in methodMissing.
  11
+ * An example can be found in FilterConfig's methodMissing .
  12
+ * 
  13
+ * Without this class it's hard to implement efficient methodMissing "caching" supporting methods with multiple signatures (same method name, different set of arguments).
  14
+ * 
  15
+ * This class could be moved to org.codehaus.groovy.grails.commons.metaclass for reuse.
  16
+ * 
  17
+ * @author Lari Hotari
  18
+ *
  19
+ */
  20
+@SuppressWarnings("rawtypes")
  21
+class DelegateMetaMethod extends MetaMethod {
  22
+    static interface DelegateMetaMethodTargetStrategy {
  23
+        public Object getTargetInstance(Object instance);
  24
+    }
  25
+    
  26
+    private MetaMethod delegateMethod;
  27
+    private DelegateMetaMethodTargetStrategy targetStrategy;
  28
+    
  29
+    public DelegateMetaMethod(MetaMethod delegateMethod, DelegateMetaMethodTargetStrategy targetStrategy) {
  30
+        this.delegateMethod=delegateMethod;
  31
+        this.targetStrategy=targetStrategy;
  32
+    }
  33
+
  34
+    public int getModifiers() {
  35
+        return delegateMethod.getModifiers();
  36
+    }
  37
+
  38
+    public String getName() {
  39
+        return delegateMethod.getName();
  40
+    }
  41
+
  42
+    public Class getReturnType() {
  43
+        return delegateMethod.getReturnType();
  44
+    }
  45
+
  46
+    public Object invoke(Object object, Object[] arguments) {
  47
+        return delegateMethod.invoke(targetStrategy.getTargetInstance(object), arguments);
  48
+    }
  49
+
  50
+    public void checkParameters(Class[] arguments) {
  51
+        delegateMethod.checkParameters(arguments);
  52
+    }
  53
+
  54
+    public CachedClass[] getParameterTypes() {
  55
+        return delegateMethod.getParameterTypes();
  56
+    }
  57
+
  58
+    public boolean isMethod(MetaMethod method) {
  59
+        return delegateMethod.isMethod(method);
  60
+    }
  61
+
  62
+    public Class[] getNativeParameterTypes() {
  63
+        return delegateMethod.getNativeParameterTypes();
  64
+    }
  65
+
  66
+    public boolean isVargsMethod(Object[] arguments) {
  67
+        return delegateMethod.isVargsMethod(arguments);
  68
+    }
  69
+
  70
+    public String toString() {
  71
+        return delegateMethod.toString();
  72
+    }
  73
+
  74
+    public boolean equals(Object obj) {
  75
+        return delegateMethod.equals(obj);
  76
+    }
  77
+
  78
+    public Object clone() {
  79
+        return delegateMethod.clone();
  80
+    }
  81
+
  82
+    public boolean isStatic() {
  83
+        return delegateMethod.isStatic();
  84
+    }
  85
+
  86
+    public boolean isAbstract() {
  87
+        return delegateMethod.isAbstract();
  88
+    }
  89
+
  90
+    public Object[] correctArguments(Object[] argumentArray) {
  91
+        return delegateMethod.correctArguments(argumentArray);
  92
+    }
  93
+
  94
+    public boolean isCacheable() {
  95
+        return delegateMethod.isCacheable();
  96
+    }
  97
+
  98
+    public String getDescriptor() {
  99
+        return delegateMethod.getDescriptor();
  100
+    }
  101
+
  102
+    public String getSignature() {
  103
+        return delegateMethod.getSignature();
  104
+    }
  105
+
  106
+    public String getMopName() {
  107
+        return delegateMethod.getMopName();
  108
+    }
  109
+
  110
+    public Object doMethodInvoke(Object object, Object[] argumentArray) {
  111
+        return delegateMethod.doMethodInvoke(targetStrategy.getTargetInstance(object), argumentArray);
  112
+    }
  113
+
  114
+    public boolean isValidMethod(Class[] arguments) {
  115
+        return delegateMethod.isValidMethod(arguments);
  116
+    }
  117
+
  118
+    public boolean isValidExactMethod(Object[] args) {
  119
+        return delegateMethod.isValidExactMethod(args);
  120
+    }
  121
+
  122
+    public boolean isValidExactMethod(Class[] args) {
  123
+        return delegateMethod.isValidExactMethod(args);
  124
+    }
  125
+
  126
+    public boolean isValidMethod(Object[] arguments) {
  127
+        return delegateMethod.isValidMethod(arguments);
  128
+    }
  129
+
  130
+    public CachedClass getDeclaringClass() {
  131
+        return delegateMethod.getDeclaringClass();
  132
+    }
  133
+}
42  grails-plugin-filters/src/main/groovy/org/codehaus/groovy/grails/plugins/web/filters/FilterConfig.groovy
@@ -34,7 +34,7 @@ import org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequest
34 34
  * @author mike
35 35
  * @author Graeme Rocher
36 36
  */
37  
-class FilterConfig extends ControllersApi{
  37
+class FilterConfig extends ControllersApi {
38 38
     String name
39 39
     Map scope
40 40
     Closure before
@@ -43,7 +43,18 @@ class FilterConfig extends ControllersApi{
43 43
     // this modelAndView overrides ControllersApi's modelAndView
44 44
     ModelAndView modelAndView
45 45
     boolean initialised = false
46  
-
  46
+    
  47
+    public FilterConfig() {
  48
+        initializeMetaClass()
  49
+    }
  50
+    
  51
+    void initializeMetaClass() {
  52
+        // use per-instance metaclass
  53
+        ExpandoMetaClass emc = new ExpandoMetaClass(getClass(), false, true)
  54
+        emc.initialize()
  55
+        setMetaClass(emc)
  56
+    }
  57
+    
47 58
     /**
48 59
      * Redirects attempt to access an 'errors' property, so we provide
49 60
      * one here with a null value.
@@ -56,7 +67,7 @@ class FilterConfig extends ControllersApi{
56 67
      * delegate any missing properties or methods to it.
57 68
      */
58 69
     def filtersDefinition
59  
-
  70
+    
60 71
     /**
61 72
      * When the filter does not have a particular property, it passes
62 73
      * the request on to the filter definition class.
@@ -65,7 +76,7 @@ class FilterConfig extends ControllersApi{
65 76
         // Delegate to the parent definition if it has this property.
66 77
         if (filtersDefinition.metaClass.hasProperty(filtersDefinition, propertyName)) {
67 78
             def getterName = GrailsClassUtils.getGetterName(propertyName)
68  
-            metaClass."$getterName" = {-> delegate.filtersDefinition."$propertyName" }
  79
+            metaClass."$getterName" = {-> delegate.filtersDefinition.getProperty(propertyName) }
69 80
             return filtersDefinition."$propertyName"
70 81
         }
71 82
 
@@ -78,18 +89,15 @@ class FilterConfig extends ControllersApi{
78 89
      */
79 90
     def methodMissing(String methodName, args) {
80 91
         // Delegate to the parent definition if it has this method.
81  
-        if (filtersDefinition.metaClass.respondsTo(filtersDefinition, methodName)) {
82  
-            if (!args) {
83  
-                // No argument method.
84  
-                metaClass."$methodName" = {-> filtersDefinition."$methodName"() }
85  
-            }
86  
-            else {
87  
-                metaClass."$methodName" = { varArgs -> filtersDefinition."$methodName"(varArgs) }
88  
-            }
89  
-
90  
-            // We've created the forwarding method now, but we still
91  
-            // need to invoke the target method this time around.
92  
-            return filtersDefinition."$methodName"(*args)
  92
+        List<MetaMethod> respondsTo = filtersDefinition.metaClass.respondsTo(filtersDefinition, methodName, args)         
  93
+        if (respondsTo) {
  94
+            // Use DelegateMetaMethod to proxy calls to actual MetaMethod for subsequent calls to this method
  95
+            DelegateMetaMethod dmm=new DelegateMetaMethod(respondsTo[0], FilterConfigDelegateMetaMethodTargetStrategy.instance)
  96
+            // register the metamethod to EMC
  97
+            metaClass.registerInstanceMethod(dmm)
  98
+            
  99
+            // for this invocation we still have to make the call
  100
+            return respondsTo[0].invoke(filtersDefinition, args)            
93 101
         }
94 102
 
95 103
         // Ideally, we would throw a MissingMethodException here
@@ -104,7 +112,7 @@ class FilterConfig extends ControllersApi{
104 112
         // The required method was not found on the parent filter definition either.
105 113
         throw new MissingMethodException(methodName, filtersDefinition.getClass(), args)
106 114
     }
107  
-
  115
+    
108 116
     String toString() {"FilterConfig[$name, scope=$scope]"}
109 117
 
110 118
     String getActionUri() {
11  .../groovy/org/codehaus/groovy/grails/plugins/web/filters/FilterConfigDelegateMetaMethodTargetStrategy.java
... ...
@@ -0,0 +1,11 @@
  1
+package org.codehaus.groovy.grails.plugins.web.filters;
  2
+
  3
+import org.codehaus.groovy.grails.plugins.web.filters.DelegateMetaMethod.DelegateMetaMethodTargetStrategy;
  4
+
  5
+class FilterConfigDelegateMetaMethodTargetStrategy implements DelegateMetaMethodTargetStrategy{
  6
+    public static final FilterConfigDelegateMetaMethodTargetStrategy instance=new FilterConfigDelegateMetaMethodTargetStrategy();
  7
+    
  8
+    public Object getTargetInstance(Object instance) {
  9
+        return ((FilterConfig)instance).getFiltersDefinition();
  10
+    }
  11
+}
95  ...s-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/plugins/web/filters/FilterConfigTests.groovy
@@ -24,11 +24,6 @@ class FilterConfigTests extends GroovyTestCase {
24 24
     private static final int INT_PROP_VALUE = 1000
25 25
     private static final String STRING_PROP_VALUE = 'Test property'
26 26
 
27  
-    void setUp() {
28  
-        ExpandoMetaClass.enableGlobally()
29  
-        GroovySystem.metaClassRegistry.removeMetaClass(FilterConfig)
30  
-    }
31  
-
32 27
     void testPropertyMissing() {
33 28
         def mockDefinition = new MockFiltersDefinition()
34 29
         def testFilterConfig = new FilterConfig(name: 'Test filter', initialised: true, filtersDefinition: mockDefinition)
@@ -57,12 +52,16 @@ class FilterConfigTests extends GroovyTestCase {
57 52
 
58 53
     void testMethodMissing() {
59 54
         def mockDefinition = new MockFiltersDefinition()
60  
-        def testFilterConfig = new FilterConfig(name: 'Test filter', initialised: true, filtersDefinition: mockDefinition)
61  
-
  55
+        def testFilterConfig = new MethodMissingCountingFilterConfig(name: 'Test filter', initialised: true, filtersDefinition: mockDefinition)
  56
+        
62 57
         // Try the 'run' method first.
63 58
         testFilterConfig.run()
64 59
         assert mockDefinition.runCalled
  60
+        
  61
+        assert testFilterConfig.methodMissingCounter == 1
65 62
 
  63
+        testFilterConfig.methodMissingCounter = 0
  64
+        
66 65
         // Now try it a couple more times to make sure that the metaclass
67 66
         // method has been registered correctly.
68 67
         mockDefinition.reset()
@@ -72,6 +71,8 @@ class FilterConfigTests extends GroovyTestCase {
72 71
         mockDefinition.reset()
73 72
         testFilterConfig.run()
74 73
         assert mockDefinition.runCalled
  74
+        
  75
+        assert testFilterConfig.methodMissingCounter == 0 
75 76
 
76 77
         // Now try with the next method.
77 78
         mockDefinition.reset()
@@ -79,6 +80,8 @@ class FilterConfigTests extends GroovyTestCase {
79 80
         assert testFilterConfig.generateNumber() == 6342
80 81
         assert mockDefinition.generateNumberCalled == true
81 82
 
  83
+        testFilterConfig.methodMissingCounter = 0
  84
+        
82 85
         mockDefinition.reset()
83 86
         mockDefinition.returnValue = '101'
84 87
         assert testFilterConfig.generateNumber() == '101'
@@ -88,6 +91,8 @@ class FilterConfigTests extends GroovyTestCase {
88 91
         mockDefinition.returnValue = 10.232
89 92
         assert testFilterConfig.generateNumber() == 10.232
90 93
         assert mockDefinition.generateNumberCalled == true
  94
+        
  95
+        assert testFilterConfig.methodMissingCounter == 0
91 96
 
92 97
         // Now for a method with arguments.
93 98
         mockDefinition.reset()
@@ -96,6 +101,8 @@ class FilterConfigTests extends GroovyTestCase {
96 101
         testFilterConfig.checkArgs('Test', 1000)
97 102
         assert mockDefinition.checkArgsCalled
98 103
 
  104
+        testFilterConfig.methodMissingCounter = 0
  105
+        
99 106
         mockDefinition.reset()
100 107
         mockDefinition.expectedStringArg = 'Test two'
101 108
         mockDefinition.expectedIntArg = 2000
@@ -107,11 +114,33 @@ class FilterConfigTests extends GroovyTestCase {
107 114
         mockDefinition.expectedIntArg = -3423
108 115
         testFilterConfig.checkArgs('Apples', -3423)
109 116
         assert mockDefinition.checkArgsCalled
  117
+        
  118
+        assert testFilterConfig.methodMissingCounter == 0
  119
+
  120
+        mockDefinition.reset()
  121
+        mockDefinition.expectedStringArg = 'Oranges'
  122
+        mockDefinition.expectedDoubleArg = 1.23d
  123
+        testFilterConfig.checkArgs('Oranges', 1.23d)
  124
+        assert mockDefinition.checkArgsCalled
  125
+
  126
+        assert testFilterConfig.methodMissingCounter == 1
  127
+        
  128
+        testFilterConfig.methodMissingCounter = 0
  129
+        
  130
+        mockDefinition.reset()
  131
+        mockDefinition.expectedStringArg = 'Pears'
  132
+        mockDefinition.expectedDoubleArg = 2.56d
  133
+        testFilterConfig.checkArgs('Pears', 2.56d)
  134
+        assert mockDefinition.checkArgsCalled
  135
+
  136
+        assert testFilterConfig.methodMissingCounter == 0
110 137
 
111 138
         // A method that takes a list as an argument.
112 139
         mockDefinition.reset()
113 140
         assert testFilterConfig.sum([1, 2, 3, 4]) == 10
114 141
         assert mockDefinition.sumCalled
  142
+        
  143
+        testFilterConfig.methodMissingCounter = 0
115 144
 
116 145
         mockDefinition.reset()
117 146
         assert testFilterConfig.sum([4, 5, 1, 10]) == 20
@@ -121,6 +150,8 @@ class FilterConfigTests extends GroovyTestCase {
121 150
         assert testFilterConfig.sum([12, 26, 3, 41]) == 82
122 151
         assert mockDefinition.sumCalled
123 152
 
  153
+        assert testFilterConfig.methodMissingCounter == 0
  154
+        
124 155
         // And now make sure the 'run' method is still available.
125 156
         mockDefinition.reset()
126 157
         testFilterConfig.run()
@@ -131,13 +162,34 @@ class FilterConfigTests extends GroovyTestCase {
131 162
             testFilterConfig.unknownMethod(23)
132 163
         }
133 164
     }
  165
+    
  166
+    // test for GRAILS-9050
  167
+    void testMethodMissingForTwoFilters() {
  168
+        def mockDefinition = new MockFiltersDefinition()
  169
+        def testFilterConfig = new MethodMissingCountingFilterConfig(name: 'Test filter', initialised: true, filtersDefinition: mockDefinition)
  170
+        
  171
+        def mockDefinition2 = new MockFiltersDefinition2()
  172
+        def testFilterConfig2 = new MethodMissingCountingFilterConfig(name: 'Test filter 2', initialised: true, filtersDefinition: mockDefinition2)
  173
+        
  174
+        mockDefinition2.reset()
  175
+        assert testFilterConfig2.hello() == "Hello from definition 2"
  176
+        assert mockDefinition2.helloCalled
134 177
 
135  
-    void tearDown() {
136  
-        ExpandoMetaClass.disableGlobally()
137  
-        GroovySystem.metaClassRegistry.removeMetaClass(FilterConfig)
  178
+        mockDefinition.reset()
  179
+        assert testFilterConfig.hello() == "Hello from definition 1"
  180
+        assert mockDefinition.helloCalled
138 181
     }
139 182
 }
140 183
 
  184
+class MethodMissingCountingFilterConfig extends FilterConfig {
  185
+    int methodMissingCounter=0
  186
+    
  187
+    def methodMissing(String methodName, args) {
  188
+        methodMissingCounter++
  189
+        super.methodMissing(methodName, args)
  190
+    }  
  191
+}
  192
+
141 193
 class MockFiltersDefinition {
142 194
     def propOne = 1000
143 195
     def prop2 = 'Test property'
@@ -147,7 +199,9 @@ class MockFiltersDefinition {
147 199
     def sumCalled
148 200
     def expectedStringArg
149 201
     def expectedIntArg
  202
+    def expectedDoubleArg
150 203
     def returnValue
  204
+    def helloCalled
151 205
 
152 206
     MockFiltersDefinition() {
153 207
         reset()
@@ -167,12 +221,23 @@ class MockFiltersDefinition {
167 221
         assert arg1 == expectedStringArg
168 222
         assert arg2 == expectedIntArg
169 223
     }
  224
+    
  225
+    void checkArgs(String arg1, double arg2) {
  226
+        checkArgsCalled = true
  227
+        assert arg1 == expectedStringArg
  228
+        assert arg2 == expectedDoubleArg
  229
+    }
170 230
 
171 231
     def sum(items) {
172 232
         sumCalled = true
173 233
         items.sum()
174 234
     }
175 235
 
  236
+    def hello() {
  237
+        helloCalled=true
  238
+        "Hello from definition 1"
  239
+    }
  240
+
176 241
     void reset() {
177 242
         runCalled = false
178 243
         generateNumberCalled = false
@@ -180,6 +245,16 @@ class MockFiltersDefinition {
180 245
         sumCalled = false
181 246
         expectedStringArg = null
182 247
         expectedIntArg = null
  248
+        expectedDoubleArg = null
183 249
         returnValue = null
  250
+        helloCalled = false
184 251
     }
185 252
 }
  253
+
  254
+class MockFiltersDefinition2 extends MockFiltersDefinition {
  255
+    
  256
+    def hello() {
  257
+        helloCalled=true
  258
+        "Hello from definition 2"
  259
+    }
  260
+}

0 notes on commit 73b248a

Please sign in to comment.
Something went wrong with that request. Please try again.