+ * There is deliberately nothing here beyond the Spring Boot entry point: no {@code WebApplication}, + * no page, no Wicket configuration. That is the point of the example — run this class and the + * starter alone gives you a working Wicket application on + * http://localhost:8080, serving its built-in placeholder page. + *
+ *+ * The patterns you would actually write in your own application are demonstrated by the + * integration tests in {@code src/test/java}, where they are also verified on every build. See this + * module's README for a map of which test shows what. + *
+ * + * @author Daniel Bartl + */ +@SpringBootApplication +public class ExampleApplication { + + public static void main(String[] args) { + SpringApplication.run(ExampleApplication.class, args); + } +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/main/resources/application.properties b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/main/resources/application.properties new file mode 100644 index 0000000000..2d28e4f58a --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/main/resources/application.properties @@ -0,0 +1,13 @@ +# None of these are required -- the starter works with no configuration at all. They are set here +# to show that externalised wicket.* properties reach Wicket, and are asserted by +# WicketPropertiesBindingTest. +# +# DEVELOPMENT is Wicket's default and enables its development-time checks; switch to DEPLOYMENT in +# production, typically via a Spring profile. +wicket.configuration=DEVELOPMENT +# /* is also the default; shown here to make the mapping explicit. +wicket.filter-path=/* +wicket.filter-name=wicket-example-filter + +# Disable banner for cleaner startup logs +spring.main.banner-mode=off diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/DefaultWebApplicationTest.java b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/DefaultWebApplicationTest.java new file mode 100644 index 0000000000..bd9ec21ce6 --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/DefaultWebApplicationTest.java @@ -0,0 +1,50 @@ +/* + * 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.wicketstuff.springboot.starter.example; + +import org.apache.wicket.protocol.http.WebApplication; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.wicketstuff.springboot.starter.DefaultWebApplication; +import org.wicketstuff.springboot.starter.WicketAutoConfiguration; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Verifies that the starter auto-configures {@link DefaultWebApplication} + * when no custom {@link WebApplication} bean is present in the application context. + * + * @author Daniel Bartl + */ +@SpringBootTest(classes = DefaultWebApplicationTest.TestConfig.class) +public class DefaultWebApplicationTest { + + @Configuration + @ImportAutoConfiguration(WicketAutoConfiguration.class) + static class TestConfig { + } + + @Autowired + private WebApplication webApplication; + + @Test + public void testDefaultWebApplicationIsLoaded() { + assertNotNull(webApplication); + assertTrue(webApplication instanceof DefaultWebApplication); + } +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/ExampleApplicationTest.java b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/ExampleApplicationTest.java new file mode 100644 index 0000000000..277172b100 --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/ExampleApplicationTest.java @@ -0,0 +1,38 @@ +/* + * 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.wicketstuff.springboot.starter.example; + +import org.apache.wicket.protocol.http.WebApplication; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Basic Spring Boot context integration test. + * + * @author Daniel Bartl + */ +@SpringBootTest +public class ExampleApplicationTest { + + @Autowired + private WebApplication webApplication; + + @Test + public void contextLoads() { + assertNotNull(webApplication); + } +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/GreetingService.java b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/GreetingService.java new file mode 100644 index 0000000000..83ef9d8af3 --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/GreetingService.java @@ -0,0 +1,22 @@ +/* + * 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.wicketstuff.springboot.starter.example; + +/** + * @author Daniel Bartl + */ +interface GreetingService { + + String greet(); +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/QuickstartHomePage.java b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/QuickstartHomePage.java new file mode 100644 index 0000000000..ab90ef1fe3 --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/QuickstartHomePage.java @@ -0,0 +1,35 @@ +/* + * 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.wicketstuff.springboot.starter.example; + +import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.spring.injection.annot.SpringBean; + +/** + * Mirrors the custom {@code HomePage} from the README's Quickstart section: a Wicket page + * that has a Spring-managed service injected via {@code @SpringBean}. + * + * @author Daniel Bartl + */ +public class QuickstartHomePage extends WebPage { + private static final long serialVersionUID = 1L; + + @SpringBean + private GreetingService greetingService; + + public QuickstartHomePage() { + add(new Label("message", greetingService.greet())); + } +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/QuickstartWebApplication.java b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/QuickstartWebApplication.java new file mode 100644 index 0000000000..10a62e421d --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/QuickstartWebApplication.java @@ -0,0 +1,32 @@ +/* + * 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.wicketstuff.springboot.starter.example; + +import org.apache.wicket.Page; +import org.apache.wicket.protocol.http.WebApplication; + +/** + * Mirrors the custom {@code WebApplication} from the README's Quickstart section: a + * hand-written subclass that the starter's {@code @ConditionalOnMissingBean} default backs + * off for, once it is registered as a Spring bean. + * + * @author Daniel Bartl + */ +class QuickstartWebApplication extends WebApplication { + + @Override + public Class extends Page> getHomePage() { + return QuickstartHomePage.class; + } +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/SpringBeanInjectionDuringInitTest.java b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/SpringBeanInjectionDuringInitTest.java new file mode 100644 index 0000000000..998af058b7 --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/SpringBeanInjectionDuringInitTest.java @@ -0,0 +1,83 @@ +/* + * 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.wicketstuff.springboot.starter.example; + +import org.apache.wicket.injection.Injector; +import org.apache.wicket.protocol.http.WebApplication; +import org.apache.wicket.spring.injection.annot.SpringBean; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Spring Boot initialises the embedded container's filters, and with them the Wicket application, + * while the application context is still refreshing. Spring injection must already be in place at + * that point, so that {@link WebApplication#init()} can rely on {@code @SpringBean}, for example to + * inject a non-component object through {@link Injector#get()}. + * + * @author Daniel Bartl + */ +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "wicket.filter-name=injection-during-init-filter") +@Import(SpringBeanInjectionDuringInitTest.InitInjectionConfig.class) +class SpringBeanInjectionDuringInitTest { + + @Autowired + private InitInjectingWebApplication application; + + @Test + void springBeansCanBeInjectedFromTheApplicationsInit() { + assertThat(application.greetingSeenDuringInit).isEqualTo("Hello from Spring, Wicket!"); + } + + @TestConfiguration + static class InitInjectionConfig { + + @Bean + InitInjectingWebApplication webApplication() { + return new InitInjectingWebApplication(); + } + + @Bean + GreetingService greetingService() { + return () -> "Hello from Spring, Wicket!"; + } + } + + static class InitInjectingWebApplication extends QuickstartWebApplication { + + private String greetingSeenDuringInit; + + @Override + protected void init() { + super.init(); + + GreetingHolder holder = new GreetingHolder(); + Injector.get().inject(holder); + greetingSeenDuringInit = holder.greetingService.greet(); + } + } + + static class GreetingHolder { + + @SpringBean + private GreetingService greetingService; + } +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/SpringBeanInjectionQuickstartTest.java b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/SpringBeanInjectionQuickstartTest.java new file mode 100644 index 0000000000..06c675c5d5 --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/SpringBeanInjectionQuickstartTest.java @@ -0,0 +1,64 @@ +/* + * 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.wicketstuff.springboot.starter.example; + +import org.apache.wicket.protocol.http.WebApplication; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.resttestclient.TestRestTemplate; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * End-to-end proof of the README's Quickstart pattern: registering a custom + * {@link WebApplication} bean makes the starter's {@code @ConditionalOnMissingBean} default + * back off, and {@code @SpringBean} injects a Spring-managed service into the page that gets + * rendered through a real embedded servlet container. + * + * @author Daniel Bartl + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestRestTemplate +@Import(SpringBeanInjectionQuickstartTest.QuickstartConfig.class) +class SpringBeanInjectionQuickstartTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void rendersCustomHomePageWithTheSpringInjectedGreeting() { + String body = restTemplate.getForObject("/", String.class); + + assertThat(body).contains("Hello from Spring, Wicket!"); + } + + @TestConfiguration + static class QuickstartConfig { + + @Bean + WebApplication webApplication() { + return new QuickstartWebApplication(); + } + + @Bean + GreetingService greetingService() { + return () -> "Hello from Spring, Wicket!"; + } + } +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/SpringBeanInjectionWithCustomFilterRegistrationTest.java b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/SpringBeanInjectionWithCustomFilterRegistrationTest.java new file mode 100644 index 0000000000..9ef1b573c3 --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter-examples/src/test/java/org/wicketstuff/springboot/starter/example/SpringBeanInjectionWithCustomFilterRegistrationTest.java @@ -0,0 +1,80 @@ +/* + * 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.wicketstuff.springboot.starter.example; + +import org.apache.wicket.protocol.http.WebApplication; +import org.apache.wicket.protocol.http.WicketFilter; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.resttestclient.TestRestTemplate; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * An application that supplies its own {@link FilterRegistrationBean} makes the starter's + * registration back off. Spring injection must keep working regardless, which is why the + * {@code SpringComponentInjector} is bound to the {@link WebApplication} bean rather than to the + * filter registration. + * + * @author Daniel Bartl + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestRestTemplate +@Import(SpringBeanInjectionWithCustomFilterRegistrationTest.CustomRegistrationConfig.class) +class SpringBeanInjectionWithCustomFilterRegistrationTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void stillInjectsSpringBeansWhenTheFilterRegistrationIsOverridden() { + ResponseEntity+ * This module deliberately declares nothing but the starter at compile scope, so the embedded + * servlet container exercised here can only have arrived transitively from the starter itself. + * Without that, a project generated by Spring Initializr with just "Wicket" selected boots as a + * plain non-web application and silently serves nothing. + *
+ *+ * A {@code bannedDependencies} enforcer rule in this module's pom keeps that precondition true: + * declaring a Spring Boot starter here at compile scope would make this test pass without the + * starter supplying anything. + *
+ *+ * The filter name is overridden because Wicket keys its application registry by it, so two tests + * that each start a real container in the same JVM collide on the default name. + *
+ * + * @author Daniel Bartl + */ +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "wicket.filter-name=self-sufficiency-filter") +@AutoConfigureTestRestTemplate +class StarterSelfSufficiencyTest { + + @Autowired + private ApplicationContext context; + + @Autowired + private TestRestTemplate restTemplate; + + /** + * Asserted on the class name rather than the type so that this class does not itself need a + * servlet-only import, which would make it fail to compile rather than fail as a test. + */ + @Test + void theStarterAloneBootsAServletWebApplication() { + assertThat(context.getClass().getName()).endsWith("ServletWebServerApplicationContext"); + } + + @Test + void theStarterAloneServesTheDefaultHomePage() { + ResponseEntity+ * Spring Boot initialises the embedded servlet container's filters, and with them the Wicket + * application, while the application context is still refreshing and before ordinary singletons + * exist. Registering the injector at bean creation is what makes {@code @SpringBean} (and + * {@link org.apache.wicket.injection.Injector#get()}) available inside + * {@link WebApplication#init()}, whichever filter registration ends up initialising the + * application. + *
+ * + * @author Daniel Bartl + */ +class SpringComponentInjectorRegistrar implements BeanPostProcessor, ApplicationContextAware { + + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof WebApplication webApplication) { + webApplication.getComponentInstantiationListeners() + .add(new SpringComponentInjector(webApplication, applicationContext)); + } + return bean; + } +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/java/org/wicketstuff/springboot/starter/WicketAutoConfiguration.java b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/java/org/wicketstuff/springboot/starter/WicketAutoConfiguration.java new file mode 100644 index 0000000000..e10b79537a --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/java/org/wicketstuff/springboot/starter/WicketAutoConfiguration.java @@ -0,0 +1,130 @@ +/* + * 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.wicketstuff.springboot.starter; + +import org.apache.wicket.protocol.http.WebApplication; +import org.apache.wicket.protocol.http.WicketFilter; +import org.apache.wicket.spring.injection.annot.SpringComponentInjector; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingFilterBean; +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.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; + +/** + * Spring Boot autoconfiguration for Apache Wicket. + *+ * This autoconfiguration is activated when Wicket's core classes are present on the classpath. + * It registers Wicket's {@link WicketFilter} into the embedded servlet container and binds the + * registered {@link WebApplication} bean. Additionally, it registers the {@link SpringComponentInjector} + * to enable Spring bean injection (via {@link org.apache.wicket.spring.injection.annot.SpringBean}) + * inside Wicket pages and components. + *
+ *+ * Set {@code wicket.enabled=false} to switch the whole autoconfiguration off. + *
+ * + * @author Daniel Bartl + */ +@AutoConfiguration +@ConditionalOnWebApplication(type = Type.SERVLET) +@ConditionalOnClass({WebApplication.class, WicketFilter.class}) +@ConditionalOnProperty(prefix = "wicket", name = "enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(WicketProperties.class) +public class WicketAutoConfiguration { + + /** + * Registers a default {@link WebApplication} bean if the application does not define one. + * This registers the {@link DefaultWebApplication} which serves the starter's {@link DefaultHomePage}. + * + * @return the default WebApplication + */ + @Bean + @ConditionalOnMissingBean(WebApplication.class) + public WebApplication webApplication() { + return new DefaultWebApplication(); + } + + /** + * Registers a {@link SpringComponentInjector} on the {@link WebApplication} bean so that Wicket + * pages and components can inject Spring beans via + * {@link org.apache.wicket.spring.injection.annot.SpringBean}. + *+ * The injector is added when the {@link WebApplication} bean is created, not later as a bean of + * its own: the servlet container initialises the Wicket filter, and so runs + * {@link WebApplication#init()}, before ordinary singletons exist. Being bound to the + * {@link WebApplication} rather than to the filter registration also keeps injection working + * for an application that supplies its own {@link FilterRegistrationBean}. + *
+ *+ * Backs off when the application defines its own {@link SpringComponentInjector} bean. + *
+ * + * @return the post-processor registering the injector on each {@link WebApplication} bean + */ + @Bean + @ConditionalOnMissingBean(SpringComponentInjector.class) + static SpringComponentInjectorRegistrar springComponentInjectorRegistrar() { + return new SpringComponentInjectorRegistrar(); + } + + /** + * Creates and configures the {@link FilterRegistrationBean} for the {@link WicketFilter}. + *+ * Backs off when the application already registers a {@link WicketFilter}, either wrapped in its + * own {@link FilterRegistrationBean} or declared as a plain filter bean, so that Wicket is never + * mapped twice. + *
+ * + * @param webApplication the auto-discovered Wicket WebApplication subclass bean + * @param properties the externalized Wicket properties + * @return the configured FilterRegistrationBean for WicketFilter + */ + @Bean + @ConditionalOnMissingFilterBean(WicketFilter.class) + public FilterRegistrationBean+ * These properties can be customized in application configuration files (e.g., {@code application.properties} + * or {@code application.yml}) under the {@code wicket} prefix. + *
+ * + * @author Daniel Bartl + */ +@ConfigurationProperties(prefix = "wicket") +public class WicketProperties { + + /** + * URL mapping pattern for the Wicket Filter. + *+ * Determines which incoming requests are processed by Wicket. Default is {@code "/*"}. + *
+ */ + private String filterPath = "/*"; + + /** + * Servlet name of the registered Wicket Filter. + *+ * Helps identify the filter in registration logs and configuration contexts. Default is {@code "wicket-filter"}. + *
+ */ + private String filterName = "wicket-filter"; + + /** + * Wicket execution configuration type. + *+ * Can be set to {@link RuntimeConfigurationType#DEVELOPMENT} or {@link RuntimeConfigurationType#DEPLOYMENT}. + * Default is {@link RuntimeConfigurationType#DEVELOPMENT}. + *
+ */ + private RuntimeConfigurationType configuration = RuntimeConfigurationType.DEVELOPMENT; + + /** + * Order of the Wicket Filter within the servlet filter chain. + *+ * Lower values run earlier. Default is {@link Ordered#LOWEST_PRECEDENCE}, so Wicket runs after + * every other filter, e.g. after Spring Security's filter chain. + *
+ */ + private int filterOrder = Ordered.LOWEST_PRECEDENCE; + + /** + * Gets the URL mapping pattern for the Wicket Filter. + * + * @return the filter URL mapping path pattern + */ + public String getFilterPath() { + return filterPath; + } + + /** + * Sets the URL mapping pattern for the Wicket Filter. + * + * @param filterPath the filter URL mapping path pattern to set + */ + public void setFilterPath(String filterPath) { + this.filterPath = filterPath; + } + + /** + * Gets the servlet name of the registered Wicket Filter. + * + * @return the servlet filter name + */ + public String getFilterName() { + return filterName; + } + + /** + * Sets the servlet name of the registered Wicket Filter. + * + * @param filterName the servlet filter name to set + */ + public void setFilterName(String filterName) { + this.filterName = filterName; + } + + /** + * Gets the Wicket execution configuration type. + * + * @return the configuration type + */ + public RuntimeConfigurationType getConfiguration() { + return configuration; + } + + /** + * Sets the Wicket execution configuration type. + * + * @param configuration the configuration type to set (DEVELOPMENT or DEPLOYMENT) + */ + public void setConfiguration(RuntimeConfigurationType configuration) { + this.configuration = configuration; + } + + /** + * Gets the order of the Wicket Filter within the servlet filter chain. + * + * @return the filter order + */ + public int getFilterOrder() { + return filterOrder; + } + + /** + * Sets the order of the Wicket Filter within the servlet filter chain. + * + * @param filterOrder the filter order to set; lower values run earlier + */ + public void setFilterOrder(int filterOrder) { + this.filterOrder = filterOrder; + } +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000000..e53d54f497 --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,14 @@ +{ + "properties": [ + { + "name": "wicket.enabled", + "type": "java.lang.Boolean", + "description": "Whether the Wicket auto-configuration is enabled. When false, no WebApplication, SpringComponentInjector or Wicket filter registration is contributed.", + "defaultValue": true + }, + { + "name": "wicket.filter-order", + "defaultValue": 2147483647 + } + ] +} diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/META-INF/spring-devtools.properties b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000000..c9c8e021ac --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1,13 @@ +# Loads Wicket and wicketstuff jars (including this starter) in Spring Boot DevTools' restart +# classloader instead of its base classloader. +# +# Wicket deserializes pages from its page store with an ObjectInputStream that resolves classes +# through the nearest application classloader on the call stack. With Wicket in the base +# classloader, that resolves the application's page classes from the base classloader too, while +# @SpringBean proxies are rebuilt for the restart classloader's classes: restoring a stored page +# (the back button, or any page after a restart) then fails with a ClassCastException. +# +# Every jar that references Wicket types has to move together, or the two classloaders mismatch +# the other way round. +restart.include.wicket=/wicket-[\\w\\d-\\.]+\\.jar +restart.include.wicketstuff=/wicketstuff-[\\w\\d-\\.]+\\.jar diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..564dc1ac5d --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.wicketstuff.springboot.starter.WicketAutoConfiguration diff --git a/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/org/wicketstuff/springboot/starter/DefaultHomePage.html b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/org/wicketstuff/springboot/starter/DefaultHomePage.html new file mode 100644 index 0000000000..5f1f88064b --- /dev/null +++ b/wicketstuff-spring-boot-starter-parent/wicketstuff-spring-boot-starter/src/main/resources/org/wicketstuff/springboot/starter/DefaultHomePage.html @@ -0,0 +1,56 @@ + + + + + +
+ This is the default page served by the Wicket Spring Boot Starter, running on
+ Apache Wicket WebApplication
+ bean yet.
+
+ Replace this page by registering your own Wicket application as a Spring bean: +
+WebApplication subclass annotated with @Component.getHomePage() to your own WebPage.@SpringBean.+ See the starter's + README + for the full Quickstart, or the + Apache Wicket documentation + to learn more about building pages and components. +
++ * wicketstuff-core pins several libraries that Spring Boot also manages, and those inherited pins + * win over the imported {@code spring-boot-dependencies} BOM. The parent pom therefore overrides + * them with the versions Spring Boot ships, so the starter is built and tested against what its + * users actually run. This test fails as soon as an override drifts from the BOM, e.g. after + * Spring Boot is bumped or a dependency update touches one of the overrides. + *
+ *
+ * The overrides reach this test as {@code aligned.