diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java index a0c9cf8015..755888dbd4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java @@ -23,30 +23,44 @@ import org.springframework.util.Assert; /** * An identifier for an actuator endpoint. Endpoint IDs may contain only letters, numbers - * and {@code '.'}. They must begin with a lower-case letter. Case is ignored when - * comparing endpoint IDs. + * {@code '.'} and {@code '-'}. They must begin with a lower-case letter. Case and syntax + * characters are ignored when comparing endpoint IDs. * * @author Phillip Webb * @since 2.0.6 */ public final class EndpointId { - private static final Pattern VALID_CHARS = Pattern.compile("[a-zA-Z0-9\\.]+"); + private static final Pattern VALID_CHARS = Pattern.compile("[a-zA-Z0-9\\.\\-]+"); private final String value; private final String lowerCaseValue; + private final String lowerCaseAlphaNumeric; + private EndpointId(String value) { Assert.hasText(value, "Value must not be empty"); Assert.isTrue(VALID_CHARS.matcher(value).matches(), - "Value must be alpha-numeric or '.'"); + "Value must only contain valid chars"); Assert.isTrue(!Character.isDigit(value.charAt(0)), "Value must not start with a number"); Assert.isTrue(!Character.isUpperCase(value.charAt(0)), "Value must not start with an uppercase letter"); this.value = value; this.lowerCaseValue = value.toLowerCase(Locale.ENGLISH); + this.lowerCaseAlphaNumeric = getAlphaNumerics(this.lowerCaseValue); + } + + private String getAlphaNumerics(String value) { + StringBuilder result = new StringBuilder(value.length()); + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9') { + result.append(ch); + } + } + return result.toString(); } @Override @@ -57,12 +71,13 @@ public final class EndpointId { if (obj == null || getClass() != obj.getClass()) { return false; } - return toLowerCaseString().equals(((EndpointId) obj).toLowerCaseString()); + return this.lowerCaseAlphaNumeric + .equals(((EndpointId) obj).lowerCaseAlphaNumeric); } @Override public int hashCode() { - return toLowerCaseString().hashCode(); + return this.lowerCaseAlphaNumeric.hashCode(); } /** diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java index eceb468071..5f55b63499 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java @@ -47,16 +47,16 @@ public class EndpointIdTests { } @Test - public void ofWhenContainsDashThrowsException() { + public void ofWhenContainsInvalidCharThrowsException() { this.thrown.expect(IllegalArgumentException.class); - this.thrown.expectMessage("Value must be alpha-numeric"); - EndpointId.of("foo-bar"); + this.thrown.expectMessage("Value must only contain valid chars"); + EndpointId.of("foo/bar"); } @Test public void ofWhenHasBadCharThrowsException() { this.thrown.expect(IllegalArgumentException.class); - this.thrown.expectMessage("Value must be alpha-numeric"); + this.thrown.expectMessage("Value must only contain valid chars"); EndpointId.of("foo!bar"); } @@ -82,13 +82,25 @@ public class EndpointIdTests { assertThat(endpointId.toString()).isEqualTo("foo.bar"); } + @Test + public void ofWhenContainsDashIsValid() { + // Ideally we wouldn't support this but there are existing endpoints using the + // pattern. See gh-14773 + EndpointId endpointId = EndpointId.of("foo-bar"); + assertThat(endpointId.toString()).isEqualTo("foo-bar"); + } + @Test public void equalsAndHashCode() { - EndpointId one = EndpointId.of("foobar"); - EndpointId two = EndpointId.of("fooBar"); - EndpointId three = EndpointId.of("barfoo"); + EndpointId one = EndpointId.of("foobar1"); + EndpointId two = EndpointId.of("fooBar1"); + EndpointId three = EndpointId.of("foo-bar1"); + EndpointId four = EndpointId.of("foo.bar1"); + EndpointId five = EndpointId.of("barfoo1"); + EndpointId six = EndpointId.of("foobar2"); assertThat(one.hashCode()).isEqualTo(two.hashCode()); - assertThat(one).isEqualTo(one).isEqualTo(two).isNotEqualTo(three); + assertThat(one).isEqualTo(one).isEqualTo(two).isEqualTo(two).isEqualTo(three) + .isEqualTo(four).isNotEqualTo(five).isNotEqualTo(six); } @Test