Skip to content

Commit

Permalink
Ensure AvailabilityChangeEvent carries generics
Browse files Browse the repository at this point in the history
Update `AvailabilityChangeEvent` to be a `PayloadEvent` and ensure
that the `getResolvableType` method returns a generic compatible
result.

Prior to this commit, a ClassCastExeption would be thrown if the
following event listener was declared:

  @eventlistener
  void onEvent(AvailabilityChangeEvent<ReadinessState> event) {
    ...
  }

Closes gh-21898
  • Loading branch information
philwebb committed Jun 12, 2020
1 parent a65ca7a commit 1604545
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 8 deletions.
Expand Up @@ -19,6 +19,8 @@
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;

/**
Expand All @@ -32,27 +34,36 @@
* @author Phillip Webb
* @since 2.3.0
*/
public class AvailabilityChangeEvent<S extends AvailabilityState> extends ApplicationEvent {

private final S state;
public class AvailabilityChangeEvent<S extends AvailabilityState> extends PayloadApplicationEvent<S> {

/**
* Create a new {@link AvailabilityChangeEvent} instance.
* @param source the source of the event
* @param state the availability state (never {@code null})
*/
public AvailabilityChangeEvent(Object source, S state) {
super(source);
Assert.notNull(state, "State must not be null");
this.state = state;
super(source, state);
}

/**
* Return the changed availability state.
* @return the availability state
*/
public S getState() {
return this.state;
return getPayload();
}

@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), getStateType());
}

private Class<?> getStateType() {
S state = getState();
if (state instanceof Enum) {
return ((Enum<?>) state).getDeclaringClass();
}
return state.getClass();
}

/**
Expand Down
Expand Up @@ -21,6 +21,10 @@

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.ResolvableType;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
Expand All @@ -39,7 +43,7 @@ class AvailabilityChangeEventTests {
@Test
void createWhenStateIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new AvailabilityChangeEvent<>(this.source, null))
.withMessage("State must not be null");
.withMessage("Payload must not be null");
}

@Test
Expand All @@ -49,6 +53,24 @@ void getStateReturnsState() {
assertThat(event.getState()).isEqualTo(state);
}

@Test
void getResolvableType() {
LivenessState state = LivenessState.CORRECT;
AvailabilityChangeEvent<LivenessState> event = new AvailabilityChangeEvent<>(this.source, state);
ResolvableType type = event.getResolvableType();
assertThat(type.resolve()).isEqualTo(AvailabilityChangeEvent.class);
assertThat(type.resolveGeneric()).isEqualTo(LivenessState.class);
}

@Test
void getResolvableTypeWhenSubclassedEnum() {
SubClassedEnum state = SubClassedEnum.TWO;
AvailabilityChangeEvent<SubClassedEnum> event = new AvailabilityChangeEvent<>(this.source, state);
ResolvableType type = event.getResolvableType();
assertThat(type.resolve()).isEqualTo(AvailabilityChangeEvent.class);
assertThat(type.resolveGeneric()).isEqualTo(SubClassedEnum.class);
}

@Test
void publishPublishesEvent() {
ApplicationContext context = mock(ApplicationContext.class);
Expand All @@ -61,4 +83,45 @@ void publishPublishesEvent() {
assertThat(event.getState()).isEqualTo(state);
}

@Test
void publishEvenToContextConsidersGenericType() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}

enum SubClassedEnum implements AvailabilityState {

ONE {

@Override
String getDescription() {
return "I have been overridden";
}

},

TWO {

@Override
String getDescription() {
return "I have aslo been overridden";
}

};

abstract String getDescription();

}

@Configuration
static class Config {

@EventListener
void onLivenessAvailabilityChange(AvailabilityChangeEvent<LivenessState> event) {
assertThat(event.getState()).isInstanceOf(LivenessState.class).isEqualTo(LivenessState.CORRECT);
}

}

}

0 comments on commit 1604545

Please sign in to comment.