Skip to content

Commit 1cbf0a0

Browse files
committed
Hot reloading support and more tests
1 parent e19d283 commit 1cbf0a0

13 files changed

+196
-11
lines changed

ViewModelsGrailsPlugin.groovy

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ class ViewModelsGrailsPlugin {
4040
"file:../../plugins/*/viewModels/**/*ViewModel.groovy",
4141
]
4242

43-
def pluginExcludes = []
43+
def pluginExcludes = [
44+
"grails-app/**/*",
45+
"web-app/**/*"
46+
]
4447

4548
def doWithSpring = {
4649
for (viewModelClass in application.viewModelClasses) {
@@ -58,7 +61,7 @@ class ViewModelsGrailsPlugin {
5861
}
5962

6063
def doWithDynamicMethods = { context ->
61-
def autowiringDecorator = new AutowiringConstructionDecorator(context)
64+
def autowiringDecorator = createConstructionDecorator(application)
6265
for (viewModelClass in application.viewModelClasses) {
6366
enhanceViewModelClass(viewModelClass, autowiringDecorator)
6467
}
@@ -84,22 +87,24 @@ class ViewModelsGrailsPlugin {
8487

8588
static private handleChange(application, event, type) {
8689
if (application.isArtefactOfType(type, event.source)) {
87-
def autowiringDecorator = new AutowiringConstructionDecorator(event.ctx)
90+
def autowiringDecorator = createConstructionDecorator(application)
8891

8992
def oldClass = application.getArtefact(type, event.source.name)
90-
application.addArtefact(type, event.source)
91-
enhanceViewModelClass(event.source, autowiringDecorator)
93+
enhanceViewModelClass(application.addArtefact(type, event.source), autowiringDecorator)
9294

9395

9496
// Reload subclasses
9597
application.getArtefacts(type).each {
9698
if (it.clazz != event.source && oldClass.clazz.isAssignableFrom(it.clazz)) {
9799
def newClass = application.classLoader.reloadClass(it.clazz.name)
98-
application.addArtefact(type, newClass)
99-
enhanceViewModelClass(newClass, autowiringDecorator)
100+
enhanceViewModelClass(application.addArtefact(type, newClass), autowiringDecorator)
100101
}
101102
}
102103
}
103104
}
105+
106+
static private createConstructionDecorator(grailsApplication) {
107+
new ReloadCapableConstructionDecorator(grailsApplication.classLoader).chainWith(new AutowiringConstructionDecorator(grailsApplication.mainContext))
108+
}
104109

105110
}

application.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#Grails Metadata file
22
#Thu Feb 24 11:17:03 EST 2011
3-
app.grails.version=1.3.6
3+
app.grails.version=1.3.4
44
app.name=view-models
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class TheController {
2+
def index = {
3+
NoConstructorViewModel vm = new NoConstructorViewModel()
4+
render(text: vm.arbitraryProperty)
5+
}
6+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2011 Luke Daley
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import org.springframework.beans.factory.InitializingBean
18+
19+
class InitializingBeanViewModel implements InitializingBean {
20+
21+
def p1 = 0
22+
def p2 = 0
23+
24+
void afterPropertiesSet() {
25+
p2 = p1 + 1
26+
}
27+
}

src/java/grails/plugin/viewmodels/ViewModelArtefactHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@ public ViewModelArtefactHandler() {
2626
super(TYPE, ViewModelClass.class, DefaultViewModelClass.class, SUFFIX);
2727
}
2828

29+
public String getPluginName() {
30+
return "view-models";
31+
}
32+
2933
}

src/java/grails/plugin/viewmodels/constructor/ConstructionDecorator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ public interface ConstructionDecorator<T> {
2424
public void decorate(T instance, Object[] originalArgs, Object[] transformedArgs);
2525

2626
public Class<?>[] getSignature(Constructor<T> constructor);
27-
27+
28+
public Constructor<T> transformConstructor(Constructor<T> constructor);
2829
}

src/java/grails/plugin/viewmodels/constructor/ConstructionDecoratorChain.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,13 @@ public void decorate(T instance, Object[] originalArgs, Object[] transformedArgs
4242
public Class<?>[] getSignature(Constructor<T> constructor) {
4343
return decorators[0].getSignature(constructor);
4444
}
45+
46+
public Constructor<T> transformConstructor(Constructor<T> constructor) {
47+
Constructor<T> transformedConstructor = constructor;
48+
for (ConstructionDecorator<T> decorator : decorators) {
49+
transformedConstructor = decorator.transformConstructor(transformedConstructor);
50+
}
51+
return transformedConstructor;
52+
}
4553

4654
}

src/java/grails/plugin/viewmodels/constructor/ConstructionDecoratorSupport.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,9 @@ public void decorate(T instance, Object[] originalArgs, Object[] transformedArgs
3030
public Class<?>[] getSignature(Constructor<T> constructor) {
3131
return constructor.getParameterTypes();
3232
}
33+
34+
public Constructor<T> transformConstructor(Constructor<T> constructor) {
35+
return constructor;
36+
}
3337

3438
}

src/java/grails/plugin/viewmodels/constructor/DecoratingMetaConstructor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public Object invoke(Object object, Object[] arguments) {
6464

6565
protected T instantiate(Object[] arguments) {
6666
try {
67-
return constructor.newInstance(arguments);
67+
return decorator.transformConstructor(constructor).newInstance(arguments);
6868
} catch (InstantiationException e) {
6969
throw new RuntimeException("Failed to invoke constructor " + constructor + " with args: " + arguments, e);
7070
} catch (IllegalAccessException e) {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2011 Luke Daley
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package grails.plugin.viewmodels.constructor;
17+
18+
import java.lang.reflect.Constructor;
19+
20+
class ReloadCapableConstructionDecorator<T> extends ConstructionDecoratorSupport<T> {
21+
22+
private final ClassLoader classLoader;
23+
24+
public ReloadCapableConstructionDecorator(ClassLoader classLoader) {
25+
this.classLoader = classLoader;
26+
}
27+
28+
public Constructor<T> transformConstructor(Constructor<T> constructor) {
29+
try {
30+
Class<T> originalClassVersion = constructor.getDeclaringClass();
31+
Class<T> newestClassVersion = (Class<T>)classLoader.loadClass(originalClassVersion.getName());
32+
33+
return newestClassVersion.getConstructor(constructor.getParameterTypes());
34+
} catch (NoSuchMethodException e) {
35+
return constructor;
36+
} catch (ClassNotFoundException e) {
37+
return constructor;
38+
}
39+
}
40+
41+
ConstructionDecoratorChain<T> chainWith(ConstructionDecorator<T> decorator) {
42+
return new ConstructionDecoratorChain(decorator, this);
43+
}
44+
45+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2011 Luke Daley
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import spock.lang.*
18+
import grails.plugin.spock.*
19+
20+
class InitialisationSpec extends IntegrationSpec {
21+
22+
def "initialising bean method is called"() {
23+
when:
24+
def vm = new InitializingBeanViewModel()
25+
26+
then:
27+
vm.p1 == 0
28+
vm.p2 == 1
29+
}
30+
31+
def "initialising bean method is called after constructor"() {
32+
when:
33+
def vm = new InitializingBeanViewModel(p1: 1)
34+
35+
then:
36+
vm.p1 == 1
37+
vm.p2 == 2
38+
}
39+
40+
}

test/integration/ReloadingSpec.groovy

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2011 Luke Daley
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import org.codehaus.groovy.grails.plugins.PluginManagerHolder
18+
import grails.plugin.spock.*
19+
import spock.lang.*
20+
21+
class ReloadingSpec extends IntegrationSpec {
22+
23+
def grailsApplication
24+
25+
def "reloading a view model class doesn't destroy autowiring"() {
26+
expect:
27+
createViewModel().exampleService != null
28+
29+
when:
30+
reloadViewModelClass()
31+
32+
then:
33+
createViewModel().exampleService != null
34+
}
35+
36+
protected createViewModel() {
37+
grailsApplication.classLoader.loadClass(NoConstructorViewModel.name).newInstance()
38+
}
39+
40+
protected reloadViewModelClass() {
41+
def newClass = grailsApplication.classLoader.reloadClass(NoConstructorViewModel.name)
42+
PluginManagerHolder.pluginManager.informOfClassChange(newClass)
43+
}
44+
45+
}

test/integration/ViewModelValidInstantiationSpec.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class ViewModelValidInstantiationSpec extends IntegrationSpec {
2525

2626
then:
2727
vm != null
28-
vm instanceof NoConstructorViewModel
28+
vm.class.name == NoConstructorViewModel.name
2929
}
3030

3131
def "instantiated view model is autowired"() {

0 commit comments

Comments
 (0)