From ee16332745caddc1e48972f127e03542953120bb Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 12 Jul 2017 09:08:43 +0100 Subject: [PATCH] Update Actuator to use the new endpoint infrastructure This commit migrates the Actuator onto the new endpoint infrastruture. In addition to the existing support for accessing the endpoints via JMX and HTTP using Spring MVC, support for access via HTTP using Jersey and WebFlux has been added. This includes using a separate management port where we now spin up an additional, appropriately configured servlet or reactive web server to expose the management context on a different HTTP port to the main application. Closes gh-2921 Closes gh-5389 Closes gh-9796 --- spring-boot-actuator/pom.xml | 44 +- .../ConditionalOnManagementPort.java | 42 + ...eChildManagementContextConfiguration.java} | 20 +- .../EnableManagementContext.java} | 26 +- .../ManagementContextAutoConfiguration.java | 256 +++++ .../ManagementContextConfiguration.java | 8 + ...ntContextConfigurationImportSelector.java} | 29 +- .../ManagementContextFactory.java | 33 + .../ManagementContextType.java} | 35 +- .../autoconfigure/ManagementPortType.java | 54 ++ .../OnManagementPortCondition.java | 75 ++ .../ReactiveWebManagementContextFactory.java | 85 ++ .../ServletWebManagementContextFactory.java | 89 ++ ...CloudFoundryActuatorAutoConfiguration.java | 67 +- .../ConditionalOnEnabledEndpoint.java | 77 ++ .../endpoint/EndpointAutoConfiguration.java | 108 +-- .../EndpointMBeanExportAutoConfiguration.java | 122 --- .../EndpointMBeanExportProperties.java | 88 -- .../EndpointWebMvcAutoConfiguration.java | 363 ------- ...tWebMvcManagementContextConfiguration.java | 241 ----- .../endpoint/ManagementContextResolver.java | 18 - .../endpoint/OnEnabledEndpointCondition.java | 131 +++ .../infrastructure}/ActuatorMediaTypes.java | 4 +- .../CachingConfigurationFactory.java | 49 + .../DefaultEndpointObjectNameFactory.java | 46 + ...dpointInfrastructureAutoConfiguration.java | 141 +++ .../infrastructure/EndpointProvider.java | 68 ++ .../infrastructure/JmxEndpointExporter.java | 134 +++ .../ManagementWebServerFactoryCustomizer.java | 89 ++ ...ntChildManagementContextConfiguration.java | 63 ++ .../ServletEndpointAutoConfiguration.java | 85 ++ ...tChildManagementContextConfiguration.java} | 208 ++-- .../WebEndpointHandlerMappingCustomizer.java | 37 + ...ructureManagementContextConfiguration.java | 146 +++ .../endpoint/infrastructure/package-info.java | 21 + .../jmx/JmxEndpointAutoConfiguration.java | 50 + .../jmx}/package-info.java | 7 +- .../endpoint/support/EndpointEnablement.java | 56 ++ .../support/EndpointEnablementProvider.java | 169 ++++ .../endpoint/support}/package-info.java | 4 +- ...HealthWebEndpointExtensionProperties.java} | 16 +- ...ndpointManagementContextConfiguration.java | 115 +++ .../endpoint/web/package-info.java | 21 + ...anagementWebSecurityAutoConfiguration.java | 47 +- .../CloudFoundryDiscoveryMvcEndpoint.java | 111 +-- .../CloudFoundryEndpointHandlerMapping.java | 78 -- .../CloudFoundryHealthMvcEndpoint.java | 30 +- .../CloudFoundrySecurityInterceptor.java | 88 +- .../ConditionalOnEnabledEndpoint.java | 55 -- .../condition/OnEnabledEndpointCondition.java | 77 -- .../actuate/endpoint/AbstractEndpoint.java | 98 -- .../actuate/endpoint/AuditEventsEndpoint.java | 50 + .../AutoConfigurationReportEndpoint.java | 23 +- .../boot/actuate/endpoint/BeansEndpoint.java | 16 +- ...ConfigurationPropertiesReportEndpoint.java | 16 +- .../boot/actuate/endpoint/Endpoint.java | 51 - .../actuate/endpoint/EnvironmentEndpoint.java | 75 +- .../boot/actuate/endpoint/FlywayEndpoint.java | 13 +- .../boot/actuate/endpoint/HealthEndpoint.java | 37 +- .../boot/actuate/endpoint/InfoEndpoint.java | 12 +- .../actuate/endpoint/LiquibaseEndpoint.java | 18 +- .../actuate/endpoint/LoggersEndpoint.java | 40 +- .../actuate/endpoint/MetricsEndpoint.java | 35 +- .../endpoint/MetricsEndpointMetricReader.java | 6 +- .../endpoint/{mvc => }/NamePatternFilter.java | 5 +- .../endpoint/RequestMappingEndpoint.java | 16 +- .../actuate/endpoint/ShutdownEndpoint.java | 19 +- ...pEndpoint.java => ThreadDumpEndpoint.java} | 18 +- .../boot/actuate/endpoint/TraceEndpoint.java | 12 +- .../endpoint/jmx/AbstractJmxEndpoint.java | 88 -- .../endpoint/jmx/AuditEventsJmxEndpoint.java | 86 -- .../jmx/AuditEventsJmxEndpointExtension.java | 55 ++ .../actuate/endpoint/jmx/DataConverter.java | 62 -- .../endpoint/jmx/DataEndpointMBean.java | 49 - .../actuate/endpoint/jmx/EndpointMBean.java | 91 -- .../endpoint/jmx/EndpointMBeanExporter.java | 374 -------- .../actuate/endpoint/jmx/JmxEndpoint.java | 56 -- .../endpoint/jmx/LoggersEndpointMBean.java | 77 -- .../endpoint/jmx/ShutdownEndpointMBean.java | 49 - .../mvc/AbstractEndpointHandlerMapping.java | 309 ------ .../mvc/AbstractEndpointMvcAdapter.java | 99 -- .../endpoint/mvc/AbstractMvcEndpoint.java | 88 -- .../mvc/AbstractNamedMvcEndpoint.java | 45 - .../endpoint/mvc/ActuatorPostMapping.java | 56 -- .../endpoint/mvc/AuditEventsMvcEndpoint.java | 64 -- .../endpoint/mvc/EndpointHandlerMapping.java | 137 --- .../endpoint/mvc/EnvironmentMvcEndpoint.java | 131 --- .../endpoint/mvc/HealthMvcEndpoint.java | 240 ----- .../endpoint/mvc/LogFileMvcEndpoint.java | 138 --- .../endpoint/mvc/LoggersMvcEndpoint.java | 84 -- .../endpoint/mvc/ManagementErrorEndpoint.java | 6 +- .../endpoint/mvc/MetricsMvcEndpoint.java | 106 --- .../actuate/endpoint/mvc/MvcEndpoint.java | 60 -- .../mvc/MvcEndpointSecurityInterceptor.java | 143 --- .../actuate/endpoint/mvc/MvcEndpoints.java | 125 --- .../endpoint/mvc/NamedMvcEndpoint.java | 37 - .../endpoint/mvc/ShutdownMvcEndpoint.java | 54 -- .../boot/actuate/endpoint/package-info.java | 3 +- .../web/AuditEventsWebEndpointExtension.java | 51 + .../web/HealthWebEndpointExtension.java | 129 +++ .../HeapDumpWebEndpoint.java} | 195 ++-- .../endpoint/web/LogFileWebEndpoint.java | 88 ++ .../main/resources/META-INF/spring.factories | 17 +- ...pointServletWebAutoConfigurationTests.java | 32 + ...ntextConfigurationImportSelectorTests.java | 123 +++ .../SpringApplicationHierarchyTests.java | 8 +- .../ConditionalOnEnabledEndpointTests.java | 364 +++++++ .../EndpointAutoConfigurationTests.java | 392 -------- ...ointMBeanExportAutoConfigurationTests.java | 215 ----- .../endpoint/EndpointMvcIntegrationTests.java | 174 ---- .../EndpointWebMvcAutoConfigurationTests.java | 885 ------------------ ...vcManagementContextConfigurationTests.java | 92 -- ...textConfigurationsImportSelectorTests.java | 74 -- .../MvcEndpointPathConfigurationTests.java | 173 ---- .../CachingConfigurationFactoryTests.java | 54 ++ ...tInfrastructureAutoConfigurationTests.java | 134 +++ ...tInfrastructureAutoConfigurationTests.java | 153 +++ .../JmxEndpointAutoConfigurationTests.java | 77 ++ .../EndpointEnablementProviderTests.java | 337 +++++++ ...ntManagementContextConfigurationTests.java | 168 ++++ ...althMvcEndpointAutoConfigurationTests.java | 172 ---- ...tContextConfigurationIntegrationTests.java | 9 +- ...iaManagementContextConfigurationTests.java | 4 +- ...mentWebSecurityAutoConfigurationTests.java | 8 +- ...FoundryActuatorAutoConfigurationTests.java | 317 +++---- ...CloudFoundryDiscoveryMvcEndpointTests.java | 174 ++-- ...oudFoundryEndpointHandlerMappingTests.java | 188 ++-- .../CloudFoundryHealthMvcEndpointTests.java | 39 +- .../CloudFoundrySecurityInterceptorTests.java | 347 ++++--- .../endpoint/AbstractEndpointTests.java | 156 --- .../endpoint/AuditEventsEndpointTests.java | 74 ++ .../AutoConfigurationReportEndpointTests.java | 32 +- .../BeansEndpointContextHierarchyTests.java | 137 --- .../actuate/endpoint/BeansEndpointTests.java | 103 +- ...sReportEndpointMethodAnnotationsTests.java | 78 +- ...onPropertiesReportEndpointParentTests.java | 86 +- ...ionPropertiesReportEndpointProxyTests.java | 34 +- ...rtiesReportEndpointSerializationTests.java | 286 +++--- ...gurationPropertiesReportEndpointTests.java | 284 +++--- .../actuate/endpoint/DumpEndpointTests.java | 58 -- .../endpoint/EnvironmentEndpointTests.java | 190 +--- .../actuate/endpoint/FlywayEndpointTests.java | 15 +- .../actuate/endpoint/HealthEndpointTests.java | 57 +- .../actuate/endpoint/InfoEndpointTests.java | 45 +- .../endpoint/LiquibaseEndpointTests.java | 62 +- .../endpoint/LoggersEndpointTests.java | 57 +- .../endpoint/MetricsEndpointTests.java | 86 +- .../{mvc => }/NamePatternFilterTests.java | 2 +- .../endpoint/RequestMappingEndpointTests.java | 56 +- .../endpoint/ShutdownEndpointTests.java | 95 +- .../endpoint/ShutdownParentEndpointTests.java | 103 -- .../endpoint/ThreadDumpEndpointTests.java | 36 + .../actuate/endpoint/TraceEndpointTests.java | 31 +- .../AuditEventsJmxEndpointExtensionTests.java | 67 ++ .../jmx/EndpointMBeanExporterTests.java | 366 -------- .../AbstractEndpointHandlerMappingTests.java | 153 --- .../mvc/AuditEventsMvcEndpointTests.java | 154 --- .../mvc/EndpointHandlerMappingTests.java | 185 ---- .../mvc/EnvironmentMvcEndpointTests.java | 211 ----- .../endpoint/mvc/HealthMvcEndpointTests.java | 274 ------ ...HeapdumpMvcEndpointSecureOptionsTests.java | 128 --- .../mvc/HeapdumpMvcEndpointTests.java | 176 ---- .../endpoint/mvc/InfoMvcEndpointTests.java | 133 --- ...vcEndpointWithNoInfoContributorsTests.java | 86 -- .../endpoint/mvc/LogFileMvcEndpointTests.java | 124 --- .../endpoint/mvc/LoggersMvcEndpointTests.java | 228 ----- .../endpoint/mvc/MetricsMvcEndpointTests.java | 198 ---- .../mvc/MvcEndpointCorsIntegrationTests.java | 42 +- .../mvc/MvcEndpointIntegrationTests.java | 90 +- .../MvcEndpointSecurityInterceptorTests.java | 171 ---- .../endpoint/mvc/MvcEndpointsTests.java | 122 --- ...rityHealthMvcEndpointIntegrationTests.java | 32 +- ...tyMvcEndpointSecurityInterceptorTests.java | 106 --- .../mvc/ShutdownMvcEndpointTests.java | 133 --- ...uditEventsEndpointWebIntegrationTests.java | 110 +++ .../AuditEventsWebEndpointExtensionTests.java | 60 ++ ...nvironmentEndpointWebIntegrationTests.java | 136 +++ .../HealthEndpointWebIntegrationTests.java | 108 +++ .../web/HeapDumpWebEndpointTests.java | 57 ++ ...eapDumpWebEndpointWebIntegrationTests.java | 123 +++ .../web/InfoEndpointWebIntegrationTests.java | 86 ++ .../endpoint/web/LogFileWebEndpointTests.java | 88 ++ ...LogFileWebEndpointWebIntegrationTests.java | 83 ++ .../LoggersEndpointWebIntegrationTests.java | 180 ++++ .../MetricsEndpointMvcIntegrationTests.java | 111 +++ .../endpoint/web/WebEndpointsRunner.java | 408 ++++++++ .../appendix-application-properties.adoc | 34 +- spring-boot-docs/src/main/asciidoc/howto.adoc | 4 +- .../asciidoc/production-ready-features.adoc | 56 +- .../main/asciidoc/spring-boot-features.adoc | 4 +- .../sample/actuator/SampleController.java | 5 +- .../SampleActuatorApplicationTests.java | 3 +- ...nsecureSampleActuatorApplicationTests.java | 4 +- .../SampleMethodSecurityApplicationTests.java | 24 +- .../mvc/WebEndpointServletHandlerMapping.java | 8 + 195 files changed, 7762 insertions(+), 11963 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ConditionalOnManagementPort.java rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/{endpoint/mvc/EndpointHandlerMappingCustomizer.java => autoconfigure/EnableChildManagementContextConfiguration.java} (59%) rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/{endpoint/mvc/ActuatorGetMapping.java => autoconfigure/EnableManagementContext.java} (52%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextAutoConfiguration.java rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/{endpoint/ManagementContextConfigurationsImportSelector.java => ManagementContextConfigurationImportSelector.java} (80%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextFactory.java rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/{endpoint/mvc/EndpointMvcAdapter.java => autoconfigure/ManagementContextType.java} (51%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementPortType.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnManagementPortCondition.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ReactiveWebManagementContextFactory.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ServletWebManagementContextFactory.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfiguration.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportProperties.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfiguration.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfiguration.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/OnEnabledEndpointCondition.java rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/{endpoint/mvc => autoconfigure/endpoint/infrastructure}/ActuatorMediaTypes.java (94%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactory.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointProvider.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporter.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ManagementWebServerFactoryCustomizer.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ReactiveEndpointChildManagementContextConfiguration.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointAutoConfiguration.java rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/{EndpointWebMvcChildContextConfiguration.java => infrastructure/ServletEndpointChildManagementContextConfiguration.java} (61%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointHandlerMappingCustomizer.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointInfrastructureManagementContextConfiguration.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/package-info.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/{jolokia => endpoint/jmx}/package-info.java (69%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablement.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProvider.java rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/{condition => autoconfigure/endpoint/support}/package-info.java (81%) rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/{health/HealthMvcEndpointProperties.java => endpoint/web/HealthWebEndpointExtensionProperties.java} (70%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/package-info.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMapping.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/ConditionalOnEnabledEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractEndpoint.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Endpoint.java rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/{mvc => }/NamePatternFilter.java (97%) rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/{DumpEndpoint.java => ThreadDumpEndpoint.java} (73%) delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractJmxEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpoint.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtension.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMapping.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractMvcEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorPostMapping.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtension.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HealthWebEndpointExtension.java rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/{mvc/HeapdumpMvcEndpoint.java => web/HeapDumpWebEndpoint.java} (57%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpoint.java create mode 100755 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointServletWebAutoConfigurationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelectorTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpointTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfigurationTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfigurationTests.java delete mode 100755 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMvcIntegrationTests.java delete mode 100755 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfigurationTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfigurationTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelectorTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/MvcEndpointPathConfigurationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactoryTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointInfrastructureAutoConfigurationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebMvcEndpointInfrastructureAutoConfigurationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProviderTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointAutoConfigurationTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AbstractEndpointTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpointTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointContextHierarchyTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/DumpEndpointTests.java rename spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/{mvc => }/NamePatternFilterTests.java (98%) delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownParentEndpointTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpointTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtensionTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMappingTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpointTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointSecureOptionsTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithNoInfoContributorsTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpointTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointsTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityMvcEndpointSecurityInterceptorTests.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpointTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsEndpointWebIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtensionTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EnvironmentEndpointWebIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointWebIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/InfoEndpointWebIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointWebIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LoggersEndpointWebIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/MetricsEndpointMvcIntegrationTests.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebEndpointsRunner.java diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 6a05a9ffad..12ecfce2d2 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -153,6 +153,16 @@ flyway-core true + + org.glassfish.jersey.core + jersey-server + true + + + org.glassfish.jersey.containers + jersey-container-servlet-core + true + org.hibernate hibernate-validator @@ -183,6 +193,11 @@ spring-jdbc true + + org.springframework + spring-webflux + true + org.springframework spring-webmvc @@ -241,16 +256,6 @@ spring-integration-core true - - org.springframework.hateoas - spring-hateoas - true - - - org.springframework.plugin - spring-plugin-core - true - org.springframework.security spring-security-web @@ -261,11 +266,6 @@ spring-security-config true - - org.webjars - hal-browser - true - org.springframework.boot @@ -298,6 +298,10 @@ json-path test + + io.projectreactor.ipc + reactor-netty + io.undertow undertow-core @@ -324,6 +328,16 @@ hsqldb test + + org.glassfish.jersey.media + jersey-media-json-jackson + test + + + org.glassfish.jersey.ext + jersey-spring3 + test + org.skyscreamer jsonassert diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ConditionalOnManagementPort.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ConditionalOnManagementPort.java new file mode 100644 index 0000000000..c50013fce0 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ConditionalOnManagementPort.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure; + +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; + +/** + * {@link Conditional} that matches based on the configuration of the management port. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(OnManagementPortCondition.class) +public @interface ConditionalOnManagementPort { + + /** + * The {@link ManagementPortType} to match. + * @return the port type + */ + ManagementPortType value(); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingCustomizer.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableChildManagementContextConfiguration.java similarity index 59% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingCustomizer.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableChildManagementContextConfiguration.java index 68af109e69..b4f4a39c99 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingCustomizer.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableChildManagementContextConfiguration.java @@ -14,21 +14,17 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint.mvc; +package org.springframework.boot.actuate.autoconfigure; + +import org.springframework.context.annotation.Configuration; /** - * Callback for customizing the {@link EndpointHandlerMapping} at configuration time. + * Configurtaion class used to enable configuration of a child management context. * - * @author Dave Syer - * @since 1.2.0 + * @author Andy Wilkinson */ -@FunctionalInterface -public interface EndpointHandlerMappingCustomizer { - - /** - * Customize the specified {@link EndpointHandlerMapping}. - * @param mapping the {@link EndpointHandlerMapping} to customize - */ - void customize(EndpointHandlerMapping mapping); +@Configuration +@EnableManagementContext(ManagementContextType.CHILD) +class EnableChildManagementContextConfiguration { } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorGetMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableManagementContext.java similarity index 52% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorGetMapping.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableManagementContext.java index 30e9bbbf8b..807538075a 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorGetMapping.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableManagementContext.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint.mvc; +package org.springframework.boot.actuate.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -22,31 +22,19 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.core.annotation.AliasFor; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.context.annotation.Import; /** - * Specialized {@link RequestMapping} for {@link RequestMethod#GET GET} requests that - * produce {@code application/json} or - * {@code application/vnd.spring-boot.actuator.v1+json} responses. + * Enables the management context. * * @author Andy Wilkinson */ -@Target(ElementType.METHOD) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented -@RequestMapping(method = RequestMethod.GET, produces = { - ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE, - MediaType.APPLICATION_JSON_VALUE }) -@interface ActuatorGetMapping { +@Import(ManagementContextConfigurationImportSelector.class) +@interface EnableManagementContext { - /** - * Alias for {@link RequestMapping#value}. - * @return the value - */ - @AliasFor(annotation = RequestMapping.class) - String[] value() default {}; + ManagementContextType value(); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextAutoConfiguration.java new file mode 100644 index 0000000000..5ac3d7b986 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextAutoConfiguration.java @@ -0,0 +1,256 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure; + +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; +import org.springframework.boot.actuate.endpoint.mvc.ManagementServletContext; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.context.event.ApplicationFailedEvent; +import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.web.context.ConfigurableWebApplicationContext; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for the management context. If the + * {@code management.port} is the same as the {@code server.port} the management context + * will be the same as the main application context. If the {@code management.port} is + * different to the {@code server.port} the management context will be a separate context + * that has the main application context as its parent. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +@Configuration +public class ManagementContextAutoConfiguration { + + @Configuration + @ConditionalOnWebApplication(type = Type.SERVLET) + static class ServletManagementContextConfiguration { + + @Bean + public ManagementServletContext managementServletContext( + final ManagementServerProperties properties) { + return () -> properties.getContextPath(); + } + + } + + @Configuration + @ConditionalOnManagementPort(ManagementPortType.SAME) + static class SameManagementContextConfiguration + implements SmartInitializingSingleton { + + private final Environment environment; + + SameManagementContextConfiguration(Environment environment) { + this.environment = environment; + } + + @Override + public void afterSingletonsInstantiated() { + veriifySslConfiguration(); + verifyContextPathConfiguration(); + if (this.environment instanceof ConfigurableEnvironment) { + addLocalManagementPortPropertyAlias( + (ConfigurableEnvironment) this.environment); + } + } + + private void veriifySslConfiguration() { + if (this.environment.getProperty("management.ssl.enabled", Boolean.class, + false)) { + throw new IllegalStateException( + "Management-specific SSL cannot be configured as the management " + + "server is not listening on a separate port"); + } + } + + private void verifyContextPathConfiguration() { + String contextPath = this.environment.getProperty("management.context-path"); + if ("".equals(contextPath) || "/".equals(contextPath)) { + throw new IllegalStateException("A management context path of '" + + contextPath + "' requires the management server to be " + + "listening on a separate port"); + } + } + + /** + * Add an alias for 'local.management.port' that actually resolves using + * 'local.server.port'. + * @param environment the environment + */ + private void addLocalManagementPortPropertyAlias( + ConfigurableEnvironment environment) { + environment.getPropertySources() + .addLast(new PropertySource("Management Server") { + @Override + public Object getProperty(String name) { + if ("local.management.port".equals(name)) { + return environment.getProperty("local.server.port"); + } + return null; + } + }); + } + + @EnableManagementContext(ManagementContextType.SAME) + static class EnableSameManagementContextConfiguration { + + } + + } + + @Configuration + @ConditionalOnManagementPort(ManagementPortType.DIFFERENT) + static class SeparateManagementContextConfiguration + implements SmartInitializingSingleton { + + private final ApplicationContext applicationContext; + + private final ManagementContextFactory managementContextFactory; + + SeparateManagementContextConfiguration(ApplicationContext applicationContext, + ManagementContextFactory managementContextFactory) { + this.applicationContext = applicationContext; + this.managementContextFactory = managementContextFactory; + } + + @Override + public void afterSingletonsInstantiated() { + ConfigurableApplicationContext managementContext = this.managementContextFactory + .createManagementContext(this.applicationContext, + EnableChildManagementContextConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + setNamespaceIfPossible(managementContext); + managementContext.setId(this.applicationContext.getId() + ":management"); + setClassLoaderIfPossible(managementContext); + CloseManagementContextListener.addIfPossible(this.applicationContext, + managementContext); + managementContext.refresh(); + } + + private void setClassLoaderIfPossible(ConfigurableApplicationContext child) { + if (child instanceof DefaultResourceLoader) { + ((AbstractApplicationContext) child) + .setClassLoader(this.applicationContext.getClassLoader()); + } + } + + private void setNamespaceIfPossible(ConfigurableApplicationContext child) { + if (child instanceof ConfigurableReactiveWebApplicationContext) { + ((ConfigurableReactiveWebApplicationContext) child) + .setNamespace("management"); + } + else if (child instanceof ConfigurableWebApplicationContext) { + ((ConfigurableWebApplicationContext) child).setNamespace("management"); + } + } + + @ConditionalOnWebApplication(type = Type.SERVLET) + static class ServletChildContextConfiguration { + + @Bean + public ServletWebManagementContextFactory servletWebChildContextFactory() { + return new ServletWebManagementContextFactory(); + } + + } + + @ConditionalOnWebApplication(type = Type.REACTIVE) + static class ReactiveChildContextConfiguration { + + @Bean + public ReactiveWebManagementContextFactory reactiveWebChildContextFactory() { + return new ReactiveWebManagementContextFactory(); + } + + } + + } + + /** + * {@link ApplicationListener} to propagate the {@link ContextClosedEvent} and + * {@link ApplicationFailedEvent} from a parent to a child. + */ + private static class CloseManagementContextListener + implements ApplicationListener { + + private final ApplicationContext parentContext; + + private final ConfigurableApplicationContext childContext; + + CloseManagementContextListener(ApplicationContext parentContext, + ConfigurableApplicationContext childContext) { + this.parentContext = parentContext; + this.childContext = childContext; + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ContextClosedEvent) { + onContextClosedEvent((ContextClosedEvent) event); + } + if (event instanceof ApplicationFailedEvent) { + onApplicationFailedEvent((ApplicationFailedEvent) event); + } + }; + + private void onContextClosedEvent(ContextClosedEvent event) { + propagateCloseIfNecessary(event.getApplicationContext()); + } + + private void onApplicationFailedEvent(ApplicationFailedEvent event) { + propagateCloseIfNecessary(event.getApplicationContext()); + } + + private void propagateCloseIfNecessary(ApplicationContext applicationContext) { + if (applicationContext == this.parentContext) { + this.childContext.close(); + } + } + + public static void addIfPossible(ApplicationContext parentContext, + ConfigurableApplicationContext childContext) { + if (parentContext instanceof ConfigurableApplicationContext) { + add((ConfigurableApplicationContext) parentContext, childContext); + } + } + + private static void add(ConfigurableApplicationContext parentContext, + ConfigurableApplicationContext childContext) { + parentContext.addApplicationListener( + new CloseManagementContextListener(parentContext, childContext)); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfiguration.java index 47fe048150..ce3b3db365 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfiguration.java @@ -46,4 +46,12 @@ import org.springframework.core.annotation.Order; @Configuration public @interface ManagementContextConfiguration { + /** + * Specifies the type of management context that is required for this configuration to + * be applied. + * @return the required management context type + * @since 2.0.0 + */ + ManagementContextType value() default ManagementContextType.ANY; + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelector.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelector.java similarity index 80% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelector.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelector.java index b529614552..6ce1ae8524 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelector.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.endpoint; +package org.springframework.boot.actuate.autoconfigure; import java.io.IOException; import java.util.ArrayList; @@ -22,7 +22,6 @@ import java.util.List; import java.util.Map; import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration; import org.springframework.context.annotation.DeferredImportSelector; import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; @@ -44,19 +43,25 @@ import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; * @see ManagementContextConfiguration */ @Order(Ordered.LOWEST_PRECEDENCE) -class ManagementContextConfigurationsImportSelector +class ManagementContextConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware { private ClassLoader classLoader; @Override public String[] selectImports(AnnotationMetadata metadata) { + ManagementContextType contextType = (ManagementContextType) metadata + .getAnnotationAttributes(EnableManagementContext.class.getName()) + .get("value"); // Find all management context configuration classes, filtering duplicates List configurations = getConfigurations(); OrderComparator.sort(configurations); List names = new ArrayList<>(); for (ManagementConfiguration configuration : configurations) { - names.add(configuration.getClassName()); + if (configuration.getContextType() == ManagementContextType.ANY + || configuration.getContextType() == contextType) { + names.add(configuration.getClassName()); + } } return names.toArray(new String[names.size()]); } @@ -102,11 +107,23 @@ class ManagementContextConfigurationsImportSelector private final int order; + private final ManagementContextType contextType; + ManagementConfiguration(MetadataReader metadataReader) { AnnotationMetadata annotationMetadata = metadataReader .getAnnotationMetadata(); this.order = readOrder(annotationMetadata); this.className = metadataReader.getClassMetadata().getClassName(); + this.contextType = readContextType(annotationMetadata); + } + + private ManagementContextType readContextType( + AnnotationMetadata annotationMetadata) { + Map annotationAttributes = annotationMetadata + .getAnnotationAttributes( + ManagementContextConfiguration.class.getName()); + return annotationAttributes == null ? ManagementContextType.ANY + : (ManagementContextType) annotationAttributes.get("value"); } private int readOrder(AnnotationMetadata annotationMetadata) { @@ -126,6 +143,10 @@ class ManagementContextConfigurationsImportSelector return this.order; } + public ManagementContextType getContextType() { + return this.contextType; + } + } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextFactory.java new file mode 100644 index 0000000000..fe054240d6 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * A factory for creating a separate management context when the management web server is + * running on a different port to the main application. + * + * @author Andy Wilkinson + */ +interface ManagementContextFactory { + + ConfigurableApplicationContext createManagementContext(ApplicationContext parent, + Class... configClasses); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointMvcAdapter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextType.java similarity index 51% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointMvcAdapter.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextType.java index 1cd2e1ad30..68d0e023c0 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointMvcAdapter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextType.java @@ -14,32 +14,31 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint.mvc; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.web.bind.annotation.ResponseBody; +package org.springframework.boot.actuate.autoconfigure; /** - * Adapter class to expose {@link Endpoint}s as {@link MvcEndpoint}s. + * Enumeration of management context types. * - * @author Dave Syer * @author Andy Wilkinson + * @since 2.0.0 */ -public class EndpointMvcAdapter extends AbstractEndpointMvcAdapter> { +public enum ManagementContextType { /** - * Create a new {@link EndpointMvcAdapter}. - * @param delegate the underlying {@link Endpoint} to adapt. + * The management context is the same as the main application context. */ - public EndpointMvcAdapter(Endpoint delegate) { - super(delegate); - } + SAME, - @Override - @ActuatorGetMapping - @ResponseBody - public Object invoke() { - return super.invoke(); - } + /** + * The management context is a separate context that is a child of the main + * application context. + */ + CHILD, + + /** + * The management context can be either the same as the main application context or a + * child of the main application context. + */ + ANY } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementPortType.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementPortType.java new file mode 100644 index 0000000000..5fd8b57a63 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementPortType.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure; + +import org.springframework.core.env.Environment; + +public enum ManagementPortType { + + /** + * The management port has been disabled. + */ + DISABLED, + + /** + * The management port is the same as the server port. + */ + SAME, + + /** + * The management port and server port are different. + */ + DIFFERENT; + + static ManagementPortType get(Environment environment) { + Integer serverPort = getPortProperty(environment, "server."); + Integer managementPort = getPortProperty(environment, "management."); + if (managementPort != null && managementPort < 0) { + return DISABLED; + } + return ((managementPort == null) + || (serverPort == null && managementPort.equals(8080)) + || (managementPort != 0 && managementPort.equals(serverPort)) ? SAME + : DIFFERENT); + } + + private static Integer getPortProperty(Environment environment, String prefix) { + return environment.getProperty(prefix + "port", Integer.class); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnManagementPortCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnManagementPortCondition.java new file mode 100644 index 0000000000..c3627d977e --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnManagementPortCondition.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure; + +import java.util.Map; + +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.ClassUtils; +import org.springframework.web.context.WebApplicationContext; + +/** + * {@link SpringBootCondition} that matches when the management server is running on a + * different port. + * + * @author Andy Wilkinson + */ +class OnManagementPortCondition extends SpringBootCondition { + + private static final String CLASS_NAME_WEB_APPLICATION_CONTEXT = "org.springframework.web.context.WebApplicationContext"; + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + ConditionMessage.Builder message = ConditionMessage + .forCondition("Management Port"); + if (!isWebApplicationContext(context)) { + return ConditionOutcome + .noMatch(message.because("non web application context")); + } + Map annotationAttributes = metadata + .getAnnotationAttributes(ConditionalOnManagementPort.class.getName()); + ManagementPortType requiredType = (ManagementPortType) annotationAttributes + .get("value"); + ManagementPortType actualType = ManagementPortType.get(context.getEnvironment()); + if (actualType == requiredType) { + return ConditionOutcome.match(message.because( + "actual port type (" + actualType + ") matched required type")); + } + return ConditionOutcome.noMatch(message.because("actual port type (" + actualType + + ") did not match required type (" + requiredType + ")")); + } + + private boolean isWebApplicationContext(ConditionContext context) { + ResourceLoader resourceLoader = context.getResourceLoader(); + if (resourceLoader instanceof ConfigurableReactiveWebApplicationContext) { + return true; + } + if (!ClassUtils.isPresent(CLASS_NAME_WEB_APPLICATION_CONTEXT, + context.getClassLoader())) { + return false; + } + return WebApplicationContext.class.isInstance(resourceLoader); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ReactiveWebManagementContextFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ReactiveWebManagementContextFactory.java new file mode 100644 index 0000000000..08167a00d1 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ReactiveWebManagementContextFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure; + +import java.lang.reflect.Modifier; + +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration; +import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; +import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; +import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * A {@link ManagementContextFactory} for WebFlux-based web applications. + * + * @author Andy Wilkinson + */ +class ReactiveWebManagementContextFactory implements ManagementContextFactory { + + @Override + public ConfigurableApplicationContext createManagementContext( + ApplicationContext parent, Class... configClasses) { + ReactiveWebServerApplicationContext child = new ReactiveWebServerApplicationContext(); + child.setParent(parent); + child.register(configClasses); + child.register(ReactiveWebServerAutoConfiguration.class); + registerReactiveWebServerFactory(parent, child); + return child; + } + + private void registerReactiveWebServerFactory(ApplicationContext parent, + GenericReactiveWebApplicationContext childContext) { + try { + ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory(); + if (beanFactory instanceof BeanDefinitionRegistry) { + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + registry.registerBeanDefinition("ReactiveWebServerFactory", + new RootBeanDefinition( + determineReactiveWebServerFactoryClass(parent))); + } + } + catch (NoSuchBeanDefinitionException ex) { + // Ignore and assume auto-configuration + } + } + + private Class determineReactiveWebServerFactoryClass(ApplicationContext parent) + throws NoSuchBeanDefinitionException { + Class factoryClass = parent.getBean(ReactiveWebServerFactory.class).getClass(); + if (cannotBeInstantiated(factoryClass)) { + throw new FatalBeanException("ReactiveWebServerFactory implementation " + + factoryClass.getName() + " cannot be instantiated. " + + "To allow a separate management port to be used, a top-level class " + + "or static inner class should be used instead"); + } + return factoryClass; + } + + private boolean cannotBeInstantiated(Class clazz) { + return clazz.isLocalClass() + || (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) + || clazz.isAnonymousClass(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ServletWebManagementContextFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ServletWebManagementContextFactory.java new file mode 100644 index 0000000000..e663d9d9e3 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ServletWebManagementContextFactory.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * A {@link ManagementContextFactory} for servlet-based web applications. + * + * @author Andy Wilkinson + */ +class ServletWebManagementContextFactory implements ManagementContextFactory { + + @Override + public ConfigurableApplicationContext createManagementContext( + ApplicationContext parent, Class... configClasses) { + AnnotationConfigServletWebServerApplicationContext child = new AnnotationConfigServletWebServerApplicationContext(); + child.setParent(parent); + List> combinedClasses = new ArrayList>( + Arrays.asList(configClasses)); + combinedClasses.add(ServletWebServerFactoryAutoConfiguration.class); + child.register(combinedClasses.toArray(new Class[combinedClasses.size()])); + registerServletWebServerFactory(parent, child); + return child; + } + + private void registerServletWebServerFactory(ApplicationContext parent, + AnnotationConfigServletWebServerApplicationContext childContext) { + try { + ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory(); + if (beanFactory instanceof BeanDefinitionRegistry) { + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + registry.registerBeanDefinition("ServletWebServerFactory", + new RootBeanDefinition( + determineServletWebServerFactoryClass(parent))); + } + } + catch (NoSuchBeanDefinitionException ex) { + // Ignore and assume auto-configuration + } + } + + private Class determineServletWebServerFactoryClass(ApplicationContext parent) + throws NoSuchBeanDefinitionException { + Class factoryClass = parent.getBean(ServletWebServerFactory.class).getClass(); + if (cannotBeInstantiated(factoryClass)) { + throw new FatalBeanException("ServletWebServerFactory implementation " + + factoryClass.getName() + " cannot be instantiated. " + + "To allow a separate management port to be used, a top-level class " + + "or static inner class should be used instead"); + } + return factoryClass; + } + + private boolean cannotBeInstantiated(Class clazz) { + return clazz.isLocalClass() + || (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) + || clazz.isAnonymousClass(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java index 2cc70cdce8..e3acc9959e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java @@ -16,34 +16,18 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.cloudfoundry.CloudFoundryEndpointHandlerMapping; -import org.springframework.boot.actuate.cloudfoundry.CloudFoundrySecurityInterceptor; -import org.springframework.boot.actuate.cloudfoundry.CloudFoundrySecurityService; -import org.springframework.boot.actuate.cloudfoundry.TokenValidator; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; -import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer; import org.springframework.boot.cloud.CloudPlatform; -import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.servlet.HandlerInterceptor; /** * {@link EnableAutoConfiguration Auto-configuration} to expose actuator endpoints for @@ -54,57 +38,10 @@ import org.springframework.web.servlet.HandlerInterceptor; */ @Configuration @ConditionalOnProperty(prefix = "management.cloudfoundry", name = "enabled", matchIfMissing = true) -@ConditionalOnBean(MvcEndpoints.class) -@AutoConfigureAfter(EndpointWebMvcAutoConfiguration.class) +@AutoConfigureAfter(ServletEndpointAutoConfiguration.class) @ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) public class CloudFoundryActuatorAutoConfiguration { - @Bean - public CloudFoundryEndpointHandlerMapping cloudFoundryEndpointHandlerMapping( - MvcEndpoints mvcEndpoints, RestTemplateBuilder restTemplateBuilder, - Environment environment) { - Set endpoints = new LinkedHashSet<>( - mvcEndpoints.getEndpoints(NamedMvcEndpoint.class)); - HandlerInterceptor securityInterceptor = getSecurityInterceptor( - restTemplateBuilder, environment); - CorsConfiguration corsConfiguration = getCorsConfiguration(); - CloudFoundryEndpointHandlerMapping mapping = new CloudFoundryEndpointHandlerMapping( - endpoints, corsConfiguration, securityInterceptor); - mapping.setPrefix("/cloudfoundryapplication"); - return mapping; - } - - private HandlerInterceptor getSecurityInterceptor( - RestTemplateBuilder restTemplateBuilder, Environment environment) { - CloudFoundrySecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService( - restTemplateBuilder, environment); - TokenValidator tokenValidator = new TokenValidator(cloudfoundrySecurityService); - HandlerInterceptor securityInterceptor = new CloudFoundrySecurityInterceptor( - tokenValidator, cloudfoundrySecurityService, - environment.getProperty("vcap.application.application_id")); - return securityInterceptor; - } - - private CloudFoundrySecurityService getCloudFoundrySecurityService( - RestTemplateBuilder restTemplateBuilder, Environment environment) { - String cloudControllerUrl = environment.getProperty("vcap.application.cf_api"); - boolean skipSslValidation = environment.getProperty( - "management.cloudfoundry.skip-ssl-validation", Boolean.class, false); - return cloudControllerUrl == null ? null - : new CloudFoundrySecurityService(restTemplateBuilder, cloudControllerUrl, - skipSslValidation); - } - - private CorsConfiguration getCorsConfiguration() { - CorsConfiguration corsConfiguration = new CorsConfiguration(); - corsConfiguration.addAllowedOrigin(CorsConfiguration.ALL); - corsConfiguration.setAllowedMethods( - Arrays.asList(HttpMethod.GET.name(), HttpMethod.POST.name())); - corsConfiguration.setAllowedHeaders( - Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type")); - return corsConfiguration; - } - /** * Nested configuration for ignored requests if Spring Security is present. */ diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpoint.java new file mode 100644 index 0000000000..c2b22080bd --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpoint.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint; + + +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.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.context.annotation.Conditional; + +/** + * {@link Conditional} that checks whether an endpoint is enabled or not. Matches + * according to the {@link Endpoint#enabledByDefault() enabledByDefault flag} and the + * specific {@link Endpoint#types() tech} that the endpoint may be restricted to. + *

+ * If no specific {@code endpoints..*} or {@code endpoints.all.*} properties are + * defined, the condition matches the {@code enabledByDefault} value regardless of the + * specific {@link EndpointType}, if any. If any property are set, they are evaluated + * with a sensible order of precedence. + *

+ * For instance if {@code endpoints.all.enabled} is {@code false} but + * {@code endpoints..enabled} is {@code true}, the condition will match. + *

+ * This condition must be placed on a {@code @Bean} method producing an endpoint as its + * id and other attributes are inferred from the {@link Endpoint} annotation set on the + * return type of the factory method. Consider the following valid example: + * + *

+ * @Configuration
+ * public class MyAutoConfiguration {
+ *
+ *     @ConditionalOnEnabledEndpoint
+ *     @Bean
+ *     public MyEndpoint myEndpoint() {
+ *         ...
+ *     }
+ *
+ *     @Endpoint(id = "my", enabledByDefault = false)
+ *     static class MyEndpoint { ... }
+ *
+ * }
+ *

+ * + * In the sample above the condition will be evaluated with the attributes specified on + * {@code MyEndpoint}. In particular, in the absence of any property in the environment, + * the condition will not match as this endpoint is disabled by default. + * + * @author Stephane Nicoll + * @since 2.0.0 + * @see Endpoint + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +@Conditional(OnEnabledEndpointCondition.class) +public @interface ConditionalOnEnabledEndpoint { + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java index ec4faf729e..940a60501b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.endpoint; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -26,11 +24,11 @@ import liquibase.integration.spring.SpringLiquibase; import org.flywaydb.core.Flyway; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint; import org.springframework.boot.actuate.endpoint.BeansEndpoint; import org.springframework.boot.actuate.endpoint.ConfigurationPropertiesReportEndpoint; -import org.springframework.boot.actuate.endpoint.DumpEndpoint; -import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.EndpointProperties; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.boot.actuate.endpoint.FlywayEndpoint; @@ -42,6 +40,7 @@ import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; +import org.springframework.boot.actuate.endpoint.ThreadDumpEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; @@ -59,10 +58,12 @@ import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; import org.springframework.boot.logging.LoggingSystem; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.env.Environment; import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; /** @@ -78,114 +79,112 @@ import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; * @author Meang Akira Tanaka * @author Ben Hale * @since 2.0.0 + * @author Andy Wilkinson */ @Configuration @AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class }) @EnableConfigurationProperties(EndpointProperties.class) public class EndpointAutoConfiguration { - private final HealthAggregator healthAggregator; - - private final Map healthIndicators; - - private final List infoContributors; - - private final Collection publicMetrics; - - private final TraceRepository traceRepository; - - public EndpointAutoConfiguration(ObjectProvider healthAggregator, - ObjectProvider> healthIndicators, - ObjectProvider> infoContributors, - ObjectProvider> publicMetrics, - ObjectProvider traceRepository) { - this.healthAggregator = healthAggregator.getIfAvailable(); - this.healthIndicators = healthIndicators.getIfAvailable(); - this.infoContributors = infoContributors.getIfAvailable(); - this.publicMetrics = publicMetrics.getIfAvailable(); - this.traceRepository = traceRepository.getIfAvailable(); + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + public EnvironmentEndpoint environmentEndpoint(Environment environment) { + return new EnvironmentEndpoint(environment); } @Bean @ConditionalOnMissingBean - public EnvironmentEndpoint environmentEndpoint() { - return new EnvironmentEndpoint(); - } - - @Bean - @ConditionalOnMissingBean - public HealthEndpoint healthEndpoint() { + @ConditionalOnEnabledEndpoint + public HealthEndpoint healthEndpoint( + ObjectProvider healthAggregator, + ObjectProvider> healthIndicators) { return new HealthEndpoint( - this.healthAggregator == null ? new OrderedHealthAggregator() - : this.healthAggregator, - this.healthIndicators == null - ? Collections.emptyMap() - : this.healthIndicators); + healthAggregator.getIfAvailable(() -> new OrderedHealthAggregator()), + healthIndicators.getIfAvailable(Collections::emptyMap)); } @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint public BeansEndpoint beansEndpoint() { return new BeansEndpoint(); } @Bean @ConditionalOnMissingBean - public InfoEndpoint infoEndpoint() throws Exception { - return new InfoEndpoint(this.infoContributors == null - ? Collections.emptyList() : this.infoContributors); + @ConditionalOnEnabledEndpoint + public InfoEndpoint infoEndpoint( + ObjectProvider> infoContributors) { + return new InfoEndpoint(infoContributors.getIfAvailable(Collections::emptyList)); } @Bean @ConditionalOnBean(LoggingSystem.class) @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem) { return new LoggersEndpoint(loggingSystem); } @Bean @ConditionalOnMissingBean - public MetricsEndpoint metricsEndpoint() { - List publicMetrics = new ArrayList<>(); - if (this.publicMetrics != null) { - publicMetrics.addAll(this.publicMetrics); - } - publicMetrics.sort(AnnotationAwareOrderComparator.INSTANCE); - return new MetricsEndpoint(publicMetrics); + @ConditionalOnEnabledEndpoint + public MetricsEndpoint metricsEndpoint( + ObjectProvider> publicMetrics) { + List sortedPublicMetrics = publicMetrics + .getIfAvailable(Collections::emptyList); + Collections.sort(sortedPublicMetrics, AnnotationAwareOrderComparator.INSTANCE); + return new MetricsEndpoint(sortedPublicMetrics); } @Bean @ConditionalOnMissingBean - public TraceEndpoint traceEndpoint() { - return new TraceEndpoint(this.traceRepository == null - ? new InMemoryTraceRepository() : this.traceRepository); + @ConditionalOnEnabledEndpoint + public TraceEndpoint traceEndpoint(ObjectProvider traceRepository) { + return new TraceEndpoint( + traceRepository.getIfAvailable(() -> new InMemoryTraceRepository())); } @Bean @ConditionalOnMissingBean - public DumpEndpoint dumpEndpoint() { - return new DumpEndpoint(); + @ConditionalOnEnabledEndpoint + public ThreadDumpEndpoint dumpEndpoint() { + return new ThreadDumpEndpoint(); } @Bean @ConditionalOnBean(ConditionEvaluationReport.class) @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) - public AutoConfigurationReportEndpoint autoConfigurationReportEndpoint() { - return new AutoConfigurationReportEndpoint(); + @ConditionalOnEnabledEndpoint + public AutoConfigurationReportEndpoint autoConfigurationReportEndpoint( + ConditionEvaluationReport conditionEvaluationReport) { + return new AutoConfigurationReportEndpoint(conditionEvaluationReport); } @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint public ShutdownEndpoint shutdownEndpoint() { return new ShutdownEndpoint(); } @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint() { return new ConfigurationPropertiesReportEndpoint(); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(AuditEventRepository.class) + @ConditionalOnEnabledEndpoint + public AuditEventsEndpoint auditEventsEndpoint( + AuditEventRepository auditEventRepository) { + return new AuditEventsEndpoint(auditEventRepository); + } + @Configuration @ConditionalOnBean(Flyway.class) @ConditionalOnClass(Flyway.class) @@ -193,6 +192,7 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint public FlywayEndpoint flywayEndpoint(Map flyways) { return new FlywayEndpoint(flyways); } @@ -206,6 +206,7 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint public LiquibaseEndpoint liquibaseEndpoint( Map liquibases) { return new LiquibaseEndpoint(liquibases); @@ -219,6 +220,7 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint public RequestMappingEndpoint requestMappingEndpoint() { RequestMappingEndpoint endpoint = new RequestMappingEndpoint(); return endpoint; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfiguration.java deleted file mode 100644 index 811004e5a8..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfiguration.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -import javax.management.MBeanServer; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.audit.AuditEventRepository; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointMBeanExportAutoConfiguration.JmxEnabledCondition; -import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.jmx.AuditEventsJmxEndpoint; -import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanExporter; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionMessage; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.util.StringUtils; - -/** - * {@link EnableAutoConfiguration Auto-configuration} to enable JMX export for - * {@link Endpoint}s. - * - * @author Christian Dupuis - * @author Andy Wilkinson - * @author Madhura Bhave - * @since 2.0.0 - */ -@Configuration -@Conditional(JmxEnabledCondition.class) -@AutoConfigureAfter({ EndpointAutoConfiguration.class, JmxAutoConfiguration.class }) -@EnableConfigurationProperties(EndpointMBeanExportProperties.class) -public class EndpointMBeanExportAutoConfiguration { - - private final EndpointMBeanExportProperties properties; - - private final ObjectMapper objectMapper; - - public EndpointMBeanExportAutoConfiguration(EndpointMBeanExportProperties properties, - ObjectProvider objectMapper) { - this.properties = properties; - this.objectMapper = objectMapper.getIfAvailable(); - } - - @Bean - public EndpointMBeanExporter endpointMBeanExporter(MBeanServer server) { - EndpointMBeanExporter mbeanExporter = new EndpointMBeanExporter( - this.objectMapper); - String domain = this.properties.getDomain(); - if (StringUtils.hasText(domain)) { - mbeanExporter.setDomain(domain); - } - mbeanExporter.setServer(server); - mbeanExporter.setEnsureUniqueRuntimeObjectNames(this.properties.isUniqueNames()); - mbeanExporter.setObjectNameStaticProperties(this.properties.getStaticNames()); - return mbeanExporter; - } - - @Bean - @ConditionalOnMissingBean(MBeanServer.class) - public MBeanServer mbeanServer() { - return new JmxAutoConfiguration().mbeanServer(); - } - - @Bean - @ConditionalOnBean(AuditEventRepository.class) - @ConditionalOnEnabledEndpoint("auditevents") - public AuditEventsJmxEndpoint auditEventsEndpoint( - AuditEventRepository auditEventRepository) { - return new AuditEventsJmxEndpoint(this.objectMapper, auditEventRepository); - } - - /** - * Condition to check that spring.jmx and endpoints.jmx are enabled. - */ - static class JmxEnabledCondition extends SpringBootCondition { - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, - AnnotatedTypeMetadata metadata) { - boolean jmxEnabled = context.getEnvironment() - .getProperty("spring.jmx.enabled", Boolean.class, true); - boolean jmxEndpointsEnabled = context.getEnvironment() - .getProperty("endpoints.jmx.enabled", Boolean.class, true); - if (jmxEnabled && jmxEndpointsEnabled) { - return ConditionOutcome.match( - ConditionMessage.forCondition("JMX Enabled").found("properties") - .items("spring.jmx.enabled", "endpoints.jmx.enabled")); - } - return ConditionOutcome.noMatch(ConditionMessage.forCondition("JMX Enabled") - .because("spring.jmx.enabled or endpoints.jmx.enabled is not set")); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportProperties.java deleted file mode 100644 index b415742fb6..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportProperties.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -import java.util.Properties; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.util.StringUtils; - -/** - * Configuration properties for JMX endpoints. - * - * @author Christian Dupuis - * @since 2.0.0 - */ -@ConfigurationProperties(prefix = "endpoints.jmx") -public class EndpointMBeanExportProperties { - - /** - * JMX domain name. Initialized with the value of 'spring.jmx.default-domain' if set. - */ - @Value("${spring.jmx.default-domain:}") - private String domain; - - /** - * Ensure that ObjectNames are modified in case of conflict. - */ - private boolean uniqueNames = false; - - /** - * Enable the JMX endpoints. - */ - private boolean enabled = true; - - /** - * Additional static properties to append to all ObjectNames of MBeans representing - * Endpoints. - */ - private Properties staticNames = new Properties(); - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getDomain() { - return this.domain; - } - - public void setDomain(String domain) { - this.domain = domain; - } - - public boolean isUniqueNames() { - return this.uniqueNames; - } - - public void setUniqueNames(boolean uniqueNames) { - this.uniqueNames = uniqueNames; - } - - public Properties getStaticNames() { - return this.staticNames; - } - - public void setStaticNames(String[] staticNames) { - this.staticNames = StringUtils.splitArrayElementsIntoProperties(staticNames, "="); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfiguration.java deleted file mode 100644 index e4194080e6..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfiguration.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -import java.lang.reflect.Modifier; - -import javax.servlet.Servlet; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.BeansException; -import org.springframework.beans.FatalBeanException; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.mvc.ManagementServletContext; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionMessage; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; -import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.context.event.ApplicationFailedEvent; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; -import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; -import org.springframework.boot.web.servlet.filter.ApplicationContextHeaderFilter; -import org.springframework.boot.web.servlet.server.ServletWebServerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ConfigurationCondition; -import org.springframework.context.annotation.Import; -import org.springframework.context.event.ContextClosedEvent; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; -import org.springframework.core.env.PropertySource; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.servlet.DispatcherServlet; - -/** - * {@link EnableAutoConfiguration Auto-configuration} to enable Spring MVC to handle - * {@link Endpoint} requests. If the {@link ManagementServerProperties} specifies a - * different port to {@link ServerProperties} a new child context is created, otherwise it - * is assumed that endpoint requests will be mapped and handled via an already registered - * {@link DispatcherServlet}. - * - * @author Dave Syer - * @author Phillip Webb - * @author Christian Dupuis - * @author Andy Wilkinson - * @author Johannes Edmeier - * @author EddĂș MelĂ©ndez - * @author Venil Noronha - * @author Madhura Bhave - * @since 2.0.0 - */ -@Configuration -@ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) -@ConditionalOnWebApplication(type = Type.SERVLET) -@EnableConfigurationProperties(ManagementServerProperties.class) -@AutoConfigureAfter({ EndpointAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - ServletWebServerFactoryAutoConfiguration.class, WebMvcAutoConfiguration.class, - RepositoryRestMvcAutoConfiguration.class, HypermediaAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class }) -public class EndpointWebMvcAutoConfiguration - implements ApplicationContextAware, SmartInitializingSingleton { - - private static final Log logger = LogFactory - .getLog(EndpointWebMvcAutoConfiguration.class); - - private ApplicationContext applicationContext; - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } - - @Bean - public ManagementContextResolver managementContextResolver() { - return new ManagementContextResolver(this.applicationContext); - } - - @Bean - public ManagementServletContext managementServletContext( - final ManagementServerProperties properties) { - return properties::getContextPath; - } - - @Override - public void afterSingletonsInstantiated() { - ManagementServerPort managementPort = ManagementServerPort.DIFFERENT; - Environment environment = this.applicationContext.getEnvironment(); - if (this.applicationContext instanceof WebApplicationContext) { - managementPort = ManagementServerPort.get(environment); - } - if (managementPort == ManagementServerPort.DIFFERENT) { - if (this.applicationContext instanceof ServletWebServerApplicationContext - && ((ServletWebServerApplicationContext) this.applicationContext) - .getWebServer() != null) { - createChildManagementContext(); - } - else { - logger.warn("Could not start management web server on " - + "different port (management endpoints are still available " - + "through JMX)"); - } - } - if (managementPort == ManagementServerPort.SAME) { - String contextPath = environment.getProperty("management.context-path"); - if ("".equals(contextPath) || "/".equals(contextPath)) { - throw new IllegalStateException("A management context path of '" - + contextPath + "' requires the management server to be " - + "listening on a separate port"); - } - if (environment.getProperty("management.ssl.enabled", Boolean.class, false)) { - throw new IllegalStateException( - "Management-specific SSL cannot be configured as the management " - + "server is not listening on a separate port"); - } - if (environment instanceof ConfigurableEnvironment) { - addLocalManagementPortPropertyAlias( - (ConfigurableEnvironment) environment); - } - } - } - - private void createChildManagementContext() { - AnnotationConfigServletWebServerApplicationContext childContext = new AnnotationConfigServletWebServerApplicationContext(); - childContext.setParent(this.applicationContext); - childContext.setNamespace("management"); - childContext.setId(this.applicationContext.getId() + ":management"); - childContext.setClassLoader(this.applicationContext.getClassLoader()); - childContext.register(EndpointWebMvcChildContextConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - ServletWebServerFactoryAutoConfiguration.class, - DispatcherServletAutoConfiguration.class); - registerServletWebServerFactory(childContext); - CloseManagementContextListener.addIfPossible(this.applicationContext, - childContext); - childContext.refresh(); - managementContextResolver().setApplicationContext(childContext); - } - - private void registerServletWebServerFactory( - AnnotationConfigServletWebServerApplicationContext childContext) { - try { - ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory(); - if (beanFactory instanceof BeanDefinitionRegistry) { - BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; - registry.registerBeanDefinition("ServletWebServerFactory", - new RootBeanDefinition(determineServletWebServerFactoryClass())); - } - } - catch (NoSuchBeanDefinitionException ex) { - // Ignore and assume auto-configuration - } - } - - private Class determineServletWebServerFactoryClass() - throws NoSuchBeanDefinitionException { - Class factoryClass = this.applicationContext - .getBean(ServletWebServerFactory.class).getClass(); - if (cannotBeInstantiated(factoryClass)) { - throw new FatalBeanException("ServletWebServerFactory implementation " - + factoryClass.getName() + " cannot be instantiated. " - + "To allow a separate management port to be used, a top-level class " - + "or static inner class should be used instead"); - } - return factoryClass; - } - - private boolean cannotBeInstantiated(Class clazz) { - return clazz.isLocalClass() - || (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) - || clazz.isAnonymousClass(); - } - - /** - * Add an alias for 'local.management.port' that actually resolves using - * 'local.server.port'. - * @param environment the environment - */ - private void addLocalManagementPortPropertyAlias( - final ConfigurableEnvironment environment) { - environment.getPropertySources() - .addLast(new PropertySource("Management Server") { - @Override - public Object getProperty(String name) { - if ("local.management.port".equals(name)) { - return environment.getProperty("local.server.port"); - } - return null; - } - }); - } - - // Put Servlets and Filters in their own nested class so they don't force early - // instantiation of ManagementServerProperties. - @Configuration - @ConditionalOnProperty(prefix = "management", name = "add-application-context-header", havingValue = "true") - protected static class ApplicationContextFilterConfiguration { - - @Bean - public ApplicationContextHeaderFilter applicationContextIdFilter( - ApplicationContext context) { - return new ApplicationContextHeaderFilter(context); - } - - } - - @Configuration - @Conditional(OnManagementMvcCondition.class) - @Import(ManagementContextConfigurationsImportSelector.class) - protected static class EndpointWebMvcConfiguration { - - } - - /** - * {@link ApplicationListener} to propagate the {@link ContextClosedEvent} and - * {@link ApplicationFailedEvent} from a parent to a child. - */ - private static class CloseManagementContextListener - implements ApplicationListener { - - private final ApplicationContext parentContext; - - private final ConfigurableApplicationContext childContext; - - CloseManagementContextListener(ApplicationContext parentContext, - ConfigurableApplicationContext childContext) { - this.parentContext = parentContext; - this.childContext = childContext; - } - - @Override - public void onApplicationEvent(ApplicationEvent event) { - if (event instanceof ContextClosedEvent) { - onContextClosedEvent((ContextClosedEvent) event); - } - if (event instanceof ApplicationFailedEvent) { - onApplicationFailedEvent((ApplicationFailedEvent) event); - } - }; - - private void onContextClosedEvent(ContextClosedEvent event) { - propagateCloseIfNecessary(event.getApplicationContext()); - } - - private void onApplicationFailedEvent(ApplicationFailedEvent event) { - propagateCloseIfNecessary(event.getApplicationContext()); - } - - private void propagateCloseIfNecessary(ApplicationContext applicationContext) { - if (applicationContext == this.parentContext) { - this.childContext.close(); - } - } - - public static void addIfPossible(ApplicationContext parentContext, - ConfigurableApplicationContext childContext) { - if (parentContext instanceof ConfigurableApplicationContext) { - add((ConfigurableApplicationContext) parentContext, childContext); - } - } - - private static void add(ConfigurableApplicationContext parentContext, - ConfigurableApplicationContext childContext) { - parentContext.addApplicationListener( - new CloseManagementContextListener(parentContext, childContext)); - } - - } - - private static class OnManagementMvcCondition extends SpringBootCondition - implements ConfigurationCondition { - - @Override - public ConfigurationPhase getConfigurationPhase() { - return ConfigurationPhase.REGISTER_BEAN; - } - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, - AnnotatedTypeMetadata metadata) { - ConditionMessage.Builder message = ConditionMessage - .forCondition("Management Server MVC"); - if (!(context.getResourceLoader() instanceof WebApplicationContext)) { - return ConditionOutcome - .noMatch(message.because("non WebApplicationContext")); - } - ManagementServerPort port = ManagementServerPort - .get(context.getEnvironment()); - if (port == ManagementServerPort.SAME) { - return ConditionOutcome.match(message.because("port is same")); - } - return ConditionOutcome.noMatch(message.because("port is not same")); - } - - } - - protected enum ManagementServerPort { - - DISABLE, SAME, DIFFERENT; - - public static ManagementServerPort get(Environment environment) { - Integer serverPort = getPortProperty(environment, "server."); - Integer managementPort = getPortProperty(environment, "management."); - if (managementPort != null && managementPort < 0) { - return DISABLE; - } - return ((managementPort == null) - || (serverPort == null && managementPort.equals(8080)) - || (managementPort != 0 && managementPort.equals(serverPort)) ? SAME - : DIFFERENT); - } - - private static Integer getPortProperty(Environment environment, String prefix) { - return environment.getProperty(prefix + "port", Integer.class); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfiguration.java deleted file mode 100644 index d68fb4f8c3..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfiguration.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.audit.AuditEventRepository; -import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration; -import org.springframework.boot.actuate.autoconfigure.health.HealthMvcEndpointProperties; -import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; -import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.endpoint.LoggersEndpoint; -import org.springframework.boot.actuate.endpoint.MetricsEndpoint; -import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer; -import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.LogFileMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpointSecurityInterceptor; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; -import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint; -import org.springframework.boot.autoconfigure.condition.ConditionMessage; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.context.annotation.Conditional; -import org.springframework.core.env.Environment; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.cors.CorsConfiguration; - -/** - * Configuration to expose {@link Endpoint} instances over Spring MVC. - * - * @author Dave Syer - * @author Ben Hale - * @author Vedran Pavic - * @author Madhura Bhave - * @since 2.0.0 - */ -@ManagementContextConfiguration -@EnableConfigurationProperties({ HealthMvcEndpointProperties.class, - EndpointCorsProperties.class }) -public class EndpointWebMvcManagementContextConfiguration { - - private final HealthMvcEndpointProperties healthMvcEndpointProperties; - - private final ManagementServerProperties managementServerProperties; - - private final EndpointCorsProperties corsProperties; - - private final List mappingCustomizers; - - public EndpointWebMvcManagementContextConfiguration( - HealthMvcEndpointProperties healthMvcEndpointProperties, - ManagementServerProperties managementServerProperties, - EndpointCorsProperties corsProperties, - ObjectProvider> mappingCustomizers) { - this.healthMvcEndpointProperties = healthMvcEndpointProperties; - this.managementServerProperties = managementServerProperties; - this.corsProperties = corsProperties; - List providedCustomizers = mappingCustomizers - .getIfAvailable(); - this.mappingCustomizers = providedCustomizers == null - ? Collections.emptyList() - : providedCustomizers; - } - - @Bean - @ConditionalOnMissingBean - public EndpointHandlerMapping endpointHandlerMapping() { - Set endpoints = mvcEndpoints().getEndpoints(); - CorsConfiguration corsConfiguration = getCorsConfiguration(this.corsProperties); - EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints, - corsConfiguration); - mapping.setPrefix(this.managementServerProperties.getContextPath()); - MvcEndpointSecurityInterceptor securityInterceptor = new MvcEndpointSecurityInterceptor( - this.managementServerProperties.getSecurity().isEnabled(), - this.managementServerProperties.getSecurity().getRoles()); - mapping.setSecurityInterceptor(securityInterceptor); - for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) { - customizer.customize(mapping); - } - return mapping; - } - - private CorsConfiguration getCorsConfiguration(EndpointCorsProperties properties) { - if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) { - return null; - } - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(properties.getAllowedOrigins()); - if (!CollectionUtils.isEmpty(properties.getAllowedHeaders())) { - configuration.setAllowedHeaders(properties.getAllowedHeaders()); - } - if (!CollectionUtils.isEmpty(properties.getAllowedMethods())) { - configuration.setAllowedMethods(properties.getAllowedMethods()); - } - if (!CollectionUtils.isEmpty(properties.getExposedHeaders())) { - configuration.setExposedHeaders(properties.getExposedHeaders()); - } - if (properties.getMaxAge() != null) { - configuration.setMaxAge(properties.getMaxAge()); - } - if (properties.getAllowCredentials() != null) { - configuration.setAllowCredentials(properties.getAllowCredentials()); - } - return configuration; - } - - @Bean - @ConditionalOnMissingBean - public MvcEndpoints mvcEndpoints() { - return new MvcEndpoints(); - } - - @Bean - @ConditionalOnBean(EnvironmentEndpoint.class) - @ConditionalOnEnabledEndpoint("env") - public EnvironmentMvcEndpoint environmentMvcEndpoint(EnvironmentEndpoint delegate) { - return new EnvironmentMvcEndpoint(delegate); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnEnabledEndpoint("heapdump") - public HeapdumpMvcEndpoint heapdumpMvcEndpoint() { - return new HeapdumpMvcEndpoint(); - } - - @Bean - @ConditionalOnBean(HealthEndpoint.class) - @ConditionalOnMissingBean - @ConditionalOnEnabledEndpoint("health") - public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate, - ManagementServerProperties managementServerProperties) { - HealthMvcEndpoint healthMvcEndpoint = new HealthMvcEndpoint(delegate, - this.managementServerProperties.getSecurity().isEnabled(), - managementServerProperties.getSecurity().getRoles()); - if (this.healthMvcEndpointProperties.getMapping() != null) { - healthMvcEndpoint - .addStatusMapping(this.healthMvcEndpointProperties.getMapping()); - } - return healthMvcEndpoint; - } - - @Bean - @ConditionalOnBean(LoggersEndpoint.class) - @ConditionalOnEnabledEndpoint("loggers") - public LoggersMvcEndpoint loggersMvcEndpoint(LoggersEndpoint delegate) { - return new LoggersMvcEndpoint(delegate); - } - - @Bean - @ConditionalOnBean(MetricsEndpoint.class) - @ConditionalOnEnabledEndpoint("metrics") - public MetricsMvcEndpoint metricsMvcEndpoint(MetricsEndpoint delegate) { - return new MetricsMvcEndpoint(delegate); - } - - @Bean - @ConditionalOnEnabledEndpoint("logfile") - @Conditional(LogFileCondition.class) - public LogFileMvcEndpoint logfileMvcEndpoint() { - return new LogFileMvcEndpoint(); - } - - @Bean - @ConditionalOnBean(ShutdownEndpoint.class) - @ConditionalOnEnabledEndpoint(value = "shutdown", enabledByDefault = false) - public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) { - return new ShutdownMvcEndpoint(delegate); - } - - @Bean - @ConditionalOnBean(AuditEventRepository.class) - @ConditionalOnEnabledEndpoint("auditevents") - public AuditEventsMvcEndpoint auditEventMvcEndpoint( - AuditEventRepository auditEventRepository) { - return new AuditEventsMvcEndpoint(auditEventRepository); - } - - private static class LogFileCondition extends SpringBootCondition { - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, - AnnotatedTypeMetadata metadata) { - Environment environment = context.getEnvironment(); - String config = environment.resolvePlaceholders("${logging.file:}"); - ConditionMessage.Builder message = ConditionMessage.forCondition("Log File"); - if (StringUtils.hasText(config)) { - return ConditionOutcome - .match(message.found("logging.file").items(config)); - } - config = environment.resolvePlaceholders("${logging.path:}"); - if (StringUtils.hasText(config)) { - return ConditionOutcome - .match(message.found("logging.path").items(config)); - } - config = environment.getProperty("endpoints.logfile.external-file"); - if (StringUtils.hasText(config)) { - return ConditionOutcome.match( - message.found("endpoints.logfile.external-file").items(config)); - } - return ConditionOutcome.noMatch(message.didNotFind("logging file").atAll()); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextResolver.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextResolver.java index 1be0288559..9318eb69b0 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextResolver.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextResolver.java @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.endpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; import org.springframework.context.ApplicationContext; /** @@ -34,23 +33,6 @@ public class ManagementContextResolver { this.applicationContext = applicationContext; } - void setApplicationContext(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } - - /** - * Return all {@link MvcEndpoints} from the management context. - * @return {@link MvcEndpoints} from the management context - */ - public MvcEndpoints getMvcEndpoints() { - try { - return getApplicationContext().getBean(MvcEndpoints.class); - } - catch (Exception ex) { - return null; - } - } - /** * Return the management {@link ApplicationContext}. * @return the management {@link ApplicationContext} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/OnEnabledEndpointCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/OnEnabledEndpointCondition.java new file mode 100644 index 0000000000..f99ad69b02 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/OnEnabledEndpointCondition.java @@ -0,0 +1,131 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint; + +import org.springframework.boot.actuate.autoconfigure.endpoint.support.EndpointEnablement; +import org.springframework.boot.actuate.autoconfigure.endpoint.support.EndpointEnablementProvider; +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.boot.endpoint.jmx.JmxEndpointExtension; +import org.springframework.boot.endpoint.web.WebEndpointExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * A condition that checks if an endpoint is enabled. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +class OnEnabledEndpointCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + EndpointAttributes endpoint = getEndpointAttributes(context, metadata); + if (!StringUtils.hasText(endpoint.id)) { + throw new IllegalStateException("Endpoint id could not be determined"); + } + EndpointEnablementProvider enablementProvider = new EndpointEnablementProvider( + context.getEnvironment()); + EndpointEnablement endpointEnablement = enablementProvider.getEndpointEnablement( + endpoint.id, endpoint.enabled, endpoint.endpointType); + return new ConditionOutcome(endpointEnablement.isEnabled(), + ConditionMessage.forCondition(ConditionalOnEnabledEndpoint.class) + .because(endpointEnablement.getReason())); + } + + private EndpointAttributes getEndpointAttributes(ConditionContext context, + AnnotatedTypeMetadata metadata) { + if (metadata instanceof MethodMetadata + && metadata.isAnnotated(Bean.class.getName())) { + MethodMetadata methodMetadata = (MethodMetadata) metadata; + try { + // We should be safe to load at this point since we are in the + // REGISTER_BEAN phase + Class returnType = ClassUtils.forName( + methodMetadata.getReturnTypeName(), context.getClassLoader()); + return extractEndpointAttributes(returnType); + } + catch (Throwable ex) { + throw new IllegalStateException("Failed to extract endpoint id for " + + methodMetadata.getDeclaringClassName() + "." + + methodMetadata.getMethodName(), ex); + } + } + throw new IllegalStateException( + "OnEnabledEndpointCondition may only be used on @Bean methods"); + } + + protected EndpointAttributes extractEndpointAttributes(Class type) { + EndpointAttributes attributes = extractEndpointAttributesFromEndpoint(type); + if (attributes != null) { + return attributes; + } + JmxEndpointExtension jmxExtension = AnnotationUtils.findAnnotation(type, + JmxEndpointExtension.class); + if (jmxExtension != null) { + return extractEndpointAttributes(jmxExtension.endpoint()); + } + WebEndpointExtension webExtension = AnnotationUtils.findAnnotation(type, + WebEndpointExtension.class); + if (webExtension != null) { + return extractEndpointAttributes(webExtension.endpoint()); + } + throw new IllegalStateException( + "OnEnabledEndpointCondition may only be used on @Bean methods that return" + + " @Endpoint, @JmxEndpointExtension, or @WebEndpointExtension"); + } + + private EndpointAttributes extractEndpointAttributesFromEndpoint( + Class endpointClass) { + Endpoint endpoint = AnnotationUtils.findAnnotation(endpointClass, Endpoint.class); + if (endpoint == null) { + return null; + } + // If both types are set, all techs are exposed + EndpointType endpointType = (endpoint.types().length == 1 ? endpoint.types()[0] + : null); + return new EndpointAttributes(endpoint.id(), endpoint.enabledByDefault(), + endpointType); + } + + private static class EndpointAttributes { + + private final String id; + + private final boolean enabled; + + private final EndpointType endpointType; + + EndpointAttributes(String id, boolean enabled, EndpointType endpointType) { + this.id = id; + this.enabled = enabled; + this.endpointType = endpointType; + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorMediaTypes.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ActuatorMediaTypes.java similarity index 94% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorMediaTypes.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ActuatorMediaTypes.java index 6817a398a3..8b4dcfa5e7 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorMediaTypes.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ActuatorMediaTypes.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint.mvc; +package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; import org.springframework.http.MediaType; @@ -23,7 +23,7 @@ import org.springframework.http.MediaType; * * @author Andy Wilkinson * @author Madhura Bhave - * @since 1.5.0 + * @since 2.0.0 */ public final class ActuatorMediaTypes { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactory.java new file mode 100644 index 0000000000..e7df115776 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import java.util.function.Function; + +import org.springframework.boot.endpoint.CachingConfiguration; +import org.springframework.core.env.Environment; + +/** + * A {@link CachingConfiguration} factory that use the {@link Environment} to extract + * the caching settings of each endpoint. + * + * @author Stephane Nicoll + */ +class CachingConfigurationFactory + implements Function { + + private final Environment environment; + + /** + * Create a new instance with the {@link Environment} to use. + * @param environment the environment + */ + CachingConfigurationFactory(Environment environment) { + this.environment = environment; + } + + @Override + public CachingConfiguration apply(String endpointId) { + String key = String.format("endpoints.%s.cache.time-to-live", endpointId); + Long ttl = this.environment.getProperty(key, Long.class, 0L); + return new CachingConfiguration(ttl); + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java new file mode 100644 index 0000000000..6bd648e01e --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.springframework.boot.endpoint.jmx.EndpointMBean; +import org.springframework.boot.endpoint.jmx.EndpointObjectNameFactory; +import org.springframework.jmx.support.ObjectNameManager; +import org.springframework.util.StringUtils; + +/** + * A {@link EndpointObjectNameFactory} that generates standard {@link ObjectName} for + * Actuator's endpoints. + * + * @author Stephane Nicoll + */ +class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory { + + private String domain = "org.springframework.boot"; + + @Override + public ObjectName generate(EndpointMBean mBean) throws MalformedObjectNameException { + StringBuilder builder = new StringBuilder(); + builder.append(this.domain); + builder.append(":type=Endpoint"); + builder.append(",name=" + StringUtils.capitalize(mBean.getEndpointId())); + return ObjectNameManager.getInstance(builder.toString()); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java new file mode 100644 index 0000000000..9bd3a32bf4 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java @@ -0,0 +1,141 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import javax.management.MBeanServer; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; +import org.springframework.boot.endpoint.ConversionServiceOperationParameterMapper; +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.boot.endpoint.OperationParameterMapper; +import org.springframework.boot.endpoint.jmx.EndpointMBeanRegistrar; +import org.springframework.boot.endpoint.jmx.JmxAnnotationEndpointDiscoverer; +import org.springframework.boot.endpoint.jmx.JmxEndpointOperation; +import org.springframework.boot.endpoint.web.WebAnnotationEndpointDiscoverer; +import org.springframework.boot.endpoint.web.WebEndpointOperation; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.util.StringUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for the endpoint infrastructure used + * by the Actuator. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + * @since 2.0.0 + */ +@Configuration +@AutoConfigureAfter(JmxAutoConfiguration.class) +public class EndpointInfrastructureAutoConfiguration { + + private final ApplicationContext applicationContext; + + public EndpointInfrastructureAutoConfiguration( + ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Bean + public OperationParameterMapper operationParameterMapper() { + DefaultConversionService conversionService = new DefaultConversionService(); + conversionService.addConverter(String.class, Date.class, (string) -> { + if (StringUtils.hasLength(string)) { + OffsetDateTime offsetDateTime = OffsetDateTime.parse(string, + DateTimeFormatter.ISO_OFFSET_DATE_TIME); + return new Date(offsetDateTime.toEpochSecond() * 1000); + } + return null; + }); + return new ConversionServiceOperationParameterMapper(conversionService); + } + + @Bean + public CachingConfigurationFactory cacheConfigurationFactory() { + return new CachingConfigurationFactory(this.applicationContext.getEnvironment()); + } + + @Bean + public JmxAnnotationEndpointDiscoverer jmxEndpointDiscoverer( + OperationParameterMapper operationParameterMapper, + CachingConfigurationFactory cachingConfigurationFactory) { + return new JmxAnnotationEndpointDiscoverer(this.applicationContext, + operationParameterMapper, cachingConfigurationFactory); + } + + @ConditionalOnSingleCandidate(MBeanServer.class) + @Bean + public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer, + JmxAnnotationEndpointDiscoverer endpointDiscoverer, + ObjectProvider objectMapper) { + EndpointProvider endpointProvider = new EndpointProvider<>( + this.applicationContext.getEnvironment(), endpointDiscoverer, + EndpointType.JMX); + EndpointMBeanRegistrar endpointMBeanRegistrar = new EndpointMBeanRegistrar( + mBeanServer, new DefaultEndpointObjectNameFactory()); + return new JmxEndpointExporter(endpointProvider, endpointMBeanRegistrar, + objectMapper.getIfAvailable(ObjectMapper::new)); + } + + @ConditionalOnWebApplication + static class WebInfrastructureConfiguration { + + private final ApplicationContext applicationContext; + + WebInfrastructureConfiguration(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Bean + public EndpointProvider webEndpointProvider( + OperationParameterMapper operationParameterMapper, + CachingConfigurationFactory cachingConfigurationFactory) { + return new EndpointProvider<>(this.applicationContext.getEnvironment(), + webEndpointDiscoverer(operationParameterMapper, + cachingConfigurationFactory), + EndpointType.WEB); + } + + private WebAnnotationEndpointDiscoverer webEndpointDiscoverer( + OperationParameterMapper operationParameterMapper, + CachingConfigurationFactory cachingConfigurationFactory) { + List mediaTypes = Arrays.asList( + ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE, + "application/json"); + return new WebAnnotationEndpointDiscoverer(this.applicationContext, + operationParameterMapper, cachingConfigurationFactory, mediaTypes, + mediaTypes); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointProvider.java new file mode 100644 index 0000000000..ab7eb14f56 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointProvider.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import java.util.Collection; +import java.util.stream.Collectors; + +import org.springframework.boot.actuate.autoconfigure.endpoint.support.EndpointEnablementProvider; +import org.springframework.boot.endpoint.EndpointDiscoverer; +import org.springframework.boot.endpoint.EndpointInfo; +import org.springframework.boot.endpoint.EndpointOperation; +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.core.env.Environment; + +/** + * Provides the endpoints that are enabled according to an {@link EndpointDiscoverer} and + * the current {@link Environment}. + * + * @param the endpoint operation type + * @author Stephane Nicoll + * @since 2.0.0 + */ +public final class EndpointProvider { + + private final EndpointDiscoverer discoverer; + + private final EndpointEnablementProvider endpointEnablementProvider; + + private final EndpointType endpointType; + + /** + * Creates a new instance. + * @param environment the environment to use to check the endpoints that are enabled + * @param discoverer the discoverer to get the initial set of endpoints + * @param endpointType the type of endpoint to handle + */ + public EndpointProvider(Environment environment, EndpointDiscoverer discoverer, + EndpointType endpointType) { + this.discoverer = discoverer; + this.endpointEnablementProvider = new EndpointEnablementProvider(environment); + this.endpointType = endpointType; + } + + public Collection> getEndpoints() { + return this.discoverer.discoverEndpoints().stream().filter(this::isEnabled) + .collect(Collectors.toList()); + } + + private boolean isEnabled(EndpointInfo endpoint) { + return this.endpointEnablementProvider.getEndpointEnablement(endpoint.getId(), + endpoint.isEnabledByDefault(), this.endpointType).isEnabled(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporter.java new file mode 100644 index 0000000000..d1a4e2b04c --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporter.java @@ -0,0 +1,134 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.jmx.EndpointMBean; +import org.springframework.boot.endpoint.jmx.EndpointMBeanRegistrar; +import org.springframework.boot.endpoint.jmx.JmxEndpointMBeanFactory; +import org.springframework.boot.endpoint.jmx.JmxEndpointOperation; +import org.springframework.boot.endpoint.jmx.JmxOperationResponseMapper; + +/** + * Exports all available {@link Endpoint} to a configurable {@link MBeanServer}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +class JmxEndpointExporter implements InitializingBean, DisposableBean { + + private final EndpointProvider endpointProvider; + + private final EndpointMBeanRegistrar endpointMBeanRegistrar; + + private final JmxEndpointMBeanFactory mBeanFactory; + + private Collection registeredObjectNames; + + JmxEndpointExporter(EndpointProvider endpointProvider, + EndpointMBeanRegistrar endpointMBeanRegistrar, + ObjectMapper objectMapper) { + this.endpointProvider = endpointProvider; + this.endpointMBeanRegistrar = endpointMBeanRegistrar; + DataConverter dataConverter = new DataConverter(objectMapper); + this.mBeanFactory = new JmxEndpointMBeanFactory(dataConverter); + } + + @Override + public void afterPropertiesSet() { + this.registeredObjectNames = registerEndpointMBeans(); + } + + @Override + public void destroy() throws Exception { + unregisterEndpointMBeans(this.registeredObjectNames); + } + + private Collection registerEndpointMBeans() { + List objectNames = new ArrayList<>(); + Collection mBeans = this.mBeanFactory.createMBeans( + this.endpointProvider.getEndpoints()); + for (EndpointMBean mBean : mBeans) { + objectNames.add(this.endpointMBeanRegistrar.registerEndpointMBean(mBean)); + } + return objectNames; + } + + private void unregisterEndpointMBeans(Collection objectNames) { + objectNames.forEach(this.endpointMBeanRegistrar::unregisterEndpointMbean); + + } + + static class DataConverter implements JmxOperationResponseMapper { + + private final ObjectMapper objectMapper; + + private final JavaType listObject; + + private final JavaType mapStringObject; + + DataConverter(ObjectMapper objectMapper) { + this.objectMapper = (objectMapper == null ? new ObjectMapper() + : objectMapper); + this.listObject = this.objectMapper.getTypeFactory() + .constructParametricType(List.class, Object.class); + this.mapStringObject = this.objectMapper.getTypeFactory() + .constructParametricType(Map.class, String.class, Object.class); + } + + @Override + public Object mapResponse(Object response) { + if (response == null) { + return null; + } + if (response instanceof String) { + return response; + } + if (response.getClass().isArray() || response instanceof Collection) { + return this.objectMapper.convertValue(response, this.listObject); + } + return this.objectMapper.convertValue(response, this.mapStringObject); + } + + @Override + public Class mapResponseType(Class responseType) { + if (responseType.equals(String.class)) { + return String.class; + } + if (responseType.isArray() || Collection.class.isAssignableFrom(responseType)) { + return List.class; + } + return Map.class; + } + + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ManagementWebServerFactoryCustomizer.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ManagementWebServerFactoryCustomizer.java new file mode 100644 index 0000000000..3527e82c06 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ManagementWebServerFactoryCustomizer.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import java.util.Collections; + +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.web.server.ConfigurableWebServerFactory; +import org.springframework.boot.web.server.ErrorPage; +import org.springframework.boot.web.server.Ssl; +import org.springframework.boot.web.server.WebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.core.Ordered; + +/** + * {@link WebServerFactoryCustomizer} that customizes the {@link WebServerFactory} used to + * create the management context's web server. + * + * @param the type of web server factory to customize + * @author Andy Wilkinson + */ +abstract class ManagementWebServerFactoryCustomizer + implements WebServerFactoryCustomizer, Ordered { + + private final ListableBeanFactory beanFactory; + + private final Class> customizerClass; + + protected ManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory, + Class> customizerClass) { + this.beanFactory = beanFactory; + this.customizerClass = customizerClass; + } + + @Override + public int getOrder() { + return 0; + } + + @Override + public final void customize(T webServerFactory) { + ManagementServerProperties managementServerProperties = BeanFactoryUtils + .beanOfTypeIncludingAncestors(this.beanFactory, + ManagementServerProperties.class); + ServerProperties serverProperties = BeanFactoryUtils + .beanOfTypeIncludingAncestors(this.beanFactory, ServerProperties.class); + WebServerFactoryCustomizer webServerFactoryCustomizer = BeanFactoryUtils + .beanOfTypeIncludingAncestors(this.beanFactory, this.customizerClass); + // Customize as per the parent context first (so e.g. the access logs go to + // the same place) + webServerFactoryCustomizer.customize(webServerFactory); + // Then reset the error pages + webServerFactory.setErrorPages(Collections.emptySet()); + // and add the management-specific bits + customize(webServerFactory, managementServerProperties, serverProperties); + } + + protected void customize(T webServerFactory, + ManagementServerProperties managementServerProperties, + ServerProperties serverProperties) { + webServerFactory.setPort(managementServerProperties.getPort()); + Ssl ssl = managementServerProperties.getSsl(); + if (ssl != null) { + webServerFactory.setSsl(ssl); + } + webServerFactory.setServerHeader(serverProperties.getServerHeader()); + webServerFactory.setAddress(managementServerProperties.getAddress()); + webServerFactory + .addErrorPages(new ErrorPage(serverProperties.getError().getPath())); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ReactiveEndpointChildManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ReactiveEndpointChildManagementContextConfiguration.java new file mode 100644 index 0000000000..41596f9c37 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ReactiveEndpointChildManagementContextConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementContextType; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.web.reactive.DefaultReactiveWebServerCustomizer; +import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; + +/** + * Configuration for reactive web endpoint infrastructure when a separate management + * context with a web server running on a different port is required. + * + * @author Andy Wilkinson + */ +@EnableWebFlux +@ManagementContextConfiguration(ManagementContextType.CHILD) +@ConditionalOnWebApplication(type = Type.REACTIVE) +class ReactiveEndpointChildManagementContextConfiguration { + + @Bean + public HttpHandler httpHandler(ApplicationContext applicationContext) { + return WebHttpHandlerBuilder.applicationContext(applicationContext).build(); + } + + @Bean + public ManagementReactiveWebServerFactoryCustomizer webServerFactoryCustomizer( + ListableBeanFactory beanFactory) { + return new ManagementReactiveWebServerFactoryCustomizer(beanFactory); + } + + static class ManagementReactiveWebServerFactoryCustomizer extends + ManagementWebServerFactoryCustomizer { + + ManagementReactiveWebServerFactoryCustomizer(ListableBeanFactory beanFactory) { + super(beanFactory, DefaultReactiveWebServerCustomizer.class); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointAutoConfiguration.java new file mode 100644 index 0000000000..2bc92539af --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointAutoConfiguration.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import javax.servlet.Servlet; + +import org.springframework.boot.actuate.autoconfigure.endpoint.ManagementContextResolver; +import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; +import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.filter.ApplicationContextHeaderFilter; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Servlet-specific endpoint + * concerns. + * + * @author Dave Syer + * @author Phillip Webb + * @author Christian Dupuis + * @author Andy Wilkinson + * @author Johannes Edmeier + * @author EddĂș MelĂ©ndez + * @author Venil Noronha + * @author Madhura Bhave + */ +@Configuration +@ConditionalOnClass(Servlet.class) +@ConditionalOnWebApplication(type = Type.SERVLET) +@EnableConfigurationProperties(ManagementServerProperties.class) +@AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class, WebMvcAutoConfiguration.class, + RepositoryRestMvcAutoConfiguration.class, HypermediaAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class }) +public class ServletEndpointAutoConfiguration { + + @Bean + public ManagementContextResolver managementContextResolver( + ApplicationContext applicationContext) { + return new ManagementContextResolver(applicationContext); + } + + // Put Servlets and Filters in their own nested class so they don't force early + // instantiation of ManagementServerProperties. + @Configuration + @ConditionalOnProperty(prefix = "management", name = "add-application-context-header", havingValue = "true") + protected static class ApplicationContextFilterConfiguration { + + @Bean + public ApplicationContextHeaderFilter applicationContextIdFilter( + ApplicationContext context) { + return new ApplicationContextHeaderFilter(context); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcChildContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointChildManagementContextConfiguration.java similarity index 61% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcChildContextConfiguration.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointChildManagementContextConfiguration.java index fda7a8db65..d580f8de2c 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcChildContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointChildManagementContextConfiguration.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.endpoint; +package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import javax.servlet.Filter; @@ -26,38 +25,37 @@ import javax.servlet.http.HttpServletResponse; import org.apache.catalina.Valve; import org.apache.catalina.valves.AccessLogValve; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.servlet.ServletContainer; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.HierarchicalBeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementContextType; import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.mvc.ManagementErrorEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.SearchStrategy; -import org.springframework.boot.autoconfigure.hateoas.HypermediaHttpMessageConverterConfiguration; +import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.servlet.DefaultServletWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorAttributes; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; -import org.springframework.boot.web.server.ErrorPage; -import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.hateoas.LinkDiscoverer; -import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.HandlerAdapter; @@ -68,50 +66,23 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.config.annotation.EnableWebMvc; /** - * Configuration triggered from {@link EndpointWebMvcAutoConfiguration} when a new - * {@link WebServer} running on a different port is required. + * Configuration for Servlet web endpoint infrastructure when a separate management + * context with a web server running on a different port is required. * * @author Dave Syer * @author Stephane Nicoll * @author Andy Wilkinson * @author EddĂș MelĂ©ndez - * @see EndpointWebMvcAutoConfiguration - * @since 2.0.0 */ @Configuration -@EnableWebMvc -@Import(ManagementContextConfigurationsImportSelector.class) -public class EndpointWebMvcChildContextConfiguration { - - @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) - public DispatcherServlet dispatcherServlet() { - DispatcherServlet dispatcherServlet = new DispatcherServlet(); - // Ensure the parent configuration does not leak down to us - dispatcherServlet.setDetectAllHandlerAdapters(false); - dispatcherServlet.setDetectAllHandlerExceptionResolvers(false); - dispatcherServlet.setDetectAllHandlerMappings(false); - dispatcherServlet.setDetectAllViewResolvers(false); - return dispatcherServlet; - } - - @Bean(name = DispatcherServlet.HANDLER_MAPPING_BEAN_NAME) - public CompositeHandlerMapping compositeHandlerMapping() { - return new CompositeHandlerMapping(); - } - - @Bean(name = DispatcherServlet.HANDLER_ADAPTER_BEAN_NAME) - public CompositeHandlerAdapter compositeHandlerAdapter() { - return new CompositeHandlerAdapter(); - } - - @Bean(name = DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME) - public CompositeHandlerExceptionResolver compositeHandlerExceptionResolver() { - return new CompositeHandlerExceptionResolver(); - } +@ManagementContextConfiguration(ManagementContextType.CHILD) +@ConditionalOnWebApplication(type = Type.SERVLET) +class ServletEndpointChildManagementContextConfiguration { @Bean - public ServerFactoryCustomization serverCustomization() { - return new ServerFactoryCustomization(); + public ManagementServletWebServerFactoryCustomization serverCustomization( + ListableBeanFactory beanFactory) { + return new ManagementServletWebServerFactoryCustomization(beanFactory); } @Bean @@ -125,28 +96,75 @@ public class EndpointWebMvcChildContextConfiguration { return new TomcatAccessLogCustomizer(); } - /* - * The error controller is present but not mapped as an endpoint in this context - * because of the DispatcherServlet having had its HandlerMapping explicitly disabled. - * So we expose the same feature but only for machine endpoints. - */ - @Bean - @ConditionalOnBean(ErrorAttributes.class) - public ManagementErrorEndpoint errorEndpoint(ErrorAttributes errorAttributes) { - return new ManagementErrorEndpoint(errorAttributes); + @EnableWebMvc + @ConditionalOnClass(DispatcherServlet.class) + static class MvcEndpointChildContextConfiguration { + + /* + * The error controller is present but not mapped as an endpoint in this context + * because of the DispatcherServlet having had its HandlerMapping explicitly + * disabled. So we expose the same feature but only for machine endpoints. + */ + @Bean + @ConditionalOnBean(ErrorAttributes.class) + public ManagementErrorEndpoint errorEndpoint(ErrorAttributes errorAttributes) { + return new ManagementErrorEndpoint(errorAttributes); + } + + @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) + public DispatcherServlet dispatcherServlet() { + DispatcherServlet dispatcherServlet = new DispatcherServlet(); + // Ensure the parent configuration does not leak down to us + dispatcherServlet.setDetectAllHandlerAdapters(false); + dispatcherServlet.setDetectAllHandlerExceptionResolvers(false); + dispatcherServlet.setDetectAllHandlerMappings(false); + dispatcherServlet.setDetectAllViewResolvers(false); + return dispatcherServlet; + } + + @Bean(name = DispatcherServlet.HANDLER_MAPPING_BEAN_NAME) + public CompositeHandlerMapping compositeHandlerMapping() { + return new CompositeHandlerMapping(); + } + + @Bean(name = DispatcherServlet.HANDLER_ADAPTER_BEAN_NAME) + public CompositeHandlerAdapter compositeHandlerAdapter() { + return new CompositeHandlerAdapter(); + } + + @Bean(name = DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME) + public CompositeHandlerExceptionResolver compositeHandlerExceptionResolver() { + return new CompositeHandlerExceptionResolver(); + } + } - /** - * Configuration to add {@link HandlerMapping} for {@link MvcEndpoint}s. - */ @Configuration - protected static class EndpointHandlerMappingConfiguration { + @ConditionalOnClass(ResourceConfig.class) + @ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet") + static class JerseyEndpointChildContextConfiguration { - @Autowired - public void handlerMapping(MvcEndpoints endpoints, - ListableBeanFactory beanFactory, EndpointHandlerMapping mapping) { - // In a child context we definitely want to see the parent endpoints - mapping.setDetectHandlerMethodsInAncestorContexts(true); + private final List resourceConfigCustomizers; + + JerseyEndpointChildContextConfiguration( + List resourceConfigCustomizers) { + this.resourceConfigCustomizers = resourceConfigCustomizers; + } + + @Bean + public ServletRegistrationBean jerseyServletRegistration() { + ServletRegistrationBean registration = new ServletRegistrationBean<>( + new ServletContainer(endpointResourceConfig()), "/*"); + return registration; + } + + @Bean + public ResourceConfig endpointResourceConfig() { + ResourceConfig resourceConfig = new ResourceConfig(); + for (ResourceConfigCustomizer customizer : this.resourceConfigCustomizers) { + customizer.customize(resourceConfig); + } + return resourceConfig; } } @@ -154,7 +172,7 @@ public class EndpointWebMvcChildContextConfiguration { @Configuration @ConditionalOnClass({ EnableWebSecurity.class, Filter.class }) @ConditionalOnBean(name = "springSecurityFilterChain", search = SearchStrategy.ANCESTORS) - public static class EndpointWebMvcChildContextSecurityConfiguration { + static class EndpointWebMvcChildContextSecurityConfiguration { @Bean public Filter springSecurityFilterChain(HierarchicalBeanFactory beanFactory) { @@ -164,60 +182,20 @@ public class EndpointWebMvcChildContextConfiguration { } - @Configuration - @ConditionalOnClass({ LinkDiscoverer.class }) - @Import(HypermediaHttpMessageConverterConfiguration.class) - @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL) - static class HypermediaConfiguration { + static class ManagementServletWebServerFactoryCustomization extends + ManagementWebServerFactoryCustomizer { - } - - static class ServerFactoryCustomization implements - WebServerFactoryCustomizer, Ordered { - - @Autowired - private ListableBeanFactory beanFactory; - - // This needs to be lazily initialized because web server customizer - // instances get their callback very early in the context lifecycle. - private ManagementServerProperties managementServerProperties; - - private ServerProperties server; - - private DefaultServletWebServerFactoryCustomizer serverCustomizer; - - @Override - public int getOrder() { - return 0; + ManagementServletWebServerFactoryCustomization(ListableBeanFactory beanFactory) { + super(beanFactory, DefaultServletWebServerFactoryCustomizer.class); } @Override - public void customize(ConfigurableServletWebServerFactory webServerFactory) { - if (this.managementServerProperties == null) { - this.managementServerProperties = BeanFactoryUtils - .beanOfTypeIncludingAncestors(this.beanFactory, - ManagementServerProperties.class); - this.server = BeanFactoryUtils.beanOfTypeIncludingAncestors( - this.beanFactory, ServerProperties.class); - this.serverCustomizer = BeanFactoryUtils.beanOfTypeIncludingAncestors( - this.beanFactory, DefaultServletWebServerFactoryCustomizer.class); - } - // Customize as per the parent context first (so e.g. the access logs go to - // the same place) - this.serverCustomizer.customize(webServerFactory); - // Then reset the error pages - webServerFactory.setErrorPages(Collections.emptySet()); - // and the context path + protected void customize(ConfigurableServletWebServerFactory webServerFactory, + ManagementServerProperties managementServerProperties, + ServerProperties serverProperties) { + super.customize(webServerFactory, managementServerProperties, + serverProperties); webServerFactory.setContextPath(""); - // and add the management-specific bits - webServerFactory.setPort(this.managementServerProperties.getPort()); - if (this.managementServerProperties.getSsl() != null) { - webServerFactory.setSsl(this.managementServerProperties.getSsl()); - } - webServerFactory.setServerHeader(this.server.getServerHeader()); - webServerFactory.setAddress(this.managementServerProperties.getAddress()); - webServerFactory - .addErrorPages(new ErrorPage(this.server.getError().getPath())); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointHandlerMappingCustomizer.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointHandlerMappingCustomizer.java new file mode 100644 index 0000000000..18dd8b9341 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointHandlerMappingCustomizer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping; + +/** + * Callback for customizing the {@link WebEndpointServletHandlerMapping} at configuration + * time. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +@FunctionalInterface +public interface WebEndpointHandlerMappingCustomizer { + + /** + * Customize the given {@code mapping}. + * @param mapping the mapping to customize + */ + void customize(WebEndpointServletHandlerMapping mapping); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointInfrastructureManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointInfrastructureManagementContextConfiguration.java new file mode 100644 index 0000000000..8e687c7b65 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointInfrastructureManagementContextConfiguration.java @@ -0,0 +1,146 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import org.glassfish.jersey.server.ResourceConfig; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointCorsProperties; +import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.endpoint.web.WebEndpointOperation; +import org.springframework.boot.endpoint.web.jersey.JerseyEndpointResourceFactory; +import org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping; +import org.springframework.boot.endpoint.web.reactive.WebEndpointReactiveHandlerMapping; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.CollectionUtils; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Management context configuration for the infrastructure for web endpoints. + * + * @author Andy Wilkinson + */ +@ConditionalOnWebApplication +@ManagementContextConfiguration +@EnableConfigurationProperties({ EndpointCorsProperties.class, + ManagementServerProperties.class }) +class WebEndpointInfrastructureManagementContextConfiguration { + + @Configuration + @ConditionalOnWebApplication(type = Type.SERVLET) + @ConditionalOnClass(ResourceConfig.class) + @ConditionalOnBean(ResourceConfig.class) + @ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet") + static class JerseyWebEndpointConfiguration { + + @Bean + public ResourceConfigCustomizer webEndpointRegistrar( + EndpointProvider provider, + ManagementServerProperties managementServerProperties) { + return (resourceConfig) -> resourceConfig.registerResources(new HashSet<>( + new JerseyEndpointResourceFactory().createEndpointResources( + managementServerProperties.getContextPath(), + provider.getEndpoints()))); + } + + } + + @Configuration + @ConditionalOnWebApplication(type = Type.SERVLET) + @ConditionalOnClass(DispatcherServlet.class) + @ConditionalOnBean(DispatcherServlet.class) + static class MvcWebEndpointConfiguration { + + private final List mappingCustomizers; + + MvcWebEndpointConfiguration( + ObjectProvider> mappingCustomizers) { + this.mappingCustomizers = mappingCustomizers + .getIfUnique(Collections::emptyList); + } + + @Bean + @ConditionalOnMissingBean + public WebEndpointServletHandlerMapping webEndpointServletHandlerMapping( + EndpointProvider provider, + EndpointCorsProperties corsProperties, + ManagementServerProperties managementServerProperties) { + WebEndpointServletHandlerMapping handlerMapping = new WebEndpointServletHandlerMapping( + managementServerProperties.getContextPath(), provider.getEndpoints(), + getCorsConfiguration(corsProperties)); + for (WebEndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) { + customizer.customize(handlerMapping); + } + return handlerMapping; + } + + private CorsConfiguration getCorsConfiguration( + EndpointCorsProperties properties) { + if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) { + return null; + } + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(properties.getAllowedOrigins()); + if (!CollectionUtils.isEmpty(properties.getAllowedHeaders())) { + configuration.setAllowedHeaders(properties.getAllowedHeaders()); + } + if (!CollectionUtils.isEmpty(properties.getAllowedMethods())) { + configuration.setAllowedMethods(properties.getAllowedMethods()); + } + if (!CollectionUtils.isEmpty(properties.getExposedHeaders())) { + configuration.setExposedHeaders(properties.getExposedHeaders()); + } + if (properties.getMaxAge() != null) { + configuration.setMaxAge(properties.getMaxAge()); + } + if (properties.getAllowCredentials() != null) { + configuration.setAllowCredentials(properties.getAllowCredentials()); + } + return configuration; + } + + } + + @ConditionalOnWebApplication(type = Type.REACTIVE) + static class ReactiveWebEndpointConfiguration { + + @Bean + @ConditionalOnMissingBean + public WebEndpointReactiveHandlerMapping webEndpointReactiveHandlerMapping( + EndpointProvider provider, + ManagementServerProperties managementServerProperties) { + return new WebEndpointReactiveHandlerMapping( + managementServerProperties.getContextPath(), provider.getEndpoints()); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/package-info.java new file mode 100644 index 0000000000..41a0bc3f57 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed 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 + * + * http://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. + */ + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} for the Actuator's endpoint infrastructure. + */ +package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java new file mode 100644 index 0000000000..34e15dde15 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.jmx; + +import org.springframework.boot.actuate.autoconfigure.endpoint.ConditionalOnEnabledEndpoint; +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; +import org.springframework.boot.actuate.endpoint.jmx.AuditEventsJmxEndpointExtension; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.endpoint.jmx.JmxEndpointExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto-configuration for JMX {@link org.springframework.boot.endpoint.Endpoint Endpoints} + * and JMX-specific {@link JmxEndpointExtension endpoint extensions}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +@AutoConfigureAfter(EndpointAutoConfiguration.class) +@Configuration +public class JmxEndpointAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + @ConditionalOnBean(AuditEventsEndpoint.class) + public AuditEventsJmxEndpointExtension auditEventsJmxEndpointExtension( + AuditEventsEndpoint auditEventsEndpoint) { + return new AuditEventsJmxEndpointExtension(auditEventsEndpoint); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/package-info.java similarity index 69% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/package-info.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/package-info.java index dfabe73189..81e2f4611d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/package-info.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ /** - * Auto-configuration for Jolokia. + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} for the Actuator's JMX endpoints. */ -package org.springframework.boot.actuate.autoconfigure.jolokia; +package org.springframework.boot.actuate.autoconfigure.endpoint.jmx; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablement.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablement.java new file mode 100644 index 0000000000..ea4fe14e7a --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablement.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.support; + +/** + * Determines if an endpoint is enabled or not. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public final class EndpointEnablement { + + private final boolean enabled; + private final String reason; + + /** + * Creates a new instance. + * @param enabled whether or not the endpoint is enabled + * @param reason a human readable reason of the decision + */ + EndpointEnablement(boolean enabled, String reason) { + this.enabled = enabled; + this.reason = reason; + } + + /** + * Return whether or not the endpoint is enabled. + * @return {@code true} if the endpoint is enabled, {@code false} otherwise + */ + public boolean isEnabled() { + return this.enabled; + } + + /** + * Return a human readable reason of the decision. + * @return the reason of the endpoint's enablement + */ + public String getReason() { + return this.reason; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProvider.java new file mode 100644 index 0000000000..65fc6c45d7 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProvider.java @@ -0,0 +1,169 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.support; + +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +/** + * Determines an endpoint's enablement based on the current {@link Environment}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public class EndpointEnablementProvider { + + private final Environment environment; + + /** + * Creates a new instance with the {@link Environment} to use. + * @param environment the environment + */ + public EndpointEnablementProvider(Environment environment) { + this.environment = environment; + } + + /** + * Return the {@link EndpointEnablement} of an endpoint with no specific tech + * exposure. + * @param endpointId the id of the endpoint + * @param enabledByDefault whether the endpoint is enabled by default or not + * @return the {@link EndpointEnablement} of that endpoint + */ + public EndpointEnablement getEndpointEnablement(String endpointId, + boolean enabledByDefault) { + return getEndpointEnablement(endpointId, enabledByDefault, null); + } + + /** + * Return the {@link EndpointEnablement} of an endpoint for a specific tech exposure. + * @param endpointId the id of the endpoint + * @param enabledByDefault whether the endpoint is enabled by default or not + * @param endpointType the requested {@link EndpointType} + * @return the {@link EndpointEnablement} of that endpoint for the specified {@link EndpointType} + */ + public EndpointEnablement getEndpointEnablement(String endpointId, + boolean enabledByDefault, EndpointType endpointType) { + if (!StringUtils.hasText(endpointId)) { + throw new IllegalArgumentException("Endpoint id must have a value"); + } + if (endpointId.equals("all")) { + throw new IllegalArgumentException("Endpoint id 'all' is a reserved value " + + "and cannot be used by an endpoint"); + } + + if (endpointType != null) { + String endpointTypeKey = createTechSpecificKey(endpointId, endpointType); + EndpointEnablement endpointTypeSpecificOutcome = getEnablementFor( + endpointTypeKey); + if (endpointTypeSpecificOutcome != null) { + return endpointTypeSpecificOutcome; + } + } + else { + // If any tech specific is on at this point we should enable the endpoint + EndpointEnablement anyTechSpecificOutcome = getAnyTechSpecificOutcomeFor( + endpointId); + if (anyTechSpecificOutcome != null) { + return anyTechSpecificOutcome; + } + } + String endpointKey = createKey(endpointId, "enabled"); + EndpointEnablement endpointSpecificOutcome = getEnablementFor(endpointKey); + if (endpointSpecificOutcome != null) { + return endpointSpecificOutcome; + } + + // All endpoints specific attributes have been looked at. Checking default value + // for the endpoint + if (!enabledByDefault) { + return new EndpointEnablement(false, + createDefaultEnablementMessage(endpointId, enabledByDefault, + endpointType)); + } + + if (endpointType != null) { + String globalTypeKey = createTechSpecificKey("all", endpointType); + EndpointEnablement globalTypeOutcome = getEnablementFor(globalTypeKey); + if (globalTypeOutcome != null) { + return globalTypeOutcome; + } + } + else { + // Check if there is a global tech required + EndpointEnablement anyTechGeneralOutcome = getAnyTechSpecificOutcomeFor("all"); + if (anyTechGeneralOutcome != null) { + return anyTechGeneralOutcome; + } + } + + String globalKey = createKey("all", "enabled"); + EndpointEnablement globalOutCome = getEnablementFor(globalKey); + if (globalOutCome != null) { + return globalOutCome; + } + return new EndpointEnablement(enabledByDefault, createDefaultEnablementMessage( + endpointId, enabledByDefault, endpointType)); + } + + private String createDefaultEnablementMessage(String endpointId, + boolean enabledByDefault, EndpointType endpointType) { + StringBuilder sb = new StringBuilder(); + sb.append(String.format("endpoint '%s' ", endpointId)); + if (endpointType != null) { + sb.append(String.format("(%s) ", endpointType.name().toLowerCase())); + } + sb.append(String.format("is %s by default", + (enabledByDefault ? "enabled" : "disabled"))); + return sb.toString(); + } + + private EndpointEnablement getAnyTechSpecificOutcomeFor(String endpointId) { + for (EndpointType endpointType : EndpointType.values()) { + String key = createTechSpecificKey(endpointId, endpointType); + EndpointEnablement outcome = getEnablementFor(key); + if (outcome != null && outcome.isEnabled()) { + return outcome; + } + } + return null; + } + + private String createTechSpecificKey(String endpointId, EndpointType endpointType) { + return createKey(endpointId, endpointType.name().toLowerCase() + ".enabled"); + } + + private String createKey(String endpointId, String suffix) { + return "endpoints." + endpointId + "." + suffix; + } + + /** + * Return an {@link EndpointEnablement} for the specified key if it is set or + * {@code null} if the key is not present in the environment. + * @param key the key to check + * @return the outcome or {@code null} if the key is no set + */ + private EndpointEnablement getEnablementFor(String key) { + if (this.environment.containsProperty(key)) { + boolean match = this.environment.getProperty(key, Boolean.class, true); + return new EndpointEnablement(match, String.format("found property %s", key)); + } + return null; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/package-info.java similarity index 81% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/package-info.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/package-info.java index e6ec0c3589..0e7ec1a12f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/package-info.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/package-info.java @@ -15,6 +15,6 @@ */ /** - * {@code @Condition} annotations and supporting classes. + * Support classes for the Actuator's endpoint auto-configuration. */ -package org.springframework.boot.actuate.condition; +package org.springframework.boot.actuate.autoconfigure.endpoint.support; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/HealthWebEndpointExtensionProperties.java similarity index 70% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointProperties.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/HealthWebEndpointExtensionProperties.java index e0a367895d..c8275c4690 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointProperties.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/HealthWebEndpointExtensionProperties.java @@ -14,35 +14,35 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.health; +package org.springframework.boot.actuate.autoconfigure.endpoint.web; import java.util.HashMap; import java.util.Map; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; +import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.http.HttpStatus; /** - * Configuration properties for the {@link HealthMvcEndpoint}. + * Configuration properties for the {@link HealthWebEndpointExtension}. * * @author Christian Dupuis + * @author Andy Wilkinson * @since 2.0.0 */ @ConfigurationProperties(prefix = "endpoints.health") -public class HealthMvcEndpointProperties { +public class HealthWebEndpointExtensionProperties { /** * Mapping of health statuses to HttpStatus codes. By default, registered health * statuses map to sensible defaults (i.e. UP maps to 200). */ - private Map mapping = new HashMap<>(); + private Map mapping = new HashMap<>(); - public Map getMapping() { + public Map getMapping() { return this.mapping; } - public void setMapping(Map mapping) { + public void setMapping(Map mapping) { this.mapping = mapping; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java new file mode 100644 index 0000000000..9d0f929c82 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.web; + +import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.ConditionalOnEnabledEndpoint; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; +import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension; +import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension; +import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint; +import org.springframework.boot.actuate.endpoint.web.LogFileWebEndpoint; +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.SearchStrategy; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.StringUtils; + +/** + * Configuration for web-specific endpoint functionality. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +@ManagementContextConfiguration +@EnableConfigurationProperties(HealthWebEndpointExtensionProperties.class) +public class WebEndpointManagementContextConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + public HeapDumpWebEndpoint heapDumpWebEndpoint() { + return new HeapDumpWebEndpoint(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + @ConditionalOnBean(value = HealthEndpoint.class, search = SearchStrategy.CURRENT) + public HealthWebEndpointExtension healthWebEndpointExtension(HealthEndpoint delegate, + HealthWebEndpointExtensionProperties extensionProperties) { + HealthWebEndpointExtension webExtension = new HealthWebEndpointExtension( + delegate); + if (extensionProperties.getMapping() != null) { + webExtension.addStatusMapping(extensionProperties.getMapping()); + } + return webExtension; + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + @ConditionalOnBean(value = AuditEventsEndpoint.class, search = SearchStrategy.CURRENT) + public AuditEventsWebEndpointExtension auditEventsWebEndpointExtension( + AuditEventsEndpoint delegate) { + return new AuditEventsWebEndpointExtension(delegate); + } + + @Bean + @ConditionalOnMissingBean + @Conditional(LogFileCondition.class) + public LogFileWebEndpoint logfileWebEndpoint(Environment environment) { + return new LogFileWebEndpoint(environment); + } + + private static class LogFileCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + Environment environment = context.getEnvironment(); + String config = environment.resolvePlaceholders("${logging.file:}"); + ConditionMessage.Builder message = ConditionMessage.forCondition("Log File"); + if (StringUtils.hasText(config)) { + return ConditionOutcome + .match(message.found("logging.file").items(config)); + } + config = environment.resolvePlaceholders("${logging.path:}"); + if (StringUtils.hasText(config)) { + return ConditionOutcome + .match(message.found("logging.path").items(config)); + } + config = environment.getProperty("endpoints.logfile.external-file"); + if (StringUtils.hasText(config)) { + return ConditionOutcome.match( + message.found("endpoints.logfile.external-file").items(config)); + } + return ConditionOutcome.noMatch(message.didNotFind("logging file").atAll()); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/package-info.java new file mode 100644 index 0000000000..9873d97fab --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed 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 + * + * http://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. + */ + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} for the Actuator's web endpoints. + */ +package org.springframework.boot.actuate.autoconfigure.endpoint.web; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/security/ManagementWebSecurityAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/security/ManagementWebSecurityAutoConfiguration.java index a3b593146b..c4e312b8eb 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/security/ManagementWebSecurityAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/security/ManagementWebSecurityAutoConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.security; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; @@ -28,9 +29,6 @@ import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.ManagementContextResolver; import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -51,6 +49,9 @@ import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SpringBootWebSecurityConfiguration; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.endpoint.EndpointInfo; +import org.springframework.boot.endpoint.web.WebEndpointOperation; +import org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; @@ -218,7 +219,8 @@ public class ManagementWebSecurityAutoConfiguration { AuthenticationEntryPoint entryPoint = entryPoint(); http.exceptionHandling().authenticationEntryPoint(entryPoint); // Match all the requests for actuator endpoints ... - http.requestMatcher(matcher); + http.requestMatcher(matcher).authorizeRequests().anyRequest() + .authenticated(); http.httpBasic().authenticationEntryPoint(entryPoint).and().cors(); // No cookies for management endpoints by default http.csrf().disable(); @@ -256,20 +258,21 @@ public class ManagementWebSecurityAutoConfiguration { private static class EndpointPaths { - public String[] getPaths(EndpointHandlerMapping endpointHandlerMapping) { + public String[] getPaths( + WebEndpointServletHandlerMapping endpointHandlerMapping) { if (endpointHandlerMapping == null) { return NO_PATHS; } - Set endpoints = endpointHandlerMapping.getEndpoints(); + Collection> endpoints = endpointHandlerMapping + .getEndpoints(); Set paths = new LinkedHashSet<>(endpoints.size()); - for (MvcEndpoint endpoint : endpoints) { - String path = endpointHandlerMapping.getPath(endpoint.getPath()); + for (EndpointInfo endpoint : endpoints) { + String path = endpointHandlerMapping.getEndpointPath() + "/" + + endpoint.getId(); paths.add(path); - if (!path.equals("")) { - paths.add(path + "/**"); - // Add Spring MVC-generated additional paths - paths.add(path + ".*"); - } + paths.add(path + "/**"); + // Add Spring MVC-generated additional paths + paths.add(path + ".*"); paths.add(path + "/"); } return paths.toArray(new String[paths.size()]); @@ -300,7 +303,7 @@ public class ManagementWebSecurityAutoConfiguration { server.getServlet().getPath(path) + "/**"); return matcher; } - // Match everything, including the sensitive and non-sensitive paths + // Match all endpoint paths return new LazyEndpointPathRequestMatcher(contextResolver, new EndpointPaths()); } @@ -323,7 +326,7 @@ public class ManagementWebSecurityAutoConfiguration { ServerProperties server = this.contextResolver.getApplicationContext() .getBean(ServerProperties.class); List matchers = new ArrayList<>(); - EndpointHandlerMapping endpointHandlerMapping = getRequiredEndpointHandlerMapping(); + WebEndpointServletHandlerMapping endpointHandlerMapping = getRequiredEndpointHandlerMapping(); for (String path : this.endpointPaths.getPaths(endpointHandlerMapping)) { matchers.add( new AntPathRequestMatcher(server.getServlet().getPath(path))); @@ -331,16 +334,18 @@ public class ManagementWebSecurityAutoConfiguration { return (matchers.isEmpty() ? MATCH_NONE : new OrRequestMatcher(matchers)); } - private EndpointHandlerMapping getRequiredEndpointHandlerMapping() { - EndpointHandlerMapping endpointHandlerMapping = null; + private WebEndpointServletHandlerMapping getRequiredEndpointHandlerMapping() { + WebEndpointServletHandlerMapping endpointHandlerMapping = null; ApplicationContext context = this.contextResolver.getApplicationContext(); - if (context.getBeanNamesForType(EndpointHandlerMapping.class).length > 0) { - endpointHandlerMapping = context.getBean(EndpointHandlerMapping.class); + if (context.getBeanNamesForType( + WebEndpointServletHandlerMapping.class).length > 0) { + endpointHandlerMapping = context + .getBean(WebEndpointServletHandlerMapping.class); } if (endpointHandlerMapping == null) { // Maybe there are actually no endpoints (e.g. management.port=-1) - endpointHandlerMapping = new EndpointHandlerMapping( - Collections.emptySet()); + endpointHandlerMapping = new WebEndpointServletHandlerMapping("", + Collections.emptySet()); } return endpointHandlerMapping; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpoint.java index a1ae48e7dd..f487938dda 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpoint.java @@ -16,75 +16,62 @@ package org.springframework.boot.actuate.cloudfoundry; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.boot.actuate.endpoint.mvc.AbstractMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; - /** * {@link MvcEndpoint} to expose HAL-formatted JSON for Cloud Foundry specific actuator * endpoints. * * @author Madhura Bhave */ -class CloudFoundryDiscoveryMvcEndpoint extends AbstractMvcEndpoint { +class CloudFoundryDiscoveryMvcEndpoint { - private final Set endpoints; + // TODO Port to new infrastructure - CloudFoundryDiscoveryMvcEndpoint(Set endpoints) { - super(""); - this.endpoints = endpoints; - } - - @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public Map> links(HttpServletRequest request) { - Map links = new LinkedHashMap<>(); - String url = request.getRequestURL().toString(); - if (url.endsWith("/")) { - url = url.substring(0, url.length() - 1); - } - links.put("self", Link.withHref(url)); - AccessLevel accessLevel = AccessLevel.get(request); - for (NamedMvcEndpoint endpoint : this.endpoints) { - if (accessLevel != null && accessLevel.isAccessAllowed(endpoint.getPath())) { - links.put(endpoint.getName(), - Link.withHref(url + "/" + endpoint.getName())); - } - } - return Collections.singletonMap("_links", links); - } - - /** - * Details for a link in the HAL response. - */ - static class Link { - - private String href; - - public String getHref() { - return this.href; - } - - public void setHref(String href) { - this.href = href; - } - - static Link withHref(Object href) { - Link link = new Link(); - link.setHref(href.toString()); - return link; - } - - } + // private final Set endpoints; + // + // CloudFoundryDiscoveryMvcEndpoint(Set endpoints) { + // this.endpoints = endpoints; + // } + // + // @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) + // @ResponseBody + // public Map> links(HttpServletRequest request) { + // Map links = new LinkedHashMap<>(); + // String url = request.getRequestURL().toString(); + // if (url.endsWith("/")) { + // url = url.substring(0, url.length() - 1); + // } + // links.put("self", Link.withHref(url)); + // AccessLevel accessLevel = AccessLevel.get(request); + // for (NamedMvcEndpoint endpoint : this.endpoints) { + // if (accessLevel != null && accessLevel.isAccessAllowed(endpoint.getPath())) { + // links.put(endpoint.getName(), + // Link.withHref(url + "/" + endpoint.getName())); + // } + // } + // return Collections.singletonMap("_links", links); + // } + // + // /** + // * Details for a link in the HAL response. + // */ + // static class Link { + // + // private String href; + // + // public String getHref() { + // return this.href; + // } + // + // public void setHref(String href) { + // this.href = href; + // } + // + // static Link withHref(Object href) { + // Link link = new Link(); + // link.setHref(href.toString()); + // return link; + // } + // + // } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMapping.java deleted file mode 100644 index 42faa7cdee..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMapping.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.cloudfoundry; - -import java.util.Iterator; -import java.util.Set; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointHandlerMapping; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.HandlerMapping; - -/** - * {@link HandlerMapping} to map {@link Endpoint}s to Cloud Foundry specific URLs. - * - * @author Madhura Bhave - * @since 2.0.0 - */ -public class CloudFoundryEndpointHandlerMapping - extends AbstractEndpointHandlerMapping { - - public CloudFoundryEndpointHandlerMapping(Set endpoints, - CorsConfiguration corsConfiguration, HandlerInterceptor securityInterceptor) { - super(endpoints, corsConfiguration); - setSecurityInterceptor(securityInterceptor); - } - - @Override - protected void postProcessEndpoints(Set endpoints) { - super.postProcessEndpoints(endpoints); - Iterator iterator = endpoints.iterator(); - HealthMvcEndpoint healthMvcEndpoint = null; - while (iterator.hasNext()) { - NamedMvcEndpoint endpoint = iterator.next(); - if (endpoint instanceof HealthMvcEndpoint) { - iterator.remove(); - healthMvcEndpoint = (HealthMvcEndpoint) endpoint; - } - } - if (healthMvcEndpoint != null) { - endpoints.add( - new CloudFoundryHealthMvcEndpoint(healthMvcEndpoint.getDelegate())); - } - } - - @Override - public void afterPropertiesSet() { - super.afterPropertiesSet(); - detectHandlerMethods(new CloudFoundryDiscoveryMvcEndpoint(getEndpoints())); - } - - @Override - protected String getPath(MvcEndpoint endpoint) { - if (endpoint instanceof NamedMvcEndpoint) { - return "/" + ((NamedMvcEndpoint) endpoint).getName(); - } - return super.getPath(endpoint); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpoint.java index 49c4e10974..1b5e202f4d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpoint.java @@ -16,31 +16,29 @@ package org.springframework.boot.actuate.cloudfoundry; -import java.security.Principal; - -import javax.servlet.http.HttpServletRequest; - import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; +import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension; /** - * Extension of {@link HealthMvcEndpoint} for Cloud Foundry. Since security for Cloud - * Foundry actuators is already handled by the {@link CloudFoundrySecurityInterceptor}, - * this endpoint skips the additional security checks done by the regular - * {@link HealthMvcEndpoint}. + * Extension of {@link HealthWebEndpointExtension} for Cloud Foundry. Since security for + * Cloud Foundry actuators is already handled by the + * {@link CloudFoundrySecurityInterceptor}, this endpoint skips the additional security + * checks done by the regular {@link HealthWebEndpointExtension}. * * @author Madhura Bhave */ -class CloudFoundryHealthMvcEndpoint extends HealthMvcEndpoint { +class CloudFoundryHealthMvcEndpoint extends HealthWebEndpointExtension { + + // TODO Port to new infrastructure CloudFoundryHealthMvcEndpoint(HealthEndpoint delegate) { super(delegate); } - - @Override - protected boolean exposeHealthDetails(HttpServletRequest request, - Principal principal) { - return true; - } + // + // @Override + // protected boolean exposeHealthDetails(HttpServletRequest request, + // Principal principal) { + // return true; + // } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java index 9e12a28861..3a766085a7 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java @@ -23,12 +23,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.util.StringUtils; -import org.springframework.web.cors.CorsUtils; -import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @@ -57,52 +51,54 @@ public class CloudFoundrySecurityInterceptor extends HandlerInterceptorAdapter { this.applicationId = applicationId; } + // TODO Port to new infrastructure + @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - if (CorsUtils.isPreFlightRequest(request)) { - return true; - } - try { - if (!StringUtils.hasText(this.applicationId)) { - throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, - "Application id is not available"); - } - if (this.cloudFoundrySecurityService == null) { - throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, - "Cloud controller URL is not available"); - } - HandlerMethod handlerMethod = (HandlerMethod) handler; - if (HttpMethod.OPTIONS.matches(request.getMethod()) - && !(handlerMethod.getBean() instanceof MvcEndpoint)) { - return true; - } - MvcEndpoint mvcEndpoint = (MvcEndpoint) handlerMethod.getBean(); - check(request, mvcEndpoint); - } - catch (CloudFoundryAuthorizationException ex) { - logger.error(ex); - response.setContentType(MediaType.APPLICATION_JSON.toString()); - response.getWriter() - .write("{\"security_error\":\"" + ex.getMessage() + "\"}"); - response.setStatus(ex.getStatusCode().value()); - return false; - } + // if (CorsUtils.isPreFlightRequest(request)) { + // return true; + // } + // try { + // if (!StringUtils.hasText(this.applicationId)) { + // throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, + // "Application id is not available"); + // } + // if (this.cloudFoundrySecurityService == null) { + // throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, + // "Cloud controller URL is not available"); + // } + // HandlerMethod handlerMethod = (HandlerMethod) handler; + // if (HttpMethod.OPTIONS.matches(request.getMethod()) + // && !(handlerMethod.getBean() instanceof MvcEndpoint)) { + // return true; + // } + // MvcEndpoint mvcEndpoint = (MvcEndpoint) handlerMethod.getBean(); + // check(request, mvcEndpoint); + // } + // catch (CloudFoundryAuthorizationException ex) { + // logger.error(ex); + // response.setContentType(MediaType.APPLICATION_JSON.toString()); + // response.getWriter() + // .write("{\"security_error\":\"" + ex.getMessage() + "\"}"); + // response.setStatus(ex.getStatusCode().value()); + // return false; + // } return true; } - private void check(HttpServletRequest request, MvcEndpoint mvcEndpoint) - throws Exception { - Token token = getToken(request); - this.tokenValidator.validate(token); - AccessLevel accessLevel = this.cloudFoundrySecurityService - .getAccessLevel(token.toString(), this.applicationId); - if (!accessLevel.isAccessAllowed(mvcEndpoint.getPath())) { - throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED, - "Access denied"); - } - accessLevel.put(request); - } + // private void check(HttpServletRequest request, MvcEndpoint mvcEndpoint) + // throws Exception { + // Token token = getToken(request); + // this.tokenValidator.validate(token); + // AccessLevel accessLevel = this.cloudFoundrySecurityService + // .getAccessLevel(token.toString(), this.applicationId); + // if (!accessLevel.isAccessAllowed(mvcEndpoint.getPath())) { + // throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED, + // "Access denied"); + // } + // accessLevel.put(request); + // } private Token getToken(HttpServletRequest request) { String authorization = request.getHeader("Authorization"); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/ConditionalOnEnabledEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/ConditionalOnEnabledEndpoint.java deleted file mode 100644 index 1363e1fc8a..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/ConditionalOnEnabledEndpoint.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.condition; - -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; - -/** - * {@link Conditional} that checks whether or not an endpoint is enabled. Matches if the - * value of the {@code endpoints..enabled} property is {@code true}. Does not match - * if the property's value or {@code enabledByDefault} is {@code false}. Otherwise, - * matches if the value of the {@code endpoints.enabled} property is {@code true} or if - * the property is not configured. - * - * @author Andy Wilkinson - * @since 1.2.4 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.METHOD, ElementType.TYPE }) -@Conditional(OnEnabledEndpointCondition.class) -@Documented -public @interface ConditionalOnEnabledEndpoint { - - /** - * The name of the endpoint. - * @return The name of the endpoint - */ - String value(); - - /** - * Returns whether or not the endpoint is enabled by default. - * @return {@code true} if the endpoint is enabled by default, otherwise {@code false} - */ - boolean enabledByDefault() default true; - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java deleted file mode 100644 index 6f0df8ea88..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.condition; - -import org.springframework.boot.autoconfigure.condition.ConditionMessage; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.context.annotation.Condition; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.env.Environment; -import org.springframework.core.type.AnnotatedTypeMetadata; - -/** - * {@link Condition} that checks whether or not an endpoint is enabled. - * - * @author Andy Wilkinson - * @author Madhura Bhave - */ -class OnEnabledEndpointCondition extends SpringBootCondition { - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, - AnnotatedTypeMetadata metadata) { - AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(metadata - .getAnnotationAttributes(ConditionalOnEnabledEndpoint.class.getName())); - String endpointName = annotationAttributes.getString("value"); - boolean enabledByDefault = annotationAttributes.getBoolean("enabledByDefault"); - ConditionOutcome outcome = determineEndpointOutcome(endpointName, - enabledByDefault, context); - if (outcome != null) { - return outcome; - } - return determineAllEndpointsOutcome(context); - } - - private ConditionOutcome determineEndpointOutcome(String endpointName, - boolean enabledByDefault, ConditionContext context) { - Environment environment = context.getEnvironment(); - String enabledProperty = "endpoints." + endpointName + ".enabled"; - if (environment.containsProperty(enabledProperty) || !enabledByDefault) { - boolean match = environment.getProperty(enabledProperty, Boolean.class, - enabledByDefault); - ConditionMessage message = ConditionMessage - .forCondition(ConditionalOnEnabledEndpoint.class, - "(" + endpointName + ")") - .because(match ? "enabled" : "disabled"); - return new ConditionOutcome(match, message); - } - return null; - } - - private ConditionOutcome determineAllEndpointsOutcome(ConditionContext context) { - boolean match = Boolean.valueOf( - context.getEnvironment().getProperty("endpoints.enabled", "true")); - ConditionMessage message = ConditionMessage - .forCondition(ConditionalOnEnabledEndpoint.class) - .because("All endpoints are " + (match ? "enabled" : "disabled") - + " by default"); - return new ConditionOutcome(match, message); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractEndpoint.java deleted file mode 100644 index b3debdedf6..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractEndpoint.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint; - -import java.util.regex.Pattern; - -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; -import org.springframework.util.Assert; - -/** - * Abstract base for {@link Endpoint} implementations. - * - * @param the endpoint data type - * @author Phillip Webb - * @author Christian Dupuis - */ -public abstract class AbstractEndpoint implements Endpoint, EnvironmentAware { - - private static final Pattern ID_PATTERN = Pattern.compile("\\w+"); - - private Environment environment; - - /** - * Endpoint identifier. With HTTP monitoring the identifier of the endpoint is mapped - * to a URL (e.g. 'foo' is mapped to '/foo'). - */ - private String id; - - /** - * Enable the endpoint. - */ - private Boolean enabled; - - /** - * Create a new endpoint instance. The endpoint will enabled flag will be based on the - * spring {@link Environment} unless explicitly set. - * @param id the endpoint ID - */ - public AbstractEndpoint(String id) { - setId(id); - } - - /** - * Create a new endpoint instance. - * @param id the endpoint ID - * @param enabled if the endpoint is enabled or not. - */ - public AbstractEndpoint(String id, boolean enabled) { - setId(id); - this.enabled = enabled; - } - - protected final Environment getEnvironment() { - return this.environment; - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - - @Override - public String getId() { - return this.id; - } - - public void setId(String id) { - Assert.notNull(id, "Id must not be null"); - Assert.isTrue(ID_PATTERN.matcher(id).matches(), - "Id must only contains letters, numbers and '_'"); - this.id = id; - } - - @Override - public boolean isEnabled() { - return EndpointProperties.isEnabled(this.environment, this.enabled); - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpoint.java new file mode 100644 index 0000000000..9468dad189 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpoint.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint; + +import java.util.Date; +import java.util.List; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.util.Assert; + +/** + * {@link Endpoint} to expose audit events. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +@Endpoint(id = "auditevents") +public class AuditEventsEndpoint { + + private final AuditEventRepository auditEventRepository; + + public AuditEventsEndpoint(AuditEventRepository auditEventRepository) { + Assert.notNull(auditEventRepository, "AuditEventRepository must not be null"); + this.auditEventRepository = auditEventRepository; + } + + @ReadOperation + public List eventsWithPrincipalDateAfterAndType(String principal, + Date after, String type) { + return this.auditEventRepository.find(principal, after, type); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java index 313a23d605..b80f99ec81 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java @@ -26,13 +26,12 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint.Report; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; import org.springframework.context.annotation.Condition; import org.springframework.util.ClassUtils; import org.springframework.util.LinkedMultiValueMap; @@ -47,19 +46,19 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @author Andy Wilkinson */ -@ConfigurationProperties(prefix = "endpoints.autoconfig") -public class AutoConfigurationReportEndpoint extends AbstractEndpoint { +@Endpoint(id = "autoconfig") +public class AutoConfigurationReportEndpoint { - @Autowired - private ConditionEvaluationReport autoConfigurationReport; + private final ConditionEvaluationReport conditionEvaluationReport; - public AutoConfigurationReportEndpoint() { - super("autoconfig"); + public AutoConfigurationReportEndpoint( + ConditionEvaluationReport conditionEvaluationReport) { + this.conditionEvaluationReport = conditionEvaluationReport; } - @Override - public Report invoke() { - return new Report(this.autoConfigurationReport); + @ReadOperation + public Report getEvaluationReport() { + return new Report(this.conditionEvaluationReport); } /** diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/BeansEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/BeansEndpoint.java index e646f4580d..54b7e79939 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/BeansEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/BeansEndpoint.java @@ -21,7 +21,8 @@ import java.util.List; import java.util.Set; import org.springframework.beans.BeansException; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; import org.springframework.boot.json.JsonParser; import org.springframework.boot.json.JsonParserFactory; import org.springframework.context.ApplicationContext; @@ -40,18 +41,13 @@ import org.springframework.util.Assert; * @author Dave Syer * @author Andy Wilkinson */ -@ConfigurationProperties(prefix = "endpoints.beans") -public class BeansEndpoint extends AbstractEndpoint> - implements ApplicationContextAware { +@Endpoint(id = "beans") +public class BeansEndpoint implements ApplicationContextAware { private final HierarchyAwareLiveBeansView liveBeansView = new HierarchyAwareLiveBeansView(); private final JsonParser parser = JsonParserFactory.getJsonParser(); - public BeansEndpoint() { - super("beans"); - } - @Override public void setApplicationContext(ApplicationContext context) throws BeansException { if (context.getEnvironment() @@ -60,8 +56,8 @@ public class BeansEndpoint extends AbstractEndpoint> } } - @Override - public List invoke() { + @ReadOperation + public List beans() { return this.parser.parseList(this.liveBeansView.getSnapshotAsJson()); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpoint.java index 8934b42a8b..fa8d5264b2 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpoint.java @@ -42,6 +42,8 @@ import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import org.springframework.beans.BeansException; import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.util.ClassUtils; @@ -61,9 +63,9 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @author Stephane Nicoll */ -@ConfigurationProperties(prefix = "endpoints.configprops") -public class ConfigurationPropertiesReportEndpoint - extends AbstractEndpoint> implements ApplicationContextAware { +@Endpoint(id = "configprops") +@ConfigurationProperties("endpoints.configprops") +public class ConfigurationPropertiesReportEndpoint implements ApplicationContextAware { private static final String CGLIB_FILTER_ID = "cglibFilter"; @@ -71,10 +73,6 @@ public class ConfigurationPropertiesReportEndpoint private ApplicationContext context; - public ConfigurationPropertiesReportEndpoint() { - super("configprops"); - } - @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; @@ -84,8 +82,8 @@ public class ConfigurationPropertiesReportEndpoint this.sanitizer.setKeysToSanitize(keysToSanitize); } - @Override - public Map invoke() { + @ReadOperation + public Map configurationProperties() { return extract(this.context); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Endpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Endpoint.java deleted file mode 100644 index 3c2647ba3f..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Endpoint.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2012-2015 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint; - -/** - * An endpoint that can be used to expose useful information to operations. Usually - * exposed via Spring MVC but could also be exposed using some other technique. Consider - * extending {@link AbstractEndpoint} if you are developing your own endpoint. - * - * @param the endpoint data type - * @author Phillip Webb - * @author Dave Syer - * @author Christian Dupuis - * @see AbstractEndpoint - */ -public interface Endpoint { - - /** - * The logical ID of the endpoint. Must only contain simple letters, numbers and '_' - * characters (i.e. a {@literal "\w"} regex). - * @return the endpoint ID - */ - String getId(); - - /** - * Return if the endpoint is enabled. - * @return if the endpoint is enabled - */ - boolean isEnabled(); - - /** - * Called to invoke the endpoint. - * @return the results of the invocation - */ - T invoke(); - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java index ab77de36f1..960a2ba183 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java @@ -21,11 +21,15 @@ import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Stream; -import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor.PropertyValueDescriptor; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.endpoint.Selector; import org.springframework.boot.origin.OriginLookup; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; @@ -37,6 +41,9 @@ import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySourcesPropertyResolver; import org.springframework.core.env.StandardEnvironment; +import org.springframework.http.HttpStatus; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.ResponseStatus; /** * {@link Endpoint} to expose {@link ConfigurableEnvironment environment} information. @@ -46,42 +53,54 @@ import org.springframework.core.env.StandardEnvironment; * @author Christian Dupuis * @author Madhura Bhave */ -@ConfigurationProperties(prefix = "endpoints.env") -public class EnvironmentEndpoint extends AbstractEndpoint { +@Endpoint(id = "env") +public class EnvironmentEndpoint { private final Sanitizer sanitizer = new Sanitizer(); - /** - * Create a new {@link EnvironmentEndpoint} instance. - */ - public EnvironmentEndpoint() { - super("env"); + private final Environment environment; + + public EnvironmentEndpoint(Environment environment) { + this.environment = environment; } public void setKeysToSanitize(String... keysToSanitize) { this.sanitizer.setKeysToSanitize(keysToSanitize); } - @Override - public EnvironmentDescriptor invoke() { + @ReadOperation + public EnvironmentDescriptor environment(String pattern) { + if (StringUtils.hasText(pattern)) { + return environment(Pattern.compile(pattern).asPredicate()); + } + return environment((name) -> true); + } + + @ReadOperation + public Object getEnvironmentEntry(@Selector String toMatch) { + return environment((name) -> toMatch.equals(name)); + } + + private EnvironmentDescriptor environment(Predicate propertyNamePredicate) { PropertyResolver resolver = getResolver(); List propertySources = new ArrayList<>(); getPropertySourcesAsMap().forEach((sourceName, source) -> { if (source instanceof EnumerablePropertySource) { - propertySources.add(describeSource(sourceName, - (EnumerablePropertySource) source, resolver)); + propertySources.add( + describeSource(sourceName, (EnumerablePropertySource) source, + resolver, propertyNamePredicate)); } }); return new EnvironmentDescriptor( - Arrays.asList(getEnvironment().getActiveProfiles()), propertySources); + Arrays.asList(this.environment.getActiveProfiles()), propertySources); } private PropertySourceDescriptor describeSource(String sourceName, - EnumerablePropertySource source, PropertyResolver resolver) { + EnumerablePropertySource source, PropertyResolver resolver, + Predicate namePredicate) { Map properties = new LinkedHashMap<>(); - for (String name : source.getPropertyNames()) { - properties.put(name, describeValueOf(name, source, resolver)); - } + Stream.of(source.getPropertyNames()).filter(namePredicate).forEach( + (name) -> properties.put(name, describeValueOf(name, source, resolver))); return new PropertySourceDescriptor(sourceName, properties); } @@ -94,7 +113,7 @@ public class EnvironmentEndpoint extends AbstractEndpoint return new PropertyValueDescriptor(sanitize(name, resolved), origin); } - public PropertyResolver getResolver() { + private PropertyResolver getResolver() { PlaceholderSanitizingPropertyResolver resolver = new PlaceholderSanitizingPropertyResolver( getPropertySources(), this.sanitizer); resolver.setIgnoreUnresolvableNestedPlaceholders(true); @@ -111,9 +130,8 @@ public class EnvironmentEndpoint extends AbstractEndpoint private MutablePropertySources getPropertySources() { MutablePropertySources sources; - Environment environment = getEnvironment(); - if (environment != null && environment instanceof ConfigurableEnvironment) { - sources = ((ConfigurableEnvironment) environment).getPropertySources(); + if (this.environment instanceof ConfigurableEnvironment) { + sources = ((ConfigurableEnvironment) this.environment).getPropertySources(); } else { sources = new StandardEnvironment().getPropertySources(); @@ -239,4 +257,17 @@ public class EnvironmentEndpoint extends AbstractEndpoint } } + /** + * Exception thrown when the specified property cannot be found. + */ + @SuppressWarnings("serial") + @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such property") + public static class NoSuchPropertyException extends RuntimeException { + + public NoSuchPropertyException(String string) { + super(string); + } + + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java index 4280ad2497..dc1d839931 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java @@ -28,8 +28,8 @@ import org.flywaydb.core.api.MigrationInfo; import org.flywaydb.core.api.MigrationState; import org.flywaydb.core.api.MigrationType; -import org.springframework.boot.actuate.endpoint.FlywayEndpoint.FlywayReport; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; import org.springframework.util.Assert; /** @@ -40,19 +40,18 @@ import org.springframework.util.Assert; * @author Andy Wilkinson * @since 1.3.0 */ -@ConfigurationProperties(prefix = "endpoints.flyway") -public class FlywayEndpoint extends AbstractEndpoint> { +@Endpoint(id = "flyway") +public class FlywayEndpoint { private final Map flyways; public FlywayEndpoint(Map flyways) { - super("flyway"); Assert.notEmpty(flyways, "Flyways must be specified"); this.flyways = flyways; } - @Override - public Map invoke() { + @ReadOperation + public Map flywayReports() { Map reports = new HashMap<>(); for (Map.Entry entry : this.flyways.entrySet()) { reports.put(entry.getKey(), diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java index 3a62ed7801..a352d57b60 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java @@ -22,7 +22,8 @@ import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; import org.springframework.util.Assert; /** @@ -32,16 +33,11 @@ import org.springframework.util.Assert; * @author Christian Dupuis * @author Andy Wilkinson */ -@ConfigurationProperties(prefix = "endpoints.health") -public class HealthEndpoint extends AbstractEndpoint { +@Endpoint(id = "health") +public class HealthEndpoint { private final HealthIndicator healthIndicator; - /** - * Time to live for cached result, in milliseconds. - */ - private long timeToLive = 1000; - /** * Create a new {@link HealthEndpoint} instance. * @param healthAggregator the health aggregator @@ -49,7 +45,6 @@ public class HealthEndpoint extends AbstractEndpoint { */ public HealthEndpoint(HealthAggregator healthAggregator, Map healthIndicators) { - super("health"); Assert.notNull(healthAggregator, "HealthAggregator must not be null"); Assert.notNull(healthIndicators, "HealthIndicators must not be null"); CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator( @@ -60,28 +55,8 @@ public class HealthEndpoint extends AbstractEndpoint { this.healthIndicator = healthIndicator; } - /** - * Time to live for cached result. This is particularly useful to cache the result of - * this endpoint to prevent a DOS attack if it is accessed anonymously. - * @return time to live in milliseconds (default 1000) - */ - public long getTimeToLive() { - return this.timeToLive; - } - - /** - * Set the time to live for cached results. - * @param timeToLive the time to live in milliseconds - */ - public void setTimeToLive(long timeToLive) { - this.timeToLive = timeToLive; - } - - /** - * Invoke all {@link HealthIndicator} delegates and collect their health information. - */ - @Override - public Health invoke() { + @ReadOperation + public Health health() { return this.healthIndicator.health(); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java index 40cef3f38b..16d779a49a 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java @@ -21,7 +21,8 @@ import java.util.Map; import org.springframework.boot.actuate.info.Info; import org.springframework.boot.actuate.info.InfoContributor; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; import org.springframework.util.Assert; /** @@ -31,8 +32,8 @@ import org.springframework.util.Assert; * @author Meang Akira Tanaka * @author Stephane Nicoll */ -@ConfigurationProperties(prefix = "endpoints.info") -public class InfoEndpoint extends AbstractEndpoint> { +@Endpoint(id = "info") +public class InfoEndpoint { private final List infoContributors; @@ -41,13 +42,12 @@ public class InfoEndpoint extends AbstractEndpoint> { * @param infoContributors the info contributors to use */ public InfoEndpoint(List infoContributors) { - super("info"); Assert.notNull(infoContributors, "Info contributors must not be null"); this.infoContributors = infoContributors; } - @Override - public Map invoke() { + @ReadOperation + public Map info() { Info.Builder builder = new Info.Builder(); for (InfoContributor contributor : this.infoContributors) { contributor.contribute(builder); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java index c36d989ca6..520b799d5c 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.endpoint; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -35,8 +34,8 @@ import liquibase.database.DatabaseFactory; import liquibase.database.jvm.JdbcConnection; import liquibase.integration.spring.SpringLiquibase; -import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint.LiquibaseReport; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -46,23 +45,18 @@ import org.springframework.util.StringUtils; * @author EddĂș MelĂ©ndez * @since 1.3.0 */ -@ConfigurationProperties(prefix = "endpoints.liquibase") -public class LiquibaseEndpoint extends AbstractEndpoint> { +@Endpoint(id = "liquibase") +public class LiquibaseEndpoint { private final Map liquibases; - public LiquibaseEndpoint(SpringLiquibase liquibase) { - this(Collections.singletonMap("default", liquibase)); - } - public LiquibaseEndpoint(Map liquibases) { - super("liquibase"); Assert.notEmpty(liquibases, "Liquibases must be specified"); this.liquibases = liquibases; } - @Override - public Map invoke() { + @ReadOperation + public Map liquibaseReports() { Map reports = new HashMap<>(); DatabaseFactory factory = DatabaseFactory.getInstance(); StandardChangeLogHistoryService service = new StandardChangeLogHistoryService(); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java index 7fe2270fef..3d2af7fc70 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java @@ -24,7 +24,10 @@ import java.util.NavigableSet; import java.util.Set; import java.util.TreeSet; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.endpoint.Selector; +import org.springframework.boot.endpoint.WriteOperation; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggingSystem; @@ -37,8 +40,8 @@ import org.springframework.util.Assert; * @author Phillip Webb * @since 1.5.0 */ -@ConfigurationProperties(prefix = "endpoints.loggers") -public class LoggersEndpoint extends AbstractEndpoint> { +@Endpoint(id = "loggers") +public class LoggersEndpoint { private final LoggingSystem loggingSystem; @@ -47,13 +50,12 @@ public class LoggersEndpoint extends AbstractEndpoint> { * @param loggingSystem the logging system to expose */ public LoggersEndpoint(LoggingSystem loggingSystem) { - super("loggers"); Assert.notNull(loggingSystem, "LoggingSystem must not be null"); this.loggingSystem = loggingSystem; } - @Override - public Map invoke() { + @ReadOperation + public Map loggers() { Collection configurations = this.loggingSystem .getLoggerConfigurations(); if (configurations == null) { @@ -65,6 +67,20 @@ public class LoggersEndpoint extends AbstractEndpoint> { return result; } + @ReadOperation + public LoggerLevels loggerLevels(@Selector String name) { + Assert.notNull(name, "Name must not be null"); + LoggerConfiguration configuration = this.loggingSystem + .getLoggerConfiguration(name); + return (configuration == null ? null : new LoggerLevels(configuration)); + } + + @WriteOperation + public void configureLogLevel(@Selector String name, LogLevel configuredLevel) { + Assert.notNull(name, "Name must not be empty"); + this.loggingSystem.setLogLevel(name, configuredLevel); + } + private NavigableSet getLevels() { Set levels = this.loggingSystem.getSupportedLogLevels(); return new TreeSet<>(levels).descendingSet(); @@ -79,18 +95,6 @@ public class LoggersEndpoint extends AbstractEndpoint> { return loggers; } - public LoggerLevels invoke(String name) { - Assert.notNull(name, "Name must not be null"); - LoggerConfiguration configuration = this.loggingSystem - .getLoggerConfiguration(name); - return (configuration == null ? null : new LoggerLevels(configuration)); - } - - public void setLogLevel(String name, LogLevel level) { - Assert.notNull(name, "Name must not be empty"); - this.loggingSystem.setLogLevel(name, level); - } - /** * Levels configured for a given logger exposed in a JSON friendly way. */ diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java index 612cdefa8a..0631679715 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java @@ -22,19 +22,24 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Pattern; import org.springframework.boot.actuate.metrics.Metric; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.endpoint.Selector; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * {@link Endpoint} to expose a collection of {@link PublicMetrics}. * * @author Dave Syer */ -@ConfigurationProperties(prefix = "endpoints.metrics") -public class MetricsEndpoint extends AbstractEndpoint> { +@Endpoint(id = "metrics") +public class MetricsEndpoint { private final List publicMetrics; @@ -52,7 +57,6 @@ public class MetricsEndpoint extends AbstractEndpoint> { * {@link AnnotationAwareOrderComparator}. */ public MetricsEndpoint(Collection publicMetrics) { - super("metrics"); Assert.notNull(publicMetrics, "PublicMetrics must not be null"); this.publicMetrics = new ArrayList<>(publicMetrics); AnnotationAwareOrderComparator.sort(this.publicMetrics); @@ -67,14 +71,31 @@ public class MetricsEndpoint extends AbstractEndpoint> { this.publicMetrics.remove(metrics); } - @Override - public Map invoke() { + @ReadOperation + public Map metrics(String pattern) { + return metrics(StringUtils.hasText(pattern) + ? Pattern.compile(pattern).asPredicate() : (name) -> true); + } + + @ReadOperation + public Map metricNamed(@Selector String requiredName) { + Map metrics = metrics((name) -> name.equals(requiredName)); + if (metrics.isEmpty()) { + return null; + } + return metrics; + } + + private Map metrics(Predicate namePredicate) { Map result = new LinkedHashMap<>(); List metrics = new ArrayList<>(this.publicMetrics); for (PublicMetrics publicMetric : metrics) { try { for (Metric metric : publicMetric.metrics()) { - result.put(metric.getName(), metric.getValue()); + if (namePredicate.test(metric.getName()) + && metric.getValue() != null) { + result.put(metric.getName(), metric.getValue()); + } } } catch (Exception ex) { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java index ee1eabbc2a..381956290f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java @@ -44,7 +44,7 @@ public class MetricsEndpointMetricReader implements MetricReader { @Override public Metric findOne(String metricName) { Metric metric = null; - Object value = this.endpoint.invoke().get(metricName); + Object value = this.endpoint.metrics(null).get(metricName); if (value != null) { metric = new Metric<>(metricName, (Number) value); } @@ -54,7 +54,7 @@ public class MetricsEndpointMetricReader implements MetricReader { @Override public Iterable> findAll() { List> metrics = new ArrayList<>(); - Map values = this.endpoint.invoke(); + Map values = this.endpoint.metrics(null); Date timestamp = new Date(); for (Entry entry : values.entrySet()) { String name = entry.getKey(); @@ -66,7 +66,7 @@ public class MetricsEndpointMetricReader implements MetricReader { @Override public long count() { - return this.endpoint.invoke().size(); + return this.endpoint.metrics(null).size(); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/NamePatternFilter.java similarity index 97% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilter.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/NamePatternFilter.java index db2ed158d0..141cf9c2b1 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/NamePatternFilter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint.mvc; +package org.springframework.boot.actuate.endpoint; import java.util.HashMap; import java.util.LinkedHashMap; @@ -32,8 +32,7 @@ import java.util.regex.PatternSyntaxException; * @author Phillip Webb * @author Sergei Egorov * @author Andy Wilkinson - * @author Dylian Bego - * @since 1.3.0 + * @since 2.0.0 */ abstract class NamePatternFilter { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java index c7caece589..ddc851522b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java @@ -25,7 +25,8 @@ import java.util.Map.Entry; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.web.method.HandlerMethod; @@ -38,9 +39,8 @@ import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; * @author Dave Syer * @author Andy Wilkinson */ -@ConfigurationProperties(prefix = "endpoints.mappings") -public class RequestMappingEndpoint extends AbstractEndpoint> - implements ApplicationContextAware { +@Endpoint(id = "mappings") +public class RequestMappingEndpoint implements ApplicationContextAware { private List handlerMappings = Collections.emptyList(); @@ -49,10 +49,6 @@ public class RequestMappingEndpoint extends AbstractEndpoint private ApplicationContext applicationContext; - public RequestMappingEndpoint() { - super("mappings"); - } - @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { @@ -75,8 +71,8 @@ public class RequestMappingEndpoint extends AbstractEndpoint this.methodMappings = methodMappings; } - @Override - public Map invoke() { + @ReadOperation + public Map mappings() { Map result = new LinkedHashMap<>(); extractHandlerMappings(this.handlerMappings, result); extractHandlerMappings(this.applicationContext, result); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ShutdownEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ShutdownEndpoint.java index 6c36ecc843..8026566337 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ShutdownEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ShutdownEndpoint.java @@ -20,7 +20,8 @@ import java.util.Collections; import java.util.Map; import org.springframework.beans.BeansException; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.WriteOperation; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; @@ -32,9 +33,8 @@ import org.springframework.context.ConfigurableApplicationContext; * @author Christian Dupuis * @author Andy Wilkinson */ -@ConfigurationProperties(prefix = "endpoints.shutdown") -public class ShutdownEndpoint extends AbstractEndpoint> - implements ApplicationContextAware { +@Endpoint(id = "shutdown", enabledByDefault = false) +public class ShutdownEndpoint implements ApplicationContextAware { private static final Map NO_CONTEXT_MESSAGE = Collections .unmodifiableMap(Collections.singletonMap("message", @@ -46,15 +46,8 @@ public class ShutdownEndpoint extends AbstractEndpoint> private ConfigurableApplicationContext context; - /** - * Create a new {@link ShutdownEndpoint} instance. - */ - public ShutdownEndpoint() { - super("shutdown", false); - } - - @Override - public Map invoke() { + @WriteOperation + public Map shutdown() { if (this.context == null) { return NO_CONTEXT_MESSAGE; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/DumpEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpoint.java similarity index 73% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/DumpEndpoint.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpoint.java index 423b4921e1..7e62399850 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/DumpEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpoint.java @@ -21,25 +21,19 @@ import java.lang.management.ThreadInfo; import java.util.Arrays; import java.util.List; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; /** * {@link Endpoint} to expose thread info. * * @author Dave Syer */ -@ConfigurationProperties(prefix = "endpoints.dump") -public class DumpEndpoint extends AbstractEndpoint> { +@Endpoint(id = "threaddump") +public class ThreadDumpEndpoint { - /** - * Create a new {@link DumpEndpoint} instance. - */ - public DumpEndpoint() { - super("dump"); - } - - @Override - public List invoke() { + @ReadOperation + public List threadDump() { return Arrays .asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/TraceEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/TraceEndpoint.java index eb30b9e64e..11f2291e1b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/TraceEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/TraceEndpoint.java @@ -20,7 +20,8 @@ import java.util.List; import org.springframework.boot.actuate.trace.Trace; import org.springframework.boot.actuate.trace.TraceRepository; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.ReadOperation; import org.springframework.util.Assert; /** @@ -28,8 +29,8 @@ import org.springframework.util.Assert; * * @author Dave Syer */ -@ConfigurationProperties(prefix = "endpoints.trace") -public class TraceEndpoint extends AbstractEndpoint> { +@Endpoint(id = "trace") +public class TraceEndpoint { private final TraceRepository repository; @@ -38,13 +39,12 @@ public class TraceEndpoint extends AbstractEndpoint> { * @param repository the trace repository */ public TraceEndpoint(TraceRepository repository) { - super("trace"); Assert.notNull(repository, "Repository must not be null"); this.repository = repository; } - @Override - public List invoke() { + @ReadOperation + public List traces() { return this.repository.findAll(); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractJmxEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractJmxEndpoint.java deleted file mode 100644 index d21ba8e6ea..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractJmxEndpoint.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.jmx; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.EndpointProperties; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; -import org.springframework.util.ObjectUtils; - -/** - * Abstract base class for {@link JmxEndpoint} implementations without a backing - * {@link Endpoint}. - * - * @author Vedran Pavic - * @author Phillip Webb - * @since 1.5.0 - */ -public abstract class AbstractJmxEndpoint implements JmxEndpoint, EnvironmentAware { - - private final DataConverter dataConverter; - - private Environment environment; - - /** - * Enable the endpoint. - */ - private Boolean enabled; - - public AbstractJmxEndpoint(ObjectMapper objectMapper) { - this.dataConverter = new DataConverter(objectMapper); - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - - protected final Environment getEnvironment() { - return this.environment; - } - - @Override - public boolean isEnabled() { - return EndpointProperties.isEnabled(this.environment, this.enabled); - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - - @Override - public String getIdentity() { - return ObjectUtils.getIdentityHexString(this); - } - - @Override - @SuppressWarnings("rawtypes") - public Class getEndpointType() { - return null; - } - - /** - * Convert the given data into JSON. - * @param data the source data - * @return the JSON representation - */ - protected Object convert(Object data) { - return this.dataConverter.convert(data); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpoint.java deleted file mode 100644 index d8690d09f5..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpoint.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.jmx; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.boot.actuate.audit.AuditEvent; -import org.springframework.boot.actuate.audit.AuditEventRepository; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.jmx.export.annotation.ManagedOperation; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * {@link JmxEndpoint} for {@link AuditEventRepository}. - * - * @author Vedran Pavic - * @since 1.5.0 - */ -@ConfigurationProperties(prefix = "endpoints.auditevents") -public class AuditEventsJmxEndpoint extends AbstractJmxEndpoint { - - private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; - - private final AuditEventRepository auditEventRepository; - - public AuditEventsJmxEndpoint(ObjectMapper objectMapper, - AuditEventRepository auditEventRepository) { - super(objectMapper); - Assert.notNull(auditEventRepository, "AuditEventRepository must not be null"); - this.auditEventRepository = auditEventRepository; - } - - @ManagedOperation(description = "Retrieves a list of audit events meeting the given criteria") - public Object getData(String dateAfter) { - List auditEvents = this.auditEventRepository - .find(parseDate(dateAfter)); - return convert(auditEvents); - } - - @ManagedOperation(description = "Retrieves a list of audit events meeting the given criteria") - public Object getData(String dateAfter, String principal) { - List auditEvents = this.auditEventRepository.find(principal, - parseDate(dateAfter)); - return convert(auditEvents); - } - - @ManagedOperation(description = "Retrieves a list of audit events meeting the given criteria") - public Object getData(String principal, String dateAfter, String type) { - List auditEvents = this.auditEventRepository.find(principal, - parseDate(dateAfter), type); - return convert(auditEvents); - } - - private Date parseDate(String date) { - try { - if (StringUtils.hasLength(date)) { - return new SimpleDateFormat(DATE_FORMAT).parse(date); - } - return null; - } - catch (ParseException ex) { - throw new IllegalArgumentException(ex); - } - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtension.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtension.java new file mode 100644 index 0000000000..88629f32f9 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtension.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.jmx; + +import java.util.Date; +import java.util.List; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.endpoint.jmx.JmxEndpointExtension; + +/** + * JMX-specific extension of the {@link AuditEventsEndpoint}. + * + * @author Vedran Pavic + * @author Andy Wilkinson + * @since 2.0.0 + */ +@JmxEndpointExtension(endpoint = AuditEventsEndpoint.class) +public class AuditEventsJmxEndpointExtension { + + private final AuditEventsEndpoint delegate; + + public AuditEventsJmxEndpointExtension(AuditEventsEndpoint delegate) { + this.delegate = delegate; + } + + @ReadOperation + public List eventsWithDateAfter(Date dateAfter) { + return this.delegate.eventsWithPrincipalDateAfterAndType(null, dateAfter, null); + } + + @ReadOperation + public List eventsWithPrincipalAndDateAfter(String principal, + Date dateAfter) { + return this.delegate.eventsWithPrincipalDateAfterAndType(principal, dateAfter, + null); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java deleted file mode 100644 index 0c519cc5b2..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.jmx; - -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Internal converter that uses an {@link ObjectMapper} to convert to JSON. - * - * @author Christian Dupuis - * @author Andy Wilkinson - * @author Phillip Webb - */ -class DataConverter { - - private final ObjectMapper objectMapper; - - private final JavaType listObject; - - private final JavaType mapStringObject; - - DataConverter(ObjectMapper objectMapper) { - this.objectMapper = (objectMapper == null ? new ObjectMapper() : objectMapper); - this.listObject = this.objectMapper.getTypeFactory() - .constructParametricType(List.class, Object.class); - this.mapStringObject = this.objectMapper.getTypeFactory() - .constructParametricType(Map.class, String.class, Object.class); - - } - - public Object convert(Object data) { - if (data == null) { - return null; - } - if (data instanceof String) { - return data; - } - if (data.getClass().isArray() || data instanceof List) { - return this.objectMapper.convertValue(data, this.listObject); - } - return this.objectMapper.convertValue(data, this.mapStringObject); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java deleted file mode 100644 index c3eccc8a0e..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.jmx; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.jmx.export.annotation.ManagedAttribute; - -/** - * Simple wrapper around {@link Endpoint} implementations that provide actuator data of - * some sort. - * - * @author Christian Dupuis - * @author Andy Wilkinson - */ -public class DataEndpointMBean extends EndpointMBean { - - /** - * Create a new {@link DataEndpointMBean} instance. - * @param beanName the bean name - * @param endpoint the endpoint to wrap - * @param objectMapper the {@link ObjectMapper} used to convert the payload - */ - public DataEndpointMBean(String beanName, Endpoint endpoint, - ObjectMapper objectMapper) { - super(beanName, endpoint, objectMapper); - } - - @ManagedAttribute(description = "Invoke the underlying endpoint") - public Object getData() { - return convert(getEndpoint().invoke()); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java deleted file mode 100644 index ce6e3e71d7..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.jmx; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; - -/** - * Base for adapters that convert {@link Endpoint} implementations to {@link JmxEndpoint}. - * - * @author Christian Dupuis - * @author Andy Wilkinson - * @author Vedran Pavic - * @author Phillip Webb - * @see JmxEndpoint - * @see DataEndpointMBean - */ -public abstract class EndpointMBean implements JmxEndpoint { - - private final DataConverter dataConverter; - - private final Endpoint endpoint; - - /** - * Create a new {@link EndpointMBean} instance. - * @param beanName the bean name - * @param endpoint the endpoint to wrap - * @param objectMapper the {@link ObjectMapper} used to convert the payload - */ - public EndpointMBean(String beanName, Endpoint endpoint, - ObjectMapper objectMapper) { - this.dataConverter = new DataConverter(objectMapper); - Assert.notNull(beanName, "BeanName must not be null"); - Assert.notNull(endpoint, "Endpoint must not be null"); - this.endpoint = endpoint; - } - - @ManagedAttribute(description = "Returns the class of the underlying endpoint") - public String getEndpointClass() { - return ClassUtils.getQualifiedName(getEndpointType()); - } - - @Override - public boolean isEnabled() { - return this.endpoint.isEnabled(); - } - - @Override - public String getIdentity() { - return ObjectUtils.getIdentityHexString(getEndpoint()); - } - - @Override - @SuppressWarnings("rawtypes") - public Class getEndpointType() { - return getEndpoint().getClass(); - } - - public Endpoint getEndpoint() { - return this.endpoint; - } - - /** - * Convert the given data into JSON. - * @param data the source data - * @return the JSON representation - */ - protected Object convert(Object data) { - return this.dataConverter.convert(data); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java deleted file mode 100644 index 2907591d31..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.jmx; - -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.locks.ReentrantLock; - -import javax.management.MBeanServer; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.LoggersEndpoint; -import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.SmartLifecycle; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.jmx.export.MBeanExportException; -import org.springframework.jmx.export.MBeanExporter; -import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource; -import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler; -import org.springframework.jmx.export.metadata.InvalidMetadataException; -import org.springframework.jmx.export.metadata.JmxAttributeSource; -import org.springframework.jmx.export.naming.MetadataNamingStrategy; -import org.springframework.jmx.export.naming.SelfNaming; -import org.springframework.jmx.support.ObjectNameManager; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -/** - * {@link SmartLifecycle} bean that registers all known {@link Endpoint}s with an - * {@link MBeanServer} using the {@link MBeanExporter} located from the application - * context. - * - * @author Christian Dupuis - * @author Andy Wilkinson - * @author Vedran Pavic - */ -public class EndpointMBeanExporter extends MBeanExporter - implements SmartLifecycle, ApplicationContextAware { - - /** - * The default JMX domain. - */ - public static final String DEFAULT_DOMAIN = "org.springframework.boot"; - - private static final Log logger = LogFactory.getLog(EndpointMBeanExporter.class); - - private final AnnotationJmxAttributeSource attributeSource = new EndpointJmxAttributeSource(); - - private final MetadataMBeanInfoAssembler assembler = new MetadataMBeanInfoAssembler( - this.attributeSource); - - private final MetadataNamingStrategy defaultNamingStrategy = new MetadataNamingStrategy( - this.attributeSource); - - private final Set> registeredEndpoints = new HashSet<>(); - - private volatile boolean autoStartup = true; - - private volatile int phase = 0; - - private volatile boolean running = false; - - private final ReentrantLock lifecycleLock = new ReentrantLock(); - - private ApplicationContext applicationContext; - - private ListableBeanFactory beanFactory; - - private String domain = DEFAULT_DOMAIN; - - private boolean ensureUniqueRuntimeObjectNames = false; - - private Properties objectNameStaticProperties = new Properties(); - - private final ObjectMapper objectMapper; - - /** - * Create a new {@link EndpointMBeanExporter} instance. - */ - public EndpointMBeanExporter() { - this(null); - } - - /** - * Create a new {@link EndpointMBeanExporter} instance. - * @param objectMapper the object mapper - */ - public EndpointMBeanExporter(ObjectMapper objectMapper) { - this.objectMapper = (objectMapper == null ? new ObjectMapper() : objectMapper); - setAutodetect(false); - setNamingStrategy(this.defaultNamingStrategy); - setAssembler(this.assembler); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - super.setBeanFactory(beanFactory); - if (beanFactory instanceof ListableBeanFactory) { - this.beanFactory = (ListableBeanFactory) beanFactory; - } - else { - logger.warn("EndpointMBeanExporter not running in a ListableBeanFactory: " - + "autodetection of Endpoints not available."); - } - } - - public void setDomain(String domain) { - this.domain = domain; - } - - @Override - public void setEnsureUniqueRuntimeObjectNames( - boolean ensureUniqueRuntimeObjectNames) { - super.setEnsureUniqueRuntimeObjectNames(ensureUniqueRuntimeObjectNames); - this.ensureUniqueRuntimeObjectNames = ensureUniqueRuntimeObjectNames; - } - - public void setObjectNameStaticProperties(Properties objectNameStaticProperties) { - this.objectNameStaticProperties = objectNameStaticProperties; - } - - protected void doStart() { - locateAndRegisterEndpoints(); - } - - protected void locateAndRegisterEndpoints() { - registerJmxEndpoints(this.beanFactory.getBeansOfType(JmxEndpoint.class)); - registerEndpoints(this.beanFactory.getBeansOfType(Endpoint.class)); - } - - private void registerJmxEndpoints(Map endpoints) { - for (Map.Entry entry : endpoints.entrySet()) { - String name = entry.getKey(); - JmxEndpoint endpoint = entry.getValue(); - Class type = (endpoint.getEndpointType() != null - ? endpoint.getEndpointType() : endpoint.getClass()); - if (!this.registeredEndpoints.contains(type) && endpoint.isEnabled()) { - try { - registerBeanNameOrInstance(endpoint, name); - } - catch (MBeanExportException ex) { - logger.error("Could not register JmxEndpoint [" + name + "]", ex); - } - this.registeredEndpoints.add(type); - } - } - } - - @SuppressWarnings("rawtypes") - private void registerEndpoints(Map endpoints) { - for (Map.Entry entry : endpoints.entrySet()) { - String name = entry.getKey(); - Endpoint endpoint = entry.getValue(); - Class type = endpoint.getClass(); - if (!this.registeredEndpoints.contains(type) && endpoint.isEnabled()) { - registerEndpoint(name, endpoint); - this.registeredEndpoints.add(type); - } - } - } - - /** - * Register a regular {@link Endpoint} with the {@link MBeanServer}. - * @param beanName the bean name - * @param endpoint the endpoint to register - * @deprecated as of 1.5 in favor of direct {@link JmxEndpoint} registration or - * {@link #adaptEndpoint(String, Endpoint)} - */ - @Deprecated - protected void registerEndpoint(String beanName, Endpoint endpoint) { - Class type = endpoint.getClass(); - if (isAnnotatedWithManagedResource(type) || (type.isMemberClass() - && isAnnotatedWithManagedResource(type.getEnclosingClass()))) { - // Endpoint is directly managed - return; - } - JmxEndpoint jmxEndpoint = adaptEndpoint(beanName, endpoint); - try { - registerBeanNameOrInstance(jmxEndpoint, beanName); - } - catch (MBeanExportException ex) { - logger.error("Could not register MBean for endpoint [" + beanName + "]", ex); - } - } - - private boolean isAnnotatedWithManagedResource(Class type) { - return AnnotationUtils.findAnnotation(type, ManagedResource.class) != null; - } - - /** - * Adapt the given {@link Endpoint} to a {@link JmxEndpoint}. - * @param beanName the bean name - * @param endpoint the endpoint to adapt - * @return an adapted endpoint - */ - protected JmxEndpoint adaptEndpoint(String beanName, Endpoint endpoint) { - if (endpoint instanceof ShutdownEndpoint) { - return new ShutdownEndpointMBean(beanName, endpoint, this.objectMapper); - } - if (endpoint instanceof LoggersEndpoint) { - return new LoggersEndpointMBean(beanName, endpoint, this.objectMapper); - } - return new DataEndpointMBean(beanName, endpoint, this.objectMapper); - } - - @Override - protected ObjectName getObjectName(Object bean, String beanKey) - throws MalformedObjectNameException { - if (bean instanceof SelfNaming) { - return ((SelfNaming) bean).getObjectName(); - } - if (bean instanceof JmxEndpoint) { - return getObjectName((JmxEndpoint) bean, beanKey); - } - return this.defaultNamingStrategy.getObjectName(bean, beanKey); - } - - private ObjectName getObjectName(JmxEndpoint jmxEndpoint, String beanKey) - throws MalformedObjectNameException { - StringBuilder builder = new StringBuilder(); - builder.append(this.domain); - builder.append(":type=Endpoint"); - builder.append(",name=" + beanKey); - if (parentContextContainsSameBean(this.applicationContext, beanKey)) { - builder.append(",context=" - + ObjectUtils.getIdentityHexString(this.applicationContext)); - } - if (this.ensureUniqueRuntimeObjectNames) { - builder.append(",identity=" + jmxEndpoint.getIdentity()); - } - builder.append(getStaticNames()); - return ObjectNameManager.getInstance(builder.toString()); - } - - private boolean parentContextContainsSameBean(ApplicationContext applicationContext, - String beanKey) { - if (applicationContext.getParent() != null) { - try { - Object bean = this.applicationContext.getParent().getBean(beanKey); - if (bean instanceof Endpoint || bean instanceof JmxEndpoint) { - return true; - } - } - catch (BeansException ex) { - // Ignore and continue - } - return parentContextContainsSameBean(applicationContext.getParent(), beanKey); - } - return false; - } - - private String getStaticNames() { - if (this.objectNameStaticProperties.isEmpty()) { - return ""; - } - StringBuilder builder = new StringBuilder(); - - for (Entry name : this.objectNameStaticProperties.entrySet()) { - builder.append("," + name.getKey() + "=" + name.getValue()); - } - return builder.toString(); - } - - @Override - public final int getPhase() { - return this.phase; - } - - @Override - public final boolean isAutoStartup() { - return this.autoStartup; - } - - @Override - public final boolean isRunning() { - this.lifecycleLock.lock(); - try { - return this.running; - } - finally { - this.lifecycleLock.unlock(); - } - } - - @Override - public final void start() { - this.lifecycleLock.lock(); - try { - if (!this.running) { - this.doStart(); - this.running = true; - } - } - finally { - this.lifecycleLock.unlock(); - } - } - - @Override - public final void stop() { - this.lifecycleLock.lock(); - try { - if (this.running) { - this.running = false; - } - } - finally { - this.lifecycleLock.unlock(); - } - } - - @Override - public final void stop(Runnable callback) { - this.lifecycleLock.lock(); - try { - this.stop(); - callback.run(); - } - finally { - this.lifecycleLock.unlock(); - } - } - - /** - * {@link JmxAttributeSource} for {@link JmxEndpoint JmxEndpoints}. - */ - private static class EndpointJmxAttributeSource extends AnnotationJmxAttributeSource { - - @Override - public org.springframework.jmx.export.metadata.ManagedResource getManagedResource( - Class beanClass) throws InvalidMetadataException { - Assert.state(super.getManagedResource(beanClass) == null, - "@ManagedResource annotation found on JmxEndpoint " + beanClass); - return new org.springframework.jmx.export.metadata.ManagedResource(); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java deleted file mode 100644 index 64a018eed0..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.jmx; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.jmx.export.annotation.ManagedResource; - -/** - * A strategy for the JMX layer on top of an {@link Endpoint}. Implementations are allowed - * to use {@code @ManagedAttribute} and the full Spring JMX machinery but should not use - * the {@link ManagedResource @ManagedResource} annotation. Implementations may be backed - * by an actual {@link Endpoint} or may be specifically designed for JMX only. - * - * @author Phillip Webb - * @since 1.5.0 - * @see EndpointMBean - * @see AbstractJmxEndpoint - */ -public interface JmxEndpoint { - - /** - * Return if the JMX endpoint is enabled. - * @return if the endpoint is enabled - */ - boolean isEnabled(); - - /** - * Return the MBean identity for this endpoint. - * @return the MBean identity. - */ - String getIdentity(); - - /** - * Return the type of {@link Endpoint} exposed, or {@code null} if this - * {@link JmxEndpoint} exposes information that cannot be represented as a traditional - * {@link Endpoint}. - * @return the endpoint type - */ - @SuppressWarnings("rawtypes") - Class getEndpointType(); - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java deleted file mode 100644 index 9a5fc17082..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.jmx; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.LoggersEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.boot.logging.LogLevel; -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.jmx.export.annotation.ManagedOperation; -import org.springframework.jmx.export.annotation.ManagedOperationParameter; -import org.springframework.jmx.export.annotation.ManagedOperationParameters; -import org.springframework.util.StringUtils; - -/** - * Adapter to expose {@link LoggersEndpoint} as an {@link MvcEndpoint}. - * - * @author Vedran Pavic - * @author Stephane Nicoll - * @since 1.5.0 - */ -public class LoggersEndpointMBean extends EndpointMBean { - - public LoggersEndpointMBean(String beanName, Endpoint endpoint, - ObjectMapper objectMapper) { - super(beanName, endpoint, objectMapper); - } - - @ManagedAttribute(description = "Get log levels for all known loggers") - public Object getLoggers() { - return convert(getEndpoint().invoke()); - } - - @ManagedOperation(description = "Get log level for a given logger") - @ManagedOperationParameters({ - @ManagedOperationParameter(name = "loggerName", description = "Name of the logger") }) - public Object getLogger(String loggerName) { - return convert(getEndpoint().invoke(loggerName)); - } - - @ManagedOperation(description = "Set log level for a given logger") - @ManagedOperationParameters({ - @ManagedOperationParameter(name = "loggerName", description = "Name of the logger"), - @ManagedOperationParameter(name = "logLevel", description = "New log level (can be null or empty String to remove the custom level)") }) - public void setLogLevel(String loggerName, String logLevel) { - getEndpoint().setLogLevel(loggerName, determineLogLevel(logLevel)); - } - - private LogLevel determineLogLevel(String logLevel) { - if (StringUtils.hasText(logLevel)) { - return LogLevel.valueOf(logLevel.toUpperCase()); - } - return null; - } - - @Override - public LoggersEndpoint getEndpoint() { - return (LoggersEndpoint) super.getEndpoint(); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java deleted file mode 100644 index 8541086fe5..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.jmx; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; -import org.springframework.jmx.export.annotation.ManagedOperation; - -/** - * Special endpoint wrapper for {@link ShutdownEndpoint}. - * - * @author Christian Dupuis - * @author Andy Wilkinson - */ -public class ShutdownEndpointMBean extends EndpointMBean { - - /** - * Create a new {@link ShutdownEndpointMBean} instance. - * @param beanName the bean name - * @param endpoint the endpoint to wrap - * @param objectMapper the {@link ObjectMapper} used to convert the payload - */ - public ShutdownEndpointMBean(String beanName, Endpoint endpoint, - ObjectMapper objectMapper) { - super(beanName, endpoint, objectMapper); - } - - @ManagedOperation(description = "Shutdown the ApplicationContext") - public Object shutdown() { - return convert(getEndpoint().invoke()); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMapping.java deleted file mode 100644 index 45b16f2fd8..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMapping.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.context.ApplicationContext; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.accept.PathExtensionContentNegotiationStrategy; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsUtils; -import org.springframework.web.servlet.HandlerExecutionChain; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; -import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; -import org.springframework.web.servlet.mvc.method.RequestMappingInfo; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; - -/** - * {@link HandlerMapping} to map {@link Endpoint}s to URLs via {@link Endpoint#getId()}. - * The semantics of {@code @RequestMapping} should be identical to a normal - * {@code @Controller}, but the endpoints should not be annotated as {@code @Controller} - * (otherwise they will be mapped by the normal MVC mechanisms). - *

- * One of the aims of the mapping is to support endpoints that work as HTTP endpoints but - * can still provide useful service interfaces when there is no HTTP server (and no Spring - * MVC on the classpath). Note that any endpoints having method signatures will break in a - * non-servlet environment. - * - * @param The endpoint type - * @author Phillip Webb - * @author Christian Dupuis - * @author Dave Syer - * @author Madhura Bhave - */ -public abstract class AbstractEndpointHandlerMapping - extends RequestMappingHandlerMapping { - - private final Set endpoints; - - private HandlerInterceptor securityInterceptor; - - private final CorsConfiguration corsConfiguration; - - private String prefix = ""; - - private boolean disabled = false; - - /** - * Create a new {@link AbstractEndpointHandlerMapping} instance. All {@link Endpoint}s - * will be detected from the {@link ApplicationContext}. The endpoints will not accept - * CORS requests. - * @param endpoints the endpoints - */ - public AbstractEndpointHandlerMapping(Collection endpoints) { - this(endpoints, null); - } - - /** - * Create a new {@link AbstractEndpointHandlerMapping} instance. All {@link Endpoint}s - * will be detected from the {@link ApplicationContext}. The endpoints will accepts - * CORS requests based on the given {@code corsConfiguration}. - * @param endpoints the endpoints - * @param corsConfiguration the CORS configuration for the endpoints - * @since 1.3.0 - */ - public AbstractEndpointHandlerMapping(Collection endpoints, - CorsConfiguration corsConfiguration) { - this.endpoints = new HashSet<>(endpoints); - postProcessEndpoints(this.endpoints); - this.corsConfiguration = corsConfiguration; - // By default the static resource handler mapping is LOWEST_PRECEDENCE - 1 - // and the RequestMappingHandlerMapping is 0 (we ideally want to be before both) - setOrder(-100); - setUseSuffixPatternMatch(false); - } - - /** - * Post process the endpoint setting before they are used. Subclasses can add or - * modify the endpoints as necessary. - * @param endpoints the endpoints to post process - */ - protected void postProcessEndpoints(Set endpoints) { - } - - @Override - public void afterPropertiesSet() { - super.afterPropertiesSet(); - if (!this.disabled) { - for (MvcEndpoint endpoint : this.endpoints) { - detectHandlerMethods(endpoint); - } - } - } - - /** - * Since all handler beans are passed into the constructor there is no need to detect - * anything here. - */ - @Override - protected boolean isHandler(Class beanType) { - return false; - } - - @Override - @Deprecated - protected void registerHandlerMethod(Object handler, Method method, - RequestMappingInfo mapping) { - if (mapping == null) { - return; - } - String[] patterns = getPatterns(handler, mapping); - if (!ObjectUtils.isEmpty(patterns)) { - super.registerHandlerMethod(handler, method, - withNewPatterns(mapping, patterns)); - } - } - - private String[] getPatterns(Object handler, RequestMappingInfo mapping) { - if (handler instanceof String) { - handler = getApplicationContext().getBean((String) handler); - } - Assert.state(handler instanceof MvcEndpoint, "Only MvcEndpoints are supported"); - String path = getPath((MvcEndpoint) handler); - return (path == null ? null : getEndpointPatterns(path, mapping)); - } - - /** - * Return the path that should be used to map the given {@link MvcEndpoint}. - * @param endpoint the endpoint to map - * @return the path to use for the endpoint or {@code null} if no mapping is required - */ - protected String getPath(MvcEndpoint endpoint) { - return endpoint.getPath(); - } - - private String[] getEndpointPatterns(String path, RequestMappingInfo mapping) { - String patternPrefix = StringUtils.hasText(this.prefix) ? this.prefix + path - : path; - Set defaultPatterns = mapping.getPatternsCondition().getPatterns(); - if (defaultPatterns.isEmpty()) { - return new String[] { patternPrefix, patternPrefix + ".json" }; - } - List patterns = new ArrayList<>(defaultPatterns); - for (int i = 0; i < patterns.size(); i++) { - patterns.set(i, patternPrefix + patterns.get(i)); - } - return patterns.toArray(new String[patterns.size()]); - } - - private RequestMappingInfo withNewPatterns(RequestMappingInfo mapping, - String[] patternStrings) { - PatternsRequestCondition patterns = new PatternsRequestCondition(patternStrings, - null, null, useSuffixPatternMatch(), useTrailingSlashMatch(), null); - return new RequestMappingInfo(patterns, mapping.getMethodsCondition(), - mapping.getParamsCondition(), mapping.getHeadersCondition(), - mapping.getConsumesCondition(), mapping.getProducesCondition(), - mapping.getCustomCondition()); - } - - @Override - protected HandlerExecutionChain getHandlerExecutionChain(Object handler, - HttpServletRequest request) { - HandlerExecutionChain chain = super.getHandlerExecutionChain(handler, request); - if (this.securityInterceptor == null || CorsUtils.isCorsRequest(request)) { - return chain; - } - return addSecurityInterceptor(chain); - } - - @Override - protected HandlerExecutionChain getCorsHandlerExecutionChain( - HttpServletRequest request, HandlerExecutionChain chain, - CorsConfiguration config) { - chain = super.getCorsHandlerExecutionChain(request, chain, config); - if (this.securityInterceptor == null) { - return chain; - } - return addSecurityInterceptor(chain); - } - - @Override - protected void extendInterceptors(List interceptors) { - interceptors.add(new SkipPathExtensionContentNegotiation()); - } - - private HandlerExecutionChain addSecurityInterceptor(HandlerExecutionChain chain) { - List interceptors = new ArrayList<>(); - if (chain.getInterceptors() != null) { - interceptors.addAll(Arrays.asList(chain.getInterceptors())); - } - interceptors.add(this.securityInterceptor); - return new HandlerExecutionChain(chain.getHandler(), - interceptors.toArray(new HandlerInterceptor[interceptors.size()])); - } - - /** - * Set the handler interceptor that will be used for security. - * @param securityInterceptor the security handler interceptor - */ - public void setSecurityInterceptor(HandlerInterceptor securityInterceptor) { - this.securityInterceptor = securityInterceptor; - } - - /** - * Set the prefix used in mappings. - * @param prefix the prefix - */ - public void setPrefix(String prefix) { - Assert.isTrue("".equals(prefix) || StringUtils.startsWithIgnoreCase(prefix, "/"), - "prefix must start with '/'"); - this.prefix = prefix; - } - - /** - * Get the prefix used in mappings. - * @return the prefix - */ - public String getPrefix() { - return this.prefix; - } - - /** - * Get the path of the endpoint. - * @param endpoint the endpoint - * @return the path used in mappings - */ - public String getPath(String endpoint) { - return this.prefix + endpoint; - } - - /** - * Sets if this mapping is disabled. - * @param disabled if the mapping is disabled - */ - public void setDisabled(boolean disabled) { - this.disabled = disabled; - } - - /** - * Returns if this mapping is disabled. - * @return {@code true} if the mapping is disabled - */ - public boolean isDisabled() { - return this.disabled; - } - - /** - * Return the endpoints. - * @return the endpoints - */ - public Set getEndpoints() { - return Collections.unmodifiableSet(this.endpoints); - } - - @Override - protected CorsConfiguration initCorsConfiguration(Object handler, Method method, - RequestMappingInfo mappingInfo) { - return this.corsConfiguration; - } - - /** - * {@link HandlerInterceptorAdapter} to ensure that - * {@link PathExtensionContentNegotiationStrategy} is skipped for actuator endpoints. - */ - private static final class SkipPathExtensionContentNegotiation - extends HandlerInterceptorAdapter { - - private static final String SKIP_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class - .getName() + ".SKIP"; - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, - Object handler) throws Exception { - request.setAttribute(SKIP_ATTRIBUTE, Boolean.TRUE); - return true; - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java deleted file mode 100644 index f3e60d2123..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.http.ResponseEntity; -import org.springframework.util.Assert; - -/** - * Abstract base class for {@link MvcEndpoint} implementations. - * - * @param The delegate endpoint - * @author Dave Syer - * @author Andy Wilkinson - * @author Phillip Webb - * @since 1.3.0 - */ -public abstract class AbstractEndpointMvcAdapter> - implements NamedMvcEndpoint { - - private final E delegate; - - /** - * Endpoint URL path. - */ - private String path; - - /** - * Create a new {@link EndpointMvcAdapter}. - * @param delegate the underlying {@link Endpoint} to adapt. - */ - public AbstractEndpointMvcAdapter(E delegate) { - Assert.notNull(delegate, "Delegate must not be null"); - this.delegate = delegate; - } - - protected Object invoke() { - if (!this.delegate.isEnabled()) { - // Shouldn't happen - shouldn't be registered when delegate's disabled - return getDisabledResponse(); - } - return this.delegate.invoke(); - } - - public E getDelegate() { - return this.delegate; - } - - @Override - public String getName() { - return this.delegate.getId(); - } - - @Override - public String getPath() { - return (this.path != null ? this.path : "/" + this.delegate.getId()); - } - - public void setPath(String path) { - while (path.endsWith("/")) { - path = path.substring(0, path.length() - 1); - } - if (!path.startsWith("/")) { - path = "/" + path; - } - this.path = path; - } - - @Override - @SuppressWarnings("rawtypes") - public Class getEndpointType() { - return this.delegate.getClass(); - } - - /** - * Returns the response that should be returned when the endpoint is disabled. - * @return The response to be returned when the endpoint is disabled - * @since 1.2.4 - * @see Endpoint#isEnabled() - */ - protected ResponseEntity getDisabledResponse() { - return MvcEndpoint.DISABLED_RESPONSE; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractMvcEndpoint.java deleted file mode 100644 index efff32ed5c..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractMvcEndpoint.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.EndpointProperties; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; -import org.springframework.util.Assert; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -/** - * Abstract base class for {@link MvcEndpoint} implementations without a backing - * {@link Endpoint}. - * - * @author Phillip Webb - * @author Lari Hotari - * @since 1.4.0 - */ -public abstract class AbstractMvcEndpoint - implements MvcEndpoint, WebMvcConfigurer, EnvironmentAware { - - private Environment environment; - - /** - * Endpoint URL path. - */ - private String path; - - /** - * Enable the endpoint. - */ - private Boolean enabled; - - public AbstractMvcEndpoint(String path) { - setPath(path); - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - - protected final Environment getEnvironment() { - return this.environment; - } - - @Override - public String getPath() { - return this.path; - } - - public void setPath(String path) { - Assert.notNull(path, "Path must not be null"); - Assert.isTrue(path.isEmpty() || path.startsWith("/"), - "Path must start with / or be empty"); - this.path = path; - } - - public boolean isEnabled() { - return EndpointProperties.isEnabled(this.environment, this.enabled); - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - - @Override - @SuppressWarnings("rawtypes") - public Class getEndpointType() { - return null; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java deleted file mode 100644 index 353070ad12..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.util.Assert; - -/** - * Abstract base class for {@link NamedMvcEndpoint} implementations without a backing - * {@link Endpoint}. - * - * @author Madhura Bhave - * @since 1.5.0 - */ -public abstract class AbstractNamedMvcEndpoint extends AbstractMvcEndpoint - implements NamedMvcEndpoint { - - private final String name; - - public AbstractNamedMvcEndpoint(String name, String path) { - super(path); - Assert.hasLength(name, "Name must not be empty"); - this.name = name; - } - - @Override - public String getName() { - return this.name; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorPostMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorPostMapping.java deleted file mode 100644 index ff4746ba2d..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorPostMapping.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -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.core.annotation.AliasFor; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -/** - * Specialized {@link RequestMapping} for {@link RequestMethod#POST POST} requests that - * consume {@code application/json} or - * {@code application/vnd.spring-boot.actuator.v1+json} requests and produce - * {@code application/json} or {@code application/vnd.spring-boot.actuator.v1+json} - * responses. - * - * @author Andy Wilkinson - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@RequestMapping(method = RequestMethod.POST, consumes = { - ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE, - MediaType.APPLICATION_JSON_VALUE }, produces = { - ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE, - MediaType.APPLICATION_JSON_VALUE }) -@interface ActuatorPostMapping { - - /** - * Alias for {@link RequestMapping#value}. - * @return the value - */ - @AliasFor(annotation = RequestMapping.class) - String[] value() default {}; - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpoint.java deleted file mode 100644 index 38fce97c76..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpoint.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.springframework.boot.actuate.audit.AuditEvent; -import org.springframework.boot.actuate.audit.AuditEventRepository; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.http.ResponseEntity; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * {@link MvcEndpoint} to expose {@link AuditEvent}s. - * - * @author Vedran Pavic - * @author Phillip Webb - * @since 1.5.0 - */ -@ConfigurationProperties(prefix = "endpoints.auditevents") -public class AuditEventsMvcEndpoint extends AbstractNamedMvcEndpoint { - - private final AuditEventRepository auditEventRepository; - - public AuditEventsMvcEndpoint(AuditEventRepository auditEventRepository) { - super("auditevents", "/auditevents"); - Assert.notNull(auditEventRepository, "AuditEventRepository must not be null"); - this.auditEventRepository = auditEventRepository; - } - - @ActuatorGetMapping - @ResponseBody - public ResponseEntity findByPrincipalAndAfterAndType( - @RequestParam(required = false) String principal, - @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssZ") Date after, - @RequestParam(required = false) String type) { - if (!isEnabled()) { - return DISABLED_RESPONSE; - } - Map result = new LinkedHashMap<>(); - result.put("events", this.auditEventRepository.find(principal, after, type)); - return ResponseEntity.ok(result); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java deleted file mode 100644 index d5c153f427..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.context.ApplicationContext; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.servlet.HandlerMapping; - -/** - * {@link HandlerMapping} to map {@link Endpoint}s to URLs via {@link Endpoint#getId()}. - * The semantics of {@code @RequestMapping} should be identical to a normal - * {@code @Controller}, but the endpoints should not be annotated as {@code @Controller} - * (otherwise they will be mapped by the normal MVC mechanisms). - *

- * One of the aims of the mapping is to support endpoints that work as HTTP endpoints but - * can still provide useful service interfaces when there is no HTTP server (and no Spring - * MVC on the classpath). Note that any endpoints having method signatures will break in a - * non-servlet environment. - * - * @author Phillip Webb - * @author Christian Dupuis - * @author Dave Syer - */ -public class EndpointHandlerMapping extends AbstractEndpointHandlerMapping { - - /** - * Create a new {@link EndpointHandlerMapping} instance. All {@link Endpoint}s will be - * detected from the {@link ApplicationContext}. The endpoints will not accept CORS - * requests. - * @param endpoints the endpoints - */ - public EndpointHandlerMapping(Collection endpoints) { - super(endpoints); - } - - /** - * Create a new {@link EndpointHandlerMapping} instance. All {@link Endpoint}s will be - * detected from the {@link ApplicationContext}. The endpoints will accepts CORS - * requests based on the given {@code corsConfiguration}. - * @param endpoints the endpoints - * @param corsConfiguration the CORS configuration for the endpoints - * @since 1.3.0 - */ - public EndpointHandlerMapping(Collection endpoints, - CorsConfiguration corsConfiguration) { - super(endpoints, corsConfiguration); - } - - @Override - public void afterPropertiesSet() { - super.afterPropertiesSet(); - detectHandlerMethods(new EndpointLinksMvcEndpoint( - getEndpoints().stream().filter(NamedMvcEndpoint.class::isInstance) - .map(NamedMvcEndpoint.class::cast).collect(Collectors.toSet()))); - } - - /** - * {@link MvcEndpoint} to provide HAL-formatted links to all the - * {@link NamedMvcEndpoint named endpoints}. - * - * @author Madhura Bhave - * @author Andy Wilkinson - */ - private static final class EndpointLinksMvcEndpoint extends AbstractMvcEndpoint { - - private final Set endpoints; - - private EndpointLinksMvcEndpoint(Set endpoints) { - super(""); - this.endpoints = endpoints; - } - - @ResponseBody - @ActuatorGetMapping - public Map> links(HttpServletRequest request) { - Map links = new LinkedHashMap<>(); - String url = request.getRequestURL().toString(); - if (url.endsWith("/")) { - url = url.substring(0, url.length() - 1); - } - links.put("self", Link.withHref(url)); - for (NamedMvcEndpoint endpoint : this.endpoints) { - links.put(endpoint.getName(), - Link.withHref(url + "/" + endpoint.getName())); - } - return Collections.singletonMap("_links", links); - } - - } - - /** - * Details for a link in the HAL response. - */ - static final class Link { - - private final String href; - - private Link(String href) { - this.href = href; - } - - public String getHref() { - return this.href; - } - - static Link withHref(Object href) { - return new Link(href.toString()); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java deleted file mode 100644 index db35991038..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.EnumerablePropertySource; -import org.springframework.core.env.Environment; -import org.springframework.core.env.PropertySource; -import org.springframework.core.env.PropertySources; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; - -/** - * Adapter to expose {@link EnvironmentEndpoint} as an {@link MvcEndpoint}. - * - * @author Dave Syer - * @author Christian Dupuis - * @author Andy Wilkinson - */ -@ConfigurationProperties(prefix = "endpoints.env") -public class EnvironmentMvcEndpoint extends EndpointMvcAdapter - implements EnvironmentAware { - - private Environment environment; - - public EnvironmentMvcEndpoint(EnvironmentEndpoint delegate) { - super(delegate); - } - - @ActuatorGetMapping("/{name:.*}") - @ResponseBody - public Object value(@PathVariable String name) { - if (!getDelegate().isEnabled()) { - // Shouldn't happen - MVC endpoint shouldn't be registered when delegate's - // disabled - return getDisabledResponse(); - } - return new NamePatternEnvironmentFilter(this.environment).getResults(name); - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - - /** - * {@link NamePatternFilter} for the Environment source. - */ - private class NamePatternEnvironmentFilter extends NamePatternFilter { - - NamePatternEnvironmentFilter(Environment source) { - super(source); - } - - @Override - protected void getNames(Environment source, NameCallback callback) { - if (source instanceof ConfigurableEnvironment) { - getNames(((ConfigurableEnvironment) source).getPropertySources(), - callback); - } - } - - private void getNames(PropertySources propertySources, NameCallback callback) { - for (PropertySource propertySource : propertySources) { - if (propertySource instanceof EnumerablePropertySource) { - EnumerablePropertySource source = (EnumerablePropertySource) propertySource; - for (String name : source.getPropertyNames()) { - callback.addName(name); - } - } - } - } - - @Override - protected Object getOptionalValue(Environment source, String name) { - Object result = getValue(name); - if (result != null) { - result = ((EnvironmentEndpoint) getDelegate()).sanitize(name, result); - } - return result; - } - - @Override - protected Object getValue(Environment source, String name) { - Object result = getValue(name); - if (result == null) { - throw new NoSuchPropertyException("No such property: " + name); - } - return ((EnvironmentEndpoint) getDelegate()).sanitize(name, result); - } - - private Object getValue(String name) { - return ((EnvironmentEndpoint) getDelegate()).getResolver().getProperty(name, - Object.class); - } - - } - - /** - * Exception thrown when the specified property cannot be found. - */ - @SuppressWarnings("serial") - @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such property") - public static class NoSuchPropertyException extends RuntimeException { - - public NoSuchPropertyException(String string) { - super(string); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java deleted file mode 100644 index de94480178..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.security.Principal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Status; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * Adapter to expose {@link HealthEndpoint} as an {@link MvcEndpoint}. - * - * @author Christian Dupuis - * @author Dave Syer - * @author Andy Wilkinson - * @author Phillip Webb - * @author EddĂș MelĂ©ndez - * @author Madhura Bhave - * @since 1.1.0 - */ -@ConfigurationProperties(prefix = "endpoints.health") -public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter { - - private static final List DEFAULT_ROLES = Arrays.asList("ROLE_ACTUATOR"); - - private final boolean secure; - - private final List roles; - - private Map statusMapping = new HashMap<>(); - - private volatile CachedHealth cachedHealth; - - public HealthMvcEndpoint(HealthEndpoint delegate) { - this(delegate, true); - } - - public HealthMvcEndpoint(HealthEndpoint delegate, boolean secure) { - this(delegate, secure, new ArrayList<>(DEFAULT_ROLES)); - } - - public HealthMvcEndpoint(HealthEndpoint delegate, boolean secure, - List roles) { - super(delegate); - Assert.notNull(roles, "Roles must not be null"); - this.secure = secure; - this.roles = roles; - setupDefaultStatusMapping(); - } - - private void setupDefaultStatusMapping() { - addStatusMapping(Status.DOWN, HttpStatus.SERVICE_UNAVAILABLE); - addStatusMapping(Status.OUT_OF_SERVICE, HttpStatus.SERVICE_UNAVAILABLE); - } - - /** - * Set specific status mappings. - * @param statusMapping a map of status code to {@link HttpStatus} - */ - public void setStatusMapping(Map statusMapping) { - Assert.notNull(statusMapping, "StatusMapping must not be null"); - this.statusMapping = new HashMap<>(statusMapping); - } - - /** - * Add specific status mappings to the existing set. - * @param statusMapping a map of status code to {@link HttpStatus} - */ - public void addStatusMapping(Map statusMapping) { - Assert.notNull(statusMapping, "StatusMapping must not be null"); - this.statusMapping.putAll(statusMapping); - } - - /** - * Add a status mapping to the existing set. - * @param status the status to map - * @param httpStatus the http status - */ - public void addStatusMapping(Status status, HttpStatus httpStatus) { - Assert.notNull(status, "Status must not be null"); - Assert.notNull(httpStatus, "HttpStatus must not be null"); - addStatusMapping(status.getCode(), httpStatus); - } - - /** - * Add a status mapping to the existing set. - * @param statusCode the status code to map - * @param httpStatus the http status - */ - public void addStatusMapping(String statusCode, HttpStatus httpStatus) { - Assert.notNull(statusCode, "StatusCode must not be null"); - Assert.notNull(httpStatus, "HttpStatus must not be null"); - this.statusMapping.put(statusCode, httpStatus); - } - - @ActuatorGetMapping - @ResponseBody - public Object invoke(HttpServletRequest request, Principal principal) { - if (!getDelegate().isEnabled()) { - // Shouldn't happen because the request mapping should not be registered - return getDisabledResponse(); - } - Health health = getHealth(request, principal); - HttpStatus status = getStatus(health); - if (status != null) { - return new ResponseEntity<>(health, status); - } - return health; - } - - private HttpStatus getStatus(Health health) { - String code = getUniformValue(health.getStatus().getCode()); - if (code != null) { - return this.statusMapping.keySet().stream() - .filter((key) -> code.equals(getUniformValue(key))) - .map(this.statusMapping::get).findFirst().orElse(null); - } - return null; - } - - private String getUniformValue(String code) { - if (code == null) { - return null; - } - StringBuilder builder = new StringBuilder(); - for (char ch : code.toCharArray()) { - if (Character.isAlphabetic(ch) || Character.isDigit(ch)) { - builder.append(Character.toLowerCase(ch)); - } - } - return builder.toString(); - } - - private Health getHealth(HttpServletRequest request, Principal principal) { - Health currentHealth = getCurrentHealth(); - if (exposeHealthDetails(request, principal)) { - return currentHealth; - } - return Health.status(currentHealth.getStatus()).build(); - } - - private Health getCurrentHealth() { - long accessTime = System.currentTimeMillis(); - CachedHealth cached = this.cachedHealth; - if (cached == null || cached.isStale(accessTime, getDelegate().getTimeToLive())) { - Health health = getDelegate().invoke(); - this.cachedHealth = new CachedHealth(health, accessTime); - return health; - } - return cached.getHealth(); - } - - protected boolean exposeHealthDetails(HttpServletRequest request, - Principal principal) { - if (!this.secure) { - return true; - } - List roles = getRoles(); - for (String role : roles) { - if (request.isUserInRole(role)) { - return true; - } - if (isSpringSecurityAuthentication(principal)) { - Authentication authentication = (Authentication) principal; - for (GrantedAuthority authority : authentication.getAuthorities()) { - String name = authority.getAuthority(); - if (role.equals(name)) { - return true; - } - } - } - } - return false; - } - - private List getRoles() { - return this.roles; - } - - private boolean isSpringSecurityAuthentication(Principal principal) { - return ClassUtils.isPresent("org.springframework.security.core.Authentication", - null) && principal instanceof Authentication; - } - - /** - * A cached {@link Health} that encapsulates the {@code Health} itself and the time at - * which it was created. - */ - static class CachedHealth { - - private final Health health; - - private final long creationTime; - - CachedHealth(Health health, long creationTime) { - this.health = health; - this.creationTime = creationTime; - } - - public boolean isStale(long accessTime, long timeToLive) { - return (accessTime - this.creationTime) >= timeToLive; - } - - public Health getHealth() { - return this.health; - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java deleted file mode 100644 index 4971c86c12..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.io.File; -import java.io.IOException; - -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.logging.LogFile; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; - -/** - * Controller that provides an API for logfiles, i.e. downloading the main logfile - * configured in environment property 'logging.file' that is standard, but optional - * property for spring-boot applications. - * - * @author Johannes Edmeier - * @author Phillip Webb - * @since 1.3.0 - */ -@ConfigurationProperties(prefix = "endpoints.logfile") -public class LogFileMvcEndpoint extends AbstractNamedMvcEndpoint { - - private static final Log logger = LogFactory.getLog(LogFileMvcEndpoint.class); - - /** - * External Logfile to be accessed. Can be used if the logfile is written by output - * redirect and not by the logging-system itself. - */ - private File externalFile; - - public LogFileMvcEndpoint() { - super("logfile", "/logfile"); - } - - public File getExternalFile() { - return this.externalFile; - } - - public void setExternalFile(File externalFile) { - this.externalFile = externalFile; - } - - @RequestMapping(method = { RequestMethod.GET, RequestMethod.HEAD }) - public void invoke(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - if (!isEnabled()) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - return; - } - Resource resource = getLogFileResource(); - if (resource != null && !resource.exists()) { - if (logger.isDebugEnabled()) { - logger.debug("Log file '" + resource + "' does not exist"); - } - resource = null; - } - Handler handler = new Handler(resource, request.getServletContext()); - handler.handleRequest(request, response); - } - - private Resource getLogFileResource() { - if (this.externalFile != null) { - return new FileSystemResource(this.externalFile); - } - LogFile logFile = LogFile.get(getEnvironment()); - if (logFile == null) { - logger.debug("Missing 'logging.file' or 'logging.path' properties"); - return null; - } - return new FileSystemResource(logFile.toString()); - } - - /** - * {@link ResourceHttpRequestHandler} to send the log file. - */ - private static class Handler extends ResourceHttpRequestHandler { - - private final Resource resource; - - Handler(Resource resource, ServletContext servletContext) { - this.resource = resource; - getLocations().add(resource); - try { - setServletContext(servletContext); - afterPropertiesSet(); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - @Override - protected void initAllowedLocations() { - this.getLocations().clear(); - } - - @Override - protected Resource getResource(HttpServletRequest request) throws IOException { - return this.resource; - } - - @Override - protected MediaType getMediaType(HttpServletRequest request, Resource resource) { - return MediaType.TEXT_PLAIN; - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java deleted file mode 100644 index e603a20821..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Map; - -import org.springframework.boot.actuate.endpoint.LoggersEndpoint; -import org.springframework.boot.actuate.endpoint.LoggersEndpoint.LoggerLevels; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.logging.LogLevel; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * Adapter to expose {@link LoggersEndpoint} as an {@link MvcEndpoint}. - * - * @author Ben Hale - * @author Kazuki Shimizu - * @author EddĂș MelĂ©ndez - * @since 1.5.0 - */ -@ConfigurationProperties(prefix = "endpoints.loggers") -public class LoggersMvcEndpoint extends EndpointMvcAdapter { - - private final LoggersEndpoint delegate; - - public LoggersMvcEndpoint(LoggersEndpoint delegate) { - super(delegate); - this.delegate = delegate; - } - - @ActuatorGetMapping("/{name:.*}") - @ResponseBody - public Object get(@PathVariable String name) { - if (!this.delegate.isEnabled()) { - // Shouldn't happen - MVC endpoint shouldn't be registered when delegate's - // disabled - return getDisabledResponse(); - } - LoggerLevels levels = this.delegate.invoke(name); - return (levels == null ? ResponseEntity.notFound().build() : levels); - } - - @ActuatorPostMapping("/{name:.*}") - @ResponseBody - public Object set(@PathVariable String name, - @RequestBody Map configuration) { - if (!this.delegate.isEnabled()) { - // Shouldn't happen - MVC endpoint shouldn't be registered when delegate's - // disabled - return getDisabledResponse(); - } - try { - LogLevel logLevel = getLogLevel(configuration); - this.delegate.setLogLevel(name, logLevel); - return ResponseEntity.noContent().build(); - } - catch (IllegalArgumentException ex) { - return ResponseEntity.badRequest().build(); - } - } - - private LogLevel getLogLevel(Map configuration) { - String level = configuration.get("configuredLevel"); - return (level == null ? null : LogLevel.valueOf(level.toUpperCase())); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java index 3eda218938..23389b9f9d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java @@ -27,9 +27,9 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.ServletWebRequest; /** - * Special {@link MvcEndpoint} for handling "/error" path when the management servlet is - * in a child context. The regular {@link ErrorController} should be available there but - * because of the way the handler mappings are set up it will not be detected. + * {@link Controller} for handling "/error" path when the management servlet is in a child + * context. The regular {@link ErrorController} should be available there but because of + * the way the handler mappings are set up it will not be detected. * * @author Dave Syer */ diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java deleted file mode 100644 index bbf74a31ad..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Map; - -import org.springframework.boot.actuate.endpoint.MetricsEndpoint; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; - -/** - * Adapter to expose {@link MetricsEndpoint} as an {@link MvcEndpoint}. - * - * @author Dave Syer - * @author Andy Wilkinson - * @author Sergei Egorov - */ -@ConfigurationProperties(prefix = "endpoints.metrics") -public class MetricsMvcEndpoint extends EndpointMvcAdapter { - - private final MetricsEndpoint delegate; - - public MetricsMvcEndpoint(MetricsEndpoint delegate) { - super(delegate); - this.delegate = delegate; - } - - @ActuatorGetMapping("/{name:.*}") - @ResponseBody - public Object value(@PathVariable String name) { - if (!this.delegate.isEnabled()) { - // Shouldn't happen - MVC endpoint shouldn't be registered when delegate's - // disabled - return getDisabledResponse(); - } - return new NamePatternMapFilter(this.delegate.invoke()).getResults(name); - } - - /** - * {@link NamePatternFilter} for the Map source. - */ - private class NamePatternMapFilter extends NamePatternFilter> { - - NamePatternMapFilter(Map source) { - super(source); - } - - @Override - protected void getNames(Map source, NameCallback callback) { - for (String name : source.keySet()) { - try { - callback.addName(name); - } - catch (NoSuchMetricException ex) { - // Metric with null value. Continue. - } - } - } - - @Override - protected Object getOptionalValue(Map source, String name) { - return source.get(name); - } - - @Override - protected Object getValue(Map source, String name) { - Object value = getOptionalValue(source, name); - if (value == null) { - throw new NoSuchMetricException("No such metric: " + name); - } - return value; - } - - } - - /** - * Exception thrown when the specified metric cannot be found. - */ - @SuppressWarnings("serial") - @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such metric") - public static class NoSuchMetricException extends RuntimeException { - - public NoSuchMetricException(String string) { - super(string); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java deleted file mode 100644 index 969bfcfc90..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Collections; -import java.util.Map; - -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -/** - * A strategy for the MVC layer on top of an {@link Endpoint}. Implementations are allowed - * to use {@code @RequestMapping} and the full Spring MVC machinery, but should not use - * {@code @Controller} or {@code @RequestMapping} at the type level (since that would lead - * to a double mapping of paths, once by the regular MVC handler mappings and once by the - * {@link EndpointHandlerMapping}). - * - * @author Dave Syer - * @see NamedMvcEndpoint - */ -public interface MvcEndpoint { - - /** - * A {@link ResponseEntity} returned for disabled endpoints. - */ - ResponseEntity> DISABLED_RESPONSE = new ResponseEntity<>( - Collections.singletonMap("message", "This endpoint is disabled"), - HttpStatus.NOT_FOUND); - - /** - * Return the MVC path of the endpoint. - * @return the endpoint path - */ - String getPath(); - - /** - * Return the type of {@link Endpoint} exposed, or {@code null} if this - * {@link MvcEndpoint} exposes information that cannot be represented as a traditional - * {@link Endpoint}. - * @return the endpoint type - */ - @SuppressWarnings("rawtypes") - Class getEndpointType(); - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java deleted file mode 100644 index 7430b36d5d..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.cors.CorsUtils; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; - -/** - * Security interceptor for MvcEndpoints. - * - * @author Madhura Bhave - * @since 1.5.0 - */ -public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter { - - private static final Log logger = LogFactory - .getLog(MvcEndpointSecurityInterceptor.class); - - private final boolean secure; - - private final List roles; - - private AtomicBoolean loggedUnauthorizedAttempt = new AtomicBoolean(); - - public MvcEndpointSecurityInterceptor(boolean secure, List roles) { - this.secure = secure; - this.roles = roles; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, - Object handler) throws Exception { - if (CorsUtils.isPreFlightRequest(request) || !this.secure) { - return true; - } - HandlerMethod handlerMethod = (HandlerMethod) handler; - if (HttpMethod.OPTIONS.matches(request.getMethod()) - && !(handlerMethod.getBean() instanceof MvcEndpoint)) { - return true; - } - if (isUserAllowedAccess(request)) { - return true; - } - sendFailureResponse(request, response); - return false; - } - - private boolean isUserAllowedAccess(HttpServletRequest request) { - AuthoritiesValidator authoritiesValidator = null; - if (isSpringSecurityAvailable()) { - authoritiesValidator = new AuthoritiesValidator(); - } - for (String role : this.roles) { - if (request.isUserInRole(role)) { - return true; - } - if (authoritiesValidator != null && authoritiesValidator.hasAuthority(role)) { - return true; - } - } - return false; - } - - private boolean isSpringSecurityAvailable() { - return ClassUtils.isPresent( - "org.springframework.security.config.annotation.web.WebSecurityConfigurer", - getClass().getClassLoader()); - } - - private void sendFailureResponse(HttpServletRequest request, - HttpServletResponse response) throws Exception { - if (request.getUserPrincipal() != null) { - String roles = StringUtils.collectionToDelimitedString(this.roles, " "); - response.sendError(HttpStatus.FORBIDDEN.value(), - "Access is denied. User must have one of the these roles: " + roles); - } - else { - logUnauthorizedAttempt(); - response.sendError(HttpStatus.UNAUTHORIZED.value(), - "Full authentication is required to access this resource."); - } - } - - private void logUnauthorizedAttempt() { - if (this.loggedUnauthorizedAttempt.compareAndSet(false, true) - && logger.isInfoEnabled()) { - logger.info("Full authentication is required to access " - + "actuator endpoints. Consider adding Spring Security " - + "or set 'management.security.enabled' to false."); - } - } - - /** - * Inner class to check authorities using Spring Security (when available). - */ - private static class AuthoritiesValidator { - - private boolean hasAuthority(String role) { - Authentication authentication = SecurityContextHolder.getContext() - .getAuthentication(); - if (authentication != null) { - for (GrantedAuthority authority : authentication.getAuthorities()) { - if (authority.getAuthority().equals(role)) { - return true; - } - } - } - return false; - } - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java deleted file mode 100644 index fe7d82dd0d..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.env.Environment; - -/** - * A registry for all {@link MvcEndpoint} beans, and a factory for a set of generic ones - * wrapping existing {@link Endpoint} instances that are not already exposed as MVC - * endpoints. - * - * @author Dave Syer - */ -public class MvcEndpoints implements ApplicationContextAware, InitializingBean { - - private ApplicationContext applicationContext; - - private final Set endpoints = new HashSet<>(); - - private Set> customTypes; - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public void afterPropertiesSet() throws Exception { - Collection existing = BeanFactoryUtils - .beansOfTypeIncludingAncestors(this.applicationContext, MvcEndpoint.class) - .values(); - this.endpoints.addAll(existing); - this.customTypes = findEndpointClasses(existing); - @SuppressWarnings("rawtypes") - Collection delegates = BeanFactoryUtils - .beansOfTypeIncludingAncestors(this.applicationContext, Endpoint.class) - .values(); - for (Endpoint endpoint : delegates) { - if (isGenericEndpoint(endpoint.getClass()) && endpoint.isEnabled()) { - EndpointMvcAdapter adapter = new EndpointMvcAdapter(endpoint); - String path = determinePath(endpoint, - this.applicationContext.getEnvironment()); - if (path != null) { - adapter.setPath(path); - } - this.endpoints.add(adapter); - } - } - } - - private Set> findEndpointClasses(Collection existing) { - Set> types = new HashSet<>(); - for (MvcEndpoint endpoint : existing) { - Class type = endpoint.getEndpointType(); - if (type != null) { - types.add(type); - } - } - return types; - } - - public Set getEndpoints() { - return this.endpoints; - } - - /** - * Return the endpoints of the specified type. - * @param the Class type of the endpoints to be returned - * @param type the endpoint type - * @return the endpoints - */ - @SuppressWarnings("unchecked") - public Set getEndpoints(Class type) { - Set result = new HashSet<>(this.endpoints.size()); - for (MvcEndpoint candidate : this.endpoints) { - if (type.isInstance(candidate)) { - result.add((E) candidate); - } - } - return Collections.unmodifiableSet(result); - } - - private boolean isGenericEndpoint(Class type) { - return !this.customTypes.contains(type) - && !MvcEndpoint.class.isAssignableFrom(type); - } - - private String determinePath(Endpoint endpoint, Environment environment) { - ConfigurationProperties configurationProperties = AnnotationUtils - .findAnnotation(endpoint.getClass(), ConfigurationProperties.class); - if (configurationProperties != null) { - return environment.getProperty(configurationProperties.prefix() + ".path"); - } - return null; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java deleted file mode 100644 index f92fb0eecf..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -/** - * An {@link MvcEndpoint} that also includes a logical name. Unlike {@link #getPath() - * endpoints paths}, it should not be possible for a user to change the endpoint name. - * Names provide a consistent way to reference an endpoint, for example they may be used - * as the {@literal 'rel'} attribute in a HAL response. - * - * @author Madhura Bhave - * @since 1.5.0 - */ -public interface NamedMvcEndpoint extends MvcEndpoint { - - /** - * Return the logical name of the endpoint. Names should be non-null, non-empty, - * alpha-numeric values. - * @return the logical name of the endpoint - */ - String getName(); - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java deleted file mode 100644 index 6424d102b0..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Collections; - -import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * Adapter to expose {@link ShutdownEndpoint} as an {@link MvcEndpoint}. - * - * @author Dave Syer - */ -@ConfigurationProperties(prefix = "endpoints.shutdown") -public class ShutdownMvcEndpoint extends EndpointMvcAdapter { - - public ShutdownMvcEndpoint(ShutdownEndpoint delegate) { - super(delegate); - } - - @PostMapping(produces = { ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE, - MediaType.APPLICATION_JSON_VALUE }) - @ResponseBody - @Override - public Object invoke() { - if (!getDelegate().isEnabled()) { - return new ResponseEntity<>( - Collections.singletonMap("message", "This endpoint is disabled"), - HttpStatus.NOT_FOUND); - } - return super.invoke(); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/package-info.java index 32a319eb89..f3e4a76aba 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/package-info.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,5 @@ /** * Endpoints used to expose actuator information. * - * @see org.springframework.boot.actuate.endpoint.Endpoint */ package org.springframework.boot.actuate.endpoint; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtension.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtension.java new file mode 100644 index 0000000000..2f955c1bef --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtension.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.endpoint.web.WebEndpointExtension; + +/** + * Web-specific extension of the {@link AuditEventsEndpoint}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +@WebEndpointExtension(endpoint = AuditEventsEndpoint.class) +public class AuditEventsWebEndpointExtension { + + private final AuditEventsEndpoint delegate; + + public AuditEventsWebEndpointExtension(AuditEventsEndpoint delegate) { + this.delegate = delegate; + } + + @ReadOperation + public Map> eventsWithPrincipalDateAfterAndType( + String principal, Date after, String type) { + return Collections.singletonMap("events", this.delegate + .eventsWithPrincipalDateAfterAndType(principal, after, type)); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HealthWebEndpointExtension.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HealthWebEndpointExtension.java new file mode 100644 index 0000000000..3848e47747 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HealthWebEndpointExtension.java @@ -0,0 +1,129 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.endpoint.web.WebEndpointExtension; +import org.springframework.boot.endpoint.web.WebEndpointResponse; +import org.springframework.http.HttpStatus; +import org.springframework.util.Assert; + +/** + * {@link WebEndpointExtension} for the {@link HealthEndpoint}. + * + * @author Christian Dupuis + * @author Dave Syer + * @author Andy Wilkinson + * @author Phillip Webb + * @author EddĂș MelĂ©ndez + * @author Madhura Bhave + * @since 2.0.0 + */ +@WebEndpointExtension(endpoint = HealthEndpoint.class) +public class HealthWebEndpointExtension { + + private Map statusMapping = new HashMap<>(); + + private final HealthEndpoint delegate; + + public HealthWebEndpointExtension(HealthEndpoint delegate) { + this.delegate = delegate; + setupDefaultStatusMapping(); + } + + private void setupDefaultStatusMapping() { + addStatusMapping(Status.DOWN, 503); + addStatusMapping(Status.OUT_OF_SERVICE, 503); + } + + /** + * Set specific status mappings. + * @param statusMapping a map of status code to {@link HttpStatus} + */ + public void setStatusMapping(Map statusMapping) { + Assert.notNull(statusMapping, "StatusMapping must not be null"); + this.statusMapping = new HashMap<>(statusMapping); + } + + /** + * Add specific status mappings to the existing set. + * @param statusMapping a map of status code to {@link HttpStatus} + */ + public void addStatusMapping(Map statusMapping) { + Assert.notNull(statusMapping, "StatusMapping must not be null"); + this.statusMapping.putAll(statusMapping); + } + + /** + * Add a status mapping to the existing set. + * @param status the status to map + * @param httpStatus the http status + */ + public void addStatusMapping(Status status, Integer httpStatus) { + Assert.notNull(status, "Status must not be null"); + Assert.notNull(httpStatus, "HttpStatus must not be null"); + addStatusMapping(status.getCode(), httpStatus); + } + + /** + * Add a status mapping to the existing set. + * @param statusCode the status code to map + * @param httpStatus the http status + */ + public void addStatusMapping(String statusCode, Integer httpStatus) { + Assert.notNull(statusCode, "StatusCode must not be null"); + Assert.notNull(httpStatus, "HttpStatus must not be null"); + this.statusMapping.put(statusCode, httpStatus); + } + + @ReadOperation + public WebEndpointResponse getHealth() { + Health health = this.delegate.health(); + Integer status = getStatus(health); + return new WebEndpointResponse(health, status); + } + + private int getStatus(Health health) { + String code = getUniformValue(health.getStatus().getCode()); + if (code != null) { + return this.statusMapping.keySet().stream() + .filter((key) -> code.equals(getUniformValue(key))) + .map(this.statusMapping::get).findFirst().orElse(200); + } + return 200; + } + + private String getUniformValue(String code) { + if (code == null) { + return null; + } + StringBuilder builder = new StringBuilder(); + for (char ch : code.toCharArray()) { + if (Character.isAlphabetic(ch) || Character.isDigit(ch)) { + builder.append(Character.toLowerCase(ch)); + } + } + return builder.toString(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpoint.java similarity index 57% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpoint.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpoint.java index 03a1300d4c..43f16df6aa 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpoint.java @@ -14,48 +14,45 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint.mvc; +package org.springframework.boot.actuate.endpoint.web; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; import java.lang.management.PlatformManagedObject; import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.zip.GZIPOutputStream; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.endpoint.web.WebEndpointResponse; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; -import org.springframework.util.StreamUtils; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseStatus; /** - * {@link MvcEndpoint} to expose heap dumps. + * Web {@link Endpoint} to expose heap dumps. * * @author Lari Hotari * @author Phillip Webb * @author Raja Kolli - * @since 1.4.0 + * @author Andy Wilkinson + * @since 2.0.0 */ @ConfigurationProperties(prefix = "endpoints.heapdump") -public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { +@Endpoint(id = "heapdump", types = EndpointType.WEB) +public class HeapDumpWebEndpoint { private final long timeout; @@ -63,28 +60,21 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { private HeapDumper heapDumper; - public HeapdumpMvcEndpoint() { + public HeapDumpWebEndpoint() { this(TimeUnit.SECONDS.toMillis(10)); } - protected HeapdumpMvcEndpoint(long timeout) { - super("heapdump", "/heapdump"); + protected HeapDumpWebEndpoint(long timeout) { this.timeout = timeout; } - @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public void invoke(@RequestParam(defaultValue = "true") boolean live, - HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - if (!isEnabled()) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - return; - } + @ReadOperation + public WebEndpointResponse heapDump(Boolean live) { try { if (this.lock.tryLock(this.timeout, TimeUnit.MILLISECONDS)) { try { - dumpHeap(live, request, response); - return; + return new WebEndpointResponse<>( + dumpHeap(live == null ? true : live)); } finally { this.lock.unlock(); @@ -94,23 +84,22 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { catch (InterruptedException ex) { Thread.currentThread().interrupt(); } - response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); + catch (IOException ex) { + return new WebEndpointResponse<>(HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + catch (HeapDumperUnavailableException ex) { + return new WebEndpointResponse<>(HttpStatus.SERVICE_UNAVAILABLE.value()); + } + return new WebEndpointResponse<>(HttpStatus.TOO_MANY_REQUESTS.value()); } - private void dumpHeap(boolean live, HttpServletRequest request, - HttpServletResponse response) - throws IOException, ServletException, InterruptedException { + private Resource dumpHeap(boolean live) throws IOException, InterruptedException { if (this.heapDumper == null) { this.heapDumper = createHeapDumper(); } File file = createTempFile(live); - try { - this.heapDumper.dumpHeap(file, live); - handle(file, request, response); - } - finally { - file.delete(); - } + this.heapDumper.dumpHeap(file, live); + return new TemporaryFileSystemResource(file); } private File createTempFile(boolean live) throws IOException { @@ -130,29 +119,6 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { return new HotSpotDiagnosticMXBeanHeapDumper(); } - /** - * Handle the heap dump file and respond. By default this method will return the - * response as a GZip stream. - * @param heapDumpFile the generated dump file - * @param request the HTTP request - * @param response the HTTP response - * @throws ServletException on servlet error - * @throws IOException on IO error - */ - protected void handle(File heapDumpFile, HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - response.setContentType("application/octet-stream"); - response.setHeader("Content-Disposition", - "attachment; filename=\"" + (heapDumpFile.getName() + ".gz") + "\""); - try (InputStream in = new FileInputStream(heapDumpFile); - GZIPOutputStream out = new GZIPOutputStream(response.getOutputStream())) { - StreamUtils.copy(in, out); - out.finish(); - } - catch (NullPointerException | FileNotFoundException ex) { - } - } - /** * Strategy interface used to dump the heap to a file. */ @@ -208,7 +174,6 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { /** * Exception to be thrown if the {@link HeapDumper} cannot be created. */ - @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) protected static class HeapDumperUnavailableException extends RuntimeException { public HeapDumperUnavailableException(String message, Throwable cause) { @@ -217,4 +182,104 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { } + private static final class TemporaryFileSystemResource extends FileSystemResource { + + private TemporaryFileSystemResource(File file) { + super(file); + } + + @Override + public ReadableByteChannel readableChannel() throws IOException { + ReadableByteChannel readableChannel = super.readableChannel(); + return new ReadableByteChannel() { + + @Override + public boolean isOpen() { + return readableChannel.isOpen(); + } + + @Override + public void close() throws IOException { + try { + readableChannel.close(); + } + finally { + getFile().delete(); + } + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return readableChannel.read(dst); + } + + }; + } + + @Override + public InputStream getInputStream() throws IOException { + InputStream delegate = super.getInputStream(); + return new InputStream() { + + @Override + public int read() throws IOException { + return delegate.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return delegate.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return delegate.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return delegate.skip(n); + } + + @Override + public int available() throws IOException { + return delegate.available(); + } + + @Override + public void close() throws IOException { + try { + delegate.close(); + } + finally { + getFile().delete(); + } + } + + @Override + public synchronized void mark(int readlimit) { + delegate.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + delegate.reset(); + } + + @Override + public boolean markSupported() { + return delegate.markSupported(); + } + + }; + } + + @Override + public boolean isFile() { + // Prevent zero-copy so we can delete the file on close + return false; + } + + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpoint.java new file mode 100644 index 0000000000..d82ee0c4af --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpoint.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.io.File; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.logging.LogFile; +import org.springframework.core.env.Environment; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +/** + * Web {@link Endpoint} that provides access to an application's log file. + * + * @author Johannes Edmeier + * @author Phillip Webb + * @author Andy Wilkinson + * @since 2.0.0 + */ +@ConfigurationProperties(prefix = "endpoints.logfile") +@Endpoint(id = "logfile", types = EndpointType.WEB) +public class LogFileWebEndpoint { + + private static final Log logger = LogFactory.getLog(LogFileWebEndpoint.class); + + private final Environment environment; + + /** + * External Logfile to be accessed. Can be used if the logfile is written by output + * redirect and not by the logging system itself. + */ + private File externalFile; + + public LogFileWebEndpoint(Environment environment) { + this.environment = environment; + } + + public File getExternalFile() { + return this.externalFile; + } + + public void setExternalFile(File externalFile) { + this.externalFile = externalFile; + } + + @ReadOperation + public Resource logFile() { + Resource logFileResource = getLogFileResource(); + if (logFileResource == null || !logFileResource.isReadable()) { + return null; + } + return logFileResource; + } + + private Resource getLogFileResource() { + if (this.externalFile != null) { + return new FileSystemResource(this.externalFile); + } + LogFile logFile = LogFile.get(this.environment); + if (logFile == null) { + logger.debug("Missing 'logging.file' or 'logging.path' properties"); + return null; + } + return new FileSystemResource(logFile.toString()); + } + +} diff --git a/spring-boot-actuator/src/main/resources/META-INF/spring.factories b/spring-boot-actuator/src/main/resources/META-INF/spring.factories index 81d9a70ceb..b6465391e2 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories +++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories @@ -1,22 +1,27 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.cache.CacheStatisticsAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryActuatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.endpoint.EndpointMBeanExportAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.MetricFilterAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.MetricRepositoryAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.MetricsDropwizardAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.MetricsChannelAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.MetricExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.PublicMetricsAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.trace.TraceRepositoryAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.trace.TraceWebFilterAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryActuatorAutoConfiguration +org.springframework.boot.actuate.autoconfigure.trace.TraceWebFilterAutoConfiguration org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration=\ -org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcManagementContextConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ReactiveEndpointChildManagementContextConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointChildManagementContextConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.WebEndpointInfrastructureManagementContextConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointManagementContextConfiguration,\ org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaManagementContextConfiguration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointServletWebAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointServletWebAutoConfigurationTests.java new file mode 100755 index 0000000000..51395a8e85 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointServletWebAutoConfigurationTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure; + +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; + +/** + * Tests for {@link ServletEndpointAutoConfiguration}. + * + * @author Phillip Webb + * @author Greg Turnquist + * @author Andy Wilkinson + * @author EddĂș MelĂ©ndez + * @author Ben Hale + */ +public class EndpointServletWebAutoConfigurationTests { + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelectorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelectorTests.java new file mode 100644 index 0000000000..692518df5b --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelectorTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.Test; + +import org.springframework.core.annotation.Order; +import org.springframework.core.type.StandardAnnotationMetadata; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ManagementContextConfigurationImportSelector}. + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +public class ManagementContextConfigurationImportSelectorTests { + + @Test + public void selectImportsShouldOrderResult() throws Exception { + String[] imports = new TestManagementContextConfigurationsImportSelector(C.class, + A.class, D.class, B.class).selectImports( + new StandardAnnotationMetadata(EnableChildContext.class)); + assertThat(imports).containsExactly(A.class.getName(), B.class.getName(), + C.class.getName(), D.class.getName()); + } + + @Test + public void selectImportsFiltersChildOnlyConfigurationWhenUsingSameContext() + throws Exception { + String[] imports = new TestManagementContextConfigurationsImportSelector( + ChildOnly.class, SameOnly.class, A.class).selectImports( + new StandardAnnotationMetadata(EnableSameContext.class)); + assertThat(imports).containsExactlyInAnyOrder(SameOnly.class.getName(), + A.class.getName()); + } + + @Test + public void selectImportsFiltersSameOnlyConfigurationWhenUsingChildContext() + throws Exception { + String[] imports = new TestManagementContextConfigurationsImportSelector( + ChildOnly.class, SameOnly.class, A.class).selectImports( + new StandardAnnotationMetadata(EnableChildContext.class)); + assertThat(imports).containsExactlyInAnyOrder(ChildOnly.class.getName(), + A.class.getName()); + } + + private static final class TestManagementContextConfigurationsImportSelector + extends ManagementContextConfigurationImportSelector { + + private final List factoryNames; + + private TestManagementContextConfigurationsImportSelector(Class... classes) { + this.factoryNames = Stream.of(classes).map(Class::getName) + .collect(Collectors.toList()); + } + + @Override + protected List loadFactoryNames() { + return this.factoryNames; + } + + } + + @Order(1) + private static class A { + + } + + @Order(2) + private static class B { + + } + + @Order(3) + private static class C { + + } + + static class D { + + } + + @ManagementContextConfiguration(ManagementContextType.CHILD) + static class ChildOnly { + + } + + @ManagementContextConfiguration(ManagementContextType.SAME) + static class SameOnly { + + } + + @EnableManagementContext(ManagementContextType.CHILD) + static class EnableChildContext { + + } + + @EnableManagementContext(ManagementContextType.SAME) + static class EnableSameContext { + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java index 9e3368355b..3d07c0b71d 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java @@ -19,7 +19,7 @@ package org.springframework.boot.actuate.autoconfigure; import org.junit.After; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointMBeanExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration; @@ -73,13 +73,13 @@ public class SpringApplicationHierarchyTests { } - @EnableAutoConfiguration(exclude = { EndpointMBeanExportAutoConfiguration.class, - ElasticsearchDataAutoConfiguration.class, + @EnableAutoConfiguration(exclude = { ElasticsearchDataAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class, MongoDataAutoConfiguration.class, Neo4jDataAutoConfiguration.class, Neo4jRepositoriesAutoConfiguration.class, RedisAutoConfiguration.class, - RedisRepositoriesAutoConfiguration.class }, excludeName = { + RedisRepositoriesAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class }, excludeName = { "org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration" }) public static class Parent { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpointTests.java new file mode 100644 index 0000000000..bb956f81f9 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpointTests.java @@ -0,0 +1,364 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint; + +import org.junit.Test; + +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.boot.endpoint.jmx.JmxEndpointExtension; +import org.springframework.boot.endpoint.web.WebEndpointExtension; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnEnabledEndpoint}. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +public class ConditionalOnEnabledEndpointTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + public void enabledByDefault() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void disabledViaSpecificProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.foo.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("foo")); + } + + @Test + public void disabledViaGeneralProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("foo")); + } + + @Test + public void enabledOverrideViaSpecificProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.foo.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaSpecificWebProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.foo.enabled=false", + "endpoints.foo.web.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaSpecificJmxProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.foo.enabled=false", + "endpoints.foo.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaSpecificAnyProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.foo.enabled=false", + "endpoints.foo.web.enabled=false", + "endpoints.foo.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaGeneralWebProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.all.web.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaGeneralJmxProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.all.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaGeneralAnyProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.all.web.enabled=false", + "endpoints.all.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void disabledEvenWithEnabledGeneralProperties() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=true", + "endpoints.all.web.enabled=true", + "endpoints.all.jmx.enabled=true", "endpoints.foo.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("foo")); + } + + @Test + public void disabledByDefaultWithAnnotationFlag() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + public void disabledByDefaultWithAnnotationFlagEvenWithGeneralProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.all.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + public void disabledByDefaultWithAnnotationFlagEvenWithGeneralWebProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.all.web.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + public void disabledByDefaultWithAnnotationFlagEvenWithGeneralJmxProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.all.jmx.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + public void enabledOverrideWithAndAnnotationFlagAndSpecificProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.bar.enabled=true") + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + public void enabledOverrideWithAndAnnotationFlagAndSpecificWebProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.bar.web.enabled=true") + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + public void enabledOverrideWithAndAnnotationFlagAndSpecificJmxProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.bar.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + public void enabledOverrideWithAndAnnotationFlagAndAnyProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.bar.web.enabled=false", + "endpoints.bar.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + public void enabledOnlyWebByDefault() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .run((context) -> assertThat(context).hasBean("onlyweb")); + } + + @Test + public void disabledOnlyWebViaEndpointProperty() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .withPropertyValues("endpoints.onlyweb.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("onlyweb")); + } + + @Test + public void disabledOnlyWebViaSpecificTechProperty() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .withPropertyValues("endpoints.onlyweb.web.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("onlyweb")); + } + + @Test + public void enableOverridesOnlyWebViaGeneralJmxPropertyHasNoEffect() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.all.jmx.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean("onlyweb")); + } + + @Test + public void enableOverridesOnlyWebViaSpecificJmxPropertyHasNoEffect() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.onlyweb.jmx.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("onlyweb")); + } + + @Test + public void enableOverridesOnlyWebViaSpecificWebProperty() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.onlyweb.web.enabled=true") + .run((context) -> assertThat(context).hasBean("onlyweb")); + } + + @Test + public void disabledOnlyWebEvenWithEnabledGeneralProperties() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class).withPropertyValues( + "endpoints.all.enabled=true", "endpoints.all.web.enabled=true", + "endpoints.onlyweb.enabled=true", "endpoints.onlyweb.web.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("foo")); + } + + @Test + public void contextFailIfEndpointTypeIsNotDetected() { + this.contextRunner.withUserConfiguration(NonEndpointBeanConfig.class) + .run((context) -> assertThat(context).hasFailed()); + } + + @Test + public void webExtensionWithEnabledByDefaultEndpoint() { + this.contextRunner.withUserConfiguration(FooWebExtensionConfig.class) + .run((context) -> assertThat(context) + .hasSingleBean(FooWebEndpointExtension.class)); + } + + @Test + public void webExtensionWithEnabledByDefaultEndpointCanBeDisabled() { + this.contextRunner.withUserConfiguration(FooWebExtensionConfig.class) + .withPropertyValues("endpoints.foo.enabled=false") + .run((context) -> assertThat(context) + .doesNotHaveBean(FooWebEndpointExtension.class)); + } + + @Test + public void jmxExtensionWithEnabledByDefaultEndpoint() { + this.contextRunner.withUserConfiguration(FooJmxExtensionConfig.class) + .run((context) -> assertThat(context) + .hasSingleBean(FooJmxEndpointExtension.class)); + } + + @Test + public void jmxExtensionWithEnabledByDefaultEndpointCanBeDisabled() { + this.contextRunner.withUserConfiguration(FooJmxExtensionConfig.class) + .withPropertyValues("endpoints.foo.enabled=false") + .run((context) -> assertThat(context) + .doesNotHaveBean(FooJmxEndpointExtension.class)); + } + + @Configuration + static class FooConfig { + + @Bean + @ConditionalOnEnabledEndpoint + public FooEndpoint foo() { + return new FooEndpoint(); + } + + } + + @Endpoint(id = "foo") + static class FooEndpoint { + + } + + @Configuration + static class BarConfig { + + @Bean + @ConditionalOnEnabledEndpoint + public BarEndpoint bar() { + return new BarEndpoint(); + } + + } + + @Endpoint(id = "bar", types = { EndpointType.WEB, + EndpointType.JMX }, enabledByDefault = false) + static class BarEndpoint { + + } + + @Configuration + static class OnlyWebConfig { + + @Bean(name = "onlyweb") + @ConditionalOnEnabledEndpoint + public OnlyWebEndpoint onlyWeb() { + return new OnlyWebEndpoint(); + } + + } + + @Endpoint(id = "onlyweb", types = EndpointType.WEB) + static class OnlyWebEndpoint { + + } + + @Configuration + static class NonEndpointBeanConfig { + + @Bean + @ConditionalOnEnabledEndpoint + public String foo() { + return "endpoint type cannot be detected"; + } + + } + + @JmxEndpointExtension(endpoint = FooEndpoint.class) + static class FooJmxEndpointExtension { + + } + + @Configuration + static class FooJmxExtensionConfig { + + @Bean + @ConditionalOnEnabledEndpoint + FooJmxEndpointExtension fooJmxEndpointExtension() { + return new FooJmxEndpointExtension(); + } + + } + + @WebEndpointExtension(endpoint = FooEndpoint.class) + static class FooWebEndpointExtension { + + } + + @Configuration + static class FooWebExtensionConfig { + + @Bean + @ConditionalOnEnabledEndpoint + FooWebEndpointExtension fooJmxEndpointExtension() { + return new FooWebEndpointExtension(); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfigurationTests.java deleted file mode 100644 index c692609c15..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfigurationTests.java +++ /dev/null @@ -1,392 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -import java.io.IOException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Properties; - -import javax.sql.DataSource; - -import liquibase.integration.spring.SpringLiquibase; -import org.flywaydb.core.Flyway; -import org.junit.After; -import org.junit.Test; - -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.PublicMetricsAutoConfiguration; -import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint; -import org.springframework.boot.actuate.endpoint.BeansEndpoint; -import org.springframework.boot.actuate.endpoint.DumpEndpoint; -import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; -import org.springframework.boot.actuate.endpoint.FlywayEndpoint; -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.endpoint.InfoEndpoint; -import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint; -import org.springframework.boot.actuate.endpoint.LoggersEndpoint; -import org.springframework.boot.actuate.endpoint.MetricsEndpoint; -import org.springframework.boot.actuate.endpoint.PublicMetrics; -import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; -import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; -import org.springframework.boot.actuate.endpoint.TraceEndpoint; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.info.Info; -import org.springframework.boot.actuate.info.InfoContributor; -import org.springframework.boot.actuate.metrics.Metric; -import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; -import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; -import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration; -import org.springframework.boot.autoconfigure.info.ProjectInfoProperties; -import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; -import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; -import org.springframework.boot.context.properties.bind.Bindable; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; -import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.Order; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PropertiesLoaderUtils; -import org.springframework.validation.BindException; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EndpointAutoConfiguration}. - * - * @author Dave Syer - * @author Phillip Webb - * @author Greg Turnquist - * @author Christian Dupuis - * @author Stephane Nicoll - * @author EddĂș MelĂ©ndez - * @author Meang Akira Tanaka - * @author Ben Hale - */ -public class EndpointAutoConfigurationTests { - - private AnnotationConfigApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void endpoints() throws Exception { - load(CustomLoggingConfig.class, EndpointAutoConfiguration.class); - assertThat(this.context.getBean(BeansEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(DumpEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(EnvironmentEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(HealthEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(InfoEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(LoggersEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(MetricsEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(ShutdownEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(TraceEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(RequestMappingEndpoint.class)).isNotNull(); - } - - @Test - public void healthEndpoint() { - load(EmbeddedDataSourceConfiguration.class, EndpointAutoConfiguration.class, - HealthIndicatorAutoConfiguration.class); - HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); - assertThat(bean).isNotNull(); - Health result = bean.invoke(); - assertThat(result).isNotNull(); - assertThat(result.getDetails().containsKey("db")).isTrue(); - } - - @Test - public void healthEndpointWithDefaultHealthIndicator() { - load(EndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class); - HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); - assertThat(bean).isNotNull(); - Health result = bean.invoke(); - assertThat(result).isNotNull(); - } - - @Test - public void loggersEndpointHasLoggers() throws Exception { - load(CustomLoggingConfig.class, EndpointAutoConfiguration.class); - LoggersEndpoint endpoint = this.context.getBean(LoggersEndpoint.class); - Map result = endpoint.invoke(); - assertThat((Map) result.get("loggers")).size().isGreaterThan(0); - } - - @Test - public void metricEndpointsHasSystemMetricsByDefault() { - load(PublicMetricsAutoConfiguration.class, EndpointAutoConfiguration.class); - MetricsEndpoint endpoint = this.context.getBean(MetricsEndpoint.class); - Map metrics = endpoint.invoke(); - assertThat(metrics.containsKey("mem")).isTrue(); - assertThat(metrics.containsKey("heap.used")).isTrue(); - } - - @Test - public void metricEndpointCustomPublicMetrics() { - load(CustomPublicMetricsConfig.class, PublicMetricsAutoConfiguration.class, - EndpointAutoConfiguration.class); - MetricsEndpoint endpoint = this.context.getBean(MetricsEndpoint.class); - Map metrics = endpoint.invoke(); - - // Custom metrics - assertThat(metrics.containsKey("foo")).isTrue(); - - // System metrics still available - assertThat(metrics.containsKey("mem")).isTrue(); - assertThat(metrics.containsKey("heap.used")).isTrue(); - - } - - @Test - public void autoConfigurationAuditEndpoints() { - load(EndpointAutoConfiguration.class, ConditionEvaluationReport.class); - assertThat(this.context.getBean(AutoConfigurationReportEndpoint.class)) - .isNotNull(); - } - - @Test - public void testInfoEndpoint() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("info.foo:bar").applyTo(this.context); - this.context.register(ProjectInfoAutoConfiguration.class, - InfoContributorAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - - InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); - assertThat(endpoint).isNotNull(); - assertThat(endpoint.invoke().get("git")).isNotNull(); - assertThat(endpoint.invoke().get("foo")).isEqualTo("bar"); - } - - @Test - public void testInfoEndpointNoGitProperties() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("spring.info.git.location:classpath:nonexistent") - .applyTo(this.context); - this.context.register(InfoContributorAutoConfiguration.class, - EndpointAutoConfiguration.class); - this.context.refresh(); - InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); - assertThat(endpoint).isNotNull(); - assertThat(endpoint.invoke().get("git")).isNull(); - } - - @Test - public void testInfoEndpointOrdering() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("info.name:foo").applyTo(this.context); - this.context.register(CustomInfoContributorsConfig.class, - ProjectInfoAutoConfiguration.class, - InfoContributorAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - - InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); - Map info = endpoint.invoke(); - assertThat(info).isNotNull(); - assertThat(info.get("name")).isEqualTo("foo"); - assertThat(info.get("version")).isEqualTo("1.0"); - Object git = info.get("git"); - assertThat(git).isInstanceOf(Map.class); - } - - @Test - public void testFlywayEndpoint() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(EmbeddedDataSourceConfiguration.class, - FlywayAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - FlywayEndpoint endpoint = this.context.getBean(FlywayEndpoint.class); - assertThat(endpoint).isNotNull(); - assertThat(endpoint.invoke()).hasSize(1); - } - - @Test - public void testFlywayEndpointWithMultipleFlywayBeans() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(MultipleFlywayBeansConfig.class, - FlywayAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeansOfType(Flyway.class)).hasSize(2); - assertThat(this.context.getBeansOfType(FlywayEndpoint.class)).hasSize(1); - } - - @Test - public void testLiquibaseEndpoint() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(EmbeddedDataSourceConfiguration.class, - LiquibaseAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - LiquibaseEndpoint endpoint = this.context.getBean(LiquibaseEndpoint.class); - assertThat(endpoint).isNotNull(); - assertThat(endpoint.invoke()).hasSize(1); - } - - @Test - public void testLiquibaseEndpointWithMultipleSpringLiquibaseBeans() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(MultipleLiquibaseBeansConfig.class, - LiquibaseAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeansOfType(SpringLiquibase.class)).hasSize(2); - assertThat(this.context.getBeansOfType(LiquibaseEndpoint.class)).hasSize(1); - } - - private void load(Class... config) { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(config); - this.context.refresh(); - } - - @Configuration - static class CustomLoggingConfig { - - @Bean - LoggingSystem loggingSystem() { - return LoggingSystem.get(getClass().getClassLoader()); - } - - } - - @Configuration - static class CustomPublicMetricsConfig { - - @Bean - PublicMetrics customPublicMetrics() { - return () -> { - Metric metric = new Metric<>("foo", 1); - return Collections.>singleton(metric); - }; - } - - } - - @Configuration - static class CustomInfoContributorsConfig { - - @Bean - @Order(InfoContributorAutoConfiguration.DEFAULT_ORDER - 1) - public InfoContributor myInfoContributor() { - return (builder) -> { - builder.withDetail("name", "bar"); - builder.withDetail("version", "1.0"); - }; - } - - @Bean - @Order(InfoContributorAutoConfiguration.DEFAULT_ORDER + 1) - public InfoContributor myAnotherContributor(ProjectInfoProperties properties) - throws IOException, BindException { - return new GitFullInfoContributor(properties.getGit().getLocation()); - } - - private static class GitFullInfoContributor implements InfoContributor { - - private static final ResolvableType STRING_OBJECT_MAP = ResolvableType - .forClassWithGenerics(Map.class, String.class, Object.class); - - private Map content = new LinkedHashMap<>(); - - GitFullInfoContributor(Resource location) throws BindException, IOException { - if (!location.exists()) { - return; - } - Properties properties = PropertiesLoaderUtils.loadProperties(location); - new Binder(new MapConfigurationPropertySource(properties)).bind("info", - Bindable.of(STRING_OBJECT_MAP).withExistingValue(this.content)); - } - - @Override - public void contribute(Info.Builder builder) { - if (!this.content.isEmpty()) { - builder.withDetail("git", this.content); - } - } - - } - - } - - static class DataSourceConfig { - - @Bean - public DataSource dataSourceOne() { - return DataSourceBuilder.create().url("jdbc:hsqldb:mem:changelogdbtest") - .username("sa").build(); - } - - @Bean - public DataSource dataSourceTwo() { - return DataSourceBuilder.create().url("jdbc:hsqldb:mem:changelogdbtest2") - .username("sa").build(); - } - - } - - @Configuration - static class MultipleFlywayBeansConfig extends DataSourceConfig { - - @Bean - public Flyway flywayOne() { - Flyway flyway = new Flyway(); - flyway.setDataSource(dataSourceOne()); - return flyway; - } - - @Bean - public Flyway flywayTwo() { - Flyway flyway = new Flyway(); - flyway.setDataSource(dataSourceTwo()); - return flyway; - } - - } - - @Configuration - static class MultipleLiquibaseBeansConfig extends DataSourceConfig { - - @Bean - public SpringLiquibase liquibaseOne() { - SpringLiquibase liquibase = new SpringLiquibase(); - liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); - liquibase.setDataSource(dataSourceOne()); - return liquibase; - } - - @Bean - public SpringLiquibase liquibaseTwo() { - SpringLiquibase liquibase = new SpringLiquibase(); - liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); - liquibase.setDataSource(dataSourceTwo()); - return liquibase; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfigurationTests.java deleted file mode 100644 index 1178e12d13..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfigurationTests.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -import javax.management.InstanceNotFoundException; -import javax.management.IntrospectionException; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; -import javax.management.ReflectionException; - -import org.junit.After; -import org.junit.Test; - -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanExporter; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.EnableMBeanExport; -import org.springframework.jmx.export.MBeanExporter; -import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.jmx.support.ObjectNameManager; -import org.springframework.mock.env.MockEnvironment; -import org.springframework.stereotype.Component; -import org.springframework.util.ObjectUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EndpointMBeanExportAutoConfiguration}. - * - */ -public class EndpointMBeanExportAutoConfigurationTests { - - private AnnotationConfigApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void testEndpointMBeanExporterIsInstalled() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, JmxAutoConfiguration.class, - EndpointAutoConfiguration.class, - EndpointMBeanExportAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBean(EndpointMBeanExporter.class)).isNotNull(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .queryNames(getObjectName("*", "*,*", this.context), null)).isNotEmpty(); - } - - @Test - public void testEndpointMBeanExporterIsNotInstalledIfManagedResource() - throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, JmxAutoConfiguration.class, - ManagedEndpoint.class, EndpointMBeanExportAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBean(EndpointMBeanExporter.class)).isNotNull(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .queryNames(getObjectName("*", "*,*", this.context), null)).isEmpty(); - } - - @Test - public void testEndpointMBeanExporterIsNotInstalledIfNestedInManagedResource() - throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, JmxAutoConfiguration.class, - NestedInManagedEndpoint.class, EndpointMBeanExportAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBean(EndpointMBeanExporter.class)).isNotNull(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .queryNames(getObjectName("*", "*,*", this.context), null)).isEmpty(); - } - - @Test(expected = NoSuchBeanDefinitionException.class) - public void testEndpointMBeanExporterIsNotInstalled() { - MockEnvironment environment = new MockEnvironment(); - environment.setProperty("endpoints.jmx.enabled", "false"); - this.context = new AnnotationConfigApplicationContext(); - this.context.setEnvironment(environment); - this.context.register(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointMBeanExportAutoConfiguration.class); - this.context.refresh(); - this.context.getBean(EndpointMBeanExporter.class); - } - - @Test - public void testEndpointMBeanExporterWithProperties() throws IntrospectionException, - InstanceNotFoundException, MalformedObjectNameException, ReflectionException { - MockEnvironment environment = new MockEnvironment(); - environment.setProperty("endpoints.jmx.domain", "test-domain"); - environment.setProperty("endpoints.jmx.unique_names", "true"); - environment.setProperty("endpoints.jmx.static_names", "key1=value1, key2=value2"); - this.context = new AnnotationConfigApplicationContext(); - this.context.setEnvironment(environment); - this.context.register(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointMBeanExportAutoConfiguration.class); - this.context.refresh(); - this.context.getBean(EndpointMBeanExporter.class); - - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer().getMBeanInfo(ObjectNameManager.getInstance( - getObjectName("test-domain", "healthEndpoint", this.context).toString() - + ",key1=value1,key2=value2"))).isNotNull(); - } - - @Test - public void testEndpointMBeanExporterInParentChild() throws IntrospectionException, - InstanceNotFoundException, MalformedObjectNameException, ReflectionException { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointMBeanExportAutoConfiguration.class); - AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); - parent.register(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointMBeanExportAutoConfiguration.class); - this.context.setParent(parent); - parent.refresh(); - this.context.refresh(); - parent.close(); - } - - private ObjectName getObjectName(String domain, String beanKey, - ApplicationContext applicationContext) throws MalformedObjectNameException { - String name = "%s:type=Endpoint,name=%s"; - if (applicationContext.getParent() != null) { - name = name + ",context=%s"; - } - if (applicationContext.getEnvironment().getProperty("endpoints.jmx.unique_names", - Boolean.class, false)) { - name = name + ",identity=" + ObjectUtils - .getIdentityHexString(applicationContext.getBean(beanKey)); - } - if (applicationContext.getParent() != null) { - return ObjectNameManager.getInstance(String.format(name, domain, beanKey, - ObjectUtils.getIdentityHexString(applicationContext))); - } - return ObjectNameManager.getInstance(String.format(name, domain, beanKey)); - } - - @Configuration - @EnableMBeanExport - public static class TestConfiguration { - - } - - @Component - @ManagedResource - public static class ManagedEndpoint extends AbstractEndpoint { - - public ManagedEndpoint() { - super("managed"); - } - - @Override - public Boolean invoke() { - return true; - } - - } - - @Configuration - @ManagedResource - public static class NestedInManagedEndpoint { - - @Bean - public Endpoint nested() { - return new Nested(); - } - - class Nested extends AbstractEndpoint { - - Nested() { - super("managed"); - } - - @Override - public Boolean invoke() { - return true; - } - - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMvcIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMvcIntegrationTests.java deleted file mode 100755 index edd4cd0ba3..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMvcIntegrationTests.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -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 java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConverters; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for MVC {@link Endpoint}s. - * - * @author Dave Syer - */ -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "foo.bar=baz") -@DirtiesContext -@TestPropertySource(properties = "management.security.enabled=false") -public class EndpointMvcIntegrationTests { - - @LocalServerPort - private int port; - - @Autowired - private TestInterceptor interceptor; - - @Test - public void envEndpointNotHidden() throws InterruptedException { - String body = new TestRestTemplate().getForObject( - "http://localhost:" + this.port + "/application/env/foo.bar", - String.class); - assertThat(body).isNotNull().contains("\"baz\""); - assertThat(this.interceptor.invoked()).isTrue(); - } - - @Test - public void healthEndpointNotHidden() throws InterruptedException { - String body = new TestRestTemplate().getForObject( - "http://localhost:" + this.port + "/application/health", String.class); - assertThat(body).isNotNull().contains("status"); - assertThat(this.interceptor.invoked()).isTrue(); - } - - @Configuration - @MinimalWebConfiguration - @Import({ JacksonAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, AuditAutoConfiguration.class }) - @RestController - protected static class Application { - - private final List> converters; - - public Application(ObjectProvider>> converters) { - this.converters = converters.getIfAvailable(); - } - - @RequestMapping("/{name}/{env}/{bar}") - public Map master(@PathVariable String name, - @PathVariable String env, @PathVariable String label) { - return Collections.singletonMap("foo", (Object) "bar"); - } - - @RequestMapping("/{name}/{env}") - public Map master(@PathVariable String name, - @PathVariable String env) { - return Collections.singletonMap("foo", (Object) "bar"); - } - - @Bean - @ConditionalOnMissingBean - public HttpMessageConverters messageConverters() { - return new HttpMessageConverters(this.converters == null - ? Collections.>emptyList() : this.converters); - } - - @Bean - public EndpointHandlerMappingCustomizer mappingCustomizer() { - return (mapping) -> mapping.setInterceptors(interceptor()); - } - - @Bean - protected TestInterceptor interceptor() { - return new TestInterceptor(); - } - - } - - @Target(ElementType.TYPE) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @Import({ ServletWebServerFactoryAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class, - WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class, - ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) - protected @interface MinimalWebConfiguration { - - } - - protected static class TestInterceptor extends HandlerInterceptorAdapter { - - private final CountDownLatch latch = new CountDownLatch(1); - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, - Object handler, ModelAndView modelAndView) throws Exception { - this.latch.countDown(); - } - - public boolean invoked() throws InterruptedException { - return this.latch.await(30, TimeUnit.SECONDS); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfigurationTests.java deleted file mode 100755 index 2c2072d3df..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfigurationTests.java +++ /dev/null @@ -1,885 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -import java.io.FileNotFoundException; -import java.net.SocketException; -import java.net.URI; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Vector; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.catalina.Valve; -import org.apache.catalina.valves.AccessLogValve; -import org.apache.http.client.HttpClient; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustSelfSignedStrategy; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContextBuilder; -import org.hamcrest.Matcher; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer; -import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; -import org.springframework.boot.context.event.ApplicationFailedEvent; -import org.springframework.boot.context.properties.source.ConfigurationPropertySources; -import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.boot.testsupport.assertj.Matched; -import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer; -import org.springframework.boot.web.context.WebServerInitializedEvent; -import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; -import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; -import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; -import org.springframework.boot.web.servlet.server.ServletWebServerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.stereotype.Controller; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.util.StreamUtils; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; -import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.startsWith; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link EndpointWebMvcAutoConfiguration}. - * - * @author Phillip Webb - * @author Greg Turnquist - * @author Andy Wilkinson - * @author EddĂș MelĂ©ndez - * @author Ben Hale - */ -public class EndpointWebMvcAutoConfigurationTests { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private final AnnotationConfigServletWebServerApplicationContext applicationContext = new AnnotationConfigServletWebServerApplicationContext(); - - private static ThreadLocal ports = new ThreadLocal<>(); - - @Before - public void setUp() { - Ports values = new Ports(); - ports.set(values); - TestPropertyValues - .of("management.security.enabled=false", "server.servlet.context-path=", - "server.port=" + ports.get().server) - .applyTo(this.applicationContext); - } - - @After - public void cleanUp() throws Exception { - this.applicationContext.close(); - } - - @Test - public void onSamePort() throws Exception { - TestPropertyValues.of("management.security.enabled=false") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/application/endpoint", ports.get().server, "endpointoutput"); - assertThat(hasHeader("/application/endpoint", ports.get().server, - "X-Application-Context")).isFalse(); - assertThat(this.applicationContext.containsBean("applicationContextIdFilter")) - .isFalse(); - } - - @Test - public void onSamePortWithHeader() throws Exception { - TestPropertyValues.of("management.add-application-context-header:true") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertThat(hasHeader("/endpoint", ports.get().server, "X-Application-Context")) - .isTrue(); - assertThat(this.applicationContext.containsBean("applicationContextIdFilter")) - .isTrue(); - } - - @Test - public void onDifferentPort() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/application/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - List interceptors = (List) ReflectionTestUtils.getField( - managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); - assertThat(interceptors).hasSize(2); - } - - @Test - public void onDifferentPortAndRootContext() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management, - "management.context-path=/").applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - List interceptors = (List) ReflectionTestUtils.getField( - managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); - assertThat(interceptors).hasSize(2); - } - - @Test - public void onDifferentPortWithSpecificServer() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(SpecificWebServerConfig.class, RootConfig.class, - DifferentPortConfig.class, EndpointConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/application/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - List interceptors = (List) ReflectionTestUtils.getField( - managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); - assertThat(interceptors).hasSize(2); - ServletWebServerFactory parentFactory = this.applicationContext - .getBean(ServletWebServerFactory.class); - ServletWebServerFactory managementFactory = managementContext - .getBean(ServletWebServerFactory.class); - assertThat(parentFactory).isInstanceOf(SpecificServletWebServerFactory.class); - assertThat(managementFactory).isInstanceOf(SpecificServletWebServerFactory.class); - assertThat(managementFactory).isNotSameAs(parentFactory); - } - - @Test - public void onDifferentPortAndContext() throws Exception { - TestPropertyValues - .of("management.port=" + ports.get().management, - "management.context-path=/admin") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/admin/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - } - - @Test - public void onDifferentPortAndMainContext() throws Exception { - TestPropertyValues - .of("server.servlet.context-path=/spring", - "management.port=" + ports.get().management, - "management.context-path=/admin") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/spring/controller", ports.get().server, "controlleroutput"); - assertContent("/admin/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - } - - @Test - public void onDifferentPortWithoutErrorMvcAutoConfiguration() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/error", ports.get().management, null); - } - - @Test - public void onDifferentPortInWebServer() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - ServletContext servletContext = mock(ServletContext.class); - given(servletContext.getInitParameterNames()) - .willReturn(new Vector().elements()); - given(servletContext.getAttributeNames()) - .willReturn(new Vector().elements()); - this.applicationContext.setServletContext(servletContext); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().management, null); - assertContent("/endpoint", ports.get().management, null); - } - - @Test - public void onDifferentPortWithPrimaryFailure() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - ApplicationFailedEvent event = mock(ApplicationFailedEvent.class); - given(event.getApplicationContext()).willReturn(this.applicationContext); - this.applicationContext.publishEvent(event); - assertThat(((ConfigurableApplicationContext) managementContext).isActive()) - .isFalse(); - } - - @Test - public void disabled() throws Exception { - TestPropertyValues.of("management.port=-1").applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/endpoint", ports.get().management, null); - } - - @Test - public void specificPortsViaProperties() throws Exception { - TestPropertyValues - .of("server.port:" + ports.get().server, - "management.port:" + ports.get().management, - "management.security.enabled:false") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/application/endpoint", ports.get().management, "endpointoutput"); - } - - @Test - public void managementContextFailureCausesMainContextFailure() throws Exception { - TestPropertyValues - .of("server.port:" + ports.get().server, - "management.port:" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.applicationContext.addApplicationListener( - (ApplicationListener) (event) -> { - if (event.getApplicationContext().getParent() != null) { - throw new RuntimeException(); - } - }); - this.thrown.expect(RuntimeException.class); - this.applicationContext.refresh(); - } - - @Test - public void contextPath() throws Exception { - TestPropertyValues - .of("management.context-path:/test", "management.security.enabled:false") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - PropertyPlaceholderAutoConfiguration.class, - JacksonAutoConfiguration.class, - ServletWebServerFactoryAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, AuditAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/test/endpoint", ports.get().server, "endpointoutput"); - } - - @Test - public void overrideServerProperties() throws Exception { - TestPropertyValues.of("server.displayName:foo").applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - PropertyPlaceholderAutoConfiguration.class, - JacksonAutoConfiguration.class, - ServletWebServerFactoryAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, AuditAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - ServerProperties serverProperties = this.applicationContext - .getBean(ServerProperties.class); - assertThat(serverProperties.getDisplayName()).isEqualTo("foo"); - } - - @Test - public void portPropertiesOnSamePort() throws Exception { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - new ServerPortInfoApplicationContextInitializer() - .initialize(this.applicationContext); - this.applicationContext.refresh(); - Integer localServerPort = this.applicationContext.getEnvironment() - .getProperty("local.server.port", Integer.class); - Integer localManagementPort = this.applicationContext.getEnvironment() - .getProperty("local.management.port", Integer.class); - assertThat(localServerPort).isNotNull(); - assertThat(localManagementPort).isNotNull(); - assertThat(localServerPort).isEqualTo(localManagementPort); - } - - @Test - public void portPropertiesOnDifferentPort() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - new ServerPortInfoApplicationContextInitializer() - .initialize(this.applicationContext); - this.applicationContext.register(RootConfig.class, DifferentPortConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - Integer localServerPort = this.applicationContext.getEnvironment() - .getProperty("local.server.port", Integer.class); - Integer localManagementPort = this.applicationContext.getEnvironment() - .getProperty("local.management.port", Integer.class); - assertThat(localServerPort).isNotNull(); - assertThat(localManagementPort).isNotNull(); - assertThat(localServerPort).isNotEqualTo(localManagementPort); - } - - @Test - public void singleRequestMappingInfoHandlerMappingBean() throws Exception { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - RequestMappingInfoHandlerMapping mapping = this.applicationContext - .getBean(RequestMappingInfoHandlerMapping.class); - assertThat(mapping).isNotEqualTo(instanceOf(EndpointHandlerMapping.class)); - } - - @Test - public void endpointsDefaultConfiguration() throws Exception { - this.applicationContext.register(LoggingConfig.class, RootConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - // /health, /metrics, /loggers, /env, /actuator, /heapdump, /auditevents - // (/shutdown is disabled by default) - assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class)).hasSize(6); - } - - @Test - public void endpointsAllDisabled() throws Exception { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - TestPropertyValues.of("endpoints.enabled:false").applyTo(this.applicationContext); - this.applicationContext.refresh(); - assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class)).isEmpty(); - } - - @Test - public void environmentEndpointDisabled() throws Exception { - endpointDisabled("env", EnvironmentMvcEndpoint.class); - } - - @Test - public void environmentEndpointEnabledOverride() throws Exception { - endpointEnabledOverride("env", EnvironmentMvcEndpoint.class); - } - - @Test - public void loggersEndpointDisabled() throws Exception { - endpointDisabled("loggers", LoggersMvcEndpoint.class); - } - - @Test - public void loggersEndpointEnabledOverride() throws Exception { - endpointEnabledOverride("loggers", LoggersMvcEndpoint.class); - } - - @Test - public void metricsEndpointDisabled() throws Exception { - endpointDisabled("metrics", MetricsMvcEndpoint.class); - } - - @Test - public void metricsEndpointEnabledOverride() throws Exception { - endpointEnabledOverride("metrics", MetricsMvcEndpoint.class); - } - - @Test - public void healthEndpointDisabled() throws Exception { - endpointDisabled("health", HealthMvcEndpoint.class); - } - - @Test - public void healthEndpointEnabledOverride() throws Exception { - endpointEnabledOverride("health", HealthMvcEndpoint.class); - } - - @Test - public void shutdownEndpointEnabled() { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - TestPropertyValues.of("endpoints.shutdown.enabled:true") - .applyTo(this.applicationContext); - this.applicationContext.refresh(); - assertThat(this.applicationContext.getBeansOfType(ShutdownMvcEndpoint.class)) - .hasSize(1); - } - - @Test - public void managementSpecificSslUsingDifferentPort() throws Exception { - TestPropertyValues - .of("management.port=" + ports.get().management, - "management.ssl.enabled=true", - "management.ssl.key-store=classpath:test.jks", - "management.ssl.key-password=password") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertHttpsContent("/controller", ports.get().management, null); - assertHttpsContent("/application/endpoint", ports.get().management, - "endpointoutput"); - assertHttpsContent("/error", ports.get().management, startsWith("{")); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - List interceptors = (List) ReflectionTestUtils.getField( - managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); - assertThat(interceptors).hasSize(2); - ManagementServerProperties managementServerProperties = this.applicationContext - .getBean(ManagementServerProperties.class); - assertThat(managementServerProperties.getSsl()).isNotNull(); - assertThat(managementServerProperties.getSsl().isEnabled()).isTrue(); - } - - @Test - public void managementSpecificSslUsingSamePortFails() throws Exception { - TestPropertyValues - .of("management.ssl.enabled=true", - "management.ssl.key-store=classpath:test.jks", - "management.ssl.key-password=password") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage("Management-specific SSL cannot be configured as the " - + "management server is not listening on a separate port"); - this.applicationContext.refresh(); - } - - @Test - public void rootManagementContextPathUsingSamePortFails() throws Exception { - TestPropertyValues.of("management.context-path=/") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage("A management context path of '/' requires the" - + " management server to be listening on a separate port"); - this.applicationContext.refresh(); - } - - @Test - public void samePortCanBeUsedWhenManagementSslIsExplicitlyDisabled() - throws Exception { - TestPropertyValues.of("management.ssl.enabled=false") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - } - - @Test - public void managementServerCanDisableSslWhenUsingADifferentPort() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management, - "server.ssl.enabled=true", "server.ssl.key-store=classpath:test.jks", - "server.ssl.key-password=password", "management.ssl.enabled=false") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertHttpsContent("/controller", ports.get().server, "controlleroutput"); - assertHttpsContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/application/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - List interceptors = (List) ReflectionTestUtils.getField( - managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); - assertThat(interceptors).hasSize(2); - ManagementServerProperties managementServerProperties = this.applicationContext - .getBean(ManagementServerProperties.class); - assertThat(managementServerProperties.getSsl()).isNotNull(); - assertThat(managementServerProperties.getSsl().isEnabled()).isFalse(); - } - - @Test - public void tomcatManagementAccessLogUsesCustomPrefix() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(TomcatWebServerConfig.class, RootConfig.class, - EndpointConfig.class, DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - TestPropertyValues.of("server.tomcat.accesslog.enabled: true") - .applyTo(this.applicationContext); - this.applicationContext.refresh(); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - ServletWebServerFactory factory = managementContext - .getBean(ServletWebServerFactory.class); - assertThat(factory).isInstanceOf(TomcatServletWebServerFactory.class); - AccessLogValve accessLogValve = findAccessLogValve( - ((TomcatServletWebServerFactory) factory)); - assertThat(accessLogValve).isNotNull(); - assertThat(accessLogValve.getPrefix()).isEqualTo("management_access_log"); - } - - @Test - public void undertowManagementAccessLogUsesCustomPrefix() throws Exception { - TestPropertyValues - .of("management.port=" + ports.get().management, - "server.undertow.accesslog.enabled: true") - .applyTo(this.applicationContext); - this.applicationContext.register(UndertowWebServerConfig.class, RootConfig.class, - EndpointConfig.class, DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - ServletWebServerFactory factory = managementContext - .getBean(ServletWebServerFactory.class); - assertThat(factory).isInstanceOf(UndertowServletWebServerFactory.class); - assertThat(((UndertowServletWebServerFactory) factory).getAccessLogPrefix()) - .isEqualTo("management_access_log."); - } - - private AccessLogValve findAccessLogValve( - TomcatServletWebServerFactory webServerFactory) { - for (Valve engineValve : webServerFactory.getEngineValves()) { - if (engineValve instanceof AccessLogValve) { - return (AccessLogValve) engineValve; - } - } - return null; - } - - private void endpointDisabled(String name, Class type) { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - TestPropertyValues.of(String.format("endpoints.%s.enabled:false", name)) - .applyTo(this.applicationContext); - this.applicationContext.refresh(); - assertThat(this.applicationContext.getBeansOfType(type)).isEmpty(); - } - - private void endpointEnabledOverride(String name, Class type) - throws Exception { - this.applicationContext.register(LoggingConfig.class, RootConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class); - ConfigurationPropertySources.attach(this.applicationContext.getEnvironment()); - TestPropertyValues - .of("endpoints.enabled:false", - String.format("endpoints.%s.enabled:true", name)) - .applyTo(this.applicationContext); - this.applicationContext.refresh(); - assertThat(this.applicationContext.getBeansOfType(type)).hasSize(1); - } - - private void assertHttpsContent(String url, int port, Object expected) - throws Exception { - assertContent("https", url, port, expected); - } - - private void assertContent(String url, int port, Object expected) throws Exception { - assertContent("http", url, port, expected); - } - - private void assertContent(String scheme, String url, int port, Object expected) - throws Exception { - - SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( - new SSLContextBuilder() - .loadTrustMaterial(null, new TrustSelfSignedStrategy()).build()); - HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory) - .build(); - HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory( - httpClient); - ClientHttpRequest request = requestFactory.createRequest( - new URI(scheme + "://localhost:" + port + url), HttpMethod.GET); - try (ClientHttpResponse response = request.execute()) { - if (HttpStatus.NOT_FOUND.equals(response.getStatusCode())) { - throw new FileNotFoundException(); - } - String actual = StreamUtils.copyToString(response.getBody(), - Charset.forName("UTF-8")); - if (expected instanceof Matcher) { - assertThat(actual).is(Matched.by((Matcher) expected)); - } - else { - assertThat(actual).isEqualTo(expected); - } - } - catch (Exception ex) { - if (expected == null) { - if (SocketException.class.isInstance(ex) - || FileNotFoundException.class.isInstance(ex)) { - return; - } - } - throw ex; - } - } - - public boolean hasHeader(String url, int port, String header) throws Exception { - SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory(); - ClientHttpRequest request = clientHttpRequestFactory - .createRequest(new URI("http://localhost:" + port + url), HttpMethod.GET); - ClientHttpResponse response = request.execute(); - return response.getHeaders().containsKey(header); - } - - private static class Ports { - - int server = 0; - - int management = 0; - - } - - @Configuration - @Import({ PropertyPlaceholderAutoConfiguration.class, - ServletWebServerFactoryAutoConfiguration.class, - JacksonAutoConfiguration.class, EndpointAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - AuditAutoConfiguration.class }) - protected static class BaseConfiguration { - - } - - @Configuration - public static class RootConfig { - - @Bean - public TestController testController() { - return new TestController(); - } - - @Bean - public ApplicationListener serverPortListener() { - return (event) -> { - int port = event.getWebServer().getPort(); - if (event.getApplicationContext().getParent() == null) { - ports.get().server = port; - } - else { - ports.get().management = port; - } - }; - } - - } - - @Configuration - public static class EndpointConfig { - - @Bean - public TestEndpoint testEndpoint() { - return new TestEndpoint(); - } - - } - - @Configuration - public static class LoggingConfig { - - @Bean - public LoggingSystem loggingSystem() { - return LoggingSystem.get(getClass().getClassLoader()); - } - - } - - @Controller - public static class TestController { - - @RequestMapping("/controller") - @ResponseBody - public String requestMappedMethod() { - return "controlleroutput"; - } - - } - - @Configuration - public static class SpecificWebServerConfig { - - @Bean - public SpecificServletWebServerFactory webServerFactory() { - return new SpecificServletWebServerFactory(); - } - - } - - @Configuration - public static class TomcatWebServerConfig { - - @Bean - public TomcatServletWebServerFactory webServerFactory() { - return new TomcatServletWebServerFactory(); - } - - } - - @Configuration - public static class UndertowWebServerConfig { - - @Bean - public UndertowServletWebServerFactory webServerFactory() { - return new UndertowServletWebServerFactory(); - } - - } - - @Configuration - public static class DifferentPortConfig { - - @Bean - public EndpointHandlerMappingCustomizer mappingCustomizer() { - return (mapping) -> mapping.setInterceptors(interceptor()); - } - - @Bean - protected TestInterceptor interceptor() { - return new TestInterceptor(); - } - - protected static class TestInterceptor extends HandlerInterceptorAdapter { - - private int count = 0; - - @Override - public void postHandle(HttpServletRequest request, - HttpServletResponse response, Object handler, - ModelAndView modelAndView) throws Exception { - this.count++; - } - - public int getCount() { - return this.count; - } - - } - - } - - public static class TestEndpoint implements MvcEndpoint { - - @RequestMapping - @ResponseBody - public String invoke() { - return "endpointoutput"; - } - - @Override - public String getPath() { - return "/endpoint"; - } - - @Override - @SuppressWarnings("rawtypes") - public Class getEndpointType() { - return Endpoint.class; - } - - } - - private static class SpecificServletWebServerFactory - extends TomcatServletWebServerFactory { - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfigurationTests.java deleted file mode 100644 index 2bf28efddb..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfigurationTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -import java.util.List; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpointSecurityInterceptor; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.mock.web.MockServletContext; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EndpointWebMvcManagementContextConfiguration}. - * - * @author Madhura Bhave - */ -public class EndpointWebMvcManagementContextConfigurationTests { - - private AnnotationConfigWebApplicationContext context; - - @Before - public void setup() { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(SecurityAutoConfiguration.class, - WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - RestTemplateAutoConfiguration.class, - EndpointWebMvcManagementContextConfiguration.class); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void endpointHandlerMapping() throws Exception { - TestPropertyValues - .of("management.security.enabled=false", - "management.security.roles=my-role,your-role") - .applyTo(this.context); - this.context.refresh(); - EndpointHandlerMapping mapping = this.context.getBean("endpointHandlerMapping", - EndpointHandlerMapping.class); - assertThat(mapping.getPrefix()).isEqualTo("/application"); - MvcEndpointSecurityInterceptor securityInterceptor = (MvcEndpointSecurityInterceptor) ReflectionTestUtils - .getField(mapping, "securityInterceptor"); - Object secure = ReflectionTestUtils.getField(securityInterceptor, "secure"); - List roles = getRoles(securityInterceptor); - assertThat(secure).isEqualTo(false); - assertThat(roles).containsExactly("my-role", "your-role"); - } - - @SuppressWarnings("unchecked") - private List getRoles(MvcEndpointSecurityInterceptor securityInterceptor) { - return (List) ReflectionTestUtils.getField(securityInterceptor, "roles"); - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelectorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelectorTests.java deleted file mode 100644 index 0436f74794..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelectorTests.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -import java.util.Arrays; -import java.util.List; - -import org.junit.Test; - -import org.springframework.core.annotation.Order; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ManagementContextConfigurationsImportSelector}. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -public class ManagementContextConfigurationsImportSelectorTests { - - @Test - public void selectImportsShouldOrderResult() throws Exception { - String[] imports = new TestManagementContextConfigurationsImportSelector() - .selectImports(null); - assertThat(imports).containsExactly(A.class.getName(), B.class.getName(), - C.class.getName(), D.class.getName()); - } - - private static class TestManagementContextConfigurationsImportSelector - extends ManagementContextConfigurationsImportSelector { - - @Override - protected List loadFactoryNames() { - return Arrays.asList(C.class.getName(), A.class.getName(), D.class.getName(), - B.class.getName()); - } - - } - - @Order(1) - private static class A { - - } - - @Order(2) - private static class B { - - } - - @Order(3) - private static class C { - - } - - static class D { - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/MvcEndpointPathConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/MvcEndpointPathConfigurationTests.java deleted file mode 100644 index a44c7aa142..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/MvcEndpointPathConfigurationTests.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.endpoint; - -import java.util.Collections; - -import liquibase.integration.spring.SpringLiquibase; -import org.flywaydb.core.Flyway; -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaManagementContextConfiguration; -import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint; -import org.springframework.boot.actuate.endpoint.BeansEndpoint; -import org.springframework.boot.actuate.endpoint.ConfigurationPropertiesReportEndpoint; -import org.springframework.boot.actuate.endpoint.DumpEndpoint; -import org.springframework.boot.actuate.endpoint.FlywayEndpoint; -import org.springframework.boot.actuate.endpoint.InfoEndpoint; -import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint; -import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; -import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; -import org.springframework.boot.actuate.endpoint.TraceEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; -import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.LogFileMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockServletContext; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for configuring the path of an MVC endpoint. - * - * @author Andy Wilkinson - * @author Ben Hale - */ -@RunWith(Parameterized.class) -public class MvcEndpointPathConfigurationTests { - - private final String endpointName; - - private final Class endpointClass; - - private AnnotationConfigWebApplicationContext context; - - @After - public void cleanUp() { - if (this.context != null) { - this.context.close(); - } - } - - @Parameters(name = "{0}") - public static Object[] parameters() { - return new Object[] { - new Object[] { "auditevents", AuditEventsMvcEndpoint.class }, - new Object[] { "autoconfig", AutoConfigurationReportEndpoint.class }, - new Object[] { "beans", BeansEndpoint.class }, - new Object[] { "configprops", - ConfigurationPropertiesReportEndpoint.class }, - new Object[] { "dump", DumpEndpoint.class }, - new Object[] { "env", EnvironmentMvcEndpoint.class }, - new Object[] { "flyway", FlywayEndpoint.class }, - new Object[] { "health", HealthMvcEndpoint.class }, - new Object[] { "info", InfoEndpoint.class }, - new Object[] { "liquibase", LiquibaseEndpoint.class }, - new Object[] { "logfile", LogFileMvcEndpoint.class }, - new Object[] { "loggers", LoggersMvcEndpoint.class }, - new Object[] { "mappings", RequestMappingEndpoint.class }, - new Object[] { "metrics", MetricsMvcEndpoint.class }, - new Object[] { "shutdown", ShutdownEndpoint.class }, - new Object[] { "trace", TraceEndpoint.class } }; - } - - public MvcEndpointPathConfigurationTests(String endpointName, - Class endpointClass) { - this.endpointName = endpointName; - this.endpointClass = endpointClass; - } - - @Test - public void pathCanBeConfigured() { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(TestConfiguration.class); - this.context.setServletContext(new MockServletContext()); - TestPropertyValues - .of("endpoints." + this.endpointName + ".path" + ":/custom/path", - "endpoints." + this.endpointName + ".enabled:true", - "logging.file:target/test.log") - .applyTo(this.context); - this.context.refresh(); - assertThat(getConfiguredPath()).isEqualTo("/custom/path"); - } - - private String getConfiguredPath() { - if (MvcEndpoint.class.isAssignableFrom(this.endpointClass)) { - return ((MvcEndpoint) this.context.getBean(this.endpointClass)).getPath(); - } - for (MvcEndpoint endpoint : this.context.getBean(MvcEndpoints.class) - .getEndpoints()) { - if (endpoint instanceof EndpointMvcAdapter && this.endpointClass - .isInstance(((EndpointMvcAdapter) endpoint).getDelegate())) { - return ((EndpointMvcAdapter) endpoint).getPath(); - } - } - throw new IllegalStateException( - "Could not get configured path for " + this.endpointClass); - } - - @Configuration - @ImportAutoConfiguration({ EndpointAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, AuditAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, - JolokiaManagementContextConfiguration.class }) - - protected static class TestConfiguration { - - @Bean - public ConditionEvaluationReport conditionEvaluationReport( - ConfigurableListableBeanFactory beanFactory) { - return ConditionEvaluationReport.get(beanFactory); - } - - @Bean - LoggingSystem loggingSystem() { - return LoggingSystem.get(getClass().getClassLoader()); - } - - @Bean - public FlywayEndpoint flyway() { - return new FlywayEndpoint(Collections.singletonMap("flyway", new Flyway())); - } - - @Bean - public LiquibaseEndpoint liquibase() { - return new LiquibaseEndpoint(new SpringLiquibase()); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactoryTests.java new file mode 100644 index 0000000000..2f715257f6 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactoryTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import org.junit.Test; + +import org.springframework.boot.endpoint.CachingConfiguration; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * Tests for {@link CachingConfigurationFactory}. + * + * @author Stephane Nicoll + */ +public class CachingConfigurationFactoryTests { + + private final MockEnvironment environment = new MockEnvironment(); + + private final CachingConfigurationFactory factory = new CachingConfigurationFactory( + this.environment); + + @Test + public void defaultConfiguration() { + CachingConfiguration configuration = this.factory.apply("test"); + assertThat(configuration).isNotNull(); + assertThat(configuration.getTimeToLive()).isEqualTo(0); + } + + @Test + public void userConfiguration() { + this.environment.setProperty("endpoints.test.cache.time-to-live", "500"); + CachingConfiguration configuration = this.factory.apply("test"); + assertThat(configuration).isNotNull(); + assertThat(configuration.getTimeToLive()).isEqualTo(500); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointInfrastructureAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointInfrastructureAutoConfigurationTests.java new file mode 100644 index 0000000000..a5ecb117c5 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointInfrastructureAutoConfigurationTests.java @@ -0,0 +1,134 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import javax.management.InstanceNotFoundException; +import javax.management.IntrospectionException; +import javax.management.MBeanInfo; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.ReflectionException; + +import org.junit.Test; + +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link EndpointInfrastructureAutoConfiguration} with JMX. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +public class JmxEndpointInfrastructureAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, + EndpointAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class)); + + @Test + public void jmxEndpointsAreExposed() { + this.contextRunner.run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + checkEndpointMBeans(mBeanServer, + new String[] { "autoconfig", "beans", "configprops", "env", "health", + "info", "mappings", "metrics", "threaddump", "trace" }, + new String[] { "shutdown" }); + }); + } + + @Test + public void jmxEndpointsCanBeDisabled() { + this.contextRunner.withPropertyValues("endpoints.all.jmx.enabled:false") + .run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + checkEndpointMBeans(mBeanServer, new String[0], + new String[] { "autoconfig", "beans", "configprops", "env", + "health", "info", "mappings", "metrics", "shutdown", + "threaddump", "trace" }); + + }); + } + + @Test + public void singleJmxEndpointCanBeEnabled() { + this.contextRunner.withPropertyValues("endpoints.all.jmx.enabled=false", + "endpoints.beans.jmx.enabled=true").run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + checkEndpointMBeans(mBeanServer, new String[] { "beans" }, + new String[] { "autoconfig", "configprops", "env", "health", + "info", "mappings", "metrics", "shutdown", + "threaddump", "trace" }); + }); + } + + private void checkEndpointMBeans(MBeanServer mBeanServer, String[] enabledEndpoints, + String[] disabledEndpoints) { + for (String enabledEndpoint : enabledEndpoints) { + assertThat(isRegistered(mBeanServer, getDefaultObjectName(enabledEndpoint))) + .as(String.format("Endpoint %s", enabledEndpoint)).isTrue(); + } + for (String disabledEndpoint : disabledEndpoints) { + assertThat(isRegistered(mBeanServer, getDefaultObjectName(disabledEndpoint))) + .as(String.format("Endpoint %s", disabledEndpoint)).isFalse(); + } + } + + private boolean isRegistered(MBeanServer mBeanServer, ObjectName objectName) { + try { + getMBeanInfo(mBeanServer, objectName); + return true; + } + catch (InstanceNotFoundException e) { + return false; + } + } + + private MBeanInfo getMBeanInfo(MBeanServer mBeanServer, ObjectName objectName) + throws InstanceNotFoundException { + try { + return mBeanServer.getMBeanInfo(objectName); + } + catch (ReflectionException | IntrospectionException ex) { + throw new IllegalStateException( + "Failed to retrieve MBeanInfo for ObjectName " + objectName, ex); + } + } + + private ObjectName getDefaultObjectName(String endpointId) { + return getObjectName("org.springframework.boot", endpointId); + } + + private ObjectName getObjectName(String domain, String endpointId) { + try { + return new ObjectName(String.format("%s:type=Endpoint,name=%s", domain, + StringUtils.capitalize(endpointId))); + } + catch (MalformedObjectNameException ex) { + throw new IllegalStateException("Invalid object name", ex); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebMvcEndpointInfrastructureAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebMvcEndpointInfrastructureAutoConfigurationTests.java new file mode 100644 index 0000000000..f3215752a8 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebMvcEndpointInfrastructureAutoConfigurationTests.java @@ -0,0 +1,153 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.infrastructure; + +import org.apache.http.HttpStatus; +import org.junit.Test; + +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.http.HttpMethod; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; + +/** + * Tests for {@link EndpointInfrastructureAutoConfiguration} with Web MVC. + * + * @author Stephane Nicoll + */ +public class WebMvcEndpointInfrastructureAutoConfigurationTests { + + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + ServletWebServerFactoryAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, + JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + WebMvcAutoConfiguration.class, EndpointAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class, + ServletEndpointAutoConfiguration.class)); + + @Test + public void webEndpointsAreExposed() { + this.contextRunner.run((context) -> { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/autoconfig")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/beans")).isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/configprops")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/env")).isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/health")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/info")).isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/mappings")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/metrics")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.POST, "/application/shutdown")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/threaddump")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/trace")).isTrue(); + }); + } + + @Test + public void webEndpointsCanBeDisabled() { + WebApplicationContextRunner contextRunner = this.contextRunner + .withPropertyValues("endpoints.all.web.enabled=false"); + contextRunner.run((context) -> { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/autoconfig")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/beans")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/configprops")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/env")).isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/health")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/info")).isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/mappings")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/metrics")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.POST, "/application/shutdown")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/threaddump")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/trace")) + .isFalse(); + }); + } + + @Test + public void singleWebEndpointCanBeEnabled() { + WebApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues( + "endpoints.all.web.enabled=false", "endpoints.beans.web.enabled=true"); + contextRunner.run((context) -> { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/autoconfig")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/beans")).isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/configprops")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/env")).isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/health")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/info")).isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/mappings")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/metrics")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.POST, "/application/shutdown")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/threaddump")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/trace")) + .isFalse(); + }); + } + + private boolean isExposed(MockMvc mockMvc, HttpMethod method, String path) + throws Exception { + MvcResult mvcResult = mockMvc.perform(request(method, path)).andReturn(); + int status = mvcResult.getResponse().getStatus(); + if (status == HttpStatus.SC_OK) { + return true; + } + else if (status == HttpStatus.SC_NOT_FOUND) { + return false; + } + throw new IllegalStateException(String + .format("Unexpected %s HTTP status for " + "endpoint %s", status, path)); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java new file mode 100644 index 0000000000..1db7a43b59 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.jmx; + +import org.junit.Test; + +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; +import org.springframework.boot.actuate.endpoint.jmx.AuditEventsJmxEndpointExtension; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link JmxEndpointAutoConfiguration}. + * + * @author Andy Wilkinson + */ +public class JmxEndpointAutoConfigurationTests { + + @Test + public void auditEventsJmxEndpointExtensionIsAutoConfigured() { + jmxExtensionIsAutoConfigured(AuditEventsJmxEndpointExtension.class, + AuditEventsEndpointConfiguration.class); + } + + @Test + public void auditEventsJmxEndpointExtensionCanBeDisabled() { + jmxExtensionCanBeDisabled(AuditEventsJmxEndpointExtension.class, "auditevents", + AuditEventsEndpointConfiguration.class); + } + + private void jmxExtensionIsAutoConfigured(Class jmxExtension, Class... config) { + contextRunner().withUserConfiguration(config) + .run((context) -> assertThat(context).hasSingleBean(jmxExtension)); + } + + private void jmxExtensionCanBeDisabled(Class jmxExtension, String id, + Class... config) { + contextRunner().withPropertyValues("endpoints." + id + ".enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(jmxExtension)); + } + + private ApplicationContextRunner contextRunner() { + return new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(JmxEndpointAutoConfiguration.class)); + } + + @Configuration + static class AuditEventsEndpointConfiguration { + + @Bean + public AuditEventsEndpoint auditEventsEndpoint() { + return new AuditEventsEndpoint(mock(AuditEventRepository.class)); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProviderTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProviderTests.java new file mode 100644 index 0000000000..206cae6167 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProviderTests.java @@ -0,0 +1,337 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.support; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.util.ObjectUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link EndpointEnablementProvider}. + * + * @author Stephane Nicoll + */ +public class EndpointEnablementProviderTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void cannotDetermineEnablementWithEmptyEndpoint() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Endpoint id must have a value"); + determineEnablement(" ", true); + } + + @Test + public void cannotDetermineEnablementOfEndpointAll() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Endpoint id 'all' is a reserved value and cannot " + + "be used by an endpoint"); + determineEnablement("all", true); + } + + @Test + public void generalEnabledByDefault() { + validate(determineEnablement("foo", true), true, + "endpoint 'foo' is enabled by default"); + } + + @Test + public void generalDisabledViaSpecificProperty() { + validate(determineEnablement("foo", true, "endpoints.foo.enabled=false"), false, + "found property endpoints.foo.enabled"); + } + + @Test + public void generalDisabledViaGeneralProperty() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=false"), false, + "found property endpoints.all.enabled"); + } + + @Test + public void generalEnabledOverrideViaSpecificProperty() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=false", + "endpoints.foo.enabled=true"), true, + "found property endpoints.foo.enabled"); + } + + @Test + public void generalEnabledOverrideViaSpecificWebProperty() { + validate(determineEnablement("foo", true, "endpoints.foo.enabled=false", + "endpoints.foo.web.enabled=true"), true, + "found property endpoints.foo.web.enabled"); + } + + @Test + public void generalEnabledOverrideViaSpecificJmxProperty() { + validate(determineEnablement("foo", true, "endpoints.foo.enabled=false", + "endpoints.foo.jmx.enabled=true"), true, + "found property endpoints.foo.jmx.enabled"); + } + + @Test + public void generalEnabledOverrideViaSpecificAnyProperty() { + validate(determineEnablement("foo", true, "endpoints.foo.enabled=false", + "endpoints.foo.web.enabled=false", "endpoints.foo.jmx.enabled=true"), + true, "found property endpoints.foo.jmx.enabled"); + } + + @Test + public void generalEnabledOverrideViaGeneralWebProperty() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=false", + "endpoints.all.web.enabled=true"), true, + "found property endpoints.all.web.enabled"); + } + + @Test + public void generalEnabledOverrideViaGeneralJmxProperty() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=false", + "endpoints.all.jmx.enabled=true"), true, + "found property endpoints.all.jmx.enabled"); + } + + @Test + public void generalEnabledOverrideViaGeneralAnyProperty() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=false", + "endpoints.all.web.enabled=false", "endpoints.all.jmx.enabled=true"), + true, "found property endpoints.all.jmx.enabled"); + } + + @Test + public void generalDisabledEvenWithEnabledGeneralProperties() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=true", + "endpoints.all.web.enabled=true", "endpoints.all.jmx.enabled=true", + "endpoints.foo.enabled=false"), false, + "found property endpoints.foo.enabled"); + } + + @Test + public void generalDisabledByDefaultWithAnnotationFlag() { + validate(determineEnablement("bar", false), false, + "endpoint 'bar' is disabled by default"); + } + + @Test + public void generalDisabledByDefaultWithAnnotationFlagEvenWithGeneralProperty() { + validate(determineEnablement("bar", false, "endpoints.all.enabled=true"), false, + "endpoint 'bar' is disabled by default"); + } + + @Test + public void generalDisabledByDefaultWithAnnotationFlagEvenWithGeneralWebProperty() { + validate(determineEnablement("bar", false, "endpoints.all.web.enabled=true"), + false, "endpoint 'bar' is disabled by default"); + } + + @Test + public void generalDisabledByDefaultWithAnnotationFlagEvenWithGeneralJmxProperty() { + validate(determineEnablement("bar", false, "endpoints.all.jmx.enabled=true"), + false, "endpoint 'bar' is disabled by default"); + } + + @Test + public void generalEnabledOverrideWithAndAnnotationFlagAndSpecificProperty() { + validate(determineEnablement("bar", false, "endpoints.bar.enabled=true"), + true, "found property endpoints.bar.enabled"); + } + + @Test + public void generalEnabledOverrideWithAndAnnotationFlagAndSpecificWebProperty() { + validate(determineEnablement("bar", false, "endpoints.bar.web.enabled=true"), + true, "found property endpoints.bar.web.enabled"); + } + + @Test + public void generalEnabledOverrideWithAndAnnotationFlagAndSpecificJmxProperty() { + validate(determineEnablement("bar", false, "endpoints.bar.jmx.enabled=true"), + true, "found property endpoints.bar.jmx.enabled"); + } + + @Test + public void generalEnabledOverrideWithAndAnnotationFlagAndAnyProperty() { + validate(determineEnablement("bar", false, "endpoints.bar.web.enabled=false", + "endpoints.bar.jmx.enabled=true"), true, + "found property endpoints.bar.jmx.enabled"); + } + + + @Test + public void specificEnabledByDefault() { + validate(determineEnablement("foo", true, EndpointType.WEB), true, + "endpoint 'foo' (web) is enabled by default"); + } + + @Test + public void specificDisabledViaEndpointProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.foo.enabled=false"), false, + "found property endpoints.foo.enabled"); + } + + @Test + public void specificDisabledViaTechProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.foo.web.enabled=false"), false, + "found property endpoints.foo.web.enabled"); + } + + @Test + public void specificNotDisabledViaUnrelatedTechProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.foo.jmx.enabled=false"), true, + "endpoint 'foo' (web) is enabled by default"); + } + + @Test + public void specificDisabledViaGeneralProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=false"), false, + "found property endpoints.all.enabled"); + } + + @Test + public void specificEnabledOverrideViaEndpointProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=false", "endpoints.foo.enabled=true"), true, + "found property endpoints.foo.enabled"); + } + + @Test + public void specificEnabledOverrideViaTechProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.foo.enabled=false", "endpoints.foo.web.enabled=true"), true, + "found property endpoints.foo.web.enabled"); + } + + @Test + public void specificEnabledOverrideHasNotEffectWithUnrelatedTechProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.foo.enabled=false", "endpoints.foo.jmx.enabled=true"), false, + "found property endpoints.foo.enabled"); + } + + @Test + public void specificEnabledOverrideViaGeneralWebProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=false", "endpoints.all.web.enabled=true"), true, + "found property endpoints.all.web.enabled"); + } + + @Test + public void specificEnabledOverrideHasNoEffectWithUnrelatedTechProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=false", "endpoints.all.jmx.enabled=true"), false, + "found property endpoints.all.enabled"); + } + + @Test + public void specificDisabledWithEndpointPropertyEvenWithEnabledGeneralProperties() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=true", "endpoints.all.web.enabled=true", + "endpoints.all.jmx.enabled=true", "endpoints.foo.enabled=false"), false, + "found property endpoints.foo.enabled"); + } + + @Test + public void specificDisabledWithTechPropertyEvenWithEnabledGeneralProperties() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=true", "endpoints.all.web.enabled=true", + "endpoints.all.jmx.enabled=true", "endpoints.foo.enabled=true", + "endpoints.foo.web.enabled=false"), false, + "found property endpoints.foo.web.enabled"); + } + + @Test + public void specificDisabledByDefaultWithAnnotationFlag() { + validate(determineEnablement("bar", false, EndpointType.WEB), false, + "endpoint 'bar' (web) is disabled by default"); + } + + @Test + public void specificDisabledByDefaultWithAnnotationFlagEvenWithGeneralProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.all.enabled=true"), false, + "endpoint 'bar' (web) is disabled by default"); + } + + @Test + public void specificDisabledByDefaultWithAnnotationFlagEvenWithGeneralWebProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.all.web.enabled=true"), false, + "endpoint 'bar' (web) is disabled by default"); + } + + @Test + public void specificDisabledByDefaultWithAnnotationFlagEvenWithGeneralJmxProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.all.jmx.enabled=true"), false, + "endpoint 'bar' (web) is disabled by default"); + } + + @Test + public void specificEnabledOverrideWithAndAnnotationFlagAndEndpointProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.bar.enabled=true"), true, + "found property endpoints.bar.enabled"); + } + + @Test + public void specificEnabledOverrideWithAndAnnotationFlagAndTechProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.bar.web.enabled=true"), true, + "found property endpoints.bar.web.enabled"); + } + + @Test + public void specificEnabledOverrideWithAndAnnotationFlagHasNoEffectWithUnrelatedTechProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.bar.jmx.enabled=true"), false, + "endpoint 'bar' (web) is disabled by default"); + } + + + private void validate(EndpointEnablement enablement, boolean enabled, + String... messages) { + assertThat(enablement).isNotNull(); + assertThat(enablement.isEnabled()).isEqualTo(enabled); + if (!ObjectUtils.isEmpty(messages)) { + assertThat(enablement.getReason()).contains(messages); + } + } + + private EndpointEnablement determineEnablement(String id, boolean enabledByDefault, + String... environment) { + return determineEnablement(id, enabledByDefault, null, environment); + } + + private EndpointEnablement determineEnablement(String id, boolean enabledByDefault, + EndpointType type, String... environment) { + MockEnvironment env = new MockEnvironment(); + TestPropertyValues.of(environment).applyTo(env); + EndpointEnablementProvider provider = new EndpointEnablementProvider(env); + return provider.getEndpointEnablement(id, enabledByDefault, type); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java new file mode 100644 index 0000000000..c12f23587d --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java @@ -0,0 +1,168 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.autoconfigure.endpoint.web; + +import java.util.Collections; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; +import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension; +import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension; +import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint; +import org.springframework.boot.actuate.endpoint.web.LogFileWebEndpoint; +import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link WebEndpointManagementContextConfiguration}. + * + * @author Andy Wilkinson + */ +public class WebEndpointManagementContextConfigurationTests { + + @Test + public void heapDumpWebEndpointIsAutoConfigured() { + beanIsAutoConfigured(HeapDumpWebEndpoint.class); + } + + @Test + public void heapDumpWebEndpointCanBeDisabled() { + beanIsNotAutoConfiguredWhenEndpointIsDisabled(HeapDumpWebEndpoint.class, + "heapdump"); + } + + @Test + public void healthWebEndpointExtensionIsAutoConfigured() { + beanIsAutoConfigured(HealthWebEndpointExtension.class, + HealthEndpointConfiguration.class); + } + + @Test + public void healthStatusMappingCanBeCustomized() { + ApplicationContextRunner contextRunner = contextRunner() + .withPropertyValues("endpoints.health.mapping.CUSTOM=500") + .withUserConfiguration(HealthEndpointConfiguration.class); + contextRunner.run((context) -> { + HealthWebEndpointExtension extension = context + .getBean(HealthWebEndpointExtension.class); + @SuppressWarnings("unchecked") + Map statusMappings = (Map) ReflectionTestUtils + .getField(extension, "statusMapping"); + assertThat(statusMappings).containsEntry("DOWN", 503); + assertThat(statusMappings).containsEntry("OUT_OF_SERVICE", 503); + assertThat(statusMappings).containsEntry("CUSTOM", 500); + }); + } + + @Test + public void healthWebEndpointExtensionCanBeDisabled() { + beanIsNotAutoConfiguredWhenEndpointIsDisabled(HealthWebEndpointExtension.class, + "health", HealthEndpointConfiguration.class); + } + + @Test + public void auditEventsWebEndpointExtensionIsAutoConfigured() { + beanIsAutoConfigured(AuditEventsWebEndpointExtension.class, + AuditEventsEndpointConfiguration.class); + } + + @Test + public void auditEventsWebEndpointExtensionCanBeDisabled() { + beanIsNotAutoConfiguredWhenEndpointIsDisabled( + AuditEventsWebEndpointExtension.class, "auditevents", + AuditEventsEndpointConfiguration.class); + } + + @Test + public void logFileWebEndpointIsAutoConfiguredWhenLoggingFileIsSet() { + contextRunner().withPropertyValues("logging.file:test.log").run( + (context) -> assertThat(context.getBeansOfType(LogFileWebEndpoint.class)) + .hasSize(1)); + } + + @Test + public void logFileWebEndpointIsAutoConfiguredWhenLoggingPathIsSet() { + contextRunner().withPropertyValues("logging.path:test/logs").run( + (context) -> assertThat(context.getBeansOfType(LogFileWebEndpoint.class)) + .hasSize(1)); + } + + @Test + public void logFileWebEndpointIsAutoConfiguredWhenExternalFileIsSet() { + contextRunner().withPropertyValues("endpoints.logfile.external-file:external.log") + .run((context) -> assertThat( + context.getBeansOfType(LogFileWebEndpoint.class)).hasSize(1)); + } + + @Test + public void logFileWebEndpointCanBeDisabled() { + contextRunner() + .withPropertyValues("logging.file:test.log", + "endpoints.logfile.enabled:false") + .run((context) -> assertThat(context) + .hasSingleBean(LogFileWebEndpoint.class)); + } + + private void beanIsAutoConfigured(Class beanType, Class... config) { + contextRunner().withUserConfiguration(config) + .run((context) -> assertThat(context).hasSingleBean(beanType)); + } + + private void beanIsNotAutoConfiguredWhenEndpointIsDisabled(Class webExtension, + String id, Class... config) { + contextRunner().withPropertyValues("endpoints." + id + ".enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(webExtension)); + } + + private ApplicationContextRunner contextRunner() { + return new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(WebEndpointManagementContextConfiguration.class)); + } + + @Configuration + static class HealthEndpointConfiguration { + + @Bean + public HealthEndpoint healthEndpoint() { + return new HealthEndpoint(new OrderedHealthAggregator(), + Collections.emptyMap()); + } + + } + + @Configuration + static class AuditEventsEndpointConfiguration { + + @Bean + public AuditEventsEndpoint auditEventsEndpoint() { + return new AuditEventsEndpoint(mock(AuditEventRepository.class)); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointAutoConfigurationTests.java deleted file mode 100644 index f2dfe150d9..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointAutoConfigurationTests.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.autoconfigure.health; - -import java.security.Principal; -import java.util.Arrays; - -import javax.servlet.http.HttpServletRequest; - -import org.junit.After; -import org.junit.Test; - -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; -import org.springframework.boot.actuate.health.AbstractHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Health.Builder; -import org.springframework.boot.actuate.health.Status; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EndpointWebMvcAutoConfiguration} of the {@link HealthMvcEndpoint}. - * - * @author Dave Syer - * @author Andy Wilkinson - */ -public class HealthMvcEndpointAutoConfigurationTests { - - private AnnotationConfigWebApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void testSecureByDefault() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(TestConfiguration.class); - this.context.refresh(); - MockHttpServletRequest request = new MockHttpServletRequest(); - Health health = (Health) this.context.getBean(HealthMvcEndpoint.class) - .invoke(request, null); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails().get("foo")).isNull(); - } - - @Test - public void testNotSecured() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(TestConfiguration.class); - TestPropertyValues.of("management.security.enabled=false").applyTo(this.context); - this.context.refresh(); - Health health = (Health) this.context.getBean(HealthMvcEndpoint.class) - .invoke(null, null); - assertThat(health.getStatus()).isEqualTo(Status.UP); - Health map = (Health) health.getDetails().get("test"); - assertThat(map.getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void testSetRoles() throws Exception { - // gh-8314 - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(TestConfiguration.class); - TestPropertyValues.of("management.security.roles[0]=super").applyTo(this.context); - this.context.refresh(); - HealthMvcEndpoint health = this.context.getBean(HealthMvcEndpoint.class); - assertThat(ReflectionTestUtils.getField(health, "roles")) - .isEqualTo(Arrays.asList("super")); - } - - @Test - public void endpointConditionalOnMissingBean() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(TestConfiguration.class, - TestHealthMvcEndpointConfiguration.class); - this.context.refresh(); - MockHttpServletRequest request = new MockHttpServletRequest(); - Health health = (Health) this.context.getBean(HealthMvcEndpoint.class) - .invoke(request, null); - assertThat(health.getDetails()).isNotEmpty(); - } - - @Configuration - @ImportAutoConfiguration({ SecurityAutoConfiguration.class, - JacksonAutoConfiguration.class, WebMvcAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, AuditAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class }) - static class TestConfiguration { - - @Bean - public TestHealthIndicator testHealthIndicator() { - return new TestHealthIndicator(); - } - - } - - @Configuration - @ImportAutoConfiguration({ SecurityAutoConfiguration.class, - JacksonAutoConfiguration.class, WebMvcAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, AuditAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class }) - static class TestHealthMvcEndpointConfiguration { - - @Bean - public HealthMvcEndpoint endpoint(HealthEndpoint endpoint) { - return new TestHealthMvcEndpoint(endpoint); - } - - } - - static class TestHealthMvcEndpoint extends HealthMvcEndpoint { - - TestHealthMvcEndpoint(HealthEndpoint delegate) { - super(delegate); - } - - @Override - protected boolean exposeHealthDetails(HttpServletRequest request, - Principal principal) { - return true; - } - - } - - static class TestHealthIndicator extends AbstractHealthIndicator { - - @Override - protected void doHealthCheck(Builder builder) throws Exception { - builder.up().withDetail("foo", "bar"); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationIntegrationTests.java index 075feb1ca7..47fda28053 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationIntegrationTests.java @@ -26,7 +26,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; @@ -99,8 +101,9 @@ public class JolokiaManagementContextConfigurationIntegrationTests { @MinimalWebConfiguration @Import({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, - JolokiaManagementContextConfiguration.class }) + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class, + ServletEndpointAutoConfiguration.class }) protected static class Application { } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java index 136f9befa5..97eebcd637 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java @@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.jolokia; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; @@ -39,7 +39,7 @@ public class JolokiaManagementContextConfigurationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration( - AutoConfigurations.of(EndpointWebMvcAutoConfiguration.class, + AutoConfigurations.of(ManagementContextAutoConfiguration.class, JolokiaManagementContextConfiguration.class)); @Test diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/web/ManagementWebSecurityAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/web/ManagementWebSecurityAutoConfigurationTests.java index 559258a607..fe1f46169a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/web/ManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/web/ManagementWebSecurityAutoConfigurationTests.java @@ -27,7 +27,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -88,7 +88,7 @@ public class ManagementWebSecurityAutoConfigurationTests { ManagementWebSecurityAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, + EndpointAutoConfiguration.class, ServletEndpointAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class); TestPropertyValues.of("security.basic.enabled:false").applyTo(this.context); this.context.refresh(); @@ -195,7 +195,7 @@ public class ManagementWebSecurityAutoConfigurationTests { ManagementWebSecurityAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, + EndpointAutoConfiguration.class, ServletEndpointAutoConfiguration.class, WebMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class); this.context.refresh(); @@ -236,7 +236,7 @@ public class ManagementWebSecurityAutoConfigurationTests { @ImportAutoConfiguration({ SecurityAutoConfiguration.class, WebMvcAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, + EndpointAutoConfiguration.class, ServletEndpointAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class, FallbackWebSecurityAutoConfiguration.class }) static class WebConfiguration { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java index 7dfeb37ffb..ef9a65b874 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java @@ -16,41 +16,6 @@ package org.springframework.boot.actuate.cloudfoundry; -import java.util.Arrays; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryActuatorAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcManagementContextConfiguration; -import org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer; -import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.context.properties.source.ConfigurationPropertySources; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.cors.CorsConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - /** * Tests for {@link CloudFoundryActuatorAutoConfiguration}. * @@ -58,146 +23,148 @@ import static org.mockito.Mockito.verify; */ public class CloudFoundryActuatorAutoConfigurationTests { - private AnnotationConfigWebApplicationContext context; + // TODO CloudFoundry support - @Before - public void setup() { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(SecurityAutoConfiguration.class, - WebMvcAutoConfiguration.class, - ManagementWebSecurityAutoConfiguration.class, - JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - RestTemplateAutoConfiguration.class, - EndpointWebMvcManagementContextConfiguration.class, - CloudFoundryActuatorAutoConfiguration.class); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void cloudFoundryPlatformActive() throws Exception { - CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); - assertThat(handlerMapping.getPrefix()).isEqualTo("/cloudfoundryapplication"); - CorsConfiguration corsConfiguration = (CorsConfiguration) ReflectionTestUtils - .getField(handlerMapping, "corsConfiguration"); - assertThat(corsConfiguration.getAllowedOrigins()).contains("*"); - assertThat(corsConfiguration.getAllowedMethods()).containsAll( - Arrays.asList(HttpMethod.GET.name(), HttpMethod.POST.name())); - assertThat(corsConfiguration.getAllowedHeaders()).containsAll( - Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type")); - } - - @Test - public void cloudFoundryPlatformActiveSetsApplicationId() throws Exception { - CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); - Object interceptor = ReflectionTestUtils.getField(handlerMapping, - "securityInterceptor"); - String applicationId = (String) ReflectionTestUtils.getField(interceptor, - "applicationId"); - assertThat(applicationId).isEqualTo("my-app-id"); - } - - @Test - public void cloudFoundryPlatformActiveSetsCloudControllerUrl() throws Exception { - CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); - Object interceptor = ReflectionTestUtils.getField(handlerMapping, - "securityInterceptor"); - Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, - "cloudFoundrySecurityService"); - String cloudControllerUrl = (String) ReflectionTestUtils - .getField(interceptorSecurityService, "cloudControllerUrl"); - assertThat(cloudControllerUrl).isEqualTo("http://my-cloud-controller.com"); - } - - @Test - public void skipSslValidation() throws Exception { - TestPropertyValues.of("management.cloudfoundry.skipSslValidation:true") - .applyTo(this.context); - ConfigurationPropertySources.attach(this.context.getEnvironment()); - this.context.refresh(); - CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); - Object interceptor = ReflectionTestUtils.getField(handlerMapping, - "securityInterceptor"); - Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, - "cloudFoundrySecurityService"); - RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils - .getField(interceptorSecurityService, "restTemplate"); - assertThat(restTemplate.getRequestFactory()) - .isInstanceOf(SkipSslVerificationHttpRequestFactory.class); - } - - @Test - public void cloudFoundryPlatformActiveAndCloudControllerUrlNotPresent() - throws Exception { - TestPropertyValues - .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") - .applyTo(this.context); - this.context.refresh(); - CloudFoundryEndpointHandlerMapping handlerMapping = this.context.getBean( - "cloudFoundryEndpointHandlerMapping", - CloudFoundryEndpointHandlerMapping.class); - Object securityInterceptor = ReflectionTestUtils.getField(handlerMapping, - "securityInterceptor"); - Object interceptorSecurityService = ReflectionTestUtils - .getField(securityInterceptor, "cloudFoundrySecurityService"); - assertThat(interceptorSecurityService).isNull(); - } - - @Test - public void cloudFoundryPathsIgnoredBySpringSecurity() throws Exception { - TestPropertyValues - .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") - .applyTo(this.context); - this.context.refresh(); - IgnoredRequestCustomizer customizer = (IgnoredRequestCustomizer) this.context - .getBean("cloudFoundryIgnoredRequestCustomizer"); - IgnoredRequestConfigurer configurer = mock(IgnoredRequestConfigurer.class); - customizer.customize(configurer); - ArgumentCaptor requestMatcher = ArgumentCaptor - .forClass(RequestMatcher.class); - verify(configurer).requestMatchers(requestMatcher.capture()); - RequestMatcher matcher = requestMatcher.getValue(); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setServletPath("/cloudfoundryapplication/my-path"); - assertThat(matcher.matches(request)).isTrue(); - request.setServletPath("/some-other-path"); - assertThat(matcher.matches(request)).isFalse(); - } - - @Test - public void cloudFoundryPlatformInactive() throws Exception { - this.context.refresh(); - assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping")) - .isFalse(); - } - - @Test - public void cloudFoundryManagementEndpointsDisabled() throws Exception { - TestPropertyValues - .of("VCAP_APPLICATION=---", "management.cloudfoundry.enabled:false") - .applyTo(this.context); - this.context.refresh(); - assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping")) - .isFalse(); - } - - private CloudFoundryEndpointHandlerMapping getHandlerMapping() { - TestPropertyValues - .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", - "vcap.application.cf_api:http://my-cloud-controller.com") - .applyTo(this.context); - this.context.refresh(); - return this.context.getBean("cloudFoundryEndpointHandlerMapping", - CloudFoundryEndpointHandlerMapping.class); - } + // private AnnotationConfigWebApplicationContext context; + // + // @Before + // public void setup() { + // this.context = new AnnotationConfigWebApplicationContext(); + // this.context.setServletContext(new MockServletContext()); + // this.context.register(SecurityAutoConfiguration.class, + // WebMvcAutoConfiguration.class, + // ManagementWebSecurityAutoConfiguration.class, + // JacksonAutoConfiguration.class, + // HttpMessageConvertersAutoConfiguration.class, + // EndpointAutoConfiguration.class, EndpointServletWebAutoConfiguration.class, + // PropertyPlaceholderAutoConfiguration.class, + // RestTemplateAutoConfiguration.class, + // EndpointWebMvcManagementContextConfiguration.class, + // CloudFoundryActuatorAutoConfiguration.class); + // } + // + // @After + // public void close() { + // if (this.context != null) { + // this.context.close(); + // } + // } + // + // @Test + // public void cloudFoundryPlatformActive() throws Exception { + // CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); + // assertThat(handlerMapping.getPrefix()).isEqualTo("/cloudfoundryapplication"); + // CorsConfiguration corsConfiguration = (CorsConfiguration) ReflectionTestUtils + // .getField(handlerMapping, "corsConfiguration"); + // assertThat(corsConfiguration.getAllowedOrigins()).contains("*"); + // assertThat(corsConfiguration.getAllowedMethods()).containsAll( + // Arrays.asList(HttpMethod.GET.name(), HttpMethod.POST.name())); + // assertThat(corsConfiguration.getAllowedHeaders()).containsAll( + // Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type")); + // } + // + // @Test + // public void cloudFoundryPlatformActiveSetsApplicationId() throws Exception { + // CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); + // Object interceptor = ReflectionTestUtils.getField(handlerMapping, + // "securityInterceptor"); + // String applicationId = (String) ReflectionTestUtils.getField(interceptor, + // "applicationId"); + // assertThat(applicationId).isEqualTo("my-app-id"); + // } + // + // @Test + // public void cloudFoundryPlatformActiveSetsCloudControllerUrl() throws Exception { + // CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); + // Object interceptor = ReflectionTestUtils.getField(handlerMapping, + // "securityInterceptor"); + // Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, + // "cloudFoundrySecurityService"); + // String cloudControllerUrl = (String) ReflectionTestUtils + // .getField(interceptorSecurityService, "cloudControllerUrl"); + // assertThat(cloudControllerUrl).isEqualTo("http://my-cloud-controller.com"); + // } + // + // @Test + // public void skipSslValidation() throws Exception { + // TestPropertyValues.of("management.cloudfoundry.skipSslValidation:true") + // .applyTo(this.context); + // ConfigurationPropertySources.attach(this.context.getEnvironment()); + // this.context.refresh(); + // CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); + // Object interceptor = ReflectionTestUtils.getField(handlerMapping, + // "securityInterceptor"); + // Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, + // "cloudFoundrySecurityService"); + // RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils + // .getField(interceptorSecurityService, "restTemplate"); + // assertThat(restTemplate.getRequestFactory()) + // .isInstanceOf(SkipSslVerificationHttpRequestFactory.class); + // } + // + // @Test + // public void cloudFoundryPlatformActiveAndCloudControllerUrlNotPresent() + // throws Exception { + // TestPropertyValues + // .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") + // .applyTo(this.context); + // this.context.refresh(); + // CloudFoundryEndpointHandlerMapping handlerMapping = this.context.getBean( + // "cloudFoundryEndpointHandlerMapping", + // CloudFoundryEndpointHandlerMapping.class); + // Object securityInterceptor = ReflectionTestUtils.getField(handlerMapping, + // "securityInterceptor"); + // Object interceptorSecurityService = ReflectionTestUtils + // .getField(securityInterceptor, "cloudFoundrySecurityService"); + // assertThat(interceptorSecurityService).isNull(); + // } + // + // @Test + // public void cloudFoundryPathsIgnoredBySpringSecurity() throws Exception { + // TestPropertyValues + // .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") + // .applyTo(this.context); + // this.context.refresh(); + // IgnoredRequestCustomizer customizer = (IgnoredRequestCustomizer) this.context + // .getBean("cloudFoundryIgnoredRequestCustomizer"); + // IgnoredRequestConfigurer configurer = mock(IgnoredRequestConfigurer.class); + // customizer.customize(configurer); + // ArgumentCaptor requestMatcher = ArgumentCaptor + // .forClass(RequestMatcher.class); + // verify(configurer).requestMatchers(requestMatcher.capture()); + // RequestMatcher matcher = requestMatcher.getValue(); + // MockHttpServletRequest request = new MockHttpServletRequest(); + // request.setServletPath("/cloudfoundryapplication/my-path"); + // assertThat(matcher.matches(request)).isTrue(); + // request.setServletPath("/some-other-path"); + // assertThat(matcher.matches(request)).isFalse(); + // } + // + // @Test + // public void cloudFoundryPlatformInactive() throws Exception { + // this.context.refresh(); + // assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping")) + // .isFalse(); + // } + // + // @Test + // public void cloudFoundryManagementEndpointsDisabled() throws Exception { + // TestPropertyValues + // .of("VCAP_APPLICATION=---", "management.cloudfoundry.enabled:false") + // .applyTo(this.context); + // this.context.refresh(); + // assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping")) + // .isFalse(); + // } + // + // private CloudFoundryEndpointHandlerMapping getHandlerMapping() { + // TestPropertyValues + // .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", + // "vcap.application.cf_api:http://my-cloud-controller.com") + // .applyTo(this.context); + // this.context.refresh(); + // return this.context.getBean("cloudFoundryEndpointHandlerMapping", + // CloudFoundryEndpointHandlerMapping.class); + // } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpointTests.java index 0d7b49255e..267f71df5f 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpointTests.java @@ -16,20 +16,6 @@ package org.springframework.boot.actuate.cloudfoundry; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; -import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; -import org.springframework.mock.web.MockHttpServletRequest; - -import static org.assertj.core.api.Assertions.assertThat; - /** * Tests for {@link CloudFoundryDiscoveryMvcEndpoint}. * @@ -37,85 +23,87 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class CloudFoundryDiscoveryMvcEndpointTests { - private CloudFoundryDiscoveryMvcEndpoint endpoint; + // TODO CloudFoundry support - private Set endpoints; - - @Before - public void setup() { - NamedMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); - this.endpoints = new LinkedHashSet<>(); - this.endpoints.add(endpoint); - this.endpoint = new CloudFoundryDiscoveryMvcEndpoint(this.endpoints); - } - - @Test - public void cloudfoundryHalJsonEndpointHasEmptyPath() throws Exception { - assertThat(this.endpoint.getPath()).isEmpty(); - } - - @Test - public void linksResponseWhenRequestUriHasNoTrailingSlash() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", - "/cloudfoundryapplication"); - AccessLevel.FULL.put(request); - Map links = this.endpoint - .links(request).get("_links"); - assertThat(links.get("self").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication"); - assertThat(links.get("a").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication/a"); - } - - @Test - public void linksResponseWhenRequestUriHasTrailingSlash() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", - "/cloudfoundryapplication/"); - AccessLevel.FULL.put(request); - Map links = this.endpoint - .links(request).get("_links"); - assertThat(links.get("self").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication"); - assertThat(links.get("a").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication/a"); - } - - @Test - public void linksResponseWhenRequestHasAccessLevelRestricted() throws Exception { - NamedMvcEndpoint testHealthMvcEndpoint = new TestMvcEndpoint( - new TestEndpoint("health")); - this.endpoints.add(testHealthMvcEndpoint); - MockHttpServletRequest request = new MockHttpServletRequest("GET", - "/cloudfoundryapplication/"); - AccessLevel.RESTRICTED.put(request); - Map links = this.endpoint - .links(request).get("_links"); - assertThat(links.get("self").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication"); - assertThat(links.get("health").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication/health"); - assertThat(links.get("a")).isNull(); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } + // private CloudFoundryDiscoveryMvcEndpoint endpoint; + // + // private Set endpoints; + // + // @Before + // public void setup() { + // NamedMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); + // this.endpoints = new LinkedHashSet<>(); + // this.endpoints.add(endpoint); + // this.endpoint = new CloudFoundryDiscoveryMvcEndpoint(this.endpoints); + // } + // + // @Test + // public void cloudfoundryHalJsonEndpointHasEmptyPath() throws Exception { + // assertThat(this.endpoint.getPath()).isEmpty(); + // } + // + // @Test + // public void linksResponseWhenRequestUriHasNoTrailingSlash() throws Exception { + // MockHttpServletRequest request = new MockHttpServletRequest("GET", + // "/cloudfoundryapplication"); + // AccessLevel.FULL.put(request); + // Map links = this.endpoint + // .links(request).get("_links"); + // assertThat(links.get("self").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication"); + // assertThat(links.get("a").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication/a"); + // } + // + // @Test + // public void linksResponseWhenRequestUriHasTrailingSlash() throws Exception { + // MockHttpServletRequest request = new MockHttpServletRequest("GET", + // "/cloudfoundryapplication/"); + // AccessLevel.FULL.put(request); + // Map links = this.endpoint + // .links(request).get("_links"); + // assertThat(links.get("self").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication"); + // assertThat(links.get("a").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication/a"); + // } + // + // @Test + // public void linksResponseWhenRequestHasAccessLevelRestricted() throws Exception { + // NamedMvcEndpoint testHealthMvcEndpoint = new TestMvcEndpoint( + // new TestEndpoint("health")); + // this.endpoints.add(testHealthMvcEndpoint); + // MockHttpServletRequest request = new MockHttpServletRequest("GET", + // "/cloudfoundryapplication/"); + // AccessLevel.RESTRICTED.put(request); + // Map links = this.endpoint + // .links(request).get("_links"); + // assertThat(links.get("self").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication"); + // assertThat(links.get("health").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication/health"); + // assertThat(links.get("a")).isNull(); + // } + // + // private static class TestEndpoint extends AbstractEndpoint { + // + // TestEndpoint(String id) { + // super(id); + // } + // + // @Override + // public Object invoke() { + // return null; + // } + // + // } + // + // private static class TestMvcEndpoint extends EndpointMvcAdapter { + // + // TestMvcEndpoint(TestEndpoint delegate) { + // super(delegate); + // } + // + // } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMappingTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMappingTests.java index 19f1f93891..05afb33341 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMappingTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMappingTests.java @@ -16,102 +16,110 @@ package org.springframework.boot.actuate.cloudfoundry; -import java.util.Collections; - -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointHandlerMappingTests; -import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.OrderedHealthAggregator; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.HandlerExecutionChain; - -import static org.assertj.core.api.Assertions.assertThat; - /** * Tests for {@link CloudFoundryEndpointHandlerMapping}. * * @author Madhura Bhave */ -public class CloudFoundryEndpointHandlerMappingTests - extends AbstractEndpointHandlerMappingTests { +public class CloudFoundryEndpointHandlerMappingTests { - @Test - public void getHandlerExecutionChainWhenEndpointHasPathShouldMapAgainstName() - throws Exception { - TestMvcEndpoint testMvcEndpoint = new TestMvcEndpoint(new TestEndpoint("a")); - testMvcEndpoint.setPath("something-else"); - CloudFoundryEndpointHandlerMapping handlerMapping = new CloudFoundryEndpointHandlerMapping( - Collections.singleton(testMvcEndpoint), null, null); - assertThat(handlerMapping.getPath(testMvcEndpoint)).isEqualTo("/a"); - } + // TODO CloudFoundry support - @Test - public void registersCloudFoundryDiscoveryEndpoint() throws Exception { - StaticApplicationContext context = new StaticApplicationContext(); - CloudFoundryEndpointHandlerMapping handlerMapping = new CloudFoundryEndpointHandlerMapping( - Collections.emptySet(), null, null); - handlerMapping.setPrefix("/test"); - handlerMapping.setApplicationContext(context); - handlerMapping.afterPropertiesSet(); - HandlerExecutionChain handler = handlerMapping - .getHandler(new MockHttpServletRequest("GET", "/test")); - HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler(); - assertThat(handlerMethod.getBean()) - .isInstanceOf(CloudFoundryDiscoveryMvcEndpoint.class); - } - - @Test - public void registersCloudFoundryHealthEndpoint() throws Exception { - StaticApplicationContext context = new StaticApplicationContext(); - HealthEndpoint delegate = new HealthEndpoint(new OrderedHealthAggregator(), - Collections.emptyMap()); - CloudFoundryEndpointHandlerMapping handlerMapping = new CloudFoundryEndpointHandlerMapping( - Collections.singleton(new TestHealthMvcEndpoint(delegate)), null, null); - handlerMapping.setPrefix("/test"); - handlerMapping.setApplicationContext(context); - handlerMapping.afterPropertiesSet(); - HandlerExecutionChain handler = handlerMapping - .getHandler(new MockHttpServletRequest("GET", "/test/health")); - HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler(); - Object handlerMethodBean = handlerMethod.getBean(); - assertThat(handlerMethodBean).isInstanceOf(CloudFoundryHealthMvcEndpoint.class); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } - - private static class TestHealthMvcEndpoint extends HealthMvcEndpoint { - - TestHealthMvcEndpoint(HealthEndpoint delegate) { - super(delegate); - } - - } + // @Test + // public void getHandlerExecutionChainWhenEndpointHasPathShouldMapAgainstName() + // throws Exception { + // TestMvcEndpoint testMvcEndpoint = new TestMvcEndpoint(new TestEndpoint("a")); + // testMvcEndpoint.setPath("something-else"); + // CloudFoundryEndpointHandlerMapping handlerMapping = new + // CloudFoundryEndpointHandlerMapping( + // Collections.singleton(testMvcEndpoint), null, null); + // assertThat(handlerMapping.getPath(testMvcEndpoint)).isEqualTo("/a"); + // } + // + // @Test + // public void doesNotRegisterHalJsonMvcEndpoint() throws Exception { + // CloudFoundryEndpointHandlerMapping handlerMapping = new + // CloudFoundryEndpointHandlerMapping( + // Collections.singleton(new TestHalJsonMvcEndpoint()), null, null); + // assertThat(handlerMapping.getEndpoints()).hasSize(0); + // } + // + // @Test + // public void registersCloudFoundryDiscoveryEndpoint() throws Exception { + // StaticApplicationContext context = new StaticApplicationContext(); + // CloudFoundryEndpointHandlerMapping handlerMapping = new + // CloudFoundryEndpointHandlerMapping( + // Collections.emptySet(), null, null); + // handlerMapping.setPrefix("/test"); + // handlerMapping.setApplicationContext(context); + // handlerMapping.afterPropertiesSet(); + // HandlerExecutionChain handler = handlerMapping + // .getHandler(new MockHttpServletRequest("GET", "/test")); + // HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler(); + // assertThat(handlerMethod.getBean()) + // .isInstanceOf(CloudFoundryDiscoveryMvcEndpoint.class); + // } + // + // @Test + // public void registersCloudFoundryHealthEndpoint() throws Exception { + // StaticApplicationContext context = new StaticApplicationContext(); + // HealthEndpoint delegate = new HealthEndpoint(new OrderedHealthAggregator(), + // Collections.emptyMap()); + // CloudFoundryEndpointHandlerMapping handlerMapping = new + // CloudFoundryEndpointHandlerMapping( + // Collections.singleton(new TestHealthMvcEndpoint(delegate)), null, null); + // handlerMapping.setPrefix("/test"); + // handlerMapping.setApplicationContext(context); + // handlerMapping.afterPropertiesSet(); + // HandlerExecutionChain handler = handlerMapping + // .getHandler(new MockHttpServletRequest("GET", "/test/health")); + // HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler(); + // Object handlerMethodBean = handlerMethod.getBean(); + // assertThat(handlerMethodBean).isInstanceOf(CloudFoundryHealthMvcEndpoint.class); + // } + // + // private static class TestEndpoint extends AbstractEndpoint { + // + // TestEndpoint(String id) { + // super(id); + // } + // + // @Override + // public Object invoke() { + // return null; + // } + // + // } + // + // private static class TestMvcEndpoint extends EndpointMvcAdapter { + // + // TestMvcEndpoint(TestEndpoint delegate) { + // super(delegate); + // } + // + // } + // + // private static class TestHalJsonMvcEndpoint extends HalJsonMvcEndpoint { + // + // TestHalJsonMvcEndpoint() { + // super(new ManagementServletContext() { + // + // @Override + // public String getContextPath() { + // return ""; + // } + // + // }); + // } + // + // } + // + // private static class TestHealthMvcEndpoint extends HealthWebEndpointExtension { + // + // TestHealthMvcEndpoint(HealthEndpoint delegate) { + // super(delegate); + // } + // + // } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpointTests.java index b8203503e1..a10f5705d7 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpointTests.java @@ -16,16 +16,6 @@ package org.springframework.boot.actuate.cloudfoundry; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Status; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - /** * Tests for {@link CloudFoundryHealthMvcEndpoint}. * @@ -33,18 +23,21 @@ import static org.mockito.Mockito.mock; */ public class CloudFoundryHealthMvcEndpointTests { - @Test - public void cloudFoundryHealthEndpointShouldAlwaysReturnAllHealthDetails() - throws Exception { - HealthEndpoint endpoint = mock(HealthEndpoint.class); - given(endpoint.isEnabled()).willReturn(true); - CloudFoundryHealthMvcEndpoint mvc = new CloudFoundryHealthMvcEndpoint(endpoint); - given(endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = mvc.invoke(null, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } + // TODO CloudFoundry-specific health endpoint? + + // @Test + // public void cloudFoundryHealthEndpointShouldAlwaysReturnAllHealthDetails() + // throws Exception { + // HealthEndpoint endpoint = mock(HealthEndpoint.class); + // given(endpoint.isEnabled()).willReturn(true); + // CloudFoundryHealthMvcEndpoint mvc = new CloudFoundryHealthMvcEndpoint(endpoint); + // given(endpoint.invoke()) + // .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); + // given(endpoint.isSensitive()).willReturn(false); + // Object result = mvc.invoke(null, null); + // assertThat(result instanceof Health).isTrue(); + // assertThat(((Health) result).getStatus() == Status.UP).isTrue(); + // assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); + // } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptorTests.java index 7a338d210d..6806991db7 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptorTests.java @@ -16,27 +16,6 @@ package org.springframework.boot.actuate.cloudfoundry; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.BDDMockito; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason; -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.util.Base64Utils; -import org.springframework.web.method.HandlerMethod; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; - /** * Tests for {@link CloudFoundrySecurityInterceptor}. * @@ -44,168 +23,170 @@ import static org.mockito.Mockito.verify; */ public class CloudFoundrySecurityInterceptorTests { - @Mock - private TokenValidator tokenValidator; + // TODO CloudFoundry-specific security - @Mock - private CloudFoundrySecurityService securityService; - - private CloudFoundrySecurityInterceptor interceptor; - - private TestMvcEndpoint endpoint; - - private HandlerMethod handlerMethod; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, - this.securityService, "my-app-id"); - this.endpoint = new TestMvcEndpoint(new TestEndpoint("a")); - this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); - this.request = new MockHttpServletRequest(); - this.response = new MockHttpServletResponse(); - } - - @Test - public void preHandleWhenRequestIsPreFlightShouldReturnTrue() throws Exception { - this.request.setMethod("OPTIONS"); - this.request.addHeader(HttpHeaders.ORIGIN, "http://example.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isTrue(); - } - - @Test - public void preHandleWhenTokenIsMissingShouldReturnFalse() throws Exception { - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isFalse(); - assertThat(this.response.getStatus()) - .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus().value()); - assertThat(this.response.getContentAsString()).contains("security_error"); - assertThat(this.response.getContentType()) - .isEqualTo(MediaType.APPLICATION_JSON.toString()); - } - - @Test - public void preHandleWhenTokenIsNotBearerShouldReturnFalse() throws Exception { - this.request.addHeader("Authorization", mockAccessToken()); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isFalse(); - assertThat(this.response.getStatus()) - .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus().value()); - } - - @Test - public void preHandleWhenApplicationIdIsNullShouldReturnFalse() throws Exception { - this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, - this.securityService, null); - this.request.addHeader("Authorization", "bearer " + mockAccessToken()); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isFalse(); - assertThat(this.response.getStatus()) - .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus().value()); - } - - @Test - public void preHandleWhenCloudFoundrySecurityServiceIsNullShouldReturnFalse() - throws Exception { - this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, null, - "my-app-id"); - this.request.addHeader("Authorization", "bearer " + mockAccessToken()); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isFalse(); - assertThat(this.response.getStatus()) - .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus().value()); - } - - @Test - public void preHandleWhenAccessIsNotAllowedShouldReturnFalse() throws Exception { - this.endpoint = new TestMvcEndpoint(new TestEndpoint("env")); - this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); - String accessToken = mockAccessToken(); - this.request.addHeader("Authorization", "bearer " + accessToken); - BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) - .willReturn(AccessLevel.RESTRICTED); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isFalse(); - assertThat(this.response.getStatus()) - .isEqualTo(Reason.ACCESS_DENIED.getStatus().value()); - } - - @Test - public void preHandleSuccessfulWithFullAccess() throws Exception { - String accessToken = mockAccessToken(); - this.request.addHeader("Authorization", "Bearer " + accessToken); - BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) - .willReturn(AccessLevel.FULL); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); - verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); - Token token = tokenArgumentCaptor.getValue(); - assertThat(token.toString()).isEqualTo(accessToken); - assertThat(preHandle).isTrue(); - assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(this.request.getAttribute("cloudFoundryAccessLevel")) - .isEqualTo(AccessLevel.FULL); - } - - @Test - public void preHandleSuccessfulWithRestrictedAccess() throws Exception { - this.endpoint = new TestMvcEndpoint(new TestEndpoint("info")); - this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); - String accessToken = mockAccessToken(); - this.request.addHeader("Authorization", "Bearer " + accessToken); - BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) - .willReturn(AccessLevel.RESTRICTED); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); - verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); - Token token = tokenArgumentCaptor.getValue(); - assertThat(token.toString()).isEqualTo(accessToken); - assertThat(preHandle).isTrue(); - assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(this.request.getAttribute("cloudFoundryAccessLevel")) - .isEqualTo(AccessLevel.RESTRICTED); - } - - private String mockAccessToken() { - return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu" - + "Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ." - + Base64Utils.encodeToString("signature".getBytes()); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } + // @Mock + // private TokenValidator tokenValidator; + // + // @Mock + // private CloudFoundrySecurityService securityService; + // + // private CloudFoundrySecurityInterceptor interceptor; + // + // private TestMvcEndpoint endpoint; + // + // private HandlerMethod handlerMethod; + // + // private MockHttpServletRequest request; + // + // private MockHttpServletResponse response; + // + // @Before + // public void setup() throws Exception { + // MockitoAnnotations.initMocks(this); + // this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, + // this.securityService, "my-app-id"); + // this.endpoint = new TestMvcEndpoint(new TestEndpoint("a")); + // this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); + // this.request = new MockHttpServletRequest(); + // this.response = new MockHttpServletResponse(); + // } + // + // @Test + // public void preHandleWhenRequestIsPreFlightShouldReturnTrue() throws Exception { + // this.request.setMethod("OPTIONS"); + // this.request.addHeader(HttpHeaders.ORIGIN, "http://example.com"); + // this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isTrue(); + // } + // + // @Test + // public void preHandleWhenTokenIsMissingShouldReturnFalse() throws Exception { + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isFalse(); + // assertThat(this.response.getStatus()) + // .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus().value()); + // assertThat(this.response.getContentAsString()).contains("security_error"); + // assertThat(this.response.getContentType()) + // .isEqualTo(MediaType.APPLICATION_JSON.toString()); + // } + // + // @Test + // public void preHandleWhenTokenIsNotBearerShouldReturnFalse() throws Exception { + // this.request.addHeader("Authorization", mockAccessToken()); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isFalse(); + // assertThat(this.response.getStatus()) + // .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus().value()); + // } + // + // @Test + // public void preHandleWhenApplicationIdIsNullShouldReturnFalse() throws Exception { + // this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, + // this.securityService, null); + // this.request.addHeader("Authorization", "bearer " + mockAccessToken()); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isFalse(); + // assertThat(this.response.getStatus()) + // .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus().value()); + // } + // + // @Test + // public void preHandleWhenCloudFoundrySecurityServiceIsNullShouldReturnFalse() + // throws Exception { + // this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, null, + // "my-app-id"); + // this.request.addHeader("Authorization", "bearer " + mockAccessToken()); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isFalse(); + // assertThat(this.response.getStatus()) + // .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus().value()); + // } + // + // @Test + // public void preHandleWhenAccessIsNotAllowedShouldReturnFalse() throws Exception { + // this.endpoint = new TestMvcEndpoint(new TestEndpoint("env")); + // this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); + // String accessToken = mockAccessToken(); + // this.request.addHeader("Authorization", "bearer " + accessToken); + // BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) + // .willReturn(AccessLevel.RESTRICTED); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isFalse(); + // assertThat(this.response.getStatus()) + // .isEqualTo(Reason.ACCESS_DENIED.getStatus().value()); + // } + // + // @Test + // public void preHandleSuccessfulWithFullAccess() throws Exception { + // String accessToken = mockAccessToken(); + // this.request.addHeader("Authorization", "Bearer " + accessToken); + // BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) + // .willReturn(AccessLevel.FULL); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); + // verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); + // Token token = tokenArgumentCaptor.getValue(); + // assertThat(token.toString()).isEqualTo(accessToken); + // assertThat(preHandle).isTrue(); + // assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); + // assertThat(this.request.getAttribute("cloudFoundryAccessLevel")) + // .isEqualTo(AccessLevel.FULL); + // } + // + // @Test + // public void preHandleSuccessfulWithRestrictedAccess() throws Exception { + // this.endpoint = new TestMvcEndpoint(new TestEndpoint("info")); + // this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); + // String accessToken = mockAccessToken(); + // this.request.addHeader("Authorization", "Bearer " + accessToken); + // BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) + // .willReturn(AccessLevel.RESTRICTED); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); + // verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); + // Token token = tokenArgumentCaptor.getValue(); + // assertThat(token.toString()).isEqualTo(accessToken); + // assertThat(preHandle).isTrue(); + // assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); + // assertThat(this.request.getAttribute("cloudFoundryAccessLevel")) + // .isEqualTo(AccessLevel.RESTRICTED); + // } + // + // private String mockAccessToken() { + // return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu" + // + "Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ." + // + Base64Utils.encodeToString("signature".getBytes()); + // } + // + // private static class TestEndpoint extends AbstractEndpoint { + // + // TestEndpoint(String id) { + // super(id); + // } + // + // @Override + // public Object invoke() { + // return null; + // } + // + // } + // + // private static class TestMvcEndpoint extends EndpointMvcAdapter { + // + // TestMvcEndpoint(TestEndpoint delegate) { + // super(delegate); + // } + // + // } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AbstractEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AbstractEndpointTests.java deleted file mode 100644 index 00d285e140..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AbstractEndpointTests.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.PropertySource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Abstract base class for endpoint tests. - * - * @param the endpoint type - * @author Phillip Webb - */ -public abstract class AbstractEndpointTests> { - - protected AnnotationConfigApplicationContext context; - - protected final Class configClass; - - private final Class type; - - private final String id; - - private final String property; - - public AbstractEndpointTests(Class configClass, Class type, String id, - String property) { - this.configClass = configClass; - this.type = type; - this.id = id; - this.property = property; - } - - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(JacksonAutoConfiguration.class, this.configClass); - this.context.refresh(); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void getId() throws Exception { - assertThat(getEndpointBean().getId()).isEqualTo(this.id); - } - - @Test - public void idOverride() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of(this.property + ".id:myid").applyTo(this.context); - this.context.register(this.configClass); - this.context.refresh(); - assertThat(getEndpointBean().getId()).isEqualTo("myid"); - } - - @Test - public void isEnabledByDefault() throws Exception { - assertThat(getEndpointBean().isEnabled()).isTrue(); - } - - @Test - public void isEnabledFallbackToEnvironment() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - PropertySource propertySource = new MapPropertySource("test", Collections - .singletonMap(this.property + ".enabled", false)); - this.context.getEnvironment().getPropertySources().addFirst(propertySource); - this.context.register(this.configClass); - this.context.refresh(); - assertThat(getEndpointBean().isEnabled()).isFalse(); - } - - @Test - @SuppressWarnings("rawtypes") - public void isExplicitlyEnabled() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - PropertySource propertySource = new MapPropertySource("test", Collections - .singletonMap(this.property + ".enabled", false)); - this.context.getEnvironment().getPropertySources().addFirst(propertySource); - this.context.register(this.configClass); - this.context.refresh(); - ((AbstractEndpoint) getEndpointBean()).setEnabled(true); - assertThat(getEndpointBean().isEnabled()).isTrue(); - } - - @Test - public void isAllEndpointsDisabled() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - PropertySource propertySource = new MapPropertySource("test", - Collections.singletonMap("endpoints.enabled", false)); - this.context.getEnvironment().getPropertySources().addFirst(propertySource); - this.context.register(this.configClass); - this.context.refresh(); - assertThat(getEndpointBean().isEnabled()).isFalse(); - } - - @Test - public void isOptIn() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - Map source = new HashMap<>(); - source.put("endpoints.enabled", false); - source.put(this.property + ".enabled", true); - PropertySource propertySource = new MapPropertySource("test", source); - this.context.getEnvironment().getPropertySources().addFirst(propertySource); - this.context.register(this.configClass); - this.context.refresh(); - assertThat(getEndpointBean().isEnabled()).isTrue(); - } - - @Test - public void serialize() throws Exception { - Object result = getEndpointBean().invoke(); - if (result != null) { - this.context.getBean(ObjectMapper.class).writeValue(System.out, result); - } - } - - @SuppressWarnings("unchecked") - protected T getEndpointBean() { - return (T) this.context.getBean(this.type); - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpointTests.java new file mode 100644 index 0000000000..37e9731bd1 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpointTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.junit.Test; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AuditEventsEndpoint}. + * + * @author Andy Wilkinson + */ +public class AuditEventsEndpointTests { + + private final AuditEventRepository repository = mock(AuditEventRepository.class); + + private final AuditEventsEndpoint endpoint = new AuditEventsEndpoint(this.repository); + + private final AuditEvent event = new AuditEvent("principal", "type", + Collections.singletonMap("a", "alpha")); + + @Test + public void eventsWithType() { + given(this.repository.find(null, null, "type")) + .willReturn(Collections.singletonList(this.event)); + List result = this.endpoint.eventsWithPrincipalDateAfterAndType(null, + null, "type"); + assertThat(result).isEqualTo(Collections.singletonList(this.event)); + } + + @Test + public void eventsWithDateAfter() { + Date date = new Date(); + given(this.repository.find(null, date, null)) + .willReturn(Collections.singletonList(this.event)); + List result = this.endpoint.eventsWithPrincipalDateAfterAndType(null, + date, null); + assertThat(result).isEqualTo(Collections.singletonList(this.event)); + } + + @Test + public void eventsWithPrincipal() { + given(this.repository.find("Joan", null, null)) + .willReturn(Collections.singletonList(this.event)); + List result = this.endpoint + .eventsWithPrincipalDateAfterAndType("Joan", null, null); + assertThat(result).isEqualTo(Collections.singletonList(this.event)); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java index da1f331d2a..c9a97ba586 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java @@ -27,8 +27,8 @@ import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.Configuration; @@ -43,24 +43,19 @@ import static org.mockito.Mockito.mock; * @author Phillip Webb * @author Andy Wilkinson */ -public class AutoConfigurationReportEndpointTests - extends AbstractEndpointTests { - - public AutoConfigurationReportEndpointTests() { - super(Config.class, AutoConfigurationReportEndpoint.class, "autoconfig", - "endpoints.autoconfig"); - } +public class AutoConfigurationReportEndpointTests { @Test public void invoke() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(this.configClass); - this.context.refresh(); - Report report = getEndpointBean().invoke(); - assertThat(report.getPositiveMatches()).isEmpty(); - assertThat(report.getNegativeMatches()).containsKey("a"); - assertThat(report.getUnconditionalClasses()).contains("b"); - assertThat(report.getExclusions()).contains("com.foo.Bar"); + new ApplicationContextRunner().withUserConfiguration(Config.class) + .run((context) -> { + Report report = context.getBean(AutoConfigurationReportEndpoint.class) + .getEvaluationReport(); + assertThat(report.getPositiveMatches()).isEmpty(); + assertThat(report.getNegativeMatches()).containsKey("a"); + assertThat(report.getUnconditionalClasses()).contains("b"); + assertThat(report.getExclusions()).contains("com.foo.Bar"); + }); } @Configuration @@ -84,8 +79,9 @@ public class AutoConfigurationReportEndpointTests } @Bean - public AutoConfigurationReportEndpoint endpoint() { - return new AutoConfigurationReportEndpoint(); + public AutoConfigurationReportEndpoint endpoint( + ConditionEvaluationReport report) { + return new AutoConfigurationReportEndpoint(report); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointContextHierarchyTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointContextHierarchyTests.java deleted file mode 100644 index dea3ce94b6..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointContextHierarchyTests.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint; - -import java.util.List; -import java.util.Map; - -import org.assertj.core.api.Condition; -import org.junit.After; -import org.junit.Test; - -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link BeansEndpoint} with an application context hierarchy. - * - * @author Andy Wilkinson - */ -public class BeansEndpointContextHierarchyTests { - - private AnnotationConfigApplicationContext parent; - - private AnnotationConfigApplicationContext child; - - @After - public void closeContexts() { - if (this.child != null) { - this.child.close(); - } - if (this.parent != null) { - this.parent.close(); - } - } - - @Test - public void beansInParentContextAreFound() { - load(BeanConfiguration.class, EndpointConfiguration.class); - BeansEndpoint endpoint = this.child.getBean(BeansEndpoint.class); - List contexts = endpoint.invoke(); - assertThat(contexts).hasSize(2); - assertThat(contexts.get(1)).has(beanNamed("bean")); - assertThat(contexts.get(0)).has(beanNamed("endpoint")); - } - - @Test - public void beansInChildContextAreNotFound() { - load(EndpointConfiguration.class, BeanConfiguration.class); - BeansEndpoint endpoint = this.child.getBean(BeansEndpoint.class); - List contexts = endpoint.invoke(); - assertThat(contexts).hasSize(1); - assertThat(contexts.get(0)).has(beanNamed("endpoint")); - assertThat(contexts.get(0)).doesNotHave(beanNamed("bean")); - } - - private void load(Class parent, Class child) { - this.parent = new AnnotationConfigApplicationContext(parent); - this.child = new AnnotationConfigApplicationContext(); - this.child.setParent(this.parent); - this.child.register(child); - this.child.refresh(); - } - - private ContextHasBeanCondition beanNamed(String beanName) { - return new ContextHasBeanCondition(beanName); - } - - private static final class ContextHasBeanCondition extends Condition { - - private final String beanName; - - private ContextHasBeanCondition(String beanName) { - this.beanName = beanName; - } - - @Override - @SuppressWarnings("unchecked") - public boolean matches(Object context) { - if (!(context instanceof Map)) { - return false; - } - List beans = (List) ((Map) context) - .get("beans"); - if (beans == null) { - return false; - } - for (Object bean : beans) { - if (!(bean instanceof Map)) { - return false; - } - if (this.beanName.equals(((Map) bean).get("bean"))) { - return true; - } - } - return false; - } - - } - - @Configuration - static class EndpointConfiguration { - - @Bean - public BeansEndpoint endpoint() { - return new BeansEndpoint(); - } - - } - - @Configuration - static class BeanConfiguration { - - @Bean - public String bean() { - return "bean"; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointTests.java index 57559ea0af..2d9145432b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,10 @@ package org.springframework.boot.actuate.endpoint; import java.util.List; import java.util.Map; +import org.assertj.core.api.Condition; import org.junit.Test; -import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -31,23 +32,93 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link BeansEndpoint}. * * @author Phillip Webb + * @author Andy Wilkinson */ -public class BeansEndpointTests extends AbstractEndpointTests { +public class BeansEndpointTests { - public BeansEndpointTests() { - super(Config.class, BeansEndpoint.class, "beans", "endpoints.beans"); + @Test + public void beansAreFound() throws Exception { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(EndpointConfiguration.class); + contextRunner.run((context) -> { + List result = context.getBean(BeansEndpoint.class).beans(); + assertThat(result).hasSize(1); + assertThat(result.get(0)).isInstanceOf(Map.class); + }); } @Test - public void invoke() throws Exception { - List result = getEndpointBean().invoke(); - assertThat(result).hasSize(1); - assertThat(result.get(0)).isInstanceOf(Map.class); + public void beansInParentContextAreFound() { + ApplicationContextRunner parentRunner = new ApplicationContextRunner() + .withUserConfiguration(BeanConfiguration.class); + parentRunner.run((parent) -> { + new ApplicationContextRunner() + .withUserConfiguration(EndpointConfiguration.class).withParent(parent) + .run(child -> { + BeansEndpoint endpoint = child.getBean(BeansEndpoint.class); + List contexts = endpoint.beans(); + assertThat(contexts).hasSize(2); + assertThat(contexts.get(1)).has(beanNamed("bean")); + assertThat(contexts.get(0)).has(beanNamed("endpoint")); + }); + }); + } + + @Test + public void beansInChildContextAreNotFound() { + ApplicationContextRunner parentRunner = new ApplicationContextRunner() + .withUserConfiguration(EndpointConfiguration.class); + parentRunner.run((parent) -> { + new ApplicationContextRunner().withUserConfiguration(BeanConfiguration.class) + .withParent(parent).run(child -> { + BeansEndpoint endpoint = child.getBean(BeansEndpoint.class); + List contexts = endpoint.beans(); + assertThat(contexts).hasSize(1); + assertThat(contexts.get(0)).has(beanNamed("endpoint")); + assertThat(contexts.get(0)).doesNotHave(beanNamed("bean")); + }); + }); + } + + private ContextHasBeanCondition beanNamed(String beanName) { + return new ContextHasBeanCondition(beanName); + } + + private static final class ContextHasBeanCondition extends Condition { + + private final String beanName; + + private ContextHasBeanCondition(String beanName) { + super("Bean named '" + beanName + "'"); + this.beanName = beanName; + } + + @Override + @SuppressWarnings("unchecked") + public boolean matches(Object context) { + if (!(context instanceof Map)) { + return false; + } + List beans = (List) ((Map) context) + .get("beans"); + if (beans == null) { + return false; + } + for (Object bean : beans) { + if (!(bean instanceof Map)) { + return false; + } + if (this.beanName.equals(((Map) bean).get("bean"))) { + return true; + } + } + return false; + } + } @Configuration - @EnableConfigurationProperties - public static class Config { + public static class EndpointConfiguration { @Bean public BeansEndpoint endpoint() { @@ -56,4 +127,14 @@ public class BeansEndpointTests extends AbstractEndpointTests { } + @Configuration + static class BeanConfiguration { + + @Bean + public String bean() { + return "bean"; + } + + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java index 5a8ca1f54b..05d28f7664 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java @@ -18,14 +18,11 @@ package org.springframework.boot.actuate.endpoint; import java.util.Map; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,53 +32,44 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ConfigurationPropertiesReportEndpoint} when used with bean methods. * * @author Dave Syer + * @author Andy Wilkinson */ public class ConfigurationPropertiesReportEndpointMethodAnnotationsTests { - private AnnotationConfigApplicationContext context; - - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } + @Test + @SuppressWarnings("unchecked") + public void testNaming() { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(Config.class) + .withPropertyValues("other.name:foo", "first.name:bar"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("other"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("other"); + assertThat(nestedProperties.get("properties")).isNotNull(); + }); } @Test @SuppressWarnings("unchecked") - public void testNaming() throws Exception { - this.context.register(Config.class); - TestPropertyValues.of("other.name:foo", "first.name:bar").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("other"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("other"); - assertThat(nestedProperties.get("properties")).isNotNull(); - } - - @Test - @SuppressWarnings("unchecked") - public void testOverride() throws Exception { - this.context.register(Other.class); - TestPropertyValues.of("other.name:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("bar"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("other"); - assertThat(nestedProperties.get("properties")).isNotNull(); + public void prefixFromBeanMethodConfigurationPropertiesCanOverridePrefixOnClass() { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(OverriddenPrefix.class) + .withPropertyValues("other.name:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("bar"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("other"); + assertThat(nestedProperties.get("properties")).isNotNull(); + }); } @Configuration @@ -109,7 +97,7 @@ public class ConfigurationPropertiesReportEndpointMethodAnnotationsTests { @Configuration @EnableConfigurationProperties - public static class Other { + public static class OverriddenPrefix { @Bean public ConfigurationPropertiesReportEndpoint endpoint() { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointParentTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointParentTests.java index 75157a987a..0986894a96 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointParentTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointParentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,11 @@ package org.springframework.boot.actuate.endpoint; import java.util.Map; -import org.junit.After; import org.junit.Test; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,53 +33,47 @@ import static org.assertj.core.api.Assertions.assertThat; * context. * * @author Dave Syer + * @author Andy Wilkinson */ public class ConfigurationPropertiesReportEndpointParentTests { - private AnnotationConfigApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - if (this.context.getParent() != null) { - ((ConfigurableApplicationContext) this.context.getParent()).close(); - } - } + @Test + @SuppressWarnings("unchecked") + public void configurationPropertiesClass() throws Exception { + new ApplicationContextRunner().withUserConfiguration(Parent.class) + .run((parent) -> { + new ApplicationContextRunner() + .withUserConfiguration(ClassConfigurationProperties.class) + .withParent(parent).run(child -> { + ConfigurationPropertiesReportEndpoint endpoint = child + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map result = endpoint.configurationProperties(); + assertThat(result.keySet()).containsExactlyInAnyOrder("parent", + "endpoint", "someProperties"); + assertThat(((Map) result.get("parent")).keySet()) + .containsExactly("testProperties"); + }); + }); } @Test - public void testInvoke() throws Exception { - AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); - parent.register(Parent.class); - parent.refresh(); - this.context = new AnnotationConfigApplicationContext(); - this.context.setParent(parent); - this.context.register(Config.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint endpoint = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map result = endpoint.invoke(); - assertThat(result).containsKey("parent"); - assertThat(result).hasSize(3); // the endpoint, the test props and the parent - // System.err.println(result); - } - - @Test - public void testInvokeWithFactory() throws Exception { - AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); - parent.register(Parent.class); - parent.refresh(); - this.context = new AnnotationConfigApplicationContext(); - this.context.setParent(parent); - this.context.register(Factory.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint endpoint = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map result = endpoint.invoke(); - assertThat(result.containsKey("parent")).isTrue(); - assertThat(result).hasSize(3); // the endpoint, the test props and the parent - // System.err.println(result); + @SuppressWarnings("unchecked") + public void configurationPropertiesBeanMethod() throws Exception { + new ApplicationContextRunner().withUserConfiguration(Parent.class) + .run((parent) -> { + new ApplicationContextRunner() + .withUserConfiguration( + BeanMethodConfigurationProperties.class) + .withParent(parent).run(child -> { + ConfigurationPropertiesReportEndpoint endpoint = child + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map result = endpoint.configurationProperties(); + assertThat(result.keySet()).containsExactlyInAnyOrder("parent", + "endpoint", "otherProperties"); + assertThat(((Map) result.get("parent")).keySet()) + .containsExactly("testProperties"); + }); + }); } @Configuration @@ -97,7 +89,7 @@ public class ConfigurationPropertiesReportEndpointParentTests { @Configuration @EnableConfigurationProperties - public static class Config { + public static class ClassConfigurationProperties { @Bean public ConfigurationPropertiesReportEndpoint endpoint() { @@ -113,7 +105,7 @@ public class ConfigurationPropertiesReportEndpointParentTests { @Configuration @EnableConfigurationProperties - public static class Factory { + public static class BeanMethodConfigurationProperties { @Bean public ConfigurationPropertiesReportEndpoint endpoint() { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointProxyTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointProxyTests.java index 89286011e3..267f87e314 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointProxyTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointProxyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +20,11 @@ import java.util.Map; import javax.sql.DataSource; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; @@ -45,30 +43,20 @@ import static org.assertj.core.api.Assertions.assertThat; * class. * * @author Phillip Webb + * @author Andy Wilkinson */ public class ConfigurationPropertiesReportEndpointProxyTests { - private AnnotationConfigApplicationContext context; - - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - @Test public void testWithProxyClass() throws Exception { - this.context.register(Config.class, SqlExecutor.class); - this.context.refresh(); - Map report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class).invoke(); - assertThat(report.toString()).contains("prefix=executor.sql"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(Config.class, SqlExecutor.class); + contextRunner.run((context) -> { + Map report = context + .getBean(ConfigurationPropertiesReportEndpoint.class) + .configurationProperties(); + assertThat(report.toString()).contains("prefix=executor.sql"); + }); } @Configuration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java index 37f8da43b5..df3ab8082b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java @@ -22,14 +22,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -42,185 +39,190 @@ import static org.assertj.core.api.Assertions.entry; * * @author Dave Syer * @author Stephane Nicoll + * @author Andy Wilkinson */ public class ConfigurationPropertiesReportEndpointSerializationTests { - private AnnotationConfigApplicationContext context; - - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - @Test @SuppressWarnings("unchecked") public void testNaming() throws Exception { - this.context.register(FooConfig.class); - TestPropertyValues.of("foo.name:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(2); - assertThat(map.get("name")).isEqualTo("foo"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(FooConfig.class) + .withPropertyValues("foo.name:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(2); + assertThat(map.get("name")).isEqualTo("foo"); + }); } @Test @SuppressWarnings("unchecked") public void testNestedNaming() throws Exception { - this.context.register(FooConfig.class); - TestPropertyValues.of("foo.bar.name:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(2); - assertThat(((Map) map.get("bar")).get("name")).isEqualTo("foo"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(FooConfig.class) + .withPropertyValues("foo.bar.name:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(2); + assertThat(((Map) map.get("bar")).get("name")) + .isEqualTo("foo"); + }); } @Test @SuppressWarnings("unchecked") public void testCycle() throws Exception { - this.context.register(CycleConfig.class); - TestPropertyValues.of("foo.name:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(1); - assertThat(map.get("error")).isEqualTo("Cannot serialize 'foo'"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(CycleConfig.class) + .withPropertyValues("foo.name:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(1); + assertThat(map.get("error")).isEqualTo("Cannot serialize 'foo'"); + }); } @Test @SuppressWarnings("unchecked") public void testMap() throws Exception { - this.context.register(MapConfig.class); - TestPropertyValues.of("foo.map.name:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(3); - assertThat(((Map) map.get("map")).get("name")).isEqualTo("foo"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(MapConfig.class) + .withPropertyValues("foo.map.name:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(3); + assertThat(((Map) map.get("map")).get("name")) + .isEqualTo("foo"); + }); } @Test @SuppressWarnings("unchecked") public void testEmptyMapIsNotAdded() throws Exception { - this.context.register(MapConfig.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - System.err.println(nestedProperties); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(2); - assertThat(map).doesNotContainKey("map"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(MapConfig.class); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + System.err.println(nestedProperties); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(2); + assertThat(map).doesNotContainKey("map"); + }); } @Test @SuppressWarnings("unchecked") public void testList() throws Exception { - this.context.register(ListConfig.class); - TestPropertyValues.of("foo.list[0]:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(3); - assertThat(((List) map.get("list")).get(0)).isEqualTo("foo"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(ListConfig.class) + .withPropertyValues("foo.list[0]:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(3); + assertThat(((List) map.get("list")).get(0)).isEqualTo("foo"); + }); } @Test @SuppressWarnings("unchecked") public void testInetAddress() throws Exception { - this.context.register(AddressedConfig.class); - TestPropertyValues.of("foo.address:192.168.1.10").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - System.err.println(nestedProperties); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(3); - assertThat(map.get("address")).isEqualTo("192.168.1.10"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(AddressedConfig.class) + .withPropertyValues("foo.address:192.168.1.10"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + System.err.println(nestedProperties); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(3); + assertThat(map.get("address")).isEqualTo("192.168.1.10"); + }); } @Test @SuppressWarnings("unchecked") + public void testInitializedMapAndList() throws Exception { - this.context.register(InitializedMapAndListPropertiesConfig.class); - TestPropertyValues.of("foo.map.entryOne:true", "foo.list[0]:abc") - .applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - assertThat(properties).containsKeys("foo"); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).containsOnlyKeys("prefix", "properties"); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map propertiesMap = (Map) nestedProperties - .get("properties"); - assertThat(propertiesMap).containsOnlyKeys("bar", "name", "map", "list"); - Map map = (Map) propertiesMap.get("map"); - assertThat(map).containsOnly(entry("entryOne", true)); - List list = (List) propertiesMap.get("list"); - assertThat(list).containsExactly("abc"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(InitializedMapAndListPropertiesConfig.class) + .withPropertyValues("foo.map.entryOne:true", "foo.list[0]:abc"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + assertThat(properties).containsKeys("foo"); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).containsOnlyKeys("prefix", "properties"); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map propertiesMap = (Map) nestedProperties + .get("properties"); + assertThat(propertiesMap).containsOnlyKeys("bar", "name", "map", "list"); + Map map = (Map) propertiesMap.get("map"); + assertThat(map).containsOnly(entry("entryOne", true)); + List list = (List) propertiesMap.get("list"); + assertThat(list).containsExactly("abc"); + }); } @Configuration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointTests.java index 74f482bf31..02e870c7f6 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointTests.java @@ -18,18 +18,20 @@ package org.springframework.boot.actuate.endpoint; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import org.junit.Test; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.CollectionUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -37,211 +39,169 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ConfigurationPropertiesReportEndpoint}. * * @author Dave Syer + * @author Andy Wilkinson */ -public class ConfigurationPropertiesReportEndpointTests - extends AbstractEndpointTests { - - public ConfigurationPropertiesReportEndpointTests() { - super(Config.class, ConfigurationPropertiesReportEndpoint.class, "configprops", - "endpoints.configprops"); - } - - @Test - public void testInvoke() throws Exception { - assertThat(getEndpointBean().invoke().size()).isGreaterThan(0); - } +public class ConfigurationPropertiesReportEndpointTests { @Test @SuppressWarnings("unchecked") - public void testNaming() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("testProperties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("test"); - assertThat(nestedProperties.get("properties")).isNotNull(); - + public void configurationPropertiesAreReturned() throws Exception { + load((properties) -> { + assertThat(properties.size()).isGreaterThan(0); + Map nestedProperties = (Map) properties + .get("testProperties"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("test"); + assertThat(nestedProperties.get("properties")).isNotNull(); + }); } @Test @SuppressWarnings("unchecked") public void entriesWithNullValuesAreNotIncluded() { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("testProperties"); - assertThat((Map) nestedProperties.get("properties")) - .doesNotContainKey("nullValue"); + load((properties) -> { + Map nestedProperties = (Map) properties + .get("testProperties"); + assertThat((Map) nestedProperties.get("properties")) + .doesNotContainKey("nullValue"); + }); } @Test @SuppressWarnings("unchecked") - public void testDefaultKeySanitization() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("654321"); + public void defaultKeySanitization() throws Exception { + load((properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); + assertThat(nestedProperties.get("myTestProperty")).isEqualTo("654321"); + }); } @Test @SuppressWarnings("unchecked") - public void testKeySanitization() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - report.setKeysToSanitize("property"); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("123456"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); - } - - @Test - @SuppressWarnings("unchecked") - public void testKeySanitizationWithList() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - report.setKeysToSanitize("property"); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("123456"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); + public void customKeySanitization() throws Exception { + load("property", (properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("dbPassword")).isEqualTo("123456"); + assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); + }); } @SuppressWarnings("unchecked") @Test - public void testKeySanitizationWithCustomPattern() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - report.setKeysToSanitize(".*pass.*"); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("654321"); + public void customPatternKeySanitization() throws Exception { + load(".*pass.*", (properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); + assertThat(nestedProperties.get("myTestProperty")).isEqualTo("654321"); + }); } @SuppressWarnings("unchecked") @Test - public void testKeySanitizationWithCustomKeysByEnvironment() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("endpoints.configprops.keys-to-sanitize:property") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("123456"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); - } - - @SuppressWarnings("unchecked") - @Test - public void testKeySanitizationWithCustomPatternByEnvironment() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("endpoints.configprops.keys-to-sanitize: .*pass.*") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("654321"); + public void keysToSanitizeCanBeConfiguredViaTheEnvironment() throws Exception { + ApplicationContextRunner tester = new ApplicationContextRunner() + .withPropertyValues( + "endpoints.configprops.keys-to-sanitize: .*pass.*, property") + .withUserConfiguration(Config.class); + tester.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); + assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); + }); } @Test @SuppressWarnings("unchecked") - public void testKeySanitizationWithCustomPatternAndKeyByEnvironment() - throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("endpoints.configprops.keys-to-sanitize: .*pass.*, property") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); - } - - @Test - @SuppressWarnings("unchecked") - public void testKeySanitizationWithCustomPatternUsingCompositeKeys() - throws Exception { + public void keySanitizationWithCustomPatternUsingCompositeKeys() throws Exception { // gh-4415 - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("endpoints.configprops.keys-to-sanitize: .*\\.secrets\\..*, .*\\.hidden\\..*") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - Map secrets = (Map) nestedProperties - .get("secrets"); - Map hidden = (Map) nestedProperties.get("hidden"); - assertThat(secrets.get("mine")).isEqualTo("******"); - assertThat(secrets.get("yours")).isEqualTo("******"); - assertThat(hidden.get("mine")).isEqualTo("******"); + load(Arrays.asList(".*\\.secrets\\..*", ".*\\.hidden\\..*"), (properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties).isNotNull(); + Map secrets = (Map) nestedProperties + .get("secrets"); + Map hidden = (Map) nestedProperties + .get("hidden"); + assertThat(secrets.get("mine")).isEqualTo("******"); + assertThat(secrets.get("yours")).isEqualTo("******"); + assertThat(hidden.get("mine")).isEqualTo("******"); + }); } @Test @SuppressWarnings("unchecked") public void mixedBoolean() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties.get("mixedBoolean")).isEqualTo(true); + load((properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties.get("mixedBoolean")).isEqualTo(true); + }); } @Test @SuppressWarnings("unchecked") public void listsAreSanitized() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties.get("listItems")).isInstanceOf(List.class); - List list = (List) nestedProperties.get("listItems"); - assertThat(list).hasSize(1); - Map item = (Map) list.get(0); - assertThat(item.get("somePassword")).isEqualTo("******"); + load((properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties.get("listItems")).isInstanceOf(List.class); + List list = (List) nestedProperties.get("listItems"); + assertThat(list).hasSize(1); + Map item = (Map) list.get(0); + assertThat(item.get("somePassword")).isEqualTo("******"); + }); } @Test @SuppressWarnings("unchecked") public void listsOfListsAreSanitized() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties.get("listOfListItems")).isInstanceOf(List.class); - List> listOfLists = (List>) nestedProperties - .get("listOfListItems"); - assertThat(listOfLists).hasSize(1); - List list = listOfLists.get(0); - assertThat(list).hasSize(1); - Map item = (Map) list.get(0); - assertThat(item.get("somePassword")).isEqualTo("******"); + load((properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties.get("listOfListItems")).isInstanceOf(List.class); + List> listOfLists = (List>) nestedProperties + .get("listOfListItems"); + assertThat(listOfLists).hasSize(1); + List list = listOfLists.get(0); + assertThat(list).hasSize(1); + Map item = (Map) list.get(0); + assertThat(item.get("somePassword")).isEqualTo("******"); + }); + } + + private void load(Consumer> properties) { + load(Collections.emptyList(), properties); + } + + private void load(String keyToSanitize, Consumer> properties) { + load(Collections.singletonList(keyToSanitize), properties); + } + + private void load(List keysToSanitize, + Consumer> properties) { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(Config.class); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + if (!CollectionUtils.isEmpty(keysToSanitize)) { + endpoint.setKeysToSanitize( + keysToSanitize.toArray(new String[keysToSanitize.size()])); + } + properties.accept(endpoint.configurationProperties()); + }); } @Configuration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/DumpEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/DumpEndpointTests.java deleted file mode 100644 index 170560dec7..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/DumpEndpointTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint; - -import java.lang.management.ThreadInfo; -import java.util.List; - -import org.junit.Test; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link DumpEndpoint}. - * - * @author Phillip Webb - */ -public class DumpEndpointTests extends AbstractEndpointTests { - - public DumpEndpointTests() { - super(Config.class, DumpEndpoint.class, "dump", "endpoints.dump"); - } - - @Test - public void invoke() throws Exception { - List threadInfo = getEndpointBean().invoke(); - assertThat(threadInfo.size()).isGreaterThan(0); - } - - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public DumpEndpoint endpoint() { - return new DumpEndpoint(); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java index ef7d6a4272..4209dd5cab 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java @@ -26,14 +26,11 @@ import org.junit.Test; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor.PropertyValueDescriptor; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.StandardEnvironment; import static org.assertj.core.api.Assertions.assertThat; @@ -47,48 +44,45 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Madhura Bhave * @author Andy Wilkinson */ -public class EnvironmentEndpointTests extends AbstractEndpointTests { +public class EnvironmentEndpointTests { - public EnvironmentEndpointTests() { - super(Config.class, EnvironmentEndpoint.class, "env", "endpoints.env"); - } - - @Override @After public void close() { System.clearProperty("VCAP_SERVICES"); } @Test - public void invoke() { - EnvironmentDescriptor env = getEndpointBean().invoke(); + public void basicResponse() { + EnvironmentDescriptor env = new EnvironmentEndpoint(new StandardEnvironment()) + .environment(null); assertThat(env.getActiveProfiles()).isEmpty(); assertThat(env.getPropertySources()).hasSize(2); } @Test - public void testCompositeSource() { - EnvironmentEndpoint report = getEndpointBean(); + public void compositeSourceIsHandledCorrectly() { + StandardEnvironment environment = new StandardEnvironment(); CompositePropertySource source = new CompositePropertySource("composite"); source.addPropertySource(new MapPropertySource("one", Collections.singletonMap("foo", (Object) "bar"))); source.addPropertySource(new MapPropertySource("two", Collections.singletonMap("foo", (Object) "spam"))); - this.context.getEnvironment().getPropertySources().addFirst(source); - EnvironmentDescriptor env = report.invoke(); + environment.getPropertySources().addFirst(source); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); assertThat(getSource("composite:one", env).getProperties().get("foo").getValue()) .isEqualTo("bar"); } @Test - public void testKeySanitization() { + public void sensitiveKeysHaveTheirValuesSanitized() { System.setProperty("dbPassword", "123456"); System.setProperty("apiKey", "123456"); System.setProperty("mySecret", "123456"); System.setProperty("myCredentials", "123456"); System.setProperty("VCAP_SERVICES", "123456"); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); + EnvironmentDescriptor env = new EnvironmentEndpoint(new StandardEnvironment()) + .environment(null); Map systemProperties = getSource( "systemProperties", env).getProperties(); assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******"); @@ -101,13 +95,13 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests systemProperties = getSource( "systemProperties", env).getProperties(); assertThat( @@ -126,12 +120,12 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests systemProperties = getSource( "systemProperties", env).getProperties(); assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("123456"); @@ -140,12 +134,12 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests systemProperties = getSource( "systemProperties", env).getProperties(); assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******"); @@ -153,131 +147,60 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests systemProperties = getSource( - "systemProperties", env).getProperties(); - assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("123456"); - assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******"); - clearSystemProperties("dbPassword", "apiKey"); - } - - @Test - public void testKeySanitizationWithCustomPatternByEnvironment() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("endpoints.env.keys-to-sanitize: .*pass.*") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - System.setProperty("dbPassword", "123456"); - System.setProperty("apiKey", "123456"); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map systemProperties = getSource( - "systemProperties", env).getProperties(); - assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******"); - assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("123456"); - clearSystemProperties("dbPassword", "apiKey"); - } - - @Test - public void testKeySanitizationWithCustomPatternAndKeyByEnvironment() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("endpoints.env.keys-to-sanitize: .*pass.*, key") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - System.setProperty("dbPassword", "123456"); - System.setProperty("apiKey", "123456"); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map systemProperties = getSource( - "systemProperties", env).getProperties(); - assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******"); - assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******"); - clearSystemProperties("dbPassword", "apiKey"); - } - @Test public void propertyWithPlaceholderResolved() { - this.context = new AnnotationConfigApplicationContext(); + StandardEnvironment environment = new StandardEnvironment(); TestPropertyValues.of("my.foo: ${bar.blah}", "bar.blah: hello") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map testProperties = getSource("test", env) - .getProperties(); - assertThat(testProperties.get("my.foo").getValue()).isEqualTo("hello"); + .applyTo(environment); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); + assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) + .isEqualTo("hello"); } @Test public void propertyWithPlaceholderNotResolved() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("my.foo: ${bar.blah}").applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map testProperties = getSource("test", env) - .getProperties(); - assertThat(testProperties.get("my.foo").getValue()).isEqualTo("${bar.blah}"); + StandardEnvironment environment = new StandardEnvironment(); + TestPropertyValues.of("my.foo: ${bar.blah}").applyTo(environment); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); + assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) + .isEqualTo("${bar.blah}"); } @Test public void propertyWithSensitivePlaceholderResolved() { - this.context = new AnnotationConfigApplicationContext(); + StandardEnvironment environment = new StandardEnvironment(); TestPropertyValues .of("my.foo: http://${bar.password}://hello", "bar.password: hello") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map testProperties = getSource("test", env) - .getProperties(); - assertThat(testProperties.get("my.foo").getValue()) + .applyTo(environment); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); + assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) .isEqualTo("http://******://hello"); } @Test public void propertyWithSensitivePlaceholderNotResolved() { - this.context = new AnnotationConfigApplicationContext(); + StandardEnvironment environment = new StandardEnvironment(); TestPropertyValues.of("my.foo: http://${bar.password}://hello") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map testProperties = getSource("test", env) - .getProperties(); - assertThat(testProperties.get("my.foo").getValue()) + .applyTo(environment); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); + assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) .isEqualTo("http://${bar.password}://hello"); } @Test @SuppressWarnings("unchecked") public void propertyWithTypeOtherThanStringShouldNotFail() { - this.context = new AnnotationConfigApplicationContext(); - MutablePropertySources propertySources = this.context.getEnvironment() - .getPropertySources(); - Map source = new HashMap<>(); + StandardEnvironment environment = new StandardEnvironment(); + MutablePropertySources propertySources = environment.getPropertySources(); + Map source = new HashMap(); source.put("foo", Collections.singletonMap("bar", "baz")); propertySources.addFirst(new MapPropertySource("test", source)); - this.context.register(Config.class); - this.context.refresh(); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); Map testProperties = getSource("test", env) .getProperties(); Map foo = (Map) testProperties.get("foo") @@ -297,15 +220,4 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests name.equals(source.getName())).findFirst().get(); } - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public EnvironmentEndpoint endpoint() { - return new EnvironmentEndpoint(); - } - - } - } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/FlywayEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/FlywayEndpointTests.java index 7705b72b0d..23a50fe01a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/FlywayEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/FlywayEndpointTests.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -33,16 +34,16 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link FlywayEndpoint}. * * @author EddĂș MelĂ©ndez + * @author Andy Wilkinson */ -public class FlywayEndpointTests extends AbstractEndpointTests { - - public FlywayEndpointTests() { - super(Config.class, FlywayEndpoint.class, "flyway", "endpoints.flyway"); - } +public class FlywayEndpointTests { @Test - public void invoke() throws Exception { - assertThat(getEndpointBean().invoke()).hasSize(1); + public void flywayReportIsProduced() throws Exception { + new ApplicationContextRunner().withUserConfiguration(Config.class) + .run((context) -> assertThat( + context.getBean(FlywayEndpoint.class).flywayReports()) + .hasSize(1)); } @Configuration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java index 766eb6cd5f..72a21a8470 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java @@ -16,18 +16,15 @@ package org.springframework.boot.actuate.endpoint; +import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.Status; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; @@ -36,39 +33,39 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Phillip Webb * @author Christian Dupuis + * @author Andy Wilkinson */ -public class HealthEndpointTests extends AbstractEndpointTests { +public class HealthEndpointTests { - public HealthEndpointTests() { - super(Config.class, HealthEndpoint.class, "health", "endpoints.health"); + @Test + public void upAndUpIsAggregatedToUp() throws Exception { + Map healthIndicators = new HashMap<>(); + healthIndicators.put("up", () -> new Health.Builder().status(Status.UP).build()); + healthIndicators.put("upAgain", + () -> new Health.Builder().status(Status.UP).build()); + HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(), + healthIndicators); + assertThat(endpoint.health().getStatus()).isEqualTo(Status.UP); } @Test - public void invoke() throws Exception { - // As FINE isn't configured in the order we get UNKNOWN - assertThat(getEndpointBean().invoke().getStatus()).isEqualTo(Status.UNKNOWN); + public void upAndDownIsAggregatedToDown() throws Exception { + Map healthIndicators = new HashMap<>(); + healthIndicators.put("up", () -> new Health.Builder().status(Status.UP).build()); + healthIndicators.put("down", + () -> new Health.Builder().status(Status.DOWN).build()); + HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(), + healthIndicators); + assertThat(endpoint.health().getStatus()).isEqualTo(Status.DOWN); } - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public HealthEndpoint endpoint(HealthAggregator healthAggregator, - Map healthIndicators) { - return new HealthEndpoint(healthAggregator, healthIndicators); - } - - @Bean - public HealthIndicator statusHealthIndicator() { - return () -> new Health.Builder().status("FINE").build(); - } - - @Bean - public HealthAggregator healthAggregator() { - return new OrderedHealthAggregator(); - } - + @Test + public void unknownStatusMapsToUnknown() throws Exception { + Map healthIndicators = new HashMap<>(); + healthIndicators.put("status", () -> new Health.Builder().status("FINE").build()); + HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(), + healthIndicators); + assertThat(endpoint.health().getStatus()).isEqualTo(Status.UNKNOWN); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java index 2181c6569b..333fde842b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java @@ -16,16 +16,12 @@ package org.springframework.boot.actuate.endpoint; -import java.util.List; +import java.util.Arrays; +import java.util.Collections; import java.util.Map; import org.junit.Test; -import org.springframework.boot.actuate.info.InfoContributor; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - import static org.assertj.core.api.Assertions.assertThat; /** @@ -34,33 +30,26 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Phillip Webb * @author Dave Syer * @author Meang Akira Tanaka + * @author Andy Wilkinson */ -public class InfoEndpointTests extends AbstractEndpointTests { +public class InfoEndpointTests { - public InfoEndpointTests() { - super(Config.class, InfoEndpoint.class, "info", "endpoints.info"); + @Test + public void info() { + InfoEndpoint endpoint = new InfoEndpoint( + Arrays.asList((builder) -> builder.withDetail("key1", "value1"), + (builder) -> builder.withDetail("key2", "value2"))); + Map info = endpoint.info(); + assertThat(info).hasSize(2); + assertThat(info).containsEntry("key1", "value1"); + assertThat(info).containsEntry("key2", "value2"); } @Test - public void invoke() throws Exception { - Map actual = getEndpointBean().invoke(); - assertThat(actual.get("key1")).isEqualTo("value1"); - } - - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public InfoContributor infoContributor() { - return (builder) -> builder.withDetail("key1", "value1"); - } - - @Bean - public InfoEndpoint endpoint(List infoContributors) { - return new InfoEndpoint(infoContributors); - } - + public void infoWithNoContributorsProducesEmptyMap() { + InfoEndpoint endpoint = new InfoEndpoint(Collections.emptyList()); + Map info = endpoint.info(); + assertThat(info).isEmpty(); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpointTests.java index 97eb5f018c..1ace1a7622 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpointTests.java @@ -16,14 +16,14 @@ package org.springframework.boot.actuate.endpoint; +import java.util.Map; + import liquibase.integration.spring.SpringLiquibase; import org.junit.Test; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -34,62 +34,36 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link LiquibaseEndpoint}. * * @author EddĂș MelĂ©ndez + * @author Andy Wilkinson */ -public class LiquibaseEndpointTests extends AbstractEndpointTests { - - public LiquibaseEndpointTests() { - super(Config.class, LiquibaseEndpoint.class, "liquibase", "endpoints.liquibase"); - } +public class LiquibaseEndpointTests { @Test - public void invoke() throws Exception { - assertThat(getEndpointBean().invoke()).hasSize(1); + public void liquibaseReportIsReturned() throws Exception { + new ApplicationContextRunner().withUserConfiguration(Config.class) + .run((context) -> assertThat( + context.getBean(LiquibaseEndpoint.class).liquibaseReports()) + .hasSize(1)); } @Test public void invokeWithCustomSchema() throws Exception { - this.context.close(); - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("spring.liquibase.default-schema=CUSTOMSCHEMA", + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(Config.class) + .withPropertyValues("liquibase.default-schema=CUSTOMSCHEMA", "spring.datasource.generate-unique-name=true", - "spring.datasource.schema=classpath:/db/create-custom-schema.sql") - .applyTo(this.context); - this.context.register(CustomSchemaConfig.class); - this.context.refresh(); - assertThat(getEndpointBean().invoke()).hasSize(1); + "spring.datasource.schema=classpath:/db/create-custom-schema.sql"); + contextRunner.run((context) -> assertThat( + context.getBean(LiquibaseEndpoint.class).liquibaseReports()).hasSize(1)); } @Configuration @Import({ EmbeddedDataSourceConfiguration.class, LiquibaseAutoConfiguration.class }) public static class Config { - private final SpringLiquibase liquibase; - - public Config(SpringLiquibase liquibase) { - this.liquibase = liquibase; - } - @Bean - public LiquibaseEndpoint endpoint() { - return new LiquibaseEndpoint(this.liquibase); - } - - } - - @Configuration - @Import({ DataSourceAutoConfiguration.class, LiquibaseAutoConfiguration.class }) - public static class CustomSchemaConfig { - - private final SpringLiquibase liquibase; - - public CustomSchemaConfig(SpringLiquibase liquibase) { - this.liquibase = liquibase; - } - - @Bean - public LiquibaseEndpoint endpoint() { - return new LiquibaseEndpoint(this.liquibase); + public LiquibaseEndpoint endpoint(Map liquibases) { + return new LiquibaseEndpoint(liquibases); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java index 6a32926595..6146a5f51a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java @@ -24,12 +24,9 @@ import java.util.Set; import org.junit.Test; import org.springframework.boot.actuate.endpoint.LoggersEndpoint.LoggerLevels; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggingSystem; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -40,21 +37,20 @@ import static org.mockito.Mockito.verify; * Tests for {@link LoggersEndpoint}. * * @author Ben Hale + * @author Andy Wilkinson */ -public class LoggersEndpointTests extends AbstractEndpointTests { +public class LoggersEndpointTests { - public LoggersEndpointTests() { - super(Config.class, LoggersEndpoint.class, "loggers", "endpoints.loggers"); - } + private final LoggingSystem loggingSystem = mock(LoggingSystem.class); @Test @SuppressWarnings("unchecked") - public void invokeShouldReturnConfigurations() throws Exception { - given(getLoggingSystem().getLoggerConfigurations()).willReturn(Collections + public void loggersShouldReturnLoggerConfigurations() throws Exception { + given(this.loggingSystem.getLoggerConfigurations()).willReturn(Collections .singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); - given(getLoggingSystem().getSupportedLogLevels()) + given(this.loggingSystem.getSupportedLogLevels()) .willReturn(EnumSet.allOf(LogLevel.class)); - Map result = getEndpointBean().invoke(); + Map result = new LoggersEndpoint(this.loggingSystem).loggers(); Map loggers = (Map) result .get("loggers"); Set levels = (Set) result.get("levels"); @@ -66,44 +62,25 @@ public class LoggersEndpointTests extends AbstractEndpointTests } @Test - public void invokeWhenNameSpecifiedShouldReturnLevels() throws Exception { - given(getLoggingSystem().getLoggerConfiguration("ROOT")) + public void loggerLevelsWhenNameSpecifiedShouldReturnLevels() throws Exception { + given(this.loggingSystem.getLoggerConfiguration("ROOT")) .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); - LoggerLevels levels = getEndpointBean().invoke("ROOT"); + LoggerLevels levels = new LoggersEndpoint(this.loggingSystem) + .loggerLevels("ROOT"); assertThat(levels.getConfiguredLevel()).isNull(); assertThat(levels.getEffectiveLevel()).isEqualTo("DEBUG"); } @Test - public void setLogLevelShouldSetLevelOnLoggingSystem() throws Exception { - getEndpointBean().setLogLevel("ROOT", LogLevel.DEBUG); - verify(getLoggingSystem()).setLogLevel("ROOT", LogLevel.DEBUG); + public void configureLogLevelShouldSetLevelOnLoggingSystem() throws Exception { + new LoggersEndpoint(this.loggingSystem).configureLogLevel("ROOT", LogLevel.DEBUG); + verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); } @Test - public void setLogLevelToNull() { - getEndpointBean().setLogLevel("ROOT", null); - verify(getLoggingSystem()).setLogLevel("ROOT", null); - } - - private LoggingSystem getLoggingSystem() { - return this.context.getBean(LoggingSystem.class); - } - - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public LoggingSystem loggingSystem() { - return mock(LoggingSystem.class); - } - - @Bean - public LoggersEndpoint endpoint(LoggingSystem loggingSystem) { - return new LoggersEndpoint(loggingSystem); - } - + public void configureLogLevelWithNullSetsLevelOnLoggingSystemToNull() { + new LoggersEndpoint(this.loggingSystem).configureLogLevel("ROOT", null); + verify(this.loggingSystem).setLogLevel("ROOT", null); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java index 9cb821f6a4..041a852ccd 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java @@ -16,21 +16,15 @@ package org.springframework.boot.actuate.endpoint; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import org.junit.Test; import org.springframework.boot.actuate.metrics.Metric; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import static org.assertj.core.api.Assertions.assertThat; @@ -39,8 +33,9 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link MetricsEndpoint}. * * @author Phillip Webb + * @author Andy Wilkinson */ -public class MetricsEndpointTests extends AbstractEndpointTests { +public class MetricsEndpointTests { private Metric metric1 = new Metric<>("a", 1); @@ -48,27 +43,62 @@ public class MetricsEndpointTests extends AbstractEndpointTests private Metric metric3 = new Metric<>("c", 3); - public MetricsEndpointTests() { - super(Config.class, MetricsEndpoint.class, "metrics", "endpoints.metrics"); + @Test + public void basicMetrics() throws Exception { + MetricsEndpoint endpoint = new MetricsEndpoint( + Collections.singletonList(() -> Arrays.asList(new Metric("a", 5), + new Metric("b", 4)))); + Map metrics = endpoint.metrics(null); + assertThat(metrics.get("a")).isEqualTo(5); + assertThat(metrics.get("b")).isEqualTo(4); } @Test - public void invoke() throws Exception { - assertThat(getEndpointBean().invoke().get("a")).isEqualTo(0.5f); + public void metricsOrderingIsReflectedInOutput() { + List publicMetrics = Arrays.asList( + new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3), + new TestPublicMetrics(1, this.metric1)); + Map metrics = new MetricsEndpoint(publicMetrics).metrics(null); + assertThat(metrics.keySet()).containsExactly("a", "b", "c"); + assertThat(metrics).hasSize(3); } @Test - public void ordered() { - List publicMetrics = new ArrayList<>(); - publicMetrics - .add(new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3)); - publicMetrics.add(new TestPublicMetrics(1, this.metric1)); - Map metrics = new MetricsEndpoint(publicMetrics).invoke(); - Iterator> iterator = metrics.entrySet().iterator(); - assertThat(iterator.next().getKey()).isEqualTo("a"); - assertThat(iterator.next().getKey()).isEqualTo("b"); - assertThat(iterator.next().getKey()).isEqualTo("c"); - assertThat(iterator.hasNext()).isFalse(); + public void singleSelectedMetric() { + List publicMetrics = Arrays.asList( + new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3), + new TestPublicMetrics(1, this.metric1)); + Map selected = new MetricsEndpoint(publicMetrics) + .metricNamed("a"); + assertThat(selected).hasSize(1); + assertThat(selected).containsEntry("a", 1); + } + + @Test + public void multipleSelectedMetrics() { + List publicMetrics = Arrays.asList( + new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3), + new TestPublicMetrics(1, this.metric1)); + Map selected = new MetricsEndpoint(publicMetrics).metrics("[ab]"); + assertThat(selected).hasSize(2); + assertThat(selected).containsEntry("a", 1); + assertThat(selected).containsEntry("b", 2); + } + + @Test + public void noMetricMatchingName() { + List publicMetrics = Arrays.asList( + new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3), + new TestPublicMetrics(1, this.metric1)); + assertThat(new MetricsEndpoint(publicMetrics).metricNamed("z")).isNull(); + } + + @Test + public void noMetricMatchingPattern() { + List publicMetrics = Arrays.asList( + new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3), + new TestPublicMetrics(1, this.metric1)); + assertThat(new MetricsEndpoint(publicMetrics).metrics("[z]")).isEmpty(); } private static class TestPublicMetrics implements PublicMetrics, Ordered { @@ -94,16 +124,4 @@ public class MetricsEndpointTests extends AbstractEndpointTests } - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public MetricsEndpoint endpoint() { - Metric metric = new Metric<>("a", 0.5f); - return new MetricsEndpoint(() -> Collections.singleton(metric)); - } - - } - } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/NamePatternFilterTests.java similarity index 98% rename from spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilterTests.java rename to spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/NamePatternFilterTests.java index 582c973444..473e7ca6d7 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilterTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/NamePatternFilterTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint.mvc; +package org.springframework.boot.actuate.endpoint; import java.util.Map; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java index 29b251a17c..5c0d425359 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,17 @@ package org.springframework.boot.actuate.endpoint; -import java.util.Arrays; import java.util.Collections; import java.util.Map; import org.junit.Test; -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; -import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; +import org.springframework.boot.endpoint.EndpointInfo; +import org.springframework.boot.endpoint.EndpointOperationType; +import org.springframework.boot.endpoint.web.OperationRequestPredicate; +import org.springframework.boot.endpoint.web.WebEndpointHttpMethod; +import org.springframework.boot.endpoint.web.WebEndpointOperation; +import org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -54,7 +57,7 @@ public class RequestMappingEndpointTests { mapping.initApplicationContext(); this.endpoint.setHandlerMappings( Collections.singletonList(mapping)); - Map result = this.endpoint.invoke(); + Map result = this.endpoint.mappings(); assertThat(result).hasSize(1); @SuppressWarnings("unchecked") Map map = (Map) result.get("/foo"); @@ -70,7 +73,7 @@ public class RequestMappingEndpointTests { mapping.initApplicationContext(); context.getDefaultListableBeanFactory().registerSingleton("mapping", mapping); this.endpoint.setApplicationContext(context); - Map result = this.endpoint.invoke(); + Map result = this.endpoint.mappings(); assertThat(result).hasSize(1); @SuppressWarnings("unchecked") Map map = (Map) result.get("/foo"); @@ -82,7 +85,7 @@ public class RequestMappingEndpointTests { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( MappingConfiguration.class); this.endpoint.setApplicationContext(context); - Map result = this.endpoint.invoke(); + Map result = this.endpoint.mappings(); assertThat(result).hasSize(1); @SuppressWarnings("unchecked") Map map = (Map) result.get("/foo"); @@ -93,20 +96,17 @@ public class RequestMappingEndpointTests { @Test public void beanMethodMappings() { StaticApplicationContext context = new StaticApplicationContext(); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(new EndpointMvcAdapter(new DumpEndpoint()))); - mapping.setApplicationContext(new StaticApplicationContext()); - mapping.afterPropertiesSet(); - context.getDefaultListableBeanFactory().registerSingleton("mapping", mapping); + context.getDefaultListableBeanFactory().registerSingleton("mapping", + createHandlerMapping()); this.endpoint.setApplicationContext(context); - Map result = this.endpoint.invoke(); + Map result = this.endpoint.mappings(); assertThat(result).hasSize(2); assertThat(result.keySet()) - .filteredOn((key) -> key.contains("/dump || /dump.json")) + .filteredOn((key) -> key.contains("[/application/test]")) .hasOnlyOneElementSatisfying( (key) -> assertThat((Map) result.get(key)) .containsOnlyKeys("bean", "method")); - assertThat(result.keySet()).filteredOn((key) -> key.contains(" || /.json")) + assertThat(result.keySet()).filteredOn((key) -> key.contains("[/application]")) .hasOnlyOneElementSatisfying( (key) -> assertThat((Map) result.get(key)) .containsOnlyKeys("bean", "method")); @@ -115,25 +115,37 @@ public class RequestMappingEndpointTests { @SuppressWarnings("unchecked") @Test public void concreteMethodMappings() { - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(new EndpointMvcAdapter(new DumpEndpoint()))); - mapping.setApplicationContext(new StaticApplicationContext()); - mapping.afterPropertiesSet(); + WebEndpointServletHandlerMapping mapping = createHandlerMapping(); this.endpoint.setMethodMappings( Collections.>singletonList(mapping)); - Map result = this.endpoint.invoke(); + Map result = this.endpoint.mappings(); assertThat(result).hasSize(2); assertThat(result.keySet()) - .filteredOn((key) -> key.contains("/dump || /dump.json")) + .filteredOn((key) -> key.contains("[/application/test]")) .hasOnlyOneElementSatisfying( (key) -> assertThat((Map) result.get(key)) .containsOnlyKeys("method")); - assertThat(result.keySet()).filteredOn((key) -> key.contains(" || /.json")) + assertThat(result.keySet()).filteredOn((key) -> key.contains("[/application]")) .hasOnlyOneElementSatisfying( (key) -> assertThat((Map) result.get(key)) .containsOnlyKeys("method")); } + private WebEndpointServletHandlerMapping createHandlerMapping() { + OperationRequestPredicate requestPredicate = new OperationRequestPredicate("test", + WebEndpointHttpMethod.GET, Collections.singletonList("application/json"), + Collections.singletonList("application/json")); + WebEndpointOperation operation = new WebEndpointOperation( + EndpointOperationType.READ, (arguments) -> "Invoked", true, + requestPredicate, "test"); + WebEndpointServletHandlerMapping mapping = new WebEndpointServletHandlerMapping( + "application", Collections.singleton(new EndpointInfo<>("test", true, + Collections.singleton(operation)))); + mapping.setApplicationContext(new StaticApplicationContext()); + mapping.afterPropertiesSet(); + return mapping; + } + @Configuration protected static class MappingConfiguration { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownEndpointTests.java index ae7e8d7662..04fdfd4f5b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownEndpointTests.java @@ -24,8 +24,11 @@ import java.util.concurrent.TimeUnit; import org.junit.Test; -import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.ContextClosedEvent; @@ -39,41 +42,60 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Dave Syer * @author Andy Wilkinson */ -public class ShutdownEndpointTests extends AbstractEndpointTests { +public class ShutdownEndpointTests { - public ShutdownEndpointTests() { - super(Config.class, ShutdownEndpoint.class, "shutdown", "endpoints.shutdown"); - } - - @Override - public void isEnabledByDefault() throws Exception { - // Shutdown is dangerous so is disabled by default - assertThat(getEndpointBean().isEnabled()).isFalse(); + @Test + public void shutdown() throws Exception { + ApplicationContextRunner contexRunner = new ApplicationContextRunner() + .withUserConfiguration(EndpointConfig.class); + contexRunner.run((context) -> { + EndpointConfig config = context.getBean(EndpointConfig.class); + ClassLoader previousTccl = Thread.currentThread().getContextClassLoader(); + Map result; + Thread.currentThread().setContextClassLoader( + new URLClassLoader(new URL[0], getClass().getClassLoader())); + try { + result = context.getBean(ShutdownEndpoint.class).shutdown(); + } + finally { + Thread.currentThread().setContextClassLoader(previousTccl); + } + assertThat((String) result.get("message")).startsWith("Shutting down"); + assertThat(((ConfigurableApplicationContext) context).isActive()).isTrue(); + assertThat(config.latch.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(config.threadContextClassLoader) + .isEqualTo(getClass().getClassLoader()); + }); } @Test - public void invoke() throws Exception { - Config config = this.context.getBean(Config.class); - ClassLoader previousTccl = Thread.currentThread().getContextClassLoader(); - Map result; - Thread.currentThread().setContextClassLoader( - new URLClassLoader(new URL[0], getClass().getClassLoader())); - try { - result = getEndpointBean().invoke(); - } - finally { - Thread.currentThread().setContextClassLoader(previousTccl); - } - assertThat((String) result.get("message")).startsWith("Shutting down"); - assertThat(this.context.isActive()).isTrue(); - assertThat(config.latch.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(config.threadContextClassLoader) - .isEqualTo(getClass().getClassLoader()); + public void shutdownChild() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder( + EmptyConfig.class).child(EndpointConfig.class) + .web(WebApplicationType.NONE).run(); + CountDownLatch latch = context.getBean(EndpointConfig.class).latch; + assertThat((String) context.getBean(ShutdownEndpoint.class).shutdown() + .get("message")).startsWith("Shutting down"); + assertThat(context.isActive()).isTrue(); + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + } + + @Test + public void shutdownParent() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder( + EndpointConfig.class).child(EmptyConfig.class) + .web(WebApplicationType.NONE).run(); + CountDownLatch parentLatch = context.getBean(EndpointConfig.class).latch; + CountDownLatch childLatch = context.getBean(EmptyConfig.class).latch; + assertThat((String) context.getBean(ShutdownEndpoint.class).shutdown() + .get("message")).startsWith("Shutting down"); + assertThat(context.isActive()).isTrue(); + assertThat(parentLatch.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(childLatch.await(10, TimeUnit.SECONDS)).isTrue(); } @Configuration - @EnableConfigurationProperties - public static class Config { + public static class EndpointConfig { private final CountDownLatch latch = new CountDownLatch(1); @@ -88,11 +110,22 @@ public class ShutdownEndpointTests extends AbstractEndpointTests listener() { return (event) -> { - this.threadContextClassLoader = Thread.currentThread() + EndpointConfig.this.threadContextClassLoader = Thread.currentThread() .getContextClassLoader(); - this.latch.countDown(); + EndpointConfig.this.latch.countDown(); }; + } + } + + @Configuration + public static class EmptyConfig { + + private final CountDownLatch latch = new CountDownLatch(1); + + @Bean + public ApplicationListener listener() { + return (event) -> EmptyConfig.this.latch.countDown(); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownParentEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownParentEndpointTests.java deleted file mode 100644 index 2db08db4bb..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownParentEndpointTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.After; -import org.junit.Test; - -import org.springframework.boot.WebApplicationType; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.util.ApplicationContextTestUtils; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.ContextClosedEvent; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ShutdownEndpoint}. - * - * @author Dave Syer - */ -public class ShutdownParentEndpointTests { - - private ConfigurableApplicationContext context; - - @After - public void close() { - ApplicationContextTestUtils.closeAll(this.context); - } - - @Test - public void shutdownChild() throws Exception { - this.context = new SpringApplicationBuilder(Config.class).child(Empty.class) - .web(WebApplicationType.NONE).run(); - CountDownLatch latch = this.context.getBean(Config.class).latch; - assertThat((String) getEndpointBean().invoke().get("message")) - .startsWith("Shutting down"); - assertThat(this.context.isActive()).isTrue(); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - @Test - public void shutdownParent() throws Exception { - this.context = new SpringApplicationBuilder(Empty.class).child(Config.class) - .web(WebApplicationType.NONE).run(); - CountDownLatch latch = this.context.getBean(Config.class).latch; - assertThat((String) getEndpointBean().invoke().get("message")) - .startsWith("Shutting down"); - assertThat(this.context.isActive()).isTrue(); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - private ShutdownEndpoint getEndpointBean() { - return this.context.getBean(ShutdownEndpoint.class); - } - - @Configuration - @EnableConfigurationProperties - public static class Config { - - private CountDownLatch latch = new CountDownLatch(1); - - @Bean - public ShutdownEndpoint endpoint() { - ShutdownEndpoint endpoint = new ShutdownEndpoint(); - endpoint.setEnabled(true); - return endpoint; - } - - @Bean - public ApplicationListener listener() { - return (event) -> this.latch.countDown(); - - } - - } - - @Configuration - public static class Empty { - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpointTests.java new file mode 100644 index 0000000000..7e1258598a --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpointTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ThreadDumpEndpoint}. + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +public class ThreadDumpEndpointTests { + + @Test + public void dumpThreads() throws Exception { + assertThat(new ThreadDumpEndpoint().threadDump().size()).isGreaterThan(0); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/TraceEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/TraceEndpointTests.java index 517d5f1207..438fa066d0 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/TraceEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/TraceEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,6 @@ import org.junit.Test; import org.springframework.boot.actuate.trace.InMemoryTraceRepository; import org.springframework.boot.actuate.trace.Trace; import org.springframework.boot.actuate.trace.TraceRepository; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; @@ -33,30 +30,16 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link TraceEndpoint}. * * @author Phillip Webb + * @author Andy Wilkinson */ -public class TraceEndpointTests extends AbstractEndpointTests { - - public TraceEndpointTests() { - super(Config.class, TraceEndpoint.class, "trace", "endpoints.trace"); - } +public class TraceEndpointTests { @Test - public void invoke() throws Exception { - Trace trace = getEndpointBean().invoke().get(0); + public void trace() throws Exception { + TraceRepository repository = new InMemoryTraceRepository(); + repository.add(Collections.singletonMap("a", "b")); + Trace trace = new TraceEndpoint(repository).traces().get(0); assertThat(trace.getInfo().get("a")).isEqualTo("b"); } - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public TraceEndpoint endpoint() { - TraceRepository repository = new InMemoryTraceRepository(); - repository.add(Collections.singletonMap("a", "b")); - return new TraceEndpoint(repository); - } - - } - } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtensionTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtensionTests.java new file mode 100644 index 0000000000..4619a66081 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtensionTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.jmx; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.junit.Test; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AuditEventsJmxEndpointExtension}. + * + * @author Andy Wilkinson + */ +public class AuditEventsJmxEndpointExtensionTests { + + private final AuditEventRepository repository = mock(AuditEventRepository.class); + + private final AuditEventsJmxEndpointExtension extension = new AuditEventsJmxEndpointExtension( + new AuditEventsEndpoint(this.repository)); + + private final AuditEvent event = new AuditEvent("principal", "type", + Collections.singletonMap("a", "alpha")); + + @Test + public void eventsWithDateAfter() { + Date date = new Date(); + given(this.repository.find(null, date, null)) + .willReturn(Collections.singletonList(this.event)); + List result = this.extension.eventsWithDateAfter(date); + assertThat(result).isEqualTo(Collections.singletonList(this.event)); + } + + @Test + public void eventsWithPrincipalAndDateAfter() { + Date date = new Date(); + given(this.repository.find("Joan", date, null)) + .willReturn(Collections.singletonList(this.event)); + List result = this.extension.eventsWithPrincipalAndDateAfter("Joan", + date); + assertThat(result).isEqualTo(Collections.singletonList(this.event)); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java deleted file mode 100644 index 91136267fd..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.jmx; - -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import javax.management.MBeanException; -import javax.management.MBeanInfo; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import org.springframework.beans.MutablePropertyValues; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.LoggersEndpoint; -import org.springframework.boot.logging.logback.LogbackLoggingSystem; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.jmx.export.MBeanExporter; -import org.springframework.jmx.support.ObjectNameManager; -import org.springframework.util.ObjectUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; - -/** - * Tests for {@link EndpointMBeanExporter} - * - * @author Christian Dupuis - * @author Andy Wilkinson - * @author Stephane Nicoll - */ -public class EndpointMBeanExporterTests { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - GenericApplicationContext context = null; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void testRegistrationOfOneEndpoint() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - MBeanInfo mbeanInfo = mbeanExporter.getServer() - .getMBeanInfo(getObjectName("endpoint1", this.context)); - assertThat(mbeanInfo).isNotNull(); - assertThat(mbeanInfo.getOperations().length).isEqualTo(2); - assertThat(mbeanInfo.getAttributes().length).isEqualTo(2); - } - - @Test - public void testSkipRegistrationOfDisabledEndpoint() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - MutablePropertyValues mpv = new MutablePropertyValues(); - mpv.add("enabled", Boolean.FALSE); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class, null, mpv)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .isRegistered(getObjectName("endpoint1", this.context))).isFalse(); - } - - @Test - public void testRegistrationOfEnabledEndpoint() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - MutablePropertyValues mpv = new MutablePropertyValues(); - mpv.add("enabled", Boolean.TRUE); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class, null, mpv)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .isRegistered(getObjectName("endpoint1", this.context))).isTrue(); - } - - @Test - public void testRegistrationTwoEndpoints() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - this.context.registerBeanDefinition("endpoint2", - new RootBeanDefinition(TestEndpoint2.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .getMBeanInfo(getObjectName("endpoint1", this.context))).isNotNull(); - assertThat(mbeanExporter.getServer() - .getMBeanInfo(getObjectName("endpoint2", this.context))).isNotNull(); - } - - @Test - public void testRegistrationWithDifferentDomain() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class, null, - new MutablePropertyValues( - Collections.singletonMap("domain", "test-domain")))); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer().getMBeanInfo( - getObjectName("test-domain", "endpoint1", false, this.context))) - .isNotNull(); - } - - @Test - public void testRegistrationWithDifferentDomainAndIdentity() throws Exception { - Map properties = new HashMap<>(); - properties.put("domain", "test-domain"); - properties.put("ensureUniqueRuntimeObjectNames", true); - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class, null, - new MutablePropertyValues(properties))); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer().getMBeanInfo( - getObjectName("test-domain", "endpoint1", true, this.context))) - .isNotNull(); - } - - @Test - public void testRegistrationWithDifferentDomainAndIdentityAndStaticNames() - throws Exception { - Map properties = new HashMap<>(); - properties.put("domain", "test-domain"); - properties.put("ensureUniqueRuntimeObjectNames", true); - Properties staticNames = new Properties(); - staticNames.put("key1", "value1"); - staticNames.put("key2", "value2"); - properties.put("objectNameStaticProperties", staticNames); - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class, null, - new MutablePropertyValues(properties))); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer().getMBeanInfo(ObjectNameManager.getInstance( - getObjectName("test-domain", "endpoint1", true, this.context).toString() - + ",key1=value1,key2=value2"))).isNotNull(); - } - - @Test - public void testRegistrationWithParentContext() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - GenericApplicationContext parent = new GenericApplicationContext(); - this.context.setParent(parent); - parent.refresh(); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .getMBeanInfo(getObjectName("endpoint1", this.context))).isNotNull(); - parent.close(); - } - - @Test - public void jsonMapConversionWithDefaultObjectMapper() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(JsonMapConversionEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - Object response = mbeanExporter.getServer().invoke( - getObjectName("endpoint1", this.context), "getData", new Object[0], - new String[0]); - assertThat(response).isInstanceOf(Map.class); - assertThat(((Map) response).get("date")).isInstanceOf(Long.class); - } - - @Test - public void jsonMapConversionWithCustomObjectMapper() throws Exception { - this.context = new GenericApplicationContext(); - ConstructorArgumentValues constructorArgs = new ConstructorArgumentValues(); - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); - constructorArgs.addIndexedArgumentValue(0, objectMapper); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class, constructorArgs, - null)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(JsonMapConversionEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - Object response = mbeanExporter.getServer().invoke( - getObjectName("endpoint1", this.context), "getData", new Object[0], - new String[0]); - assertThat(response).isInstanceOf(Map.class); - assertThat(((Map) response).get("date")).isInstanceOf(String.class); - } - - @Test - public void jsonListConversion() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(JsonListConversionEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - Object response = mbeanExporter.getServer().invoke( - getObjectName("endpoint1", this.context), "getData", new Object[0], - new String[0]); - assertThat(response).isInstanceOf(List.class); - assertThat(((List) response).get(0)).isInstanceOf(Long.class); - } - - @Test - public void loggerEndpointLowerCaseLogLevel() throws Exception { - MBeanExporter mbeanExporter = registerLoggersEndpoint(); - Object response = mbeanExporter.getServer().invoke( - getObjectName("loggersEndpoint", this.context), "setLogLevel", - new Object[] { "com.example", "trace" }, - new String[] { String.class.getName(), String.class.getName() }); - assertThat(response).isNull(); - } - - @Test - public void loggerEndpointUnknownLogLevel() throws Exception { - MBeanExporter mbeanExporter = registerLoggersEndpoint(); - this.thrown.expect(MBeanException.class); - this.thrown.expectCause(hasMessage(containsString("No enum constant"))); - this.thrown.expectCause(hasMessage(containsString("LogLevel.INVALID"))); - mbeanExporter.getServer().invoke(getObjectName("loggersEndpoint", this.context), - "setLogLevel", new Object[] { "com.example", "invalid" }, - new String[] { String.class.getName(), String.class.getName() }); - } - - private MBeanExporter registerLoggersEndpoint() { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - RootBeanDefinition bd = new RootBeanDefinition(LoggersEndpoint.class); - ConstructorArgumentValues values = new ConstructorArgumentValues(); - values.addIndexedArgumentValue(0, - new LogbackLoggingSystem(getClass().getClassLoader())); - bd.setConstructorArgumentValues(values); - this.context.registerBeanDefinition("loggersEndpoint", bd); - this.context.refresh(); - return this.context.getBean(EndpointMBeanExporter.class); - } - - private ObjectName getObjectName(String beanKey, GenericApplicationContext context) - throws MalformedObjectNameException { - return getObjectName("org.springframework.boot", beanKey, false, context); - } - - private ObjectName getObjectName(String domain, String beanKey, - boolean includeIdentity, ApplicationContext applicationContext) - throws MalformedObjectNameException { - if (includeIdentity) { - return ObjectNameManager.getInstance(String.format( - "%s:type=Endpoint,name=%s,identity=%s", domain, beanKey, ObjectUtils - .getIdentityHexString(applicationContext.getBean(beanKey)))); - } - return ObjectNameManager - .getInstance(String.format("%s:type=Endpoint,name=%s", domain, beanKey)); - } - - public static class TestEndpoint extends AbstractEndpoint { - - public TestEndpoint() { - super("test"); - } - - @Override - public String invoke() { - return "hello world"; - } - - } - - public static class TestEndpoint2 extends TestEndpoint { - - } - - public static class JsonMapConversionEndpoint - extends AbstractEndpoint> { - - public JsonMapConversionEndpoint() { - super("json_map_conversion"); - } - - @Override - public Map invoke() { - Map result = new LinkedHashMap<>(); - result.put("date", new Date()); - return result; - } - - } - - public static class JsonListConversionEndpoint - extends AbstractEndpoint> { - - public JsonListConversionEndpoint() { - super("json_list_conversion"); - } - - @Override - public List invoke() { - return Arrays.asList(new Date()); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMappingTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMappingTests.java deleted file mode 100644 index 4f7a6e205c..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMappingTests.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; - -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.servlet.HandlerInterceptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link AbstractEndpointHandlerMapping}. - * - * @author Madhura Bhave - */ -public abstract class AbstractEndpointHandlerMappingTests { - - private final StaticApplicationContext context = new StaticApplicationContext(); - - @Test - public void securityInterceptorShouldBePresentForNonCorsRequest() throws Exception { - HandlerInterceptor securityInterceptor = mock(HandlerInterceptor.class); - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - AbstractEndpointHandlerMapping mapping = new TestEndpointHandlerMapping<>( - Collections.singletonList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.setSecurityInterceptor(securityInterceptor); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("POST", "/a")).getInterceptors()) - .contains(securityInterceptor); - } - - @Test - public void securityInterceptorIfNullShouldNotBeAdded() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - AbstractEndpointHandlerMapping mapping = new TestEndpointHandlerMapping<>( - Collections.singletonList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("POST", "/a")).getInterceptors()) - .hasSize(1); - } - - @Test - public void securityInterceptorShouldBePresentAfterCorsInterceptorForCorsRequest() - throws Exception { - HandlerInterceptor securityInterceptor = mock(HandlerInterceptor.class); - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - AbstractEndpointHandlerMapping mapping = new TestEndpointHandlerMapping<>( - Collections.singletonList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.setSecurityInterceptor(securityInterceptor); - mapping.afterPropertiesSet(); - MockHttpServletRequest request = request("POST", "/a"); - request.addHeader("Origin", "http://example.com"); - assertThat(mapping.getHandler(request).getInterceptors().length).isEqualTo(3); - assertThat(mapping.getHandler(request).getInterceptors()[2]) - .isEqualTo(securityInterceptor); - } - - @Test - public void pathNotMappedWhenGetPathReturnsNull() throws Exception { - TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); - TestActionEndpoint other = new TestActionEndpoint(new TestEndpoint("b")); - AbstractEndpointHandlerMapping mapping = new TestEndpointHandlerMapping<>( - Arrays.asList(endpoint, other)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandlerMethods()).hasSize(1); - assertThat(mapping.getHandler(request("GET", "/a"))).isNull(); - assertThat(mapping.getHandler(request("POST", "/b"))).isNotNull(); - } - - private MockHttpServletRequest request(String method, String requestURI) { - return new MockHttpServletRequest(method, requestURI); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } - - private static class TestActionEndpoint extends EndpointMvcAdapter { - - TestActionEndpoint(TestEndpoint delegate) { - super(delegate); - } - - @Override - @PostMapping - public Object invoke() { - return null; - } - - } - - private static class TestEndpointHandlerMapping - extends AbstractEndpointHandlerMapping { - - TestEndpointHandlerMapping(Collection endpoints) { - super(endpoints); - } - - @Override - protected String getPath(MvcEndpoint endpoint) { - if (endpoint instanceof TestActionEndpoint) { - return super.getPath(endpoint); - } - return null; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpointTests.java deleted file mode 100644 index 1964174bef..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpointTests.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.time.Instant; -import java.util.Collections; -import java.util.Date; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.audit.AuditEvent; -import org.springframework.boot.actuate.audit.AuditEventRepository; -import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link AuditEventsMvcEndpoint}. - * - * @author Vedran Pavic - */ -@SpringBootTest -@RunWith(SpringRunner.class) -@TestPropertySource(properties = "management.security.enabled=false") -public class AuditEventsMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(AuditEventsMvcEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @Test - public void contentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/auditevents").param("after", - "2016-11-01T10:00:00+0000")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void contentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform( - get("/application/auditevents").param("after", "2016-11-01T10:00:00+0000") - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()).andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void invokeWhenDisabledShouldReturnNotFoundStatus() throws Exception { - this.context.getBean(AuditEventsMvcEndpoint.class).setEnabled(false); - this.mvc.perform(get("/application/auditevents").param("after", - "2016-11-01T10:00:00+0000")).andExpect(status().isNotFound()); - } - - @Test - public void invokeFilterByDateAfter() throws Exception { - this.mvc.perform(get("/application/auditevents").param("after", - "2016-11-01T13:00:00+0000")).andExpect(status().isOk()) - .andExpect(content().string("{\"events\":[]}")); - } - - @Test - public void invokeFilterByPrincipalAndDateAfter() throws Exception { - this.mvc.perform(get("/application/auditevents").param("principal", "user") - .param("after", "2016-11-01T10:00:00+0000")) - .andExpect(status().isOk()) - .andExpect(content().string( - containsString("\"principal\":\"user\",\"type\":\"login\""))) - .andExpect(content().string(not(containsString("admin")))); - } - - @Test - public void invokeFilterByPrincipalAndDateAfterAndType() throws Exception { - this.mvc.perform(get("/application/auditevents").param("principal", "admin") - .param("after", "2016-11-01T10:00:00+0000").param("type", "logout")) - .andExpect(status().isOk()) - .andExpect(content().string( - containsString("\"principal\":\"admin\",\"type\":\"logout\""))) - .andExpect(content().string(not(containsString("login")))); - } - - @Test - public void invokeFilterWithoutDateAfterReturnBadRequestStatus() throws Exception { - this.mvc.perform(get("/application/auditevents")) - .andExpect(status().isBadRequest()); - } - - @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - protected static class TestConfiguration { - - @Bean - public AuditEventRepository auditEventsRepository() { - AuditEventRepository repository = new InMemoryAuditEventRepository(3); - repository.add(createEvent("2016-11-01T11:00:00Z", "admin", "login")); - repository.add(createEvent("2016-11-01T12:00:00Z", "admin", "logout")); - repository.add(createEvent("2016-11-01T12:00:00Z", "user", "login")); - return repository; - } - - private AuditEvent createEvent(String instant, String principal, String type) { - return new AuditEvent(Date.from(Instant.parse(instant)), principal, type, - Collections.emptyMap()); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java deleted file mode 100644 index 74687fc841..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.lang.reflect.Method; -import java.util.Arrays; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.HttpRequestMethodNotSupportedException; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.method.HandlerMethod; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EndpointHandlerMapping}. - * - * @author Phillip Webb - * @author Dave Syer - */ -public class EndpointHandlerMappingTests extends AbstractEndpointHandlerMappingTests { - - private final StaticApplicationContext context = new StaticApplicationContext(); - - private Method method; - - @Before - public void init() throws Exception { - this.method = ReflectionUtils.findMethod(TestMvcEndpoint.class, "invoke"); - } - - @Test - public void withoutPrefix() throws Exception { - TestMvcEndpoint endpointA = new TestMvcEndpoint(new TestEndpoint("a")); - TestMvcEndpoint endpointB = new TestMvcEndpoint(new TestEndpoint("b")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpointA, endpointB)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("GET", "/a")).getHandler()) - .isEqualTo(new HandlerMethod(endpointA, this.method)); - assertThat(mapping.getHandler(request("GET", "/b")).getHandler()) - .isEqualTo(new HandlerMethod(endpointB, this.method)); - assertThat(mapping.getHandler(request("GET", "/c"))).isNull(); - assertThat((((HandlerMethod) mapping.getHandler(request("GET", "/")).getHandler()) - .getMethod().getName())).isEqualTo("links"); - } - - @Test - public void withPrefix() throws Exception { - TestMvcEndpoint endpointA = new TestMvcEndpoint(new TestEndpoint("a")); - TestMvcEndpoint endpointB = new TestMvcEndpoint(new TestEndpoint("b")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpointA, endpointB)); - mapping.setApplicationContext(this.context); - mapping.setPrefix("/a"); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a/a")) - .getHandler()).isEqualTo(new HandlerMethod(endpointA, this.method)); - assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a/b")) - .getHandler()).isEqualTo(new HandlerMethod(endpointB, this.method)); - assertThat( - (((HandlerMethod) mapping.getHandler(request("GET", "/a")).getHandler()) - .getMethod().getName())).isEqualTo("links"); - assertThat( - (((HandlerMethod) mapping.getHandler(request("GET", "/a/")).getHandler()) - .getMethod().getName())).isEqualTo("links"); - } - - @Test(expected = HttpRequestMethodNotSupportedException.class) - public void onlyGetHttpMethodForNonActionEndpoints() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("GET", "/a"))).isNotNull(); - assertThat(mapping.getHandler(request("POST", "/a"))).isNull(); - } - - @Test - public void postHttpMethodForActionEndpoints() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("POST", "/a"))).isNotNull(); - } - - @Test(expected = HttpRequestMethodNotSupportedException.class) - public void onlyPostHttpMethodForActionEndpoints() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("POST", "/a"))).isNotNull(); - assertThat(mapping.getHandler(request("GET", "/a"))).isNull(); - } - - @Test - public void disabled() throws Exception { - TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpoint)); - mapping.setDisabled(true); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("GET", "/a"))).isNull(); - } - - @Test - public void duplicatePath() throws Exception { - TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); - TestActionEndpoint other = new TestActionEndpoint(new TestEndpoint("a")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpoint, other)); - mapping.setDisabled(true); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("GET", "/a"))).isNull(); - assertThat(mapping.getHandler(request("POST", "/a"))).isNull(); - } - - private MockHttpServletRequest request(String method, String requestURI) { - return new MockHttpServletRequest(method, requestURI); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } - - private static class TestActionEndpoint extends EndpointMvcAdapter { - - TestActionEndpoint(TestEndpoint delegate) { - super(delegate); - } - - @Override - @PostMapping - public Object invoke() { - return null; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java deleted file mode 100644 index 0daf978817..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.MutablePropertySources; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link EnvironmentMvcEndpoint} - * - * @author Dave Syer - * @author Andy Wilkinson - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@TestPropertySource(properties = "management.security.enabled=false") -@DirtiesContext -public class EnvironmentMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(EnvironmentEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - TestPropertyValues.of("foo:bar", "fool:baz") - .applyTo((ConfigurableApplicationContext) this.context); - } - - @Test - public void homeContentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/env")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void homeContentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(get("/application/env").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void subContentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/env/foo")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void subContentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(get("/application/env/foo").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void home() throws Exception { - this.mvc.perform(get("/application/env")).andExpect(status().isOk()) - .andExpect(content().string(containsString("systemProperties"))); - } - - @Test - public void sub() throws Exception { - this.mvc.perform(get("/application/env/foo")).andExpect(status().isOk()) - .andExpect(content().string("{\"foo\":\"bar\"}")); - } - - @Test - public void subWhenDisabled() throws Exception { - this.context.getBean(EnvironmentEndpoint.class).setEnabled(false); - this.mvc.perform(get("/application/env/foo")).andExpect(status().isNotFound()); - } - - @Test - public void regex() throws Exception { - Map map = new HashMap<>(); - map.put("food", null); - ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources() - .addFirst(new MapPropertySource("null-value", map)); - this.mvc.perform(get("/application/env/foo.*")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"foo\":\"bar\""))) - .andExpect(content().string(containsString("\"fool\":\"baz\""))); - } - - @Test - public void nestedPathWhenPlaceholderCannotBeResolvedShouldReturnUnresolvedProperty() - throws Exception { - Map map = new HashMap<>(); - map.put("my.foo", "${my.bar}"); - ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources() - .addFirst(new MapPropertySource("unresolved-placeholder", map)); - this.mvc.perform(get("/application/env/my.foo")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"my.foo\":\"${my.bar}\""))); - } - - @Test - public void nestedPathWithSensitivePlaceholderShouldSanitize() throws Exception { - Map map = new HashMap<>(); - map.put("my.foo", "${my.password}"); - map.put("my.password", "hello"); - ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources() - .addFirst(new MapPropertySource("placeholder", map)); - this.mvc.perform(get("/application/env/my.foo")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"my.foo\":\"******\""))); - } - - @Test - public void nestedPathMatchedByRegexWhenPlaceholderCannotBeResolvedShouldReturnUnresolvedProperty() - throws Exception { - Map map = new HashMap<>(); - map.put("my.foo", "${my.bar}"); - ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources() - .addFirst(new MapPropertySource("unresolved-placeholder", map)); - this.mvc.perform(get("/application/env/my.*")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"my.foo\":\"${my.bar}\""))); - } - - @Test - public void nestedPathMatchedByRegexWithSensitivePlaceholderShouldSanitize() - throws Exception { - Map map = new HashMap<>(); - map.put("my.foo", "${my.password}"); - map.put("my.password", "hello"); - ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources() - .addFirst(new MapPropertySource("placeholder", map)); - this.mvc.perform(get("/application/env/my.*")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"my.foo\":\"******\""))); - } - - @Test - public void propertyWithTypeOtherThanStringShouldNotFail() throws Exception { - MutablePropertySources propertySources = ((ConfigurableEnvironment) this.context - .getEnvironment()).getPropertySources(); - Map source = new HashMap<>(); - source.put("foo", Collections.singletonMap("bar", "baz")); - propertySources.addFirst(new MapPropertySource("test", source)); - this.mvc.perform(get("/application/env/foo.*")).andExpect(status().isOk()) - .andExpect(content().string("{\"foo\":{\"bar\":\"baz\"}}")); - } - - @Configuration - @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, AuditAutoConfiguration.class }) - public static class TestConfiguration { - - @Bean - public EnvironmentEndpoint endpoint() { - return new EnvironmentEndpoint(); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java deleted file mode 100644 index 998e5f7584..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import javax.servlet.http.HttpServletRequest; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Status; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link HealthMvcEndpoint}. - * - * @author Christian Dupuis - * @author Dave Syer - * @author Andy Wilkinson - * @author EddĂș MelĂ©ndez - * @author Madhura Bhave - */ -public class HealthMvcEndpointTests { - - private static final List SECURITY_ROLES = new ArrayList<>( - Arrays.asList("HERO")); - - private HttpServletRequest request = new MockHttpServletRequest(); - - private HealthEndpoint endpoint = null; - - private HealthMvcEndpoint mvc = null; - - private HttpServletRequest defaultUser = createAuthenticationRequest("ROLE_ACTUATOR"); - - private HttpServletRequest hero = createAuthenticationRequest("HERO"); - - private HttpServletRequest createAuthenticationRequest(String role) { - MockServletContext servletContext = new MockServletContext(); - servletContext.declareRoles(role); - return new MockHttpServletRequest(servletContext); - } - - @Before - public void init() { - this.endpoint = mock(HealthEndpoint.class); - given(this.endpoint.isEnabled()).willReturn(true); - this.mvc = new HealthMvcEndpoint(this.endpoint); - } - - @Test - public void up() { - given(this.endpoint.invoke()).willReturn(new Health.Builder().up().build()); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - } - - @SuppressWarnings("unchecked") - @Test - public void down() { - given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof ResponseEntity).isTrue(); - ResponseEntity response = (ResponseEntity) result; - assertThat(response.getBody().getStatus() == Status.DOWN).isTrue(); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); - } - - @Test - @SuppressWarnings("unchecked") - public void customMapping() { - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().status("OK").build()); - this.mvc.setStatusMapping( - Collections.singletonMap("OK", HttpStatus.INTERNAL_SERVER_ERROR)); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof ResponseEntity).isTrue(); - ResponseEntity response = (ResponseEntity) result; - assertThat(response.getBody().getStatus().equals(new Status("OK"))).isTrue(); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); - } - - @Test - @SuppressWarnings("unchecked") - public void customMappingWithRelaxedName() { - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().outOfService().build()); - this.mvc.setStatusMapping(Collections.singletonMap("out-OF-serVice", - HttpStatus.INTERNAL_SERVER_ERROR)); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof ResponseEntity).isTrue(); - ResponseEntity response = (ResponseEntity) result; - assertThat(response.getBody().getStatus().equals(Status.OUT_OF_SERVICE)).isTrue(); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); - } - - @Test - public void presenceOfRightRoleShouldExposeDetails() { - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void managementSecurityDisabledShouldExposeDetails() throws Exception { - this.mvc = new HealthMvcEndpoint(this.endpoint, false); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void rightRoleNotPresentShouldNotExposeDetails() { - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.hero, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isNull(); - } - - @Test - public void rightAuthorityPresentShouldExposeDetails() throws Exception { - this.mvc = new HealthMvcEndpoint(this.endpoint, true, SECURITY_ROLES); - Authentication principal = mock(Authentication.class); - Set authorities = Collections - .singleton(new SimpleGrantedAuthority("HERO")); - doReturn(authorities).when(principal).getAuthorities(); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, principal); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void customRolePresentShouldExposeDetails() { - this.mvc = new HealthMvcEndpoint(this.endpoint, true, SECURITY_ROLES); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.hero, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void customRoleShouldNotExposeDetailsForDefaultRole() { - this.mvc = new HealthMvcEndpoint(this.endpoint, true, SECURITY_ROLES); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isNull(); - } - - @Test - public void customRoleFromListShouldExposeDetails() { - // gh-8314 - this.mvc = new HealthMvcEndpoint(this.endpoint, true, - Arrays.asList("HERO", "USER")); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.hero, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void customRoleFromListShouldNotExposeDetailsForDefaultRole() { - // gh-8314 - this.mvc = new HealthMvcEndpoint(this.endpoint, true, - Arrays.asList("HERO", "USER")); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isNull(); - } - - @Test - public void healthIsCached() { - given(this.endpoint.getTimeToLive()).willReturn(10000L); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, null); - assertThat(result instanceof Health).isTrue(); - Health health = (Health) result; - assertThat(health.getStatus() == Status.UP).isTrue(); - assertThat(health.getDetails()).hasSize(1); - assertThat(health.getDetails().get("foo")).isEqualTo("bar"); - given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); - result = this.mvc.invoke(this.request, null); // insecure now - assertThat(result instanceof Health).isTrue(); - health = (Health) result; - // so the result is cached - assertThat(health.getStatus() == Status.UP).isTrue(); - // but the details are hidden - assertThat(health.getDetails()).isEmpty(); - } - - @Test - public void noCachingWhenTimeToLiveIsZero() { - given(this.endpoint.getTimeToLive()).willReturn(0L); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); - result = this.mvc.invoke(this.request, null); - @SuppressWarnings("unchecked") - Health health = ((ResponseEntity) result).getBody(); - assertThat(health.getStatus() == Status.DOWN).isTrue(); - } - - @Test - public void newValueIsReturnedOnceTtlExpires() throws InterruptedException { - given(this.endpoint.getTimeToLive()).willReturn(50L); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - Thread.sleep(100); - given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); - result = this.mvc.invoke(this.request, null); - @SuppressWarnings("unchecked") - Health health = ((ResponseEntity) result).getBody(); - assertThat(health.getStatus() == Status.DOWN).isTrue(); - } -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointSecureOptionsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointSecureOptionsTests.java deleted file mode 100644 index 3744951a22..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointSecureOptionsTests.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.util.FileCopyUtils; -import org.springframework.web.context.WebApplicationContext; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link HeapdumpMvcEndpoint} OPTIONS call with security. - * - * @author Phillip Webb - */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class HeapdumpMvcEndpointSecureOptionsTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Autowired - private TestHeapdumpMvcEndpoint endpoint; - - @Before - public void setup() { - this.context.getBean(HeapdumpMvcEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @After - public void reset() { - this.endpoint.reset(); - } - - @Test - public void invokeOptionsShouldReturnSize() throws Exception { - this.mvc.perform(options("/application/heapdump")).andExpect(status().isOk()); - } - - @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - public static class TestConfiguration { - - @Bean - public HeapdumpMvcEndpoint endpoint() { - return new TestHeapdumpMvcEndpoint(); - } - - } - - private static class TestHeapdumpMvcEndpoint extends HeapdumpMvcEndpoint { - - private boolean available; - - private boolean locked; - - private String heapDump; - - TestHeapdumpMvcEndpoint() { - super(TimeUnit.SECONDS.toMillis(1)); - reset(); - } - - public void reset() { - this.available = true; - this.locked = false; - this.heapDump = "HEAPDUMP"; - } - - @Override - protected HeapDumper createHeapDumper() { - return (file, live) -> { - if (!TestHeapdumpMvcEndpoint.this.available) { - throw new HeapDumperUnavailableException("Not available", null); - } - if (TestHeapdumpMvcEndpoint.this.locked) { - throw new InterruptedException(); - } - if (file.exists()) { - throw new IOException("File exists"); - } - FileCopyUtils.copy(this.heapDump.getBytes(), file); - }; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java deleted file mode 100644 index 3f3c87bfb7..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; - -import org.fusesource.hawtbuf.ByteArrayInputStream; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.util.FileCopyUtils; -import org.springframework.web.context.WebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link HeapdumpMvcEndpoint}. - * - * @author Phillip Webb - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@TestPropertySource(properties = "management.security.enabled=false") -public class HeapdumpMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Autowired - private TestHeapdumpMvcEndpoint endpoint; - - @Before - public void setup() { - this.context.getBean(HeapdumpMvcEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @After - public void reset() { - this.endpoint.reset(); - } - - @Test - public void invokeWhenDisabledShouldReturnNotFoundStatus() throws Exception { - this.endpoint.setEnabled(false); - this.mvc.perform(get("/application/heapdump")).andExpect(status().isNotFound()); - } - - @Test - public void invokeWhenNotAvailableShouldReturnServiceUnavailableStatus() - throws Exception { - this.endpoint.setAvailable(false); - this.mvc.perform(get("/application/heapdump")) - .andExpect(status().isServiceUnavailable()); - } - - @Test - public void invokeWhenLockedShouldReturnTooManyRequestsStatus() throws Exception { - this.endpoint.setLocked(true); - this.mvc.perform(get("/application/heapdump")) - .andExpect(status().isTooManyRequests()); - assertThat(Thread.interrupted()).isTrue(); - } - - @Test - public void invokeShouldReturnGzipContent() throws Exception { - MvcResult result = this.mvc.perform(get("/application/heapdump")) - .andExpect(status().isOk()).andReturn(); - byte[] bytes = result.getResponse().getContentAsByteArray(); - GZIPInputStream stream = new GZIPInputStream(new ByteArrayInputStream(bytes)); - byte[] uncompressed = FileCopyUtils.copyToByteArray(stream); - assertThat(uncompressed).isEqualTo("HEAPDUMP".getBytes()); - } - - @Test - public void invokeOptionsShouldReturnSize() throws Exception { - this.mvc.perform(options("/application/heapdump")).andExpect(status().isOk()); - } - - @Import({ JacksonAutoConfiguration.class, AuditAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - public static class TestConfiguration { - - @Bean - public HeapdumpMvcEndpoint endpoint() { - return new TestHeapdumpMvcEndpoint(); - } - - } - - private static class TestHeapdumpMvcEndpoint extends HeapdumpMvcEndpoint { - - private boolean available; - - private boolean locked; - - private String heapDump; - - TestHeapdumpMvcEndpoint() { - super(TimeUnit.SECONDS.toMillis(1)); - reset(); - } - - public void reset() { - this.available = true; - this.locked = false; - this.heapDump = "HEAPDUMP"; - } - - @Override - protected HeapDumper createHeapDumper() { - return (file, live) -> { - if (!TestHeapdumpMvcEndpoint.this.available) { - throw new HeapDumperUnavailableException("Not available", null); - } - if (TestHeapdumpMvcEndpoint.this.locked) { - throw new InterruptedException(); - } - if (file.exists()) { - throw new IOException("File exists"); - } - FileCopyUtils.copy(this.heapDump.getBytes(), file); - }; - } - - public void setAvailable(boolean available) { - this.available = available; - } - - public void setLocked(boolean locked) { - this.locked = locked; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java deleted file mode 100644 index 96bb0a523d..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.InfoEndpoint; -import org.springframework.boot.actuate.info.InfoContributor; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link InfoEndpoint} with {@link MockMvc}. - * - * @author Meang Akira Tanaka - * @author Stephane Nicoll - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@TestPropertySource(properties = { "info.app.name=MyService", - "management.security.enabled=false" }) -public class InfoMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(InfoEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @Test - public void home() throws Exception { - this.mvc.perform(get("/application/info")).andExpect(status().isOk()) - .andExpect(content().string(containsString( - "\"beanName1\":{\"key11\":\"value11\",\"key12\":\"value12\"}"))) - .andExpect(content().string(containsString( - "\"beanName2\":{\"key21\":\"value21\",\"key22\":\"value22\"}"))); - } - - @Test - public void contentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/info")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void contentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(get("/application/info").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Import({ JacksonAutoConfiguration.class, AuditAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - public static class TestConfiguration { - - @Bean - public InfoEndpoint endpoint() { - return new InfoEndpoint(Arrays.asList(beanName1(), beanName2())); - } - - @Bean - public InfoContributor beanName1() { - return (builder) -> { - Map content = new LinkedHashMap<>(); - content.put("key11", "value11"); - content.put("key12", "value12"); - builder.withDetail("beanName1", content); - }; - } - - @Bean - public InfoContributor beanName2() { - return (builder) -> { - Map content = new LinkedHashMap<>(); - content.put("key21", "value21"); - content.put("key22", "value22"); - builder.withDetail("beanName2", content); - }; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithNoInfoContributorsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithNoInfoContributorsTests.java deleted file mode 100644 index f13413ed0b..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithNoInfoContributorsTests.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Collections; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.InfoEndpoint; -import org.springframework.boot.actuate.info.InfoContributor; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link InfoEndpoint} with {@link MockMvc} when there are no - * {@link InfoContributor InfoContributors}. - * - * @author Meang Akira Tanaka - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@TestPropertySource(properties = "management.security.enabled=false") -public class InfoMvcEndpointWithNoInfoContributorsTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(InfoEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @Test - public void home() throws Exception { - this.mvc.perform(get("/application/info")).andExpect(status().isOk()); - } - - @Import({ JacksonAutoConfiguration.class, AuditAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - public static class TestConfiguration { - - @Bean - public InfoEndpoint endpoint() { - return new InfoEndpoint(Collections.emptyList()); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpointTests.java deleted file mode 100644 index 772b1ba7bc..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpointTests.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.io.File; -import java.io.IOException; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.mock.env.MockEnvironment; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.util.FileCopyUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link LogFileMvcEndpoint}. - * - * @author Johannes Edmeier - * @author Phillip Webb - */ -public class LogFileMvcEndpointTests { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - private LogFileMvcEndpoint mvc; - - private MockEnvironment environment; - - private File logFile; - - @Before - public void before() throws IOException { - this.logFile = this.temp.newFile(); - FileCopyUtils.copy("--TEST--".getBytes(), this.logFile); - this.environment = new MockEnvironment(); - this.mvc = new LogFileMvcEndpoint(); - this.mvc.setEnvironment(this.environment); - } - - @Test - public void notAvailableWithoutLogFile() throws Exception { - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest( - HttpMethod.HEAD.name(), "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); - } - - @Test - public void notAvailableWithMissingLogFile() throws Exception { - this.environment.setProperty("logging.file", "no_test.log"); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest( - HttpMethod.HEAD.name(), "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); - } - - @Test - public void availableWithLogFile() throws Exception { - this.environment.setProperty("logging.file", this.logFile.getAbsolutePath()); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest( - HttpMethod.HEAD.name(), "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - } - - @Test - public void notAvailableIfDisabled() throws Exception { - this.environment.setProperty("logging.file", this.logFile.getAbsolutePath()); - this.mvc.setEnabled(false); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest( - HttpMethod.HEAD.name(), "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); - } - - @Test - public void invokeGetsContent() throws Exception { - this.environment.setProperty("logging.file", this.logFile.getAbsolutePath()); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), - "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(response.getContentAsString()).isEqualTo("--TEST--"); - } - - @Test - public void invokeGetsContentExternalFile() throws Exception { - this.mvc.setExternalFile(this.logFile); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), - "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat("--TEST--").isEqualTo(response.getContentAsString()); - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java deleted file mode 100644 index d28a5adc50..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.Collections; -import java.util.EnumSet; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.LoggersEndpoint; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.logging.LogLevel; -import org.springframework.boot.logging.LoggerConfiguration; -import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link LoggersMvcEndpoint}. - * - * @author Ben Hale - * @author Phillip Webb - * @author EddĂș MelĂ©ndez - * @author Stephane Nicoll - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@TestPropertySource(properties = "management.security.enabled=false") -public class LoggersMvcEndpointTests { - - private static final String PATH = "/application/loggers"; - - @Autowired - private WebApplicationContext context; - - @Autowired - private LoggingSystem loggingSystem; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(LoggersEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context) - .alwaysDo(MockMvcResultHandlers.print()).build(); - } - - @Before - @After - public void resetMocks() { - Mockito.reset(this.loggingSystem); - given(this.loggingSystem.getSupportedLogLevels()) - .willReturn(EnumSet.allOf(LogLevel.class)); - } - - @Test - public void getLoggerShouldReturnAllLoggerConfigurations() throws Exception { - given(this.loggingSystem.getLoggerConfigurations()).willReturn(Collections - .singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); - String expected = "{\"levels\":[\"OFF\",\"FATAL\",\"ERROR\",\"WARN\",\"INFO\",\"DEBUG\",\"TRACE\"]," - + "\"loggers\":{\"ROOT\":{\"configuredLevel\":null,\"effectiveLevel\":\"DEBUG\"}}}"; - this.mvc.perform(get(PATH + "")).andExpect(status().isOk()) - .andExpect(content().json(expected)); - } - - @Test - public void getLoggersWhenDisabledShouldReturnNotFound() throws Exception { - this.context.getBean(LoggersEndpoint.class).setEnabled(false); - this.mvc.perform(get(PATH + "")).andExpect(status().isNotFound()); - } - - @Test - public void getLoggerShouldReturnLogLevels() throws Exception { - given(this.loggingSystem.getLoggerConfiguration("ROOT")) - .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); - this.mvc.perform(get(PATH + "/ROOT")).andExpect(status().isOk()) - .andExpect(content().string(equalTo( - "{\"configuredLevel\":null,\"effectiveLevel\":\"DEBUG\"}"))); - } - - @Test - public void getLoggersRootWhenDisabledShouldReturnNotFound() throws Exception { - this.context.getBean(LoggersEndpoint.class).setEnabled(false); - this.mvc.perform(get(PATH + "/ROOT")).andExpect(status().isNotFound()); - } - - @Test - public void getLoggersWhenLoggerNotFoundShouldReturnNotFound() throws Exception { - this.mvc.perform(get(PATH + "/com.does.not.exist")) - .andExpect(status().isNotFound()); - } - - @Test - public void contentTypeForGetDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get(PATH + "")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void contentTypeForGetCanBeApplicationJson() throws Exception { - this.mvc.perform(get(PATH + "").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void setLoggerUsingApplicationJsonShouldSetLogLevel() throws Exception { - this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{\"configuredLevel\":\"debug\"}")) - .andExpect(status().isNoContent()); - verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); - } - - @Test - public void setLoggerUsingActuatorV2JsonShouldSetLogLevel() throws Exception { - this.mvc.perform(post(PATH + "/ROOT") - .contentType(ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON) - .content("{\"configuredLevel\":\"debug\"}")) - .andExpect(status().isNoContent()); - verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); - } - - @Test - public void setLoggerWhenDisabledShouldReturnNotFound() throws Exception { - this.context.getBean(LoggersEndpoint.class).setEnabled(false); - this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{\"configuredLevel\":\"DEBUG\"}")) - .andExpect(status().isNotFound()); - verifyZeroInteractions(this.loggingSystem); - } - - @Test - public void setLoggerWithWrongLogLevel() throws Exception { - this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{\"configuredLevel\":\"other\"}")) - .andExpect(status().is4xxClientError()); - verifyZeroInteractions(this.loggingSystem); - } - - @Test - public void setLoggerWithNullLogLevel() throws Exception { - this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{\"configuredLevel\": null}")) - .andExpect(status().isNoContent()); - verify(this.loggingSystem).setLogLevel("ROOT", null); - } - - @Test - public void setLoggerWithNoLogLevel() throws Exception { - this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{}")).andExpect(status().isNoContent()); - verify(this.loggingSystem).setLogLevel("ROOT", null); - } - - @Test - public void logLevelForLoggerWithNameThatCouldBeMistakenForAPathExtension() - throws Exception { - given(this.loggingSystem.getLoggerConfiguration("com.png")) - .willReturn(new LoggerConfiguration("com.png", null, LogLevel.DEBUG)); - this.mvc.perform(get(PATH + "/com.png")).andExpect(status().isOk()) - .andExpect(content().string(equalTo( - "{\"configuredLevel\":null,\"effectiveLevel\":\"DEBUG\"}"))); - } - - @Configuration - @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - public static class TestConfiguration { - - @Bean - public LoggingSystem loggingSystem() { - return mock(LoggingSystem.class); - } - - @Bean - public LoggersEndpoint endpoint(LoggingSystem loggingSystem) { - return new LoggersEndpoint(loggingSystem); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java deleted file mode 100644 index 206a896790..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.ArrayList; -import java.util.Collections; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.MetricsEndpoint; -import org.springframework.boot.actuate.metrics.Metric; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link MetricsMvcEndpoint} - * - * @author Andy Wilkinson - * @author Sergei Egorov - */ -@RunWith(SpringRunner.class) -@DirtiesContext -@SpringBootTest -@TestPropertySource(properties = "management.security.enabled=false") -public class MetricsMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(MetricsEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @Test - public void home() throws Exception { - this.mvc.perform(get("/application/metrics")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"foo\":1"))); - } - - @Test - public void homeContentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/metrics")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void homeContentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(get("/application/metrics").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void specificMetricContentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/metrics/foo")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void specificMetricContentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(get("/application/metrics/foo").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void homeWhenDisabled() throws Exception { - this.context.getBean(MetricsEndpoint.class).setEnabled(false); - this.mvc.perform(get("/application/metrics")).andExpect(status().isNotFound()); - } - - @Test - public void specificMetric() throws Exception { - this.mvc.perform(get("/application/metrics/foo")).andExpect(status().isOk()) - .andExpect(content().string(equalTo("{\"foo\":1}"))); - } - - @Test - public void specificMetricWithNameThatCouldBeMistakenForAPathExtension() - throws Exception { - this.mvc.perform(get("/application/metrics/bar.png")).andExpect(status().isOk()) - .andExpect(content().string(equalTo("{\"bar.png\":1}"))); - } - - @Test - public void specificMetricWhenDisabled() throws Exception { - this.context.getBean(MetricsEndpoint.class).setEnabled(false); - this.mvc.perform(get("/application/metrics/foo")) - .andExpect(status().isNotFound()); - } - - @Test - public void specificMetricThatDoesNotExist() throws Exception { - this.mvc.perform(get("/application/metrics/bar")) - .andExpect(status().isNotFound()); - } - - @Test - public void regexAll() throws Exception { - String expected = "\"foo\":1,\"bar.png\":1,\"group1.a\":1,\"group1.b\":1," - + "\"group2.a\":1,\"group2_a\":1"; - this.mvc.perform(get("/application/metrics/.*")).andExpect(status().isOk()) - .andExpect(content().string(containsString(expected))); - } - - @Test - public void regexGroupDot() throws Exception { - String expected = "\"group1.a\":1,\"group1.b\":1,\"group2.a\":1"; - this.mvc.perform(get("/application/metrics/group[0-9]+\\..*")) - .andExpect(status().isOk()) - .andExpect(content().string(containsString(expected))); - } - - @Test - public void regexGroup1() throws Exception { - String expected = "\"group1.a\":1,\"group1.b\":1"; - this.mvc.perform(get("/application/metrics/group1\\..*")) - .andExpect(status().isOk()) - .andExpect(content().string(containsString(expected))); - } - - @Test - public void specificMetricWithDot() throws Exception { - this.mvc.perform(get("/application/metrics/group2.a")).andExpect(status().isOk()) - .andExpect(content().string(containsString("1"))); - } - - @Import({ JacksonAutoConfiguration.class, AuditAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - public static class TestConfiguration { - - @Bean - public MetricsEndpoint endpoint() { - return new MetricsEndpoint(() -> { - ArrayList> metrics = new ArrayList<>(); - metrics.add(new Metric<>("foo", 1)); - metrics.add(new Metric<>("bar.png", 1)); - metrics.add(new Metric<>("group1.a", 1)); - metrics.add(new Metric<>("group1.b", 1)); - metrics.add(new Metric<>("group2.a", 1)); - metrics.add(new Metric<>("group2_a", 1)); - metrics.add(new Metric("baz", null)); - return Collections.unmodifiableList(metrics); - }); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java index ae4072bf2c..f0c197d87f 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java @@ -19,15 +19,20 @@ package org.springframework.boot.actuate.endpoint.mvc; import org.junit.Before; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaManagementContextConfiguration; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; +import org.springframework.boot.actuate.endpoint.BeansEndpoint; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportMessage; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.mock.web.MockServletContext; import org.springframework.test.web.servlet.MockMvc; @@ -54,18 +59,19 @@ public class MvcEndpointCorsIntegrationTests { this.context.setServletContext(new MockServletContext()); this.context.register(JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class, - JolokiaManagementContextConfiguration.class, - WebMvcAutoConfiguration.class); + WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + EndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class, + ServletEndpointAutoConfiguration.class); } @Test public void corsIsDisabledByDefault() throws Exception { - createMockMvc() - .perform(options("/application/beans").header("Origin", "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) - .andExpect( + MockMvc mockMvc = createMockMvc(); + System.out.println(new ConditionEvaluationReportMessage( + this.context.getBean(ConditionEvaluationReport.class))); + mockMvc.perform(options("/application/beans").header("Origin", "foo.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect( header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } @@ -137,7 +143,7 @@ public class MvcEndpointCorsIntegrationTests { TestPropertyValues.of("endpoints.cors.allowed-origins:foo.example.com", "endpoints.cors.allowed-methods:GET,HEAD").applyTo(this.context); createMockMvc() - .perform(options("/application/health") + .perform(options("/application/beans") .header(HttpHeaders.ORIGIN, "foo.example.com") .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")) .andExpect(status().isOk()).andExpect(header() @@ -178,4 +184,14 @@ public class MvcEndpointCorsIntegrationTests { .andExpect(status().isOk()); } + @Configuration + static class EndpointConfiguration { + + @Bean + public BeansEndpoint beansEndpoint() { + return new BeansEndpoint(); + } + + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointIntegrationTests.java index 324ca1b908..4e4aad1fe0 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointIntegrationTests.java @@ -19,9 +19,11 @@ package org.springframework.boot.actuate.endpoint.mvc; import org.junit.After; import org.junit.Test; +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -30,6 +32,7 @@ import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguratio import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.annotation.Import; @@ -42,10 +45,8 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import static org.hamcrest.Matchers.startsWith; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -55,8 +56,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. */ public class MvcEndpointIntegrationTests { - private static final String LINE_SEPARATOR = System.getProperty("line.separator"); - private AnnotationConfigWebApplicationContext context; @After @@ -65,52 +64,6 @@ public class MvcEndpointIntegrationTests { this.context.close(); } - @Test - public void defaultJsonResponseIsNotIndented() throws Exception { - TestSecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR")); - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(SecureConfiguration.class); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/application/mappings")) - .andExpect(content().string(startsWith("{\""))); - } - - @Test - public void jsonResponsesCanBeIndented() throws Exception { - assertIndentedJsonResponse(SecureConfiguration.class); - } - - @Test - public void jsonResponsesCanBeIndentedWhenSpringHateoasIsAutoConfigured() - throws Exception { - assertIndentedJsonResponse(SpringHateoasConfiguration.class); - } - - @Test - public void jsonResponsesCanBeIndentedWhenSpringDataRestIsAutoConfigured() - throws Exception { - assertIndentedJsonResponse(SpringDataRestConfiguration.class); - } - - @Test - public void fileExtensionNotFound() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(DefaultConfiguration.class); - MockMvc mockMvc = createMockMvc(); - mockMvc.perform(get("/application/beans.cmd")).andExpect(status().isNotFound()); - } - - @Test - public void jsonExtensionProvided() throws Exception { - TestSecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR")); - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(SecureConfiguration.class); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/application/beans.json")).andExpect(status().isOk()); - } - @Test public void endpointsAreSecureByDefault() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); @@ -129,19 +82,6 @@ public class MvcEndpointIntegrationTests { mockMvc.perform(get("/management/beans")).andExpect(status().isUnauthorized()); } - @Test - public void endpointsAreSecureWithNonActuatorRoleWithCustomContextPath() - throws Exception { - TestSecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken("user", "N/A", "ROLE_USER")); - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(SecureConfiguration.class); - TestPropertyValues.of("management.context-path:/management") - .applyTo(this.context); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/management/beans")).andExpect(status().isForbidden()); - } - @Test public void endpointsAreSecureWithActuatorRoleWithCustomContextPath() throws Exception { @@ -174,22 +114,6 @@ public class MvcEndpointIntegrationTests { mockMvc.perform(get("/application/beans")).andExpect(status().isOk()); } - private void assertIndentedJsonResponse(Class configuration) throws Exception { - TestSecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR")); - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(configuration); - TestPropertyValues.of("spring.jackson.serialization.indent-output:true") - .applyTo(this.context); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/application/mappings")) - .andExpect(content().string(startsWith("{" + LINE_SEPARATOR))); - } - - private MockMvc createMockMvc() { - return doCreateMockMvc(); - } - private MockMvc createSecureMockMvc() { return doCreateMockMvc(springSecurity()); } @@ -205,10 +129,12 @@ public class MvcEndpointIntegrationTests { } @ImportAutoConfiguration({ JacksonAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, AuditAutoConfiguration.class, + ServletEndpointAutoConfiguration.class, AuditAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class, - AuditAutoConfiguration.class }) + ManagementContextAutoConfiguration.class, AuditAutoConfiguration.class, + DispatcherServletAutoConfiguration.class }) static class DefaultConfiguration { } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java deleted file mode 100644 index aa2cb7274e..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.security.Principal; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import javax.servlet.http.HttpServletResponse; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.test.rule.OutputCapture; -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.method.HandlerMethod; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link MvcEndpointSecurityInterceptor}. - * - * @author Madhura Bhave - */ -public class MvcEndpointSecurityInterceptorTests { - - @Rule - public OutputCapture output = new OutputCapture(); - - private MvcEndpointSecurityInterceptor securityInterceptor; - - private TestMvcEndpoint mvcEndpoint; - - private TestEndpoint endpoint; - - private HandlerMethod handlerMethod; - - private MockHttpServletRequest request; - - private HttpServletResponse response; - - private MockServletContext servletContext; - - private List roles; - - @Before - public void setup() throws Exception { - this.roles = Arrays.asList("SUPER_HERO"); - this.securityInterceptor = new MvcEndpointSecurityInterceptor(true, this.roles); - this.endpoint = new TestEndpoint("a"); - this.mvcEndpoint = new TestMvcEndpoint(this.endpoint); - this.handlerMethod = new HandlerMethod(this.mvcEndpoint, "invoke"); - this.servletContext = new MockServletContext(); - this.request = new MockHttpServletRequest(this.servletContext); - this.response = mock(HttpServletResponse.class); - } - - @Test - public void securityDisabledShouldAllowAccess() throws Exception { - this.securityInterceptor = new MvcEndpointSecurityInterceptor(false, this.roles); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isTrue(); - } - - @Test - public void endpointIfRoleIsPresentShouldAllowAccess() throws Exception { - this.servletContext.declareRoles("SUPER_HERO"); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isTrue(); - } - - @Test - public void endpointIfNotAuthenticatedShouldNotAllowAccess() throws Exception { - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isFalse(); - verify(this.response).sendError(HttpStatus.UNAUTHORIZED.value(), - "Full authentication is required to access this resource."); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isFalse(); - assertThat(this.output.toString()) - .containsOnlyOnce("Full authentication is required to access actuator " - + "endpoints. Consider adding Spring Security or set " - + "'management.security.enabled' to false"); - } - - @Test - public void endpointIfRoleIsNotCorrectShouldNotAllowAccess() throws Exception { - Principal principal = mock(Principal.class); - this.request.setUserPrincipal(principal); - this.servletContext.declareRoles("HERO"); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isFalse(); - verify(this.response).sendError(HttpStatus.FORBIDDEN.value(), - "Access is denied. User must have one of the these roles: SUPER_HERO"); - } - - @Test - public void endpointIfRoleNotCorrectShouldCheckAuthorities() throws Exception { - Principal principal = mock(Principal.class); - this.request.setUserPrincipal(principal); - Authentication authentication = mock(Authentication.class); - Set authorities = Collections - .singleton(new SimpleGrantedAuthority("SUPER_HERO")); - doReturn(authorities).when(authentication).getAuthorities(); - SecurityContextHolder.getContext().setAuthentication(authentication); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isTrue(); - } - - @Test - public void endpointIfRoleAndAuthoritiesNotCorrectShouldNotAllowAccess() - throws Exception { - Principal principal = mock(Principal.class); - this.request.setUserPrincipal(principal); - Authentication authentication = mock(Authentication.class); - Set authorities = Collections - .singleton(new SimpleGrantedAuthority("HERO")); - doReturn(authorities).when(authentication).getAuthorities(); - SecurityContextHolder.getContext().setAuthentication(authentication); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isFalse(); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointsTests.java deleted file mode 100644 index 3e3b6a984f..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointsTests.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.support.StaticApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link MvcEndpoints}. - * - * @author Dave Syer - */ -public class MvcEndpointsTests { - - private MvcEndpoints endpoints = new MvcEndpoints(); - - private StaticApplicationContext context = new StaticApplicationContext(); - - @Test - public void picksUpEndpointDelegates() throws Exception { - this.context.getDefaultListableBeanFactory().registerSingleton("endpoint", - new TestEndpoint()); - this.endpoints.setApplicationContext(this.context); - this.endpoints.afterPropertiesSet(); - assertThat(this.endpoints.getEndpoints()).hasSize(1); - } - - @Test - public void picksUpEndpointDelegatesFromParent() throws Exception { - StaticApplicationContext parent = new StaticApplicationContext(); - this.context.setParent(parent); - parent.getDefaultListableBeanFactory().registerSingleton("endpoint", - new TestEndpoint()); - this.endpoints.setApplicationContext(this.context); - this.endpoints.afterPropertiesSet(); - assertThat(this.endpoints.getEndpoints()).hasSize(1); - } - - @Test - public void picksUpMvcEndpoints() throws Exception { - this.context.getDefaultListableBeanFactory().registerSingleton("endpoint", - new EndpointMvcAdapter(new TestEndpoint())); - this.endpoints.setApplicationContext(this.context); - this.endpoints.afterPropertiesSet(); - assertThat(this.endpoints.getEndpoints()).hasSize(1); - } - - @Test - public void changesPath() throws Exception { - TestPropertyValues.of("endpoints.test.path=/foo/bar/").applyTo(this.context); - this.context.getDefaultListableBeanFactory().registerSingleton("endpoint", - new TestEndpoint()); - this.endpoints.setApplicationContext(this.context); - this.endpoints.afterPropertiesSet(); - assertThat(this.endpoints.getEndpoints()).hasSize(1); - assertThat(this.endpoints.getEndpoints().iterator().next().getPath()) - .isEqualTo("/foo/bar"); - } - - @Test - public void getEndpointsForSpecifiedType() throws Exception { - this.context.getDefaultListableBeanFactory().registerSingleton("endpoint-1", - new TestMvcEndpoint(new TestEndpoint())); - this.context.getDefaultListableBeanFactory().registerSingleton("endpoint-2", - new OtherTestMvcEndpoint(new TestEndpoint())); - this.endpoints.setApplicationContext(this.context); - this.endpoints.afterPropertiesSet(); - assertThat(this.endpoints.getEndpoints(TestMvcEndpoint.class)).hasSize(1); - } - - @ConfigurationProperties("endpoints.test") - protected static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint() { - super("test"); - } - - @Override - public String invoke() { - return "foo"; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(Endpoint delegate) { - super(delegate); - } - - } - - private static class OtherTestMvcEndpoint extends EndpointMvcAdapter { - - OtherTestMvcEndpoint(Endpoint delegate) { - super(delegate); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java index 560c664739..4329b23ece 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java @@ -16,18 +16,24 @@ package org.springframework.boot.actuate.endpoint.mvc; +import java.util.Map; + import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; +import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.testsupport.runner.classpath.ClassPathExclusions; @@ -75,9 +81,11 @@ public class NoSpringSecurityHealthMvcEndpointIntegrationTests { } @ImportAutoConfiguration({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class }) + HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class, + ServletEndpointAutoConfiguration.class }) @Configuration static class TestConfiguration { @@ -86,6 +94,18 @@ public class NoSpringSecurityHealthMvcEndpointIntegrationTests { return () -> Health.up().withDetail("hello", "world").build(); } + @Bean + public HealthEndpoint healthEndpoint( + Map healthIndicators) { + return new HealthEndpoint(new OrderedHealthAggregator(), healthIndicators); + } + + @Bean + public HealthWebEndpointExtension healthWebEndpointExtension( + HealthEndpoint delegate) { + return new HealthWebEndpointExtension(delegate); + } + } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityMvcEndpointSecurityInterceptorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityMvcEndpointSecurityInterceptorTests.java deleted file mode 100644 index b09383dada..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityMvcEndpointSecurityInterceptorTests.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.security.Principal; -import java.util.Arrays; -import java.util.List; - -import javax.servlet.http.HttpServletResponse; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.testsupport.runner.classpath.ClassPathExclusions; -import org.springframework.boot.testsupport.runner.classpath.ModifiedClassPathRunner; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.web.method.HandlerMethod; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link MvcEndpointSecurityInterceptor} when Spring Security is not available. - * - * @author Madhura Bhave - */ -@RunWith(ModifiedClassPathRunner.class) -@ClassPathExclusions("spring-security-*.jar") -public class NoSpringSecurityMvcEndpointSecurityInterceptorTests { - - private MvcEndpointSecurityInterceptor securityInterceptor; - - private TestMvcEndpoint mvcEndpoint; - - private TestEndpoint endpoint; - - private HandlerMethod handlerMethod; - - private MockHttpServletRequest request; - - private HttpServletResponse response; - - private MockServletContext servletContext; - - private List roles; - - @Before - public void setup() throws Exception { - this.roles = Arrays.asList("SUPER_HERO"); - this.securityInterceptor = new MvcEndpointSecurityInterceptor(true, this.roles); - this.endpoint = new TestEndpoint("a"); - this.mvcEndpoint = new TestMvcEndpoint(this.endpoint); - this.handlerMethod = new HandlerMethod(this.mvcEndpoint, "invoke"); - this.servletContext = new MockServletContext(); - this.request = new MockHttpServletRequest(this.servletContext); - this.response = mock(HttpServletResponse.class); - } - - @Test - public void endpointIfRoleNotPresentShouldNotValidateAuthorities() throws Exception { - Principal principal = mock(Principal.class); - this.request.setUserPrincipal(principal); - this.servletContext.declareRoles("HERO"); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isFalse(); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpointTests.java deleted file mode 100644 index 88ae84a49c..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpointTests.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed 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 - * - * http://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.springframework.boot.actuate.endpoint.mvc; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link ShutdownMvcEndpoint}. - * - * @author Dave Syer - * - */ -@SpringBootTest(properties = { "management.security.enabled=false", - "endpoints.shutdown.enabled=true" }) -@RunWith(SpringRunner.class) -public class ShutdownMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @Test - public void contentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(post("/application/shutdown")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - assertThat(this.context.getBean(CountDownLatch.class).await(30, TimeUnit.SECONDS)) - .isTrue(); - } - - @Test - public void contentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(post("/application/shutdown").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - assertThat(this.context.getBean(CountDownLatch.class).await(30, TimeUnit.SECONDS)) - .isTrue(); - } - - @Configuration - @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - public static class TestConfiguration { - - @Bean - public TestShutdownEndpoint endpoint() { - return new TestShutdownEndpoint(contextCloseLatch()); - } - - @Bean - public CountDownLatch contextCloseLatch() { - return new CountDownLatch(1); - } - - } - - private static class TestShutdownEndpoint extends ShutdownEndpoint { - - private final CountDownLatch contextCloseLatch; - - TestShutdownEndpoint(CountDownLatch contextCloseLatch) { - this.contextCloseLatch = contextCloseLatch; - } - - @Override - public void setApplicationContext(ApplicationContext context) - throws BeansException { - ConfigurableApplicationContext mockContext = mock( - ConfigurableApplicationContext.class); - willAnswer((invocation) -> { - TestShutdownEndpoint.this.contextCloseLatch.countDown(); - return null; - }).given(mockContext).close(); - super.setApplicationContext(mockContext); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..7505f8d6c2 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsEndpointWebIntegrationTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.time.Instant; +import java.util.Collections; +import java.util.Date; + +import net.minidev.json.JSONArray; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link AuditEventsEndpoint} and + * {@link AuditEventsWebEndpointExtension} exposed by Jersey, Spring MVC, and WebFlux. + * + * @author Vedran Pavic + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +public class AuditEventsEndpointWebIntegrationTests { + + private static WebTestClient client; + + @Test + public void eventsWithDateAfter() throws Exception { + client.get() + .uri((builder) -> builder.path("/application/auditevents") + .queryParam("after", "2016-11-01T13:00:00%2B00:00").build()) + .exchange().expectStatus().isOk().expectBody().jsonPath("events") + .isEmpty(); + } + + @Test + public void eventsWithPrincipalAndDateAfter() throws Exception { + client.get() + .uri((builder) -> builder.path("/application/auditevents") + .queryParam("after", "2016-11-01T10:00:00%2B00:00") + .queryParam("principal", "user").build()) + .exchange().expectStatus().isOk().expectBody() + .jsonPath("events.[*].principal") + .isEqualTo(new JSONArray().appendElement("user")); + } + + @Test + public void eventsWithPrincipalDateAfterAndType() throws Exception { + client.get() + .uri((builder) -> builder.path("/application/auditevents") + .queryParam("after", "2016-11-01T10:00:00%2B00:00") + .queryParam("principal", "admin").queryParam("type", "logout") + .build()) + .exchange().expectStatus().isOk().expectBody() + .jsonPath("events.[*].principal") + .isEqualTo(new JSONArray().appendElement("admin")) + .jsonPath("events.[*].type") + .isEqualTo(new JSONArray().appendElement("logout")); + } + + @Configuration + protected static class TestConfiguration { + + @Bean + public AuditEventRepository auditEventsRepository() { + AuditEventRepository repository = new InMemoryAuditEventRepository(3); + repository.add(createEvent("2016-11-01T11:00:00Z", "admin", "login")); + repository.add(createEvent("2016-11-01T12:00:00Z", "admin", "logout")); + repository.add(createEvent("2016-11-01T12:00:00Z", "user", "login")); + return repository; + } + + @Bean + public AuditEventsEndpoint auditEventsEndpoint() { + return new AuditEventsEndpoint(auditEventsRepository()); + } + + @Bean + public AuditEventsWebEndpointExtension auditEventsWebEndpointExtension() { + return new AuditEventsWebEndpointExtension(auditEventsEndpoint()); + } + + private AuditEvent createEvent(String instant, String principal, String type) { + return new AuditEvent(Date.from(Instant.parse(instant)), principal, type, + Collections.emptyMap()); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtensionTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtensionTests.java new file mode 100644 index 0000000000..763cb842fd --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtensionTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AuditEventsWebEndpointExtension}. + * + * @author Andy Wilkinson + */ +public class AuditEventsWebEndpointExtensionTests { + + private final AuditEventRepository repository = mock(AuditEventRepository.class); + + private final AuditEventsWebEndpointExtension extension = new AuditEventsWebEndpointExtension( + new AuditEventsEndpoint(this.repository)); + + private final AuditEvent event = new AuditEvent("principal", "type", + Collections.singletonMap("a", "alpha")); + + @Test + public void delegatesResponseIsAvailableFromEventsKeyInMap() { + Date date = new Date(); + given(this.repository.find("principal", date, "type")) + .willReturn(Collections.singletonList(this.event)); + Map> result = this.extension + .eventsWithPrincipalDateAfterAndType("principal", date, "type"); + assertThat(result).hasSize(1); + assertThat(result).containsEntry("events", Collections.singletonList(this.event)); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EnvironmentEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EnvironmentEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..ca55c7f9c5 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EnvironmentEndpointWebIntegrationTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; + +@RunWith(WebEndpointsRunner.class) +public class EnvironmentEndpointWebIntegrationTests { + + private static WebTestClient client; + + private static ConfigurableApplicationContext context; + + @Before + public void prepareEnvironment() { + TestPropertyValues.of("foo:bar", "fool:baz").applyTo(context); + } + + @Test + public void home() throws Exception { + client.get().uri("/application/env").exchange().expectStatus().isOk().expectBody() + .jsonPath("propertySources[?(@.name=='systemProperties')]").exists(); + } + + @Test + public void sub() throws Exception { + client.get().uri("/application/env/foo").exchange().expectStatus().isOk() + .expectBody().jsonPath(forProperty("test", "foo")).isEqualTo("bar"); + } + + @Test + public void regex() throws Exception { + Map map = new HashMap<>(); + map.put("food", null); + EnvironmentEndpointWebIntegrationTests.context.getEnvironment() + .getPropertySources().addFirst(new MapPropertySource("null-value", map)); + client.get().uri("/application/env?pattern=foo.*").exchange().expectStatus() + .isOk().expectBody().jsonPath(forProperty("test", "foo")).isEqualTo("bar") + .jsonPath(forProperty("test", "fool")).isEqualTo("baz"); + } + + @Test + public void nestedPathWhenPlaceholderCannotBeResolvedShouldReturnUnresolvedProperty() + throws Exception { + Map map = new HashMap(); + map.put("my.foo", "${my.bar}"); + context.getEnvironment().getPropertySources() + .addFirst(new MapPropertySource("unresolved-placeholder", map)); + client.get().uri("/application/env/my.foo").exchange().expectStatus().isOk() + .expectBody().jsonPath(forProperty("unresolved-placeholder", "my.foo")) + .isEqualTo("${my.bar}"); + } + + @Test + public void nestedPathWithSensitivePlaceholderShouldSanitize() throws Exception { + Map map = new HashMap(); + map.put("my.foo", "${my.password}"); + map.put("my.password", "hello"); + context.getEnvironment().getPropertySources() + .addFirst(new MapPropertySource("placeholder", map)); + client.get().uri("/application/env/my.foo").exchange().expectStatus().isOk() + .expectBody().jsonPath(forProperty("placeholder", "my.foo")) + .isEqualTo("******"); + } + + @Test + public void nestedPathMatchedByRegexWhenPlaceholderCannotBeResolvedShouldReturnUnresolvedProperty() + throws Exception { + Map map = new HashMap(); + map.put("my.foo", "${my.bar}"); + context.getEnvironment().getPropertySources() + .addFirst(new MapPropertySource("unresolved-placeholder", map)); + client.get().uri("/application/env?pattern=my.*").exchange().expectStatus().isOk() + .expectBody() + .jsonPath( + "propertySources[?(@.name=='unresolved-placeholder')].properties.['my.foo'].value") + .isEqualTo("${my.bar}"); + } + + @Test + public void nestedPathMatchedByRegexWithSensitivePlaceholderShouldSanitize() + throws Exception { + Map map = new HashMap(); + map.put("my.foo", "${my.password}"); + map.put("my.password", "hello"); + context.getEnvironment().getPropertySources() + .addFirst(new MapPropertySource("placeholder", map)); + client.get().uri("/application/env?pattern=my.*").exchange().expectStatus().isOk() + .expectBody().jsonPath(forProperty("placeholder", "my.foo")) + .isEqualTo("******"); + } + + private String forProperty(String source, String name) { + return "propertySources[?(@.name=='" + source + "')].properties.['" + name + + "'].value"; + } + + @Configuration + static class TestConfiguration { + + @Bean + public EnvironmentEndpoint endpoint(Environment environment) { + return new EnvironmentEndpoint(environment); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..6b063eb327 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link HealthEndpoint} and {@link HealthWebEndpointExtension} + * exposed by Jersey, Spring MVC, and WebFlux. + * + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +public class HealthEndpointWebIntegrationTests { + + private static WebTestClient client; + + private static ConfigurableApplicationContext context; + + @Test + public void whenHealthIsUp200ResponseIsReturned() throws Exception { + client.get().uri("/application/health").exchange().expectStatus().isOk() + .expectBody().jsonPath("status").isEqualTo("UP").jsonPath("alpha.status") + .isEqualTo("UP").jsonPath("bravo.status").isEqualTo("UP"); + } + + @Test + public void whenHealthIsDown503ResponseIsReturned() throws Exception { + context.getBean("alphaHealthIndicator", TestHealthIndicator.class) + .setHealth(Health.down().build()); + client.get().uri("/application/health").exchange().expectStatus() + .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status") + .isEqualTo("DOWN").jsonPath("alpha.status").isEqualTo("DOWN") + .jsonPath("bravo.status").isEqualTo("UP"); + } + + @Configuration + public static class TestConfiguration { + + @Bean + public HealthEndpoint healthEndpoint( + Map healthIndicators) { + return new HealthEndpoint(new OrderedHealthAggregator(), healthIndicators); + } + + @Bean + public HealthWebEndpointExtension healthWebEndpointExtension( + HealthEndpoint delegate) { + return new HealthWebEndpointExtension(delegate); + } + + @Bean + public TestHealthIndicator alphaHealthIndicator() { + return new TestHealthIndicator(); + } + + @Bean + public TestHealthIndicator bravoHealthIndicator() { + return new TestHealthIndicator(); + } + + } + + private static class TestHealthIndicator implements HealthIndicator { + + private Health health = Health.up().build(); + + @Override + public Health health() { + Health result = this.health; + this.health = Health.up().build(); + return result; + } + + void setHealth(Health health) { + this.health = health; + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointTests.java new file mode 100644 index 0000000000..6ee9fda1bb --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.util.concurrent.CountDownLatch; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HeapDumpWebEndpoint}. + * + * @author Andy Wilkinson + */ +public class HeapDumpWebEndpointTests { + + @Test + public void parallelRequestProducesTooManyRequestsResponse() + throws InterruptedException { + CountDownLatch dumpingLatch = new CountDownLatch(1); + CountDownLatch blockingLatch = new CountDownLatch(1); + HeapDumpWebEndpoint slowEndpoint = new HeapDumpWebEndpoint(2500) { + + @Override + protected HeapDumper createHeapDumper() + throws HeapDumperUnavailableException { + return (file, live) -> { + dumpingLatch.countDown(); + blockingLatch.await(); + }; + } + + }; + Thread thread = new Thread(() -> slowEndpoint.heapDump(true)); + thread.start(); + dumpingLatch.await(); + assertThat(slowEndpoint.heapDump(true).getStatus()).isEqualTo(429); + blockingLatch.countDown(); + thread.join(); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..866bb584c4 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointWebIntegrationTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.FileCopyUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link HeapDumpWebEndpoint} exposed by Jersey, Spring MVC, and + * WebFlux. + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +public class HeapDumpWebEndpointWebIntegrationTests { + + private static WebTestClient client; + + private static ConfigurableApplicationContext context; + + private TestHeapDumpWebEndpoint endpoint; + + @Before + public void configureEndpoint() { + this.endpoint = context.getBean(TestHeapDumpWebEndpoint.class); + this.endpoint.setAvailable(true); + } + + @Test + public void invokeWhenNotAvailableShouldReturnServiceUnavailableStatus() + throws Exception { + this.endpoint.setAvailable(false); + client.get().uri("/application/heapdump").exchange().expectStatus() + .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); + } + + @Test + public void getRequestShouldReturnHeapDumpInResponseBody() throws Exception { + client.get().uri("/application/heapdump").exchange().expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_OCTET_STREAM) + .expectBody(String.class).isEqualTo("HEAPDUMP"); + assertThat(this.endpoint.file.exists()).isFalse(); + } + + @Configuration + public static class TestConfiguration { + + @Bean + public HeapDumpWebEndpoint endpoint() { + return new TestHeapDumpWebEndpoint(); + } + + } + + private static class TestHeapDumpWebEndpoint extends HeapDumpWebEndpoint { + + private boolean available; + + private String heapDump = "HEAPDUMP"; + + private File file; + + TestHeapDumpWebEndpoint() { + super(TimeUnit.SECONDS.toMillis(1)); + reset(); + } + + public void reset() { + this.available = true; + } + + @Override + protected HeapDumper createHeapDumper() { + return (file, live) -> { + this.file = file; + if (!TestHeapDumpWebEndpoint.this.available) { + throw new HeapDumperUnavailableException("Not available", null); + } + if (file.exists()) { + throw new IOException("File exists"); + } + FileCopyUtils.copy(TestHeapDumpWebEndpoint.this.heapDump.getBytes(), + file); + }; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/InfoEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/InfoEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..edd25bf5ad --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/InfoEndpointWebIntegrationTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.endpoint.InfoEndpoint; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link InfoEndpoint} exposed by Jersey, Spring MVC, and WebFlux. + * + * @author Meang Akira Tanaka + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +@TestPropertySource(properties = { "info.app.name=MyService" }) +public class InfoEndpointWebIntegrationTests { + + private static WebTestClient client; + + @Test + public void info() throws Exception { + client.get().uri("/application/info").accept(MediaType.APPLICATION_JSON) + .exchange().expectStatus().isOk().expectBody().jsonPath("beanName1.key11") + .isEqualTo("value11").jsonPath("beanName1.key12").isEqualTo("value12") + .jsonPath("beanName2.key21").isEqualTo("value21") + .jsonPath("beanName2.key22").isEqualTo("value22"); + } + + @Configuration + public static class TestConfiguration { + + @Bean + public InfoEndpoint endpoint() { + return new InfoEndpoint(Arrays.asList(beanName1(), beanName2())); + } + + @Bean + public InfoContributor beanName1() { + return (builder) -> { + Map content = new LinkedHashMap<>(); + content.put("key11", "value11"); + content.put("key12", "value12"); + builder.withDetail("beanName1", content); + }; + } + + @Bean + public InfoContributor beanName2() { + return (builder) -> { + Map content = new LinkedHashMap<>(); + content.put("key21", "value21"); + content.put("key22", "value22"); + builder.withDetail("beanName2", content); + }; + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointTests.java new file mode 100644 index 0000000000..8b4e428c13 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.springframework.core.io.Resource; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StreamUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LogFileWebEndpoint}. + * + * @author Johannes Edmeier + * @author Phillip Webb + * @author Andy Wilkinson + */ +public class LogFileWebEndpointTests { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private final MockEnvironment environment = new MockEnvironment(); + + private final LogFileWebEndpoint endpoint = new LogFileWebEndpoint(this.environment); + + private File logFile; + + @Before + public void before() throws IOException { + this.logFile = this.temp.newFile(); + FileCopyUtils.copy("--TEST--".getBytes(), this.logFile); + } + + @Test + public void nullResponseWithoutLogFile() throws Exception { + assertThat(this.endpoint.logFile()).isNull(); + } + + @Test + public void nullResponseWithMissingLogFile() throws Exception { + this.environment.setProperty("logging.file", "no_test.log"); + assertThat(this.endpoint.logFile()).isNull(); + } + + @Test + public void resourceResponseWithLogFile() throws Exception { + this.environment.setProperty("logging.file", this.logFile.getAbsolutePath()); + Resource resource = this.endpoint.logFile(); + assertThat(resource).isNotNull(); + assertThat(StreamUtils.copyToString(resource.getInputStream(), + StandardCharsets.UTF_8)).isEqualTo("--TEST--"); + } + + @Test + public void resourceResponseWithExternalLogFile() throws Exception { + this.endpoint.setExternalFile(this.logFile); + Resource resource = this.endpoint.logFile(); + assertThat(resource).isNotNull(); + assertThat(StreamUtils.copyToString(resource.getInputStream(), + StandardCharsets.UTF_8)).isEqualTo("--TEST--"); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..42d6d10e94 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointWebIntegrationTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.io.File; +import java.io.IOException; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.FileCopyUtils; + +/** + * Integration tests for {@link LogFileWebEndpoint} exposed by Jersey, Spring MVC, and + * WebFlux. + * + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +public class LogFileWebEndpointWebIntegrationTests { + + private static ConfigurableApplicationContext context; + + private static WebTestClient client; + + @Rule + public final TemporaryFolder temp = new TemporaryFolder(); + + private File logFile; + + @Before + public void setUp() throws IOException { + this.logFile = this.temp.newFile(); + FileCopyUtils.copy("--TEST--".getBytes(), this.logFile); + } + + @Test + public void getRequestProduces404ResponseWhenLogFileNotFound() throws Exception { + client.get().uri("/application/logfile").exchange().expectStatus().isNotFound(); + } + + @Test + public void getRequestProducesResponseWithLogFile() throws Exception { + TestPropertyValues.of("logging.file:" + this.logFile.getAbsolutePath()) + .applyTo(context); + client.get().uri("/application/logfile").exchange().expectStatus().isOk() + .expectBody(String.class).isEqualTo("--TEST--"); + } + + @Configuration + static class TestConfiguration { + + @Bean + public LogFileWebEndpoint logFileEndpoint(Environment environment) { + return new LogFileWebEndpoint(environment); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LoggersEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LoggersEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..a7d9f6dee0 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LoggersEndpointWebIntegrationTests.java @@ -0,0 +1,180 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; + +import net.minidev.json.JSONArray; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ActuatorMediaTypes; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint; +import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggingSystem; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Integration tests for {@link LoggersEndpoint} when exposed via Jersey, Spring MVC, and + * WebFlux. + * + * @author Ben Hale + * @author Phillip Webb + * @author EddĂș MelĂ©ndez + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +public class LoggersEndpointWebIntegrationTests { + + private static ConfigurableApplicationContext context; + + private static WebTestClient client; + + private LoggingSystem loggingSystem; + + @Before + @After + public void resetMocks() { + this.loggingSystem = context.getBean(LoggingSystem.class); + Mockito.reset(this.loggingSystem); + given(this.loggingSystem.getSupportedLogLevels()) + .willReturn(EnumSet.allOf(LogLevel.class)); + } + + @Test + public void getLoggerShouldReturnAllLoggerConfigurations() throws Exception { + given(this.loggingSystem.getLoggerConfigurations()).willReturn(Collections + .singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); + client.get().uri("/application/loggers").exchange().expectStatus().isOk() + .expectBody().jsonPath("$.length()").isEqualTo(2).jsonPath("levels") + .isEqualTo(jsonArrayOf("OFF", "FATAL", "ERROR", "WARN", "INFO", "DEBUG", + "TRACE")) + .jsonPath("loggers.length()").isEqualTo(1) + .jsonPath("loggers.ROOT.length()").isEqualTo(2) + .jsonPath("loggers.ROOT.configuredLevel").isEqualTo(null) + .jsonPath("loggers.ROOT.effectiveLevel").isEqualTo("DEBUG"); + } + + @Test + public void getLoggerShouldReturnLogLevels() throws Exception { + given(this.loggingSystem.getLoggerConfiguration("ROOT")) + .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); + client.get().uri("/application/loggers/ROOT").exchange().expectStatus().isOk() + .expectBody().jsonPath("$.length()").isEqualTo(2) + .jsonPath("configuredLevel").isEqualTo(null).jsonPath("effectiveLevel") + .isEqualTo("DEBUG"); + } + + @Test + public void getLoggersWhenLoggerNotFoundShouldReturnNotFound() throws Exception { + client.get().uri("/application/loggers/com.does.not.exist").exchange() + .expectStatus().isNotFound(); + } + + @Test + public void setLoggerUsingApplicationJsonShouldSetLogLevel() throws Exception { + client.post().uri("/application/loggers/ROOT") + .contentType(MediaType.APPLICATION_JSON) + .syncBody(Collections.singletonMap("configuredLevel", "debug")).exchange() + .expectStatus().isNoContent(); + verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); + } + + @Test + public void setLoggerUsingActuatorV2JsonShouldSetLogLevel() throws Exception { + client.post().uri("/application/loggers/ROOT") + .contentType(ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON) + .syncBody(Collections.singletonMap("configuredLevel", "debug")).exchange() + .expectStatus().isNoContent(); + verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); + } + + @Test + public void setLoggerWithWrongLogLevelResultInBadRequestResponse() throws Exception { + client.post().uri("/application/loggers/ROOT") + .contentType(MediaType.APPLICATION_JSON) + .syncBody(Collections.singletonMap("configuredLevel", "other")).exchange() + .expectStatus().isBadRequest(); + verifyZeroInteractions(this.loggingSystem); + } + + @Test + public void setLoggerWithNullLogLevel() throws Exception { + client.post().uri("/application/loggers/ROOT") + .contentType(ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON) + .syncBody(Collections.singletonMap("configuredLevel", null)).exchange() + .expectStatus().isNoContent(); + verify(this.loggingSystem).setLogLevel("ROOT", null); + } + + @Test + public void setLoggerWithNoLogLevel() throws Exception { + client.post().uri("/application/loggers/ROOT") + .contentType(ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON) + .syncBody(Collections.emptyMap()).exchange().expectStatus().isNoContent(); + verify(this.loggingSystem).setLogLevel("ROOT", null); + } + + @Test + public void logLevelForLoggerWithNameThatCouldBeMistakenForAPathExtension() + throws Exception { + given(this.loggingSystem.getLoggerConfiguration("com.png")) + .willReturn(new LoggerConfiguration("com.png", null, LogLevel.DEBUG)); + client.get().uri("/application/loggers/com.png").exchange().expectStatus().isOk() + .expectBody().jsonPath("$.length()").isEqualTo(2) + .jsonPath("configuredLevel").isEqualTo(null).jsonPath("effectiveLevel") + .isEqualTo("DEBUG"); + } + + private JSONArray jsonArrayOf(Object... entries) { + JSONArray array = new JSONArray(); + array.addAll(Arrays.asList(entries)); + return array; + } + + @Configuration + static class TestConfiguration { + + @Bean + public LoggingSystem loggingSystem() { + return mock(LoggingSystem.class); + } + + @Bean + public LoggersEndpoint endpoint(LoggingSystem loggingSystem) { + return new LoggersEndpoint(loggingSystem); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/MetricsEndpointMvcIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/MetricsEndpointMvcIntegrationTests.java new file mode 100644 index 0000000000..27d8f1fd6e --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/MetricsEndpointMvcIntegrationTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.endpoint.MetricsEndpoint; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link MetricsEndpoint} when exposed via Spring MVC + * + * @author Andy Wilkinson + * @author Sergei Egorov + */ +@RunWith(WebEndpointsRunner.class) +public class MetricsEndpointMvcIntegrationTests { + + private static WebTestClient client; + + @Test + public void home() { + client.get().uri("/application/metrics").exchange().expectStatus().isOk() + .expectBody().jsonPath("foo").isEqualTo(1); + } + + @Test + public void specificMetric() { + client.get().uri("/application/metrics/foo").exchange().expectStatus().isOk() + .expectBody().jsonPath("foo").isEqualTo(1); + } + + @Test + public void specificMetricWithDot() throws Exception { + client.get().uri("/application/metrics/group2.a").exchange().expectStatus().isOk() + .expectBody().jsonPath("$.length()").isEqualTo(1).jsonPath("['group2.a']") + .isEqualTo("1"); + } + + @Test + public void specificMetricWithNameThatCouldBeMistakenForAPathExtension() { + client.get().uri("/application/metrics/bar.png").exchange().expectStatus().isOk() + .expectBody().jsonPath("['bar.png']").isEqualTo(1); + } + + @Test + public void specificMetricThatDoesNotExist() throws Exception { + client.get().uri("/application/metrics/bar").exchange().expectStatus() + .isNotFound(); + } + + @Test + public void regexAll() throws Exception { + client.get().uri("/application/metrics?pattern=.*").exchange().expectStatus() + .isOk().expectBody().jsonPath("$.length()").isEqualTo(6).jsonPath("foo") + .isEqualTo(1).jsonPath("['bar.png']").isEqualTo(1) + .jsonPath("['group1.a']").isEqualTo(1).jsonPath("['group1.b']") + .isEqualTo(1).jsonPath("['group2.a']").isEqualTo(1).jsonPath("group2_a") + .isEqualTo(1); + } + + @Test + public void regexGroupDot() throws Exception { + client.get().uri("/application/metrics?pattern=group%5B0-9%5D%5C..*").exchange() + .expectStatus().isOk().expectBody().jsonPath("$.length()").isEqualTo(3) + .jsonPath("['group1.a']").isEqualTo(1).jsonPath("['group1.b']") + .isEqualTo(1).jsonPath("['group2.a']").isEqualTo(1); + } + + @Test + public void regexGroup1() throws Exception { + client.get().uri("/application/metrics?pattern=group1%5C..*").exchange() + .expectStatus().isOk().expectBody().jsonPath("['group1.a']").isEqualTo(1) + .jsonPath("['group1.b']").isEqualTo(1).jsonPath("$.length()") + .isEqualTo(2); + } + + @Configuration + static class TestConfiguration { + + @Bean + public MetricsEndpoint endpoint() { + return new MetricsEndpoint(() -> Arrays.asList(new Metric<>("foo", 1), + new Metric<>("bar.png", 1), new Metric<>("group1.a", 1), + new Metric<>("group1.b", 1), new Metric<>("group2.a", 1), + new Metric<>("group2_a", 1), new Metric("baz", null))); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebEndpointsRunner.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebEndpointsRunner.java new file mode 100644 index 0000000000..43799b6fa0 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebEndpointsRunner.java @@ -0,0 +1,408 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed 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 + * + * http://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.springframework.boot.actuate.endpoint.web; + +import java.lang.reflect.Modifier; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.glassfish.jersey.server.ResourceConfig; +import org.junit.runner.Runner; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.Suite; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; + +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; +import org.springframework.web.util.DefaultUriBuilderFactory; +import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; + +/** + * A custom {@link Runner} that tests web endpoints that are made available over HTTP + * using Jersey, Spring MVC, and WebFlux. + *

+ * The following types can be automatically injected into static fields on the test class: + *

    + *
  • {@link WebTestClient}
  • + *
  • {@link ConfigurableApplicationContext}
  • + *
+ *

+ * The {@link PropertySource PropertySources} that belong to the application context's + * {@link org.springframework.core.env.Environment} are reset at the end of every test. + * This means that {@link TestPropertyValues} can be used in a test without affecting the + * {@code Environment} of other tests in the same class. + * + * @author Andy Wilkinson + */ +public class WebEndpointsRunner extends Suite { + + public WebEndpointsRunner(Class testClass) throws InitializationError { + super(testClass, createRunners(testClass)); + } + + private static List createRunners(Class clazz) throws InitializationError { + return Arrays.asList(new JerseyWebEndpointsRunner(clazz), + new MvcWebEndpointsRunner(clazz), new ReactiveWebEndpointsRunner(clazz)); + } + + private static class AbstractWebEndpointsRunner extends BlockJUnit4ClassRunner { + + private final TestContext testContext; + + private final String name; + + protected AbstractWebEndpointsRunner(Class klass, String name, + Function>, ConfigurableApplicationContext> contextLoader) + throws InitializationError { + super(klass); + this.name = name; + this.testContext = new TestContext(klass, contextLoader); + } + + @Override + protected final String getName() { + return this.name; + } + + @Override + protected String testName(FrameworkMethod method) { + return super.testName(method) + "[" + getName() + "]"; + } + + @Override + protected Statement withBeforeClasses(Statement statement) { + Statement delegate = super.withBeforeClasses(statement); + return new Statement() { + + @Override + public void evaluate() throws Throwable { + AbstractWebEndpointsRunner.this.testContext.beforeClass(); + delegate.evaluate(); + } + + }; + } + + @Override + protected Statement withAfterClasses(Statement statement) { + Statement delegate = super.withAfterClasses(statement); + return new Statement() { + + @Override + public void evaluate() throws Throwable { + try { + delegate.evaluate(); + } + finally { + AbstractWebEndpointsRunner.this.testContext.afterClass(); + } + } + + }; + } + + @Override + protected Statement withBefores(FrameworkMethod method, Object target, + Statement statement) { + Statement delegate = super.withBefores(method, target, statement); + return new Statement() { + + @Override + public void evaluate() throws Throwable { + AbstractWebEndpointsRunner.this.testContext.beforeTest(); + delegate.evaluate(); + } + + }; + } + + @Override + protected Statement withAfters(FrameworkMethod method, Object target, + Statement statement) { + Statement delegate = super.withAfters(method, target, statement); + return new Statement() { + + @Override + public void evaluate() throws Throwable { + try { + delegate.evaluate(); + } + finally { + AbstractWebEndpointsRunner.this.testContext.afterTest(); + } + } + + }; + } + + } + + private static final class MvcWebEndpointsRunner extends AbstractWebEndpointsRunner { + + private MvcWebEndpointsRunner(Class klass) throws InitializationError { + super(klass, "Spring MVC", (classes) -> { + AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(); + classes.add(MvcTestConfiguration.class); + context.register(classes.toArray(new Class[classes.size()])); + context.refresh(); + return context; + }); + } + + @Configuration + @ImportAutoConfiguration({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class, + ServletEndpointAutoConfiguration.class }) + static class MvcTestConfiguration { + + @Bean + public TomcatServletWebServerFactory tomcat() { + return new TomcatServletWebServerFactory(0); + } + + } + + } + + private static final class JerseyWebEndpointsRunner + extends AbstractWebEndpointsRunner { + + private JerseyWebEndpointsRunner(Class klass) throws InitializationError { + super(klass, "Jersey", (classes) -> { + AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(); + classes.add(JerseyAppConfiguration.class); + classes.add(JerseyInfrastructureConfiguration.class); + context.register(classes.toArray(new Class[classes.size()])); + context.refresh(); + return context; + }); + } + + @Configuration + @Import({ JacksonAutoConfiguration.class, JerseyAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class }) + static class JerseyInfrastructureConfiguration { + + @Bean + public TomcatServletWebServerFactory tomcat() { + return new TomcatServletWebServerFactory(0); + } + + } + + @Configuration + static class JerseyAppConfiguration { + + @Bean + public ResourceConfig resourceConfig() { + return new ResourceConfig(); + } + + } + + } + + private static final class ReactiveWebEndpointsRunner + extends AbstractWebEndpointsRunner { + + private ReactiveWebEndpointsRunner(Class klass) throws InitializationError { + super(klass, "Reactive", (classes) -> { + ReactiveWebServerApplicationContext context = new ReactiveWebServerApplicationContext(); + classes.add(ReactiveInfrastructureConfiguration.class); + context.register(classes.toArray(new Class[classes.size()])); + context.refresh(); + return context; + }); + } + + @Configuration + @ImportAutoConfiguration({ JacksonAutoConfiguration.class, + WebFluxAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class }) + static class ReactiveInfrastructureConfiguration + implements ApplicationListener { + + @Bean + public NettyReactiveWebServerFactory netty() { + return new NettyReactiveWebServerFactory(0); + } + + @Override + public void onApplicationEvent(WebServerInitializedEvent event) { + portHolder().setPort(event.getWebServer().getPort()); + } + + @Bean + public HttpHandler httpHandler(ApplicationContext applicationContext) { + return WebHttpHandlerBuilder.applicationContext(applicationContext) + .build(); + } + + @Bean + public PortHolder portHolder() { + return new PortHolder(); + } + + } + + } + + private static final class PortHolder { + + private int port; + + int getPort() { + return this.port; + } + + void setPort(int port) { + this.port = port; + } + + } + + private static final class TestContext { + + private final Class testClass; + + private final Function>, ConfigurableApplicationContext> applicationContextLoader; + + private ConfigurableApplicationContext applicationContext; + + private List> propertySources; + + TestContext(Class testClass, + Function>, ConfigurableApplicationContext> applicationContextLoader) { + this.testClass = testClass; + this.applicationContextLoader = applicationContextLoader; + } + + private void beforeClass() { + this.applicationContext = createApplicationContext(); + WebTestClient webTestClient = createWebTestClient(); + injectIfPossible(this.testClass, webTestClient); + injectIfPossible(this.testClass, this.applicationContext); + } + + private void beforeTest() { + capturePropertySources(); + } + + private void afterTest() { + restorePropertySources(); + } + + private void afterClass() { + if (this.applicationContext != null) { + this.applicationContext.close(); + } + } + + private ConfigurableApplicationContext createApplicationContext() { + return this.applicationContextLoader + .apply(new ArrayList<>(Stream.of(this.testClass.getDeclaredClasses()) + .filter((candidate) -> AnnotationUtils.findAnnotation( + candidate, Configuration.class) != null) + .collect(Collectors.toList()))); + } + + private WebTestClient createWebTestClient() { + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory( + "http://localhost:" + determinePort()); + uriBuilderFactory.setEncodingMode(EncodingMode.NONE); + WebTestClient webTestClient = WebTestClient.bindToServer() + .uriBuilderFactory(uriBuilderFactory) + .responseTimeout(Duration.ofSeconds(30)).build(); + return webTestClient; + } + + private int determinePort() { + if (this.applicationContext instanceof AnnotationConfigServletWebServerApplicationContext) { + return ((AnnotationConfigServletWebServerApplicationContext) this.applicationContext) + .getWebServer().getPort(); + } + return this.applicationContext.getBean(PortHolder.class).getPort(); + } + + private void injectIfPossible(Class target, Object value) { + ReflectionUtils.doWithFields(target, (field) -> { + if (Modifier.isStatic(field.getModifiers()) + && field.getType().isInstance(value)) { + ReflectionUtils.makeAccessible(field); + ReflectionUtils.setField(field, null, value); + } + }); + } + + private void capturePropertySources() { + this.propertySources = new ArrayList<>(); + this.applicationContext.getEnvironment().getPropertySources() + .forEach(this.propertySources::add); + } + + private void restorePropertySources() { + List names = new ArrayList<>(); + MutablePropertySources propertySources = this.applicationContext + .getEnvironment().getPropertySources(); + propertySources + .forEach((propertySource) -> names.add(propertySource.getName())); + names.forEach(propertySources::remove); + this.propertySources.forEach(propertySources::addLast); + } + + } + +} diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 016604ebc0..563c71fe9c 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1085,63 +1085,31 @@ content into your application; rather pick only the properties that you need. # ENDPOINTS ({sc-spring-boot-actuator}/endpoint/AbstractEndpoint.{sc-ext}[AbstractEndpoint] subclasses) endpoints.enabled=true # Enable endpoints. endpoints.auditevents.enabled= # Enable the endpoint. - endpoints.auditevents.path= # Endpoint path. endpoints.autoconfig.enabled= # Enable the endpoint. - endpoints.autoconfig.id= # Endpoint identifier. - endpoints.autoconfig.path= # Endpoint path. endpoints.beans.enabled= # Enable the endpoint. - endpoints.beans.id= # Endpoint identifier. - endpoints.beans.path= # Endpoint path. endpoints.configprops.enabled= # Enable the endpoint. - endpoints.configprops.id= # Endpoint identifier. endpoints.configprops.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services # Keys that should be sanitized. Keys can be simple strings that the property ends with or regex expressions. - endpoints.configprops.path= # Endpoint path. - endpoints.docs.curies.enabled=false # Enable the curie generation. - endpoints.docs.enabled=true # Enable actuator docs endpoint. - endpoints.docs.path=/docs # - endpoints.dump.enabled= # Enable the endpoint. - endpoints.dump.id= # Endpoint identifier. - endpoints.dump.path= # Endpoint path. endpoints.env.enabled= # Enable the endpoint. - endpoints.env.id= # Endpoint identifier. endpoints.env.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services # Keys that should be sanitized. Keys can be simple strings that the property ends with or regex expressions. - endpoints.env.path= # Endpoint path. endpoints.flyway.enabled= # Enable the endpoint. - endpoints.flyway.id= # Endpoint identifier. endpoints.health.enabled= # Enable the endpoint. - endpoints.health.id= # Endpoint identifier. endpoints.health.mapping.*= # Mapping of health statuses to HttpStatus codes. By default, registered health statuses map to sensible defaults (i.e. UP maps to 200). - endpoints.health.path= # Endpoint path. - endpoints.health.time-to-live=1000 # Time to live for cached result, in milliseconds. endpoints.heapdump.enabled= # Enable the endpoint. - endpoints.heapdump.path= # Endpoint path. endpoints.info.enabled= # Enable the endpoint. - endpoints.info.id= # Endpoint identifier. - endpoints.info.path= # Endpoint path. endpoints.liquibase.enabled= # Enable the endpoint. - endpoints.liquibase.id= # Endpoint identifier. endpoints.logfile.enabled=true # Enable the endpoint. endpoints.logfile.external-file= # External Logfile to be accessed. endpoints.logfile.path=/logfile # Endpoint URL path. endpoints.loggers.enabled=true # Enable the endpoint. - endpoints.loggers.id= # Endpoint identifier. - endpoints.loggers.path=/logfile # Endpoint path. endpoints.mappings.enabled= # Enable the endpoint. - endpoints.mappings.id= # Endpoint identifier. - endpoints.mappings.path= # Endpoint path. endpoints.metrics.enabled= # Enable the endpoint. endpoints.metrics.filter.enabled=true # Enable the metrics servlet filter. endpoints.metrics.filter.gauge-submissions=merged # Http filter gauge submissions (merged, per-http-method) endpoints.metrics.filter.counter-submissions=merged # Http filter counter submissions (merged, per-http-method) - endpoints.metrics.id= # Endpoint identifier. - endpoints.metrics.path= # Endpoint path. endpoints.shutdown.enabled= # Enable the endpoint. - endpoints.shutdown.id= # Endpoint identifier. - endpoints.shutdown.path= # Endpoint path. + endpoints.threaddump.enabled= # Enable the endpoint. endpoints.trace.enabled= # Enable the endpoint. endpoints.trace.filter.enabled=true # Enable the trace servlet filter. - endpoints.trace.id= # Endpoint identifier. - endpoints.trace.path= # Endpoint path. # ENDPOINTS CORS CONFIGURATION ({sc-spring-boot-actuator}/autoconfigure/EndpointCorsProperties.{sc-ext}[EndpointCorsProperties]) endpoints.cors.allow-credentials= # Set whether credentials are supported. When not set, credentials are not supported. diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 8b432fb424..764aa98648 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -65,8 +65,8 @@ rules of thumb: `+@Conditional*+` annotations to find out what features they enable and when. Add `--debug` to the command line or a System property `-Ddebug` to get a log on the console of all the auto-configuration decisions that were made in your app. In a running - Actuator app look at the `autoconfig` endpoint ('`/autoconfig`' or the JMX equivalent) for - the same information. + Actuator app look at the `autoconfig` endpoint (`/application/autoconfig` or the JMX + equivalent) for the same information. * Look for classes that are `@ConfigurationProperties` (e.g. {sc-spring-boot-autoconfigure}/web/ServerProperties.{sc-ext}[`ServerProperties`]) and read from there the available external configuration options. The diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 15402b9176..a459d12eb9 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -82,9 +82,6 @@ The following technology agnostic endpoints are available: |`configprops` |Displays a collated list of all `@ConfigurationProperties`. -|`dump` -|Performs a thread dump. - |`env` |Exposes properties from Spring's `ConfigurableEnvironment`. @@ -112,11 +109,15 @@ The following technology agnostic endpoints are available: |`shutdown` |Allows the application to be gracefully shutdown (not enabled by default). +|`threaddump` +|Performs a thread dump. + |`trace` |Displays trace information (by default the last 100 HTTP requests). |=== -If you are using Spring MVC, the following additional endpoints can also be used: +If your application is a web application (Spring MVC, Spring WebFlux, or Jersey), the +following additional endpoints can also be used: [cols="2,5"] |=== @@ -234,8 +235,9 @@ from `/management`. === CORS support http://en.wikipedia.org/wiki/Cross-origin_resource_sharing[Cross-origin resource sharing] (CORS) is a http://www.w3.org/TR/cors/[W3C specification] that allows you to specify in a -flexible way what kind of cross domain requests are authorized. Actuator's MVC endpoints -can be configured to support such scenarios. +flexible way what kind of cross domain requests are authorized. If you are using Spring +MVC or Spring WebFlux, Actuator's web endpoints can be configured to support such +scenarios. CORS support is disabled by default and is only enabled once the `endpoints.cors.allowed-origins` property has been set. The configuration below permits @@ -254,18 +256,15 @@ for a complete list of options. [[production-ready-customizing-endpoints-programmatically]] === Adding custom endpoints -If you add a `@Bean` of type `Endpoint` then it will automatically be exposed over JMX and -HTTP (if there is an server available). An HTTP endpoints can be customized further by -creating a bean of type `MvcEndpoint`. Your `MvcEndpoint` is not a `@Controller` but it -can use `@RequestMapping` (and `@Managed*`) to expose resources. +If you add a `@Bean` annotated with `@Endpoint`, any methods annotated with +`@ReadOperation` or `@WriteOperation` will automatically be exposed over JMX and, in a web +application, over HTTP as well. TIP: If you are doing this as a library feature consider adding a configuration class annotated with `@ManagementContextConfiguration` to `/META-INF/spring.factories` under the key `org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration`. If -you do that then the endpoint will move to a child context with all the other MVC -endpoints if your users ask for a separate management port or address. A configuration -declared this way can be a `WebConfigurerAdapter` if it wants to add static resources (for -instance) to the management endpoints. +you do that then the endpoint will move to a child context with all the other web +endpoints endpoints if your users ask for a separate management port or address. @@ -289,13 +288,6 @@ overall health status. If no `HealthIndicator` returns a status that is known to -=== Security with HealthIndicators -Health responses are also cached to prevent "`denial of service`" attacks. Use the -`endpoints.health.time-to-live` property if you want to change the default cache period -of 1000 milliseconds. - - - ==== Auto-configured HealthIndicators The following `HealthIndicators` are auto-configured by Spring Boot when appropriate: @@ -561,7 +553,8 @@ is exposed as `/application/health`. === Customizing the management endpoint paths Sometimes it is useful to customize the prefix for the management endpoints. For example, your application might already use `/application` for another purpose. -You can use the `management.context-path` property to change the prefix for your management endpoint: +You can use the `management.context-path` property to change the prefix for your +management endpoint: [source,properties,indent=0] ---- @@ -571,21 +564,9 @@ You can use the `management.context-path` property to change the prefix for your The `application.properties` example above will change the endpoint from `/application/{id}` to `/manage/{id}` (e.g. `/manage/info`). -You can also change the "`path`" of an endpoint (using `endpoints.{name}.path`) which then -changes the default resource path for the MVC endpoint. There is no validation on -those values (so you can use anything that is legal in a URL path). For example, to change -the location of the `/health` endpoint to `/ping/me` you can set -`endpoints.health.path=/ping/me`. - NOTE: Even if an endpoint path is configured separately, it is still relative to the `management.context-path`. -TIP: If you provide a custom `MvcEndpoint` remember to include a settable `path` property, -and default it to `/{id}` if you want your code to behave like the standard MVC endpoints. -(Take a look at the `HealthMvcEndpoint` to see how you might do that.) If your custom -endpoint is an `Endpoint` (not an `MvcEndpoint`) then Spring Boot will take care of the -path for you. - [[production-ready-customizing-management-server-port]] @@ -686,10 +667,7 @@ The information exposed by the health endpoint varies depending on whether or no accessed anonymously, and whether or not the enclosing application is secure. By default, when accessed anonymously in a secure application, any details about the server's health are hidden and the endpoint will simply indicate whether or not the server -is up or down. Furthermore the response is cached for a configurable period to prevent the -endpoint being used in a denial of service attack. The `endpoints.health.time-to-live` -property is used to configure the caching period in milliseconds. It defaults to 1000, -i.e. one second. +is up or down. Sample summarized HTTP response (default for anonymous request): @@ -1429,7 +1407,7 @@ customize the file name and path via the `Writer` constructor. == Cloud Foundry support Spring Boot's actuator module includes additional support that is activated when you deploy to a compatible Cloud Foundry instance. The `/cloudfoundryapplication` path -provides an alternative secured route to all `NamedMvcEndpoint` beans. +provides an alternative secured route to all `@Endpoint` beans. The extended support allows Cloud Foundry management UIs (such as the web application that you can use to view deployed applications) to be augmented with Spring diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 0b0f537879..5db4040d1a 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1219,8 +1219,8 @@ early instantiation. There is a validation sample] so you can see how to set things up. TIP: The `spring-boot-actuator` module includes an endpoint that exposes all -`@ConfigurationProperties` beans. Simply point your web browser to `/configprops` -or use the equivalent JMX endpoint. See the +`@ConfigurationProperties` beans. Simply point your web browser to +`/application/configprops` or use the equivalent JMX endpoint. See the _<>_. section for details. diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/SampleController.java b/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/SampleController.java index 3cd2396e5e..838c3ddbf6 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/SampleController.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/SampleController.java @@ -24,6 +24,7 @@ import java.util.Map; import org.hibernate.validator.constraints.NotBlank; import org.springframework.context.annotation.Description; +import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -41,14 +42,14 @@ public class SampleController { this.helloWorldService = helloWorldService; } - @GetMapping("/") + @GetMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Map hello() { return Collections.singletonMap("message", this.helloWorldService.getHelloMessage()); } - @PostMapping("/") + @PostMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Map olleh(@Validated Message message) { Map model = new LinkedHashMap<>(); diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java index 7c126fd539..8ca1ca437a 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java @@ -185,7 +185,8 @@ public class SampleActuatorApplicationTests { @Test public void traceWithParameterMap() throws Exception { - this.restTemplate.getForEntity("/application/health?param1=value1", String.class); + this.restTemplate.withBasicAuth("user", getPassword()) + .getForEntity("/application/health?param1=value1", String.class); @SuppressWarnings("rawtypes") ResponseEntity entity = this.restTemplate .withBasicAuth("user", getPassword()) diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ServletPathInsecureSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ServletPathInsecureSampleActuatorApplicationTests.java index cc3a44b6a3..f58f3b37a9 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ServletPathInsecureSampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ServletPathInsecureSampleActuatorApplicationTests.java @@ -62,8 +62,8 @@ public class ServletPathInsecureSampleActuatorApplicationTests { @Test public void testMetricsIsSecure() throws Exception { @SuppressWarnings("rawtypes") - ResponseEntity entity = this.restTemplate.getForEntity("/spring//application/metrics", - Map.class); + ResponseEntity entity = this.restTemplate + .getForEntity("/spring/application/metrics", Map.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); } diff --git a/spring-boot-samples/spring-boot-sample-web-method-security/src/test/java/sample/security/method/SampleMethodSecurityApplicationTests.java b/spring-boot-samples/spring-boot-sample-web-method-security/src/test/java/sample/security/method/SampleMethodSecurityApplicationTests.java index 818f729f12..27087525f2 100644 --- a/spring-boot-samples/spring-boot-sample-web-method-security/src/test/java/sample/security/method/SampleMethodSecurityApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-web-method-security/src/test/java/sample/security/method/SampleMethodSecurityApplicationTests.java @@ -105,8 +105,8 @@ public class SampleMethodSecurityApplicationTests { @Test public void testManagementProtected() throws Exception { - ResponseEntity entity = this.restTemplate.getForEntity("/application/beans", - String.class); + ResponseEntity entity = this.restTemplate + .getForEntity("/application/beans", String.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); } @@ -116,8 +116,8 @@ public class SampleMethodSecurityApplicationTests { "admin", "admin"); this.restTemplate.getRestTemplate().getInterceptors().add(basicAuthInterceptor); try { - ResponseEntity entity = this.restTemplate.getForEntity("/application/beans", - String.class); + ResponseEntity entity = this.restTemplate + .getForEntity("/application/beans", String.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); } finally { @@ -126,22 +126,6 @@ public class SampleMethodSecurityApplicationTests { } } - @Test - public void testManagementUnauthorizedAccess() throws Exception { - BasicAuthorizationInterceptor basicAuthInterceptor = new BasicAuthorizationInterceptor( - "user", "user"); - this.restTemplate.getRestTemplate().getInterceptors().add(basicAuthInterceptor); - try { - ResponseEntity entity = this.restTemplate.getForEntity("/application/beans", - String.class); - assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - } - finally { - this.restTemplate.getRestTemplate().getInterceptors() - .remove(basicAuthInterceptor); - } - } - private void getCsrf(MultiValueMap form, HttpHeaders headers) { ResponseEntity page = this.restTemplate.getForEntity("/login", String.class); diff --git a/spring-boot/src/main/java/org/springframework/boot/endpoint/web/mvc/WebEndpointServletHandlerMapping.java b/spring-boot/src/main/java/org/springframework/boot/endpoint/web/mvc/WebEndpointServletHandlerMapping.java index 77065dd8c1..59af550ff4 100644 --- a/spring-boot/src/main/java/org/springframework/boot/endpoint/web/mvc/WebEndpointServletHandlerMapping.java +++ b/spring-boot/src/main/java/org/springframework/boot/endpoint/web/mvc/WebEndpointServletHandlerMapping.java @@ -106,6 +106,14 @@ public class WebEndpointServletHandlerMapping extends RequestMappingInfoHandlerM setOrder(-100); } + public Collection> getEndpoints() { + return this.webEndpoints; + } + + public String getEndpointPath() { + return this.endpointPath; + } + @Override protected void initHandlerMethods() { this.webEndpoints.stream()