Skip to content
Browse files

Merge branch '2.2.x'

Conflicts:
	grails-web/src/main/groovy/org/codehaus/groovy/grails/web/mapping/DefaultLinkGenerator.groovy
  • Loading branch information...
2 parents b03eba1 + b509b4e commit 35b39fc2b370b82ba8ef33d0c35c045ced3ec420 @graemerocher graemerocher committed
View
33 grails-core/src/main/groovy/grails/util/Mixin.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package grails.util;
+
+import org.codehaus.groovy.grails.compiler.injection.MixinTransformation;
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+@GroovyASTTransformationClass("org.codehaus.groovy.grails.compiler.injection.MixinTransformation")
+public @interface Mixin {
+ Class [] value ();
+}
View
143 ...re/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/MixinTransformation.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.codehaus.groovy.grails.compiler.injection;
+
+import grails.util.GrailsNameUtils;
+import grails.util.Mixin;
+import groovy.lang.GroovyObjectSupport;
+import org.codehaus.groovy.ast.*;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.ASTTransformation;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+/**
+ * The logic for the {@link grails.util.Mixin} location transform.
+ *
+ * @author Graeme Rocher
+ * @since 2.1.2
+ */
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+public class MixinTransformation implements ASTTransformation {
+
+ public static final ClassNode GROOVY_OBJECT_CLASS_NODE = new ClassNode(GroovyObjectSupport.class);
+ private static final ClassNode MY_TYPE = new ClassNode(Mixin.class);
+ private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
+ public static final String OBJECT_CLASS = "java.lang.Object";
+
+ public void visit(ASTNode[] astNodes, SourceUnit source) {
+ if (!(astNodes[0] instanceof AnnotationNode) || !(astNodes[1] instanceof AnnotatedNode)) {
+ throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class");
+ }
+
+ AnnotatedNode parent = (AnnotatedNode) astNodes[1];
+ AnnotationNode node = (AnnotationNode) astNodes[0];
+ if (!MY_TYPE.equals(node.getClassNode()) || !(parent instanceof ClassNode)) {
+ return;
+ }
+
+ ClassNode classNode = (ClassNode) parent;
+ String cName = classNode.getName();
+ if (classNode.isInterface()) {
+ throw new RuntimeException("Error processing interface '" + cName + "'. " +
+ MY_TYPE_NAME + " not allowed for interfaces.");
+ }
+
+ ListExpression values = getListOfClasses(node);
+
+ weaveMixinsIntoClass(classNode, values);
+
+ }
+ public void weaveMixinsIntoClass(ClassNode classNode, ListExpression values) {
+ if (values != null) {
+ for (Expression current : values.getExpressions()) {
+ if (current instanceof ClassExpression) {
+ ClassExpression ce = (ClassExpression) current;
+
+ ClassNode mixinClassNode = ce.getType();
+
+ final String fieldName = '$' + GrailsNameUtils.getPropertyName(mixinClassNode.getName());
+
+ GrailsASTUtils.addFieldIfNonExistent(classNode, mixinClassNode, fieldName);
+ VariableExpression fieldReference = new VariableExpression(fieldName);
+
+ while (!mixinClassNode.getName().equals(OBJECT_CLASS)) {
+ final List<MethodNode> mixinMethods = mixinClassNode.getMethods();
+
+ for (MethodNode mixinMethod : mixinMethods) {
+ if (isCandidateMethod(mixinMethod) && !hasDeclaredMethod(classNode, mixinMethod)) {
+ if (mixinMethod.isStatic()) {
+ MethodNode methodNode = GrailsASTUtils.addDelegateStaticMethod(classNode, mixinMethod);
+ }
+ else {
+ MethodNode methodNode = GrailsASTUtils.addDelegateInstanceMethod(classNode, fieldReference, mixinMethod, false);
+ }
+ }
+ }
+
+ List<PropertyNode> properties = mixinClassNode.getProperties();
+ System.out.println("properties = " + properties);
+ for (PropertyNode property : properties) {
+ System.out.println("property = " + property);
+ }
+
+ mixinClassNode = mixinClassNode.getSuperClass();
+ }
+ }
+ }
+ }
+ }
+
+ protected boolean hasDeclaredMethod(ClassNode classNode, MethodNode mixinMethod) {
+ return classNode.hasDeclaredMethod(mixinMethod.getName(), mixinMethod.getParameters());
+ }
+ protected ListExpression getListOfClasses(AnnotationNode node) {
+ Expression value = node.getMember("value");
+ ListExpression values = null;
+ if (value instanceof ListExpression) {
+ values = (ListExpression) value;
+ } else if (value instanceof ClassExpression) {
+ values = new ListExpression();
+ values.addExpression(value);
+ }
+
+ return values;
+ }
+
+ protected boolean isCandidateMethod(MethodNode declaredMethod) {
+ return isAddableMethod(declaredMethod);
+ }
+
+ public static boolean isAddableMethod(MethodNode declaredMethod) {
+ ClassNode groovyMethods = GROOVY_OBJECT_CLASS_NODE;
+ String methodName = declaredMethod.getName();
+ return !declaredMethod.isSynthetic() &&
+ !methodName.contains("$") &&
+ Modifier.isPublic(declaredMethod.getModifiers()) &&
+ !Modifier.isAbstract(declaredMethod.getModifiers()) &&
+ !groovyMethods.hasMethod(declaredMethod.getName(), declaredMethod.getParameters());
+ }
+
+}
+
View
20 ...main/groovy/org/codehaus/groovy/grails/compiler/injection/test/TestForTransformation.java
@@ -75,6 +75,7 @@
import org.codehaus.groovy.grails.plugins.web.filters.FiltersConfigArtefactHandler;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.transform.GroovyASTTransformation;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.Resource;
@@ -106,6 +107,10 @@
public static final ClassNode BEFORE_CLASS_NODE = new ClassNode(Before.class);
public static final AnnotationNode BEFORE_ANNOTATION = new AnnotationNode(BEFORE_CLASS_NODE);
+ public static final ClassNode AFTER_CLASS_NODE = new ClassNode(After.class);
+ public static final AnnotationNode AFTER_ANNOTATION = new AnnotationNode(AFTER_CLASS_NODE);
+
+
public static final AnnotationNode TEST_ANNOTATION = new AnnotationNode(new ClassNode(Test.class));
public static final ClassNode GROOVY_TEST_CASE_CLASS = new ClassNode(GroovyTestCase.class);
@@ -207,6 +212,8 @@ else if (isSpock) {
* @param ce The class expression that represents the class to test
*/
public void testFor(ClassNode classNode, ClassExpression ce) {
+
+ autoAnnotateSetupTeardown(classNode);
boolean junit3Test = isJunit3Test(classNode);
// make sure the 'log' property is not the one from GroovyTestCase
@@ -235,6 +242,19 @@ public void testFor(ClassNode classNode, ClassExpression ce) {
}
}
+ private void autoAnnotateSetupTeardown(ClassNode classNode) {
+ MethodNode setupMethod = classNode.getMethod(SET_UP_METHOD, GrailsArtefactClassInjector.ZERO_PARAMETERS);
+ if ( setupMethod != null && setupMethod.getAnnotations(BEFORE_CLASS_NODE).size() == 0) {
+ setupMethod.addAnnotation(BEFORE_ANNOTATION);
+ }
+
+ MethodNode tearDown = classNode.getMethod(TEAR_DOWN_METHOD, GrailsArtefactClassInjector.ZERO_PARAMETERS);
+ if ( tearDown != null && tearDown.getAnnotations(AFTER_CLASS_NODE).size() == 0) {
+ tearDown.addAnnotation(AFTER_ANNOTATION);
+ }
+
+ }
+
private Map<ClassNode, List<Class>> wovenMixins = new HashMap<ClassNode, List<Class>>();
protected MethodNode weaveMock(ClassNode classNode, ClassExpression value, boolean isClassUnderTest) {
View
35 grails-plugin-testing/src/test/groovy/grails/test/mixin/MetaClassCleanupSpec.groovy
@@ -4,6 +4,7 @@ import grails.test.mixin.support.GrailsUnitTestMixin
import org.junit.AfterClass
import org.junit.Assert
+import grails.util.Mixin
import spock.lang.Specification
@@ -55,6 +56,27 @@ class MetaClassCleanupSpec extends Specification {
greeting == 'goodbye'
}
+
+ def "Test that mixins are re-applied after cleanup - step 1"() {
+ given:"A mixin class"
+ def a = new A()
+
+ when:"A method is called that uses a mixin"
+ def rs = a.doStuff()
+
+ then:"The the mixin method works"
+ rs == "A with mixin: mixMe from AMixin mix static"
+ }
+
+ def "Test that mixins are re-applied after cleanup - step 2"() {
+ given:"A mixin class"
+ def a = new A()
+ when:"A method is called that uses a mixin"
+ def rs = a.doStuff()
+
+ then:"The the mixin method works"
+ rs == "A with mixin: mixMe from AMixin mix static"
+ }
@AfterClass
static void checkCleanup() {
def a = new Author()
@@ -84,3 +106,16 @@ class HelloService {
new XmlParser().parseText(xml).@message
}
}
+
+class AMixin {
+ String prop = "foo"
+ String mixMe() {"mixMe from AMixin"}
+ static mixStatic() { "mix static"}
+}
+
+@Mixin(AMixin)
+class A {
+ String doStuff() {
+ "A with mixin: ${mixMe()} ${mixStatic()}"
+ }
+}
View
48 grails-plugin-testing/src/test/groovy/grails/test/mixin/SetupTeardownInvokeTests.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package grails.test.mixin
+
+import org.junit.Test
+import org.junit.Before
+
+/**
+ */
+@TestFor(FirstController)
+class SetupTeardownInvokeTests {
+
+ void setUp() {
+ controller.value = 'World!'
+ }
+
+ void tearDown() {
+ controller.counter++
+ }
+
+ @Test
+ void testThatSetupWasInvoked() {
+ assert controller.value == 'World!'
+ }
+
+ @Test
+ void testThatSetupWasInvoked2() {
+ assert controller.counter == 1
+ }
+}
+class FirstController {
+ String value
+ static int counter = 0
+}
View
28 grails-web/src/main/groovy/org/codehaus/groovy/grails/web/mapping/CachingLinkGenerator.java
@@ -18,6 +18,7 @@
import java.util.Map;
+import org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequest;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
@@ -72,7 +73,7 @@ public String link(Map attrs, String encoding) {
return super.link(attrs, encoding);
}
- final String key = LINK_PREFIX + createKey(attrs);
+ final String key = makeKey(LINK_PREFIX, attrs);
Object resourceLink = linkCache.get(key);
if (resourceLink == null) {
resourceLink = super.link(attrs, encoding);
@@ -94,12 +95,11 @@ private boolean isCacheable(Map attrs) {
}
// Based on DGM toMapString, but with StringBuilder instead of StringBuffer
- private String createKey(Map map) {
- if (map.isEmpty()) {
- return EMPTY_MAP_STRING;
+ private void appendMapKey(StringBuilder buffer, Map map) {
+ if (map==null || map.isEmpty()) {
+ buffer.append(EMPTY_MAP_STRING);
}
-
- StringBuilder buffer = new StringBuilder(OPENING_BRACKET);
+ buffer.append(OPENING_BRACKET);
boolean first = true;
for (Object o : map.entrySet()) {
Map.Entry entry = (Map.Entry) o;
@@ -114,7 +114,6 @@ private String createKey(Map map) {
appendKeyValue(buffer, map, key, value);
}
buffer.append(CLOSING_BRACKET);
- return buffer.toString();
}
private boolean appendCommaIfNotFirst(StringBuilder buffer, boolean first) {
@@ -138,7 +137,7 @@ private void appendKeyValue(StringBuilder buffer, Map map, Object key, Object va
@Override
public String resource(Map attrs) {
- final String key = RESOURCE_PREFIX + attrs;
+ final String key = makeKey(RESOURCE_PREFIX, attrs);
Object resourceLink = linkCache.get(key);
if (resourceLink == null) {
resourceLink = super.resource(attrs);
@@ -147,6 +146,19 @@ public String resource(Map attrs) {
return resourceLink.toString();
}
+ protected String makeKey(String prefix, Map attrs) {
+ StringBuilder sb=new StringBuilder();
+ sb.append(prefix);
+ if(getConfiguredServerBaseURL()==null && isAbsolute(attrs)) {
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ if(webRequest != null) {
+ sb.append(webRequest.getBaseUrl());
+ }
+ }
+ appendMapKey(sb, attrs);
+ return sb.toString();
+ }
+
private ConcurrentLinkedHashMap<String, Object> createDefaultCache() {
return new ConcurrentLinkedHashMap.Builder<String, Object>()
.maximumWeightedCapacity(DEFAULT_MAX_WEIGHTED_CAPACITY)
View
57 ...ls-web/src/main/groovy/org/codehaus/groovy/grails/web/mapping/DefaultLinkGenerator.groovy
@@ -129,21 +129,9 @@ class DefaultLinkGenerator implements LinkGenerator, PluginManagerAware {
mapping = urlMappingsHolder.getReverseMapping(controller,action,pluginName,params)
}
- boolean absolute = false
- def o = attrs.get(ATTRIBUTE_ABSOLUTE)
- if (o instanceof Boolean) {
- absolute = o
- } else {
- if (o != null) {
- try {
- def str = o.toString()
- if (str) {
- absolute = Boolean.parseBoolean(str)
- }
- } catch(e) {}
- }
- }
+ boolean absolute = isAbsolute(attrs)
+
if (!absolute) {
url = mapping.createRelativeURL(convertedControllerName, convertedActionName, params, encoding, frag)
final contextPathAttribute = attrs.get(ATTRIBUTE_CONTEXT_PATH)
@@ -167,6 +155,24 @@ class DefaultLinkGenerator implements LinkGenerator, PluginManagerAware {
return writer.toString()
}
+ protected boolean isAbsolute(Map attrs) {
+ boolean absolute = false
+ def o = attrs.get(ATTRIBUTE_ABSOLUTE)
+ if(o instanceof Boolean) {
+ absolute = o
+ } else {
+ if(o != null) {
+ try {
+ def str = o.toString()
+ if(str) {
+ absolute = Boolean.parseBoolean(str)
+ }
+ } catch(e){}
+ }
+ }
+ return absolute
+ }
+
/**
* {@inheritDoc }
*/
@@ -232,8 +238,7 @@ class DefaultLinkGenerator implements LinkGenerator, PluginManagerAware {
return base
}
- def abs = attrs.absolute
- if (Boolean.valueOf(abs)) {
+ if (isAbsolute(attrs)) {
def u = makeServerURL()
if (u) {
return u
@@ -251,22 +256,10 @@ class DefaultLinkGenerator implements LinkGenerator, PluginManagerAware {
if (!u) {
// Leave it null if we're in production so we can throw
final webRequest = GrailsWebRequest.lookup()
- final request = webRequest?.currentRequest
- if (request != null) {
- int port = request.serverPort
- String scheme = request.scheme
- String contextPath = request.contextPath
-
- String url = "${scheme}://${request.serverName}"
- if ((scheme == "http" && port != 80) || (scheme == "https" && port != 443)) {
- return contextPath ? "$url:$port$contextPath" : "$url:$port"
- }
- return contextPath ? "$url$contextPath" : url
- }
- else {
- if (!Environment.isWarDeployed()) {
- u = "http://localhost:${System.getProperty('server.port') ?: '8080'}${contextPath ?: '' }"
- }
+
+ u = webRequest?.baseUrl
+ if (!u && !Environment.isWarDeployed()) {
+ u = "http://localhost:${System.getProperty('server.port') ?: '8080'}${contextPath ?: '' }"
}
}
return u
View
20 grails-web/src/main/groovy/org/codehaus/groovy/grails/web/servlet/mvc/GrailsWebRequest.java
@@ -64,6 +64,7 @@
private final List<ParameterCreationListener> parameterCreationListeners = new ArrayList<ParameterCreationListener>();
private final UrlPathHelper urlHelper = new UrlPathHelper();
private ApplicationContext applicationContext;
+ private String baseUrl;
public GrailsWebRequest(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {
super(request);
@@ -331,4 +332,23 @@ public static GrailsApplication lookupApplication() {
public void setId(Object id) {
getParams().put(GrailsWebRequest.ID_PARAMETER, id);
}
+
+ public String getBaseUrl() {
+ if(baseUrl == null) {
+ HttpServletRequest request=getCurrentRequest();
+ String scheme =request.getScheme();
+ StringBuilder sb=new StringBuilder();
+ sb.append(scheme).append("://").append(request.getServerName());
+ int port = request.getServerPort();
+ if(("http".equals(scheme) && port != 80) || ("https".equals(scheme) && port != 443)) {
+ sb.append(":").append(port);
+ }
+ String contextPath = request.getContextPath();
+ if(contextPath != null) {
+ sb.append(contextPath);
+ }
+ baseUrl=sb.toString();
+ }
+ return baseUrl;
+ }
}
View
51 grails-web/src/test/groovy/org/codehaus/groovy/grails/web/mapping/LinkGeneratorSpec.groovy
@@ -147,26 +147,71 @@ class LinkGeneratorSpec extends Specification {
when:
baseUrl = null
resource = mainCssResource + [absolute:true]
-
+ webRequest.baseUrl = null
then:
link == "http://localhost/$resource.dir/$resource.file"
when:
request.serverPort = 8081
-
+ webRequest.baseUrl = null
then:
link == "http://localhost:8081/$resource.dir/$resource.file"
when:
request.contextPath = "/blah"
request.serverPort = 8081
-
+ webRequest.baseUrl = null
then:
link == "http://localhost:8081/blah/$resource.dir/$resource.file"
}
+ def "caching should take request Host header, scheme and port in to account"() {
+
+ given:
+ final webRequest = GrailsWebUtil.bindMockWebRequest()
+ MockHttpServletRequest request = webRequest.currentRequest
+ baseUrl = null
+ def cachingGenerator = getGenerator(true)
+
+ when:
+ resource = mainCssResource + [absolute:true]
+ def cachedlink = cachingGenerator.resource(resource)
+
+ then:
+ cachedlink == "http://localhost/$resource.dir/$resource.file"
+
+ when:
+ request.serverName = "some.other.host"
+ request.scheme = "https"
+ request.serverPort = 443
+ webRequest.baseUrl = null
+ cachedlink = cachingGenerator.resource(resource)
+ then:
+ cachedlink == "https://some.other.host/$resource.dir/$resource.file"
+
+ when:
+ request.serverName = "localhost"
+ request.scheme = "http"
+ request.serverPort = 8081
+ webRequest.baseUrl = null
+ cachedlink = cachingGenerator.resource(resource)
+ then:
+ cachedlink == "http://localhost:8081/$resource.dir/$resource.file"
+
+ when:
+ request.contextPath = "/blah"
+ request.serverPort = 8081
+ webRequest.baseUrl = null
+ cachedlink = cachingGenerator.resource(resource)
+ then:
+ cachedlink == "http://localhost:8081/blah/$resource.dir/$resource.file"
+
+
+ }
+
+
void cleanup() {
RequestContextHolder.setRequestAttributes(null)
}

0 comments on commit 35b39fc

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