Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .mvn/extensions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<extension>
<groupId>io.spring.develocity.conventions</groupId>
<artifactId>develocity-conventions-maven-extension</artifactId>
<version>0.0.26</version>
<version>0.0.27</version>
</extension>
</extensions>
2 changes: 1 addition & 1 deletion .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.10.0/apache-maven-3.10.0-bin.zip
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ limitations under the License.

</profile>

<!-- Release train profile to make sure the default profile gets deactivated during releases -->

<profile>
<id>releaseTrain</id>
</profile>

<!-- For integration builds against milestones and snapshots -->

<profile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public static FormattableType of(ResolvableType type) {
.toList();

return resolved != null
? CACHE.computeIfAbsent(type.toString(), __ -> new FormattableType(resolved.getName(), generics))
? CACHE.computeIfAbsent(type.toString(), __ -> new FormattableType(resolved.getTypeName(), generics))
: WILDCARD;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@

import static org.assertj.core.api.Assertions.*;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.NamedExecutable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.springframework.core.ResolvableType;
import org.springframework.modulith.core.FormattableType.NonModuleTypeAbbreviation;

Expand Down Expand Up @@ -128,12 +133,56 @@ void rendersDeclaredGenerics() throws Exception {
assertThat(type.getAbbreviatedFullName()).isEqualTo("j.u.List<j.u.Map<j.l.String, j.l.Integer>>");
}

@TestFactory // GH-1901
Stream<DynamicTest> handlesArrayTypesFromResolvableType() throws Exception {

var method = Sample.class.getMethod("arrays", Integer[].class, byte[].class, Integer[][].class, List.class);

var tests = Stream.of(
new $(method, 0, "j.l.Integer[]"),
new $(method, 1, "byte[]"),
new $(method, 2, "j.l.Integer[][]"),
new $(method, 3, "j.u.List<j.l.Integer[]>"));

return DynamicTest.stream(tests);
}

@Test // GH-1901
void rendersArrayTypesConsistentlyIndependentOfLookupOrder() {

var fromResolvableType = FormattableType.of(ResolvableType.forClass(Sample[].class));
var fromClass = FormattableType.of(Sample[].class);

assertThat(fromClass.getFullName())
.isEqualTo(fromResolvableType.getFullName())
.isEqualTo("org.springframework.modulith.core.FormattableTypeUnitTests.Sample[]");
}

interface Sample {

<T> List<?> wildcarded(List<T> parameterized);

void classWildcard(Class<?> type);

List<Map<String, Integer>> genericReturnType();

void arrays(Integer[] integers, byte[] bytes, Integer[][] matrix, List<Integer[]> list);
}

record $(ResolvableType type, String expected) implements NamedExecutable {

public $(Method method, int parameterIndex, String expected) {
this(ResolvableType.forMethodParameter(method, parameterIndex), expected);
}

@Override
public final String toString() {
return "%s renders as %s".formatted(type, expected);
}

@Override
public void execute() throws Throwable {
assertThat(FormattableType.of(type).getAbbreviatedFullName()).isEqualTo(expected);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ public DiagramOptions withStyle(DiagramStyle style) {

/**
* Configuration setting to define whether modules that do not have a relationship to any other module shall be
* retained in the diagrams created. The default is {@link ElementsWithoutRelationships#HIDDEN}. See
* retained in the diagrams created. The default is {@link ElementsWithoutRelationships#VISIBLE}. See
* {@link DiagramOptions#withExclusions(Predicate)} for a more fine-grained way of defining which modules to exclude
* in case you flip this to {@link ElementsWithoutRelationships#VISIBLE}.
*
Expand Down Expand Up @@ -859,7 +859,7 @@ public DiagramOptions withSkinParam(String name, @Nullable String value) {
public static DiagramOptions defaults() {
return new DiagramOptions(ALL_TYPES, DependencyDepth.IMMEDIATE, it -> false, it -> true, it -> false, null,
__ -> Optional.empty(), it -> it.getDisplayName(), DiagramStyle.C4,
ElementsWithoutRelationships.HIDDEN, new LinkedHashMap<>());
ElementsWithoutRelationships.VISIBLE, new LinkedHashMap<>());
}

/**
Expand Down
7 changes: 6 additions & 1 deletion spring-modulith-events/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,16 @@

</profile>

<!-- Needs to be declared to disable default profile -->
<!-- Needs to be declared to disable profiles active by default during releases -->

<profile>
<id>sonatype</id>
</profile>

<profile>
<id>releaseTrain</id>
</profile>

</profiles>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ class RabbitEventExternalizerConfiguration {
private static final Logger logger = LoggerFactory.getLogger(RabbitEventExternalizerConfiguration.class);
private static final String PUBLISHER_CONFIRM_TYPE = "spring.rabbitmq.publisher-confirm-type";

private final EvaluationContext evaluationContext;

RabbitEventExternalizerConfiguration(BeanFactory factory) {

var context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(factory));

this.evaluationContext = context;
}

@Bean
@ConditionalOnProperty(name = ExternalizationMode.PROPERTY, havingValue = "module-listener", matchIfMissing = true)
EventExternalizerModuleListener rabbitEventExternalizer(EventExternalizationConfiguration configuration,
Expand All @@ -76,22 +86,22 @@ EventExternalizerModuleListener rabbitEventExternalizer(EventExternalizationConf
logger.debug("Registering domain event externalization to RabbitMQ…");

return new EventExternalizerModuleListener(configuration,
createRabbitTransport(configuration, operations, factory));
createRabbitTransport(configuration, operations), factory);
}

@AutoConfiguration
@ConditionalOnProperty(name = ExternalizationMode.PROPERTY, havingValue = "outbox")
static class RabbitOutboxConfiguration {
class RabbitOutboxConfiguration {

private final OutboxEventExternalizer externalizer;

RabbitOutboxConfiguration(EventExternalizationConfiguration configuration, RabbitMessageOperations operations,
BeanFactory beanFactory, OutboxEventExternalizerFactory factory, Environment environment) {
OutboxEventExternalizerFactory factory, Environment environment) {

Assert.state("correlated".equalsIgnoreCase(environment.getProperty(PUBLISHER_CONFIRM_TYPE)),
() -> "RabbitMQ outbox event externalization requires " + PUBLISHER_CONFIRM_TYPE + "=correlated!");

this.externalizer = factory.forTransport(createConfirmingRabbitTransport(configuration, operations, beanFactory));
this.externalizer = factory.forTransport(createConfirmingRabbitTransport(configuration, operations));
}

@AutoConfiguration
Expand Down Expand Up @@ -121,28 +131,26 @@ JobRunrExternalizationTransport jobRunrRabbitOutboxExternalizer() {
}
}

private static EventExternalizationTransport createRabbitTransport(
EventExternalizationConfiguration configuration, RabbitMessageOperations operations,
BeanFactory factory) {
private EventExternalizationTransport createRabbitTransport(
EventExternalizationConfiguration configuration, RabbitMessageOperations operations) {

return (payload, target) -> {

send(payload, target, configuration, Collections.emptyMap(), operations, factory);
send(payload, target, configuration, Collections.emptyMap(), operations);

return CompletableFuture.completedFuture(null);
};
}

private static EventExternalizationTransport createConfirmingRabbitTransport(
EventExternalizationConfiguration configuration, RabbitMessageOperations operations,
BeanFactory factory) {
private EventExternalizationTransport createConfirmingRabbitTransport(EventExternalizationConfiguration configuration,
RabbitMessageOperations operations) {

return (payload, target) -> {

var correlation = new CorrelationData();
var headers = Map.of(AmqpHeaders.PUBLISH_CONFIRM_CORRELATION, (Object) correlation);

send(payload, target, configuration, headers, operations, factory);
send(payload, target, configuration, headers, operations);

return correlation.getFuture().thenAccept(confirm -> {

Expand All @@ -154,22 +162,13 @@ private static EventExternalizationTransport createConfirmingRabbitTransport(
};
}

private static void send(Object payload, RoutingTarget target, EventExternalizationConfiguration configuration,
Map<String, Object> additionalHeaders, RabbitMessageOperations operations, BeanFactory factory) {

var routing = BrokerRouting.of(target, createContext(factory));
private void send(Object payload, RoutingTarget target, EventExternalizationConfiguration configuration,
Map<String, Object> additionalHeaders, RabbitMessageOperations operations) {

var routing = BrokerRouting.of(target, evaluationContext);
var headers = new HashMap<>(configuration.getHeadersFor(payload));
headers.putAll(additionalHeaders);

operations.convertAndSend(routing.getTarget(payload), routing.getKey(payload), payload, additionalHeaders);
}

private static EvaluationContext createContext(BeanFactory factory) {

var context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(factory));

return context;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Role;
Expand Down Expand Up @@ -89,8 +89,8 @@ static EventExternalizationConfiguration eventExternalizationConfiguration(BeanF
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnProperty(name = ExternalizationMode.PROPERTY, havingValue = "outbox")
static OutboxEventExternalizerFactory outboxEventExternalizerFactory(EventExternalizationConfiguration configuration,
ApplicationEventPublisher publisher) {
return new OutboxEventExternalizerFactory(configuration, publisher);
ApplicationContext context) {
return new OutboxEventExternalizerFactory(configuration, context);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ static DefaultFailedEventPublications failedEventPublications(
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnBean(EventPublicationRegistry.class)
static CompletionRegisteringAdvisor completionRegisteringAdvisor(ObjectFactory<EventPublicationRegistry> registry) {
return EventPublicationConfiguration.completionRegisteringAdvisor(registry);
static CompletionRegisteringAdvisor completionRegisteringAdvisor(ObjectFactory<EventPublicationRegistry> registry,
ObjectFactory<Environment> environment) {
return EventPublicationConfiguration.completionRegisteringAdvisor(registry, environment);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.modulith.events.core.DefaultAbandonedEventPublications;
import org.springframework.modulith.events.core.DefaultEventPublicationRegistry;
import org.springframework.modulith.events.core.DefaultFailedEventPublications;
import org.springframework.modulith.events.core.EventListenerMethodMetadata;
import org.springframework.modulith.events.core.EventPublicationRegistry;
import org.springframework.modulith.events.core.EventPublicationRepository;
import org.springframework.modulith.events.support.CompletionRegisteringAdvisor;
Expand Down Expand Up @@ -81,7 +82,9 @@ static DefaultFailedEventPublications failedEventPublications(

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static CompletionRegisteringAdvisor completionRegisteringAdvisor(ObjectFactory<EventPublicationRegistry> registry) {
return new CompletionRegisteringAdvisor(registry::getObject);
static CompletionRegisteringAdvisor completionRegisteringAdvisor(ObjectFactory<EventPublicationRegistry> registry,
ObjectFactory<Environment> environment) {
return new CompletionRegisteringAdvisor(registry::getObject,
EventListenerMethodMetadata.of(environment::getObject));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,18 @@ public class StalenessProperties implements Staleness {
@Nullable Duration resubmitted,
@Nullable Duration checkInterval) {

this.published = published == null ? Duration.ZERO : published;
this.processing = processing == null ? Duration.ZERO : processing;
this.resubmitted = resubmitted == null ? Duration.ZERO : resubmitted;
this.checkInterval = checkInterval == null ? Duration.ofMinutes(1) : checkInterval;
this.published = published == null ? UNCONFIGURED_DURATION : requirePositive(published, "published");
this.processing = processing == null ? UNCONFIGURED_DURATION : requirePositive(processing, "processing");
this.resubmitted = resubmitted == null ? UNCONFIGURED_DURATION : requirePositive(resubmitted, "resubmitted");
this.checkInterval = checkInterval == null ? Duration.ofMinutes(1)
: requirePositive(checkInterval, "checkInterval");
}

boolean monitorStaleness() {

return !published.equals(Duration.ZERO)
|| !processing.equals(Duration.ZERO)
|| !resubmitted.equals(Duration.ZERO);
return !published.equals(UNCONFIGURED_DURATION)
|| !processing.equals(UNCONFIGURED_DURATION)
|| !resubmitted.equals(UNCONFIGURED_DURATION);
}

Duration getCheckInterval() {
Expand All @@ -95,7 +96,15 @@ public Duration getStaleness(Status status) {
case PUBLISHED -> published;
case PROCESSING -> processing;
case RESUBMITTED -> resubmitted;
default -> Duration.ZERO;
default -> throw new IllegalArgumentException("Unsupported status: " + status);
};
}

private static Duration requirePositive(Duration duration, String name) {

Assert.isTrue(!duration.isZero() && !duration.isNegative(),
() -> "Staleness property '%s' must be greater than zero but was %s!".formatted(name, duration));

return duration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,10 @@ private void propagateStateTransition(Object event, PublicationTargetIdentifier

private void markFailed(Status status, Staleness staleness) {

if (!staleness.isMonitored(status)) {
return;
}

var duration = staleness.getStaleness(status);
var reference = clock.instant().minus(duration);
var result = events.findByStatus(status).stream()
Expand Down
Loading