Skip to content

Commit 5f7e6a2

Browse files
committed
GH-522 - Support for package info types.
Introduce @PackageInfo annotation to allow marking a types as alternative to Java's native package-info.java so that annotation lookups on a JavaPackage will also find annotations placed on that type. Useful to declare package scoped metadata like @ApplicationModule and @NamedInterface for languages that do not support packages well enough (read: Kotlin).
1 parent f3c111f commit 5f7e6a2

File tree

21 files changed

+461
-66
lines changed

21 files changed

+461
-66
lines changed

spring-modulith-api/src/main/java/org/springframework/modulith/ApplicationModule.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
*
2626
* @author Oliver Drotbohm
2727
*/
28-
@Target({ ElementType.PACKAGE, ElementType.ANNOTATION_TYPE })
28+
@PackageInfo
29+
@Target({ ElementType.PACKAGE, ElementType.TYPE })
2930
@Retention(RetentionPolicy.RUNTIME)
3031
public @interface ApplicationModule {
3132

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
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+
* https://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 org.springframework.modulith;
17+
18+
import java.lang.annotation.ElementType;
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
import java.lang.annotation.Target;
22+
23+
/**
24+
* An annotation to mark a type as an alternative to Java's native {@code package-info.java}. Classes annotated like
25+
* this will be considered, too, when looking up annotations on a package.
26+
*
27+
* @author Oliver Drotbohm
28+
* @since 1.2
29+
*/
30+
@Target({ ElementType.TYPE })
31+
@Retention(RetentionPolicy.RUNTIME)
32+
public @interface PackageInfo {}

spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import static com.tngtech.archunit.base.DescribedPredicate.*;
1919
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
2020
import static java.lang.System.*;
21-
import static org.springframework.modulith.core.Types.*;
21+
import static org.springframework.modulith.core.SyntacticSugar.*;
2222
import static org.springframework.modulith.core.Types.JavaXTypes.*;
2323
import static org.springframework.modulith.core.Types.SpringDataTypes.*;
2424
import static org.springframework.modulith.core.Types.SpringTypes.*;
@@ -84,6 +84,8 @@ public class ApplicationModule {
8484
*/
8585
ApplicationModule(JavaPackage basePackage, boolean useFullyQualifiedModuleNames) {
8686

87+
Assert.notNull(basePackage, "Base package must not be null!");
88+
8789
this.basePackage = basePackage;
8890
this.information = ApplicationModuleInformation.of(basePackage);
8991
this.namedInterfaces = isOpen()

spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleInformation.java

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,23 @@
1515
*/
1616
package org.springframework.modulith.core;
1717

18+
import java.lang.annotation.Annotation;
1819
import java.util.Arrays;
1920
import java.util.List;
2021
import java.util.Optional;
22+
import java.util.function.Predicate;
2123
import java.util.function.Supplier;
2224
import java.util.stream.Stream;
2325

26+
import org.jmolecules.ddd.annotation.Module;
2427
import org.springframework.modulith.ApplicationModule;
2528
import org.springframework.modulith.ApplicationModule.Type;
2629
import org.springframework.util.Assert;
2730
import org.springframework.util.ClassUtils;
2831
import org.springframework.util.StringUtils;
2932

33+
import com.tngtech.archunit.core.domain.JavaClass;
34+
3035
/**
3136
* Abstraction for low-level module information. Used to support different annotations to configure metadata about a
3237
* module.
@@ -43,13 +48,15 @@ interface ApplicationModuleInformation {
4348
*/
4449
public static ApplicationModuleInformation of(JavaPackage javaPackage) {
4550

51+
var lookup = AnnotationLookup.of(javaPackage, __ -> true);
52+
4653
if (ClassUtils.isPresent("org.jmolecules.ddd.annotation.Module",
4754
ApplicationModuleInformation.class.getClassLoader())
48-
&& JMoleculesModule.supports(javaPackage)) {
49-
return new JMoleculesModule(javaPackage);
55+
&& JMoleculesModule.supports(lookup)) {
56+
return new JMoleculesModule(lookup);
5057
}
5158

52-
return new SpringModulithModule(javaPackage);
59+
return new SpringModulithModule(lookup);
5360
}
5461

5562
/**
@@ -84,14 +91,14 @@ default Optional<String> getDisplayName() {
8491
*/
8592
static class JMoleculesModule implements ApplicationModuleInformation {
8693

87-
private final Optional<org.jmolecules.ddd.annotation.Module> annotation;
94+
private final Optional<Module> annotation;
8895

89-
public static boolean supports(JavaPackage javaPackage) {
90-
return javaPackage.getAnnotation(org.jmolecules.ddd.annotation.Module.class).isPresent();
96+
public static boolean supports(AnnotationLookup lookup) {
97+
return lookup.lookup(Module.class).isPresent();
9198
}
9299

93-
public JMoleculesModule(JavaPackage javaPackage) {
94-
this.annotation = javaPackage.getAnnotation(org.jmolecules.ddd.annotation.Module.class);
100+
public <A extends Annotation> JMoleculesModule(AnnotationLookup lookup) {
101+
this.annotation = lookup.lookup(Module.class);
95102
}
96103

97104
/*
@@ -102,11 +109,11 @@ public JMoleculesModule(JavaPackage javaPackage) {
102109
public Optional<String> getDisplayName() {
103110

104111
Supplier<Optional<String>> fallback = () -> annotation //
105-
.map(org.jmolecules.ddd.annotation.Module::value) //
112+
.map(Module::value) //
106113
.filter(StringUtils::hasText);
107114

108115
return annotation //
109-
.map(org.jmolecules.ddd.annotation.Module::name) //
116+
.map(Module::name) //
110117
.filter(StringUtils::hasText)
111118
.or(fallback);
112119
}
@@ -144,20 +151,20 @@ static class SpringModulithModule implements ApplicationModuleInformation {
144151
*
145152
* @param javaPackage must not be {@literal null}.
146153
*/
147-
public static boolean supports(JavaPackage javaPackage) {
154+
public static boolean supports(AnnotationLookup lookup) {
148155

149-
Assert.notNull(javaPackage, "Java package must not be null!");
156+
Assert.notNull(lookup, "Annotation lookup must not be null!");
150157

151-
return javaPackage.getAnnotation(ApplicationModule.class).isPresent();
158+
return lookup.lookup(ApplicationModule.class).isPresent();
152159
}
153160

154161
/**
155162
* Creates a new {@link SpringModulithModule} for the given {@link JavaPackage}.
156163
*
157164
* @param javaPackage must not be {@literal null}.
158165
*/
159-
public SpringModulithModule(JavaPackage javaPackage) {
160-
this.annotation = javaPackage.getAnnotation(ApplicationModule.class);
166+
public SpringModulithModule(AnnotationLookup lookup) {
167+
this.annotation = lookup.lookup(ApplicationModule.class);
161168
}
162169

163170
/*
@@ -194,4 +201,21 @@ public boolean isOpen() {
194201
return annotation.map(it -> it.type().equals(Type.OPEN)).orElse(false);
195202
}
196203
}
204+
205+
interface AnnotationLookup {
206+
207+
static AnnotationLookup of(JavaPackage javaPackage,
208+
Predicate<JavaClass> typeSelector) {
209+
210+
return new AnnotationLookup() {
211+
212+
@Override
213+
public <A extends Annotation> Optional<A> lookup(Class<A> annotation) {
214+
return javaPackage.findAnnotation(annotation);
215+
}
216+
};
217+
}
218+
219+
<A extends Annotation> Optional<A> lookup(Class<A> annotation);
220+
}
197221
}

spring-modulith-core/src/main/java/org/springframework/modulith/core/ArchitecturallyEvidentType.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.modulith.core;
1717

18+
import static org.springframework.modulith.core.SyntacticSugar.*;
1819
import static org.springframework.modulith.core.Types.JavaXTypes.*;
1920

2021
import java.util.ArrayList;
@@ -194,8 +195,8 @@ static class SpringAwareArchitecturallyEvidentType extends ArchitecturallyEviden
194195
* Methods (meta-)annotated with @EventListener.
195196
*/
196197
private static final Predicate<JavaMethod> IS_ANNOTATED_EVENT_LISTENER = it -> //
197-
Types.isAnnotatedWith(SpringTypes.AT_EVENT_LISTENER).test(it) //
198-
|| Types.isAnnotatedWith(SpringTypes.AT_TX_EVENT_LISTENER).test(it);
198+
isAnnotatedWith(SpringTypes.AT_EVENT_LISTENER).test(it) //
199+
|| isAnnotatedWith(SpringTypes.AT_TX_EVENT_LISTENER).test(it);
199200

200201
/**
201202
* {@code ApplicationListener.onApplicationEvent(…)}
@@ -232,7 +233,7 @@ public boolean isAggregateRoot() {
232233
*/
233234
@Override
234235
public boolean isRepository() {
235-
return Types.isAnnotatedWith(SpringTypes.AT_REPOSITORY).test(getType());
236+
return isAnnotatedWith(SpringTypes.AT_REPOSITORY).test(getType());
236237
}
237238

238239
/*
@@ -241,7 +242,7 @@ public boolean isRepository() {
241242
*/
242243
@Override
243244
public boolean isService() {
244-
return Types.isAnnotatedWith(SpringTypes.AT_SERVICE).test(getType());
245+
return isAnnotatedWith(SpringTypes.AT_SERVICE).test(getType());
245246
}
246247

247248
/*
@@ -250,7 +251,7 @@ public boolean isService() {
250251
*/
251252
@Override
252253
public boolean isController() {
253-
return Types.isAnnotatedWith(SpringTypes.AT_CONTROLLER).test(getType());
254+
return isAnnotatedWith(SpringTypes.AT_CONTROLLER).test(getType());
254255
}
255256

256257
/*
@@ -268,7 +269,7 @@ public boolean isEventListener() {
268269
*/
269270
@Override
270271
public boolean isConfigurationProperties() {
271-
return Types.isAnnotatedWith(SpringTypes.AT_CONFIGURATION_PROPERTIES).test(getType());
272+
return isAnnotatedWith(SpringTypes.AT_CONFIGURATION_PROPERTIES).test(getType());
272273
}
273274

274275
/*
@@ -365,15 +366,15 @@ public boolean isRepository() {
365366
*/
366367
@Override
367368
public boolean isController() {
368-
return Types.isAnnotatedWith("org.springframework.data.rest.webmvc.BasePathAwareController")
369+
return isAnnotatedWith("org.springframework.data.rest.webmvc.BasePathAwareController")
369370
.test(getType());
370371
}
371372
}
372373

373374
static class JMoleculesArchitecturallyEvidentType extends ArchitecturallyEvidentType {
374375

375-
private static final Predicate<JavaMethod> IS_ANNOTATED_EVENT_LISTENER = Types
376-
.isAnnotatedWith(JMoleculesTypes.AT_DOMAIN_EVENT_HANDLER)::test;
376+
private static final Predicate<JavaMethod> IS_ANNOTATED_EVENT_LISTENER = isAnnotatedWith(
377+
JMoleculesTypes.AT_DOMAIN_EVENT_HANDLER)::test;
377378

378379
JMoleculesArchitecturallyEvidentType(JavaClass type) {
379380
super(type);
@@ -388,7 +389,7 @@ public boolean isEntity() {
388389

389390
JavaClass type = getType();
390391

391-
return Types.isAnnotatedWith(org.jmolecules.ddd.annotation.Entity.class).test(type) || //
392+
return isAnnotatedWith(org.jmolecules.ddd.annotation.Entity.class).test(type) || //
392393
type.isAssignableTo(org.jmolecules.ddd.types.Entity.class);
393394
}
394395

@@ -401,7 +402,7 @@ public boolean isAggregateRoot() {
401402

402403
JavaClass type = getType();
403404

404-
return Types.isAnnotatedWith(org.jmolecules.ddd.annotation.AggregateRoot.class).test(type) //
405+
return isAnnotatedWith(org.jmolecules.ddd.annotation.AggregateRoot.class).test(type) //
405406
|| type.isAssignableTo(org.jmolecules.ddd.types.AggregateRoot.class);
406407
}
407408

@@ -414,7 +415,7 @@ public boolean isRepository() {
414415

415416
JavaClass type = getType();
416417

417-
return Types.isAnnotatedWith(org.jmolecules.ddd.annotation.Repository.class).test(type)
418+
return isAnnotatedWith(org.jmolecules.ddd.annotation.Repository.class).test(type)
418419
|| type.isAssignableTo(org.jmolecules.ddd.types.Repository.class);
419420
}
420421

@@ -427,7 +428,7 @@ public boolean isService() {
427428

428429
JavaClass type = getType();
429430

430-
return Types.isAnnotatedWith(org.jmolecules.ddd.annotation.Service.class).test(type);
431+
return isAnnotatedWith(org.jmolecules.ddd.annotation.Service.class).test(type);
431432
}
432433

433434
/*
@@ -448,7 +449,7 @@ public boolean isValueObject() {
448449

449450
JavaClass type = getType();
450451

451-
return Types.isAnnotatedWith(org.jmolecules.ddd.annotation.ValueObject.class).test(type)
452+
return isAnnotatedWith(org.jmolecules.ddd.annotation.ValueObject.class).test(type)
452453
|| type.isAssignableTo(org.jmolecules.ddd.types.ValueObject.class);
453454
}
454455
}

spring-modulith-core/src/main/java/org/springframework/modulith/core/DependencyType.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.modulith.core;
1717

18+
import static org.springframework.modulith.core.SyntacticSugar.*;
19+
1820
import java.util.Arrays;
1921
import java.util.Collection;
2022
import java.util.function.Predicate;
@@ -125,8 +127,8 @@ static DependencyType forParameter(JavaClass type) {
125127
}
126128

127129
static DependencyType forCodeUnit(JavaCodeUnit codeUnit) {
128-
return Types.isAnnotatedWith(SpringTypes.AT_EVENT_LISTENER).test(codeUnit) //
129-
|| Types.isAnnotatedWith(JMoleculesTypes.AT_DOMAIN_EVENT_HANDLER).test(codeUnit) //
130+
return isAnnotatedWith(SpringTypes.AT_EVENT_LISTENER).test(codeUnit) //
131+
|| isAnnotatedWith(JMoleculesTypes.AT_DOMAIN_EVENT_HANDLER).test(codeUnit) //
130132
? EVENT_LISTENER
131133
: DEFAULT;
132134
}

0 commit comments

Comments
 (0)