Skip to content
Open
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
76c0cc3
Report a partly DSL-shaped beans block instead of dropping it
codeconsole Sep 1, 2026
942cce5
Reject a sibling bean call that cannot return the singleton
codeconsole Sep 1, 2026
dedfe26
Document parameter annotations, and cover the optional-dependency case
codeconsole Sep 1, 2026
4ec20d1
Add .typeArguments(...) so a declared type can carry type arguments
codeconsole Sep 1, 2026
2aabf4b
Pin annotate's array-valued attributes, which only the docs disclaimed
codeconsole Sep 1, 2026
a0c90c9
Stop the sibling-bean check at a nested closure
codeconsole Sep 1, 2026
6509f29
Fail a stray beans statement on a plugin descriptor too
codeconsole Sep 1, 2026
683432b
Fix checkstyle import order and codenarc UnnecessaryGString violations
codeconsole Sep 1, 2026
97ae226
Merge branch '8.0.x' into feat/beans-dsl-diagnostics-8.0.x
codeconsole Sep 2, 2026
c2768dd
Check static sibling bean calls on a proxied host too
codeconsole Sep 3, 2026
6fbfd28
Only record an annotation type in visited when descending into it
codeconsole Sep 3, 2026
5a08d84
Merge remote-tracking branch 'origin/feat/beans-dsl-diagnostics-8.0.x…
codeconsole Sep 3, 2026
e49a39e
Merge branch '8.0.x' into feat/beans-dsl-diagnostics-8.0.x
codeconsole Sep 3, 2026
e27cb9b
beans DSL: declare a bean as its interface and still construct it
codeconsole Sep 3, 2026
b1c0c42
beans DSL: infer type arguments from what the bean constructs
codeconsole Sep 3, 2026
bf31c48
beans DSL: reach @Bean's own attributes instead of colliding with it
codeconsole Sep 3, 2026
9d685b9
beans DSL: reject a post-processor bean that is not staticMethod()
codeconsole Sep 3, 2026
8f30584
beans DSL: condition a bean on the Grails environment
codeconsole Sep 3, 2026
910dc72
Dump the generated members to a directory on request
codeconsole Sep 4, 2026
2867e4d
Resolve sibling bean calls against hand-written @Bean methods too
codeconsole Sep 4, 2026
4a572c7
Document the one-expression form for a property-configured bean
codeconsole Sep 4, 2026
5ea9efe
Add .conditionalOnBean(...), the positive counterpart
codeconsole Sep 4, 2026
1690234
Derive the set of all qualifier names instead of restating it
codeconsole Sep 4, 2026
8f8e86a
Let every condition qualifier tell same-named beans apart
codeconsole Sep 4, 2026
507dd2c
Add .conditionalOnProperty(...), the condition applications actually use
codeconsole Sep 4, 2026
b8a8d00
Rename .grailsEnv(...) to .conditionalOnGrailsEnv(...)
codeconsole Sep 4, 2026
69c5de6
Add .conditionalOnExpression(...), for what the others cannot say
codeconsole Sep 4, 2026
fb07669
Give the DSL reference structure, and say a block can be split
codeconsole Sep 4, 2026
85b1417
Order the java.io imports, which checkstyleMain rejects
codeconsole Sep 4, 2026
0a91ed9
Merge into any qualifier-attached annotation, not only @Bean
codeconsole Sep 4, 2026
e0f99fc
Add .aliases(...), so a bean can keep an old name
codeconsole Sep 4, 2026
4959bf4
Let .scope(...) take the annotation's other attributes
codeconsole Sep 4, 2026
7df2280
Re-home anonymous inner classes in a lifted body
codeconsole Sep 4, 2026
6e32b25
Set originType when re-homing an anonymous inner class
codeconsole Sep 4, 2026
a2603b0
Add .conditionalOnClass(...), the condition optional integrations are…
codeconsole Sep 4, 2026
3c498eb
Take a compile-time String constant as a member name
codeconsole Sep 4, 2026
41d3665
Add group(...), the nested conditionally-guarded configuration class
codeconsole Sep 4, 2026
1085bed
Merge branch '8.0.x' into feat/beans-dsl-diagnostics-8.0.x
codeconsole Sep 4, 2026
e45b3a2
Merge branch '8.0.x' into feat/beans-dsl-diagnostics-8.0.x
codeconsole Sep 5, 2026
c4cadbb
Stop asserting an ordering @ConditionalOnBean does not guarantee
codeconsole Sep 6, 2026
f7d22ce
Fall back rather than throw when Grails cannot answer
codeconsole Sep 7, 2026
cf68805
Document the new DSL surface in the user guide
codeconsole Sep 7, 2026
a488d6a
Merge branch '8.0.x' into feat/beans-dsl-diagnostics-8.0.x
codeconsole Sep 8, 2026
902f082
Fix three defects the new forms introduced, and pin what was missing
codeconsole Sep 8, 2026
6663088
Reject an anonymous class reaching a member that moved to the sibling
codeconsole Sep 8, 2026
9b266f4
Merge branch '8.0.x' into feat/beans-dsl-diagnostics-8.0.x
codeconsole Sep 9, 2026
4c01501
Merge branch '8.0.x' into feat/beans-dsl-diagnostics-8.0.x
codeconsole Sep 9, 2026
67d20dc
Give a lifted anonymous class an enclosing method
codeconsole Sep 9, 2026
191830e
Run the anonymous-class reach check for a group too
codeconsole Sep 9, 2026
635f284
Count a property-style read as a use of the moved accessor
codeconsole Sep 9, 2026
55fa461
Set the enclosing method on nested-closure anonymous classes too
codeconsole Sep 10, 2026
4fe4570
Catch every spelling of a reference the anonymous class cannot reach
codeconsole Sep 10, 2026
b952b27
Reach: one rule for both host kinds, and the runtime's own resolution
codeconsole Sep 10, 2026
5615328
Repair an anonymous class in a closure parameter default too
codeconsole Sep 10, 2026
c4546d2
Make the reach check follow the lift exactly
codeconsole Sep 10, 2026
0c25aef
Stop the reach check at closures, and let both field spellings agree
codeconsole Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* https://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.compiler.beans;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Conditional;

import org.grails.compiler.beans.OnGrailsEnvCondition;

/**
* Registers the annotated bean or configuration only in the named Grails environments.
*
* <p>This is what the {@code beans} DSL's {@code .conditionalOnGrailsEnv(...)} qualifier compiles to. It exists
* because {@code @ConditionalOnProperty(name = "grails.env", ...)} - the obvious way to write this -
* is only correct when the environment was set explicitly with {@code -Dgrails.env}. Grails
* otherwise infers the current environment (development when running from a build, production from
* a packaged jar) without that property existing anywhere, so the property-based condition silently
* never matches and the bean is silently absent. {@link OnGrailsEnvCondition} asks Grails.</p>
*
* @see OnGrailsEnvCondition
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(OnGrailsEnvCondition.class)
public @interface ConditionalOnGrailsEnv {

/**
* The environment names to match, as {@code grails.util.Environment} reports them - the
* built-in ones lowercased ({@code development}, {@code production}, {@code test},
* {@code application}), or a custom environment's own name. Matching any one is enough.
*/
String[] value();

}
307 changes: 240 additions & 67 deletions grails-beans-dsl/src/main/java/grails/compiler/beans/GrailsBeans.java

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* https://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.grails.compiler.beans;

import java.lang.reflect.Method;
import java.util.Locale;
import java.util.Map;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;

/**
* Matches when the current Grails environment is one of those named by
* {@code grails.compiler.beans.ConditionalOnGrailsEnv}.
*
* <p>The environment comes from {@code grails.util.Environment.getCurrent()}, which is the only
* answer that accounts for Grails inferring an environment nobody set. That class lives in a module
* this one is a dependency of, so it is reached reflectively rather than by import; where it is
* absent the {@code grails.env} property is consulted instead, which covers the case the property
* was designed for - an explicitly set environment - and leaves the condition unmatched otherwise
* rather than guessing.</p>
*/
public class OnGrailsEnvCondition implements Condition {

private static final String ENVIRONMENT_CLASS = "grails.util.Environment";
private static final String ENVIRONMENT_PROPERTY = "grails.env";
private static final String ANNOTATION_NAME = "grails.compiler.beans.ConditionalOnGrailsEnv";

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ANNOTATION_NAME);
if (attributes == null) {
return false;
}
Object value = attributes.get("value");
if (!(value instanceof String[])) {
return false;
}
String current = currentEnvironmentName(context);
if (current == null) {
return false;
}
for (String candidate : (String[]) value) {
if (current.equalsIgnoreCase(candidate)) {
return true;
}
}
return false;
}

private String currentEnvironmentName(ConditionContext context) {
String fromGrails = currentEnvironmentNameFromGrails(context.getClassLoader());
if (fromGrails != null) {
return fromGrails;
}
return context.getEnvironment().getProperty(ENVIRONMENT_PROPERTY);
}

private String currentEnvironmentNameFromGrails(ClassLoader classLoader) {
try {
// ConditionContext.getClassLoader() is allowed to be null early in bootstrapping.
// Spring's own class-presence checks fall back to getDefaultClassLoader(), which tries
// the thread context loader first - the one that can see Grails when this class and the
// application are loaded by different loaders, as under Boot's LaunchedClassLoader.
ClassLoader loader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader();
Class<?> environmentClass = Class.forName(ENVIRONMENT_CLASS, true, loader);
Object current = environmentClass.getMethod("getCurrent").invoke(null);
if (current == null) {
return null;
}
Method getName = environmentClass.getMethod("getName");
Object name = getName.invoke(current);
return name == null ? null : name.toString().toLowerCase(Locale.ENGLISH);
}
catch (ReflectiveOperationException | RuntimeException | LinkageError ignored) {
// Not a Grails application, or an Environment that cannot answer - fall back to the
// property rather than fail a condition the rest of the context depends on.
//
// LinkageError is not paranoia: ClassNotFoundException only covers the class being
// absent from the loader that was asked. A class present but unlinkable - a transitive
// dependency missing, a version mismatch - raises NoClassDefFoundError, and initializing
// Environment (Class.forName with initialize = true, which is what reading getCurrent
// needs) can raise ExceptionInInitializerError. Both are LinkageError, neither is a
// ReflectiveOperationException, and either would otherwise be thrown out of matches()
// and fail the whole configuration this condition was meant to skip quietly.
// VirtualMachineError is deliberately still not caught.
return null;
}
}

}
Loading
Loading