├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── acme │ │ └── sample │ │ ├── SpringBootFacesApplication.java │ │ ├── jsf │ │ └── FacesViewScope.java │ │ └── web │ │ ├── FormBean.java │ │ └── FormController.java ├── resources │ └── application.yml └── webapp │ ├── WEB-INF │ ├── faces-config.xml │ └── web.xml │ └── index.xhtml └── test └── java └── org └── acme └── sample └── SpringBootFacesApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled 2 | *.class 3 | *.jar 4 | *.war 5 | 6 | # Maven 7 | /target 8 | 9 | # Eclipse 10 | /.classpath 11 | /.project 12 | /.settings 13 | /.metadata 14 | 15 | # Git 16 | !.gitignore 17 | !.gitkeep 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ATTENTION: I do not recommend this example anymore! Better use [JoinFaces](http://joinfaces.org/) 2 | 3 | Spring Boot With JSF Sample 4 | ================================================================= 5 | 6 | * Shows how to configure JSF 2.2.x with Spring Boot 1.2.x on Servlet API 3.x 7 | 8 | ### Running 9 | 10 | This setup can run in all possible ways: 11 | 12 | * Either deploy on Tomcat inside Eclipse 13 | * Or execute `SpringBootFacesApplication` as Java application 14 | * Or run `mvn spring-boot:run` 15 | * Or run `mvn package` and then: 16 | * either deploy `target\projectname.war` 17 | * or run `java -jar target\projectname.war` 18 | 19 | ### More things shown 20 | 21 | * How to configure Faces View Scope with Spring 22 | * How to include PrimeFaces 23 | * How to include a custom theme for PrimeFaces (in this case primefaces-bootstrap-theme) 24 | * How to include Twitter Bootstrap CSS for responsive layout 25 | * Spring Boot Actuator is added under /sysinfo path (try for example /sysinfo/metrics) 26 | * How to write simple http tests 27 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 1.2.3.RELEASE 9 | 10 | 11 | 12 | de.igormukhin.samples 13 | spring-boot-org.acme.sample-jsf 14 | 0.0.1-SNAPSHOT 15 | 16 | war 17 | 18 | 19 | 20 | 7 21 | 22 | 23 | 7.0.62 24 | 25 | 2.2.11 26 | 5.2 27 | 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-tomcat 42 | provided 43 | 44 | 45 | 46 | org.apache.tomcat.embed 47 | tomcat-embed-jasper 48 | provided 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-actuator 54 | 55 | 56 | 57 | org.primefaces 58 | primefaces 59 | ${primefaces.version} 60 | 61 | 62 | 63 | 64 | 65 | org.primefaces.themes 66 | bootstrap 67 | 1.0.10 68 | 69 | 70 | 71 | org.glassfish 72 | javax.faces 73 | ${jsf-mojarra.version} 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-starter-test 79 | test 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-maven-plugin 89 | 90 | 93 | false 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/main/java/org/acme/sample/SpringBootFacesApplication.java: -------------------------------------------------------------------------------- 1 | package org.acme.sample; 2 | 3 | import java.util.Collections; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import javax.faces.application.ProjectStage; 8 | import javax.servlet.ServletContainerInitializer; 9 | import javax.servlet.ServletContext; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.annotation.HandlesTypes; 12 | 13 | import org.acme.sample.jsf.FacesViewScope; 14 | import org.apache.catalina.Context; 15 | import org.primefaces.util.Constants; 16 | import org.springframework.beans.factory.config.CustomScopeConfigurer; 17 | import org.springframework.boot.SpringApplication; 18 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 20 | import org.springframework.boot.builder.SpringApplicationBuilder; 21 | import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; 22 | import org.springframework.boot.context.embedded.ServletContextInitializer; 23 | import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer; 24 | import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; 25 | import org.springframework.boot.context.web.NonEmbeddedServletContainerFactory; 26 | import org.springframework.boot.context.web.SpringBootServletInitializer; 27 | import org.springframework.context.annotation.Bean; 28 | import org.springframework.context.annotation.ComponentScan; 29 | import org.springframework.context.annotation.Configuration; 30 | 31 | import com.sun.faces.config.FacesInitializer; 32 | 33 | @Configuration 34 | @ComponentScan 35 | @EnableAutoConfiguration 36 | public class SpringBootFacesApplication extends SpringBootServletInitializer { 37 | 38 | public static void main(String[] args) throws Exception { 39 | SpringApplication.run(SpringBootFacesApplication.class, args); 40 | } 41 | 42 | @Override 43 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 44 | return application.sources(SpringBootFacesApplication.class); 45 | } 46 | 47 | @Bean 48 | public static CustomScopeConfigurer customScopeConfigurer() { 49 | CustomScopeConfigurer configurer = new CustomScopeConfigurer(); 50 | configurer.setScopes(Collections.singletonMap( 51 | FacesViewScope.NAME, new FacesViewScope())); 52 | return configurer; 53 | } 54 | 55 | @Bean 56 | public ServletContextInitializer servletContextCustomizer() { 57 | return new ServletContextInitializer() { 58 | @Override 59 | public void onStartup(ServletContext sc) throws ServletException { 60 | sc.setInitParameter(Constants.ContextParams.THEME, "bootstrap"); 61 | sc.setInitParameter(Constants.ContextParams.FONT_AWESOME, "true"); 62 | sc.setInitParameter(ProjectStage.PROJECT_STAGE_PARAM_NAME, ProjectStage.Development.name()); 63 | } 64 | }; 65 | } 66 | 67 | /** 68 | * This bean is only needed when running with embedded Tomcat. 69 | */ 70 | @Bean 71 | @ConditionalOnMissingBean(NonEmbeddedServletContainerFactory.class) 72 | public EmbeddedServletContainerFactory embeddedServletContainerFactory() { 73 | TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory(); 74 | 75 | tomcat.addContextCustomizers(new TomcatContextCustomizer() { 76 | @Override 77 | public void customize(Context context) { 78 | // register FacesInitializer 79 | context.addServletContainerInitializer(new FacesInitializer(), 80 | getServletContainerInitializerHandlesTypes(FacesInitializer.class)); 81 | 82 | // add configuration from web.xml 83 | context.addWelcomeFile("index.jsf"); 84 | 85 | // register additional mime-types that Spring Boot doesn't register 86 | context.addMimeMapping("eot", "application/vnd.ms-fontobject"); 87 | context.addMimeMapping("ttf", "application/x-font-ttf"); 88 | context.addMimeMapping("woff", "application/x-font-woff"); 89 | } 90 | }); 91 | 92 | return tomcat; 93 | } 94 | 95 | @SuppressWarnings("rawtypes") 96 | private Set> getServletContainerInitializerHandlesTypes(Class sciClass) { 97 | HandlesTypes annotation = sciClass.getAnnotation(HandlesTypes.class); 98 | if (annotation == null) { 99 | return Collections.emptySet(); 100 | } 101 | 102 | Class[] classesArray = annotation.value(); 103 | Set> classesSet = new HashSet>(classesArray.length); 104 | for (Class clazz: classesArray) { 105 | classesSet.add(clazz); 106 | } 107 | 108 | return classesSet; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/org/acme/sample/jsf/FacesViewScope.java: -------------------------------------------------------------------------------- 1 | package org.acme.sample.jsf; 2 | 3 | import java.util.Map; 4 | 5 | import javax.faces.context.FacesContext; 6 | 7 | import org.springframework.beans.factory.ObjectFactory; 8 | import org.springframework.beans.factory.config.Scope; 9 | 10 | /** 11 | * Ein Bean, dass die View-Scope von JSF bei Spring bekannt macht. 12 | * 13 | *

Diese Scope wird standardmäßig in /alf-jsf/src/main/resources/alf-jsf-beans.xml registiert.

14 | * 15 | *

Ohne dieser Datei kann man die Scope auf folgende Weise in Spring registrieren: 16 | *

{@code
17 |  *    
18 |  *        
19 |  *            
20 |  *                
21 |  *                    
22 |  *                
23 |  *            
24 |  *        
25 |  *    
26 |  * }
27 | *

28 | * 29 | * 30 | */ 31 | public class FacesViewScope implements Scope { 32 | 33 | public static final String NAME = "view"; 34 | 35 | @Override 36 | public Object get(String name, ObjectFactory objectFactory) { 37 | FacesContext facesContext = FacesContext.getCurrentInstance(); 38 | if (facesContext == null) { 39 | throw new IllegalStateException("FacesContext.getCurrentInstance() returned null"); 40 | } 41 | 42 | Map viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap(); 43 | 44 | if (viewMap.containsKey(name)) { 45 | return viewMap.get(name); 46 | } else { 47 | Object object = objectFactory.getObject(); 48 | viewMap.put(name, object); 49 | 50 | return object; 51 | } 52 | } 53 | 54 | @Override 55 | public Object remove(String name) { 56 | return FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name); 57 | } 58 | 59 | @Override 60 | public String getConversationId() { 61 | return null; 62 | } 63 | 64 | @Override 65 | public void registerDestructionCallback(String name, Runnable callback) { 66 | // Not supported by JSF for view scope 67 | } 68 | 69 | @Override 70 | public Object resolveContextualObject(String key) { 71 | return null; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/acme/sample/web/FormBean.java: -------------------------------------------------------------------------------- 1 | package org.acme.sample.web; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.acme.sample.jsf.FacesViewScope; 8 | import org.springframework.context.annotation.Scope; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component("formBean") 12 | @Scope(FacesViewScope.NAME) 13 | public class FormBean implements Serializable { 14 | 15 | private static final long serialVersionUID = 1L; 16 | 17 | private List submittedValues = new ArrayList<>(); 18 | 19 | private String field; 20 | 21 | public String getField() { 22 | return field; 23 | } 24 | 25 | public void setField(String field) { 26 | this.field = field; 27 | } 28 | 29 | public List getSubmittedValues() { 30 | return submittedValues; 31 | } 32 | 33 | public void setSubmittedValues(List submittedValues) { 34 | this.submittedValues = submittedValues; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/acme/sample/web/FormController.java: -------------------------------------------------------------------------------- 1 | package org.acme.sample.web; 2 | 3 | import javax.faces.application.FacesMessage; 4 | import javax.faces.context.FacesContext; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Scope; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component("formController") 11 | @Scope("request") 12 | public class FormController { 13 | @Autowired 14 | private FormBean formBean; 15 | 16 | public void submit() { 17 | formBean.getSubmittedValues().add(formBean.getField()); 18 | 19 | FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Value submitted.")); 20 | } 21 | 22 | public void reset() { 23 | formBean.getSubmittedValues().clear(); 24 | formBean.setField(null); 25 | 26 | FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Form reset.")); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | management: 2 | context-path: /sysinfo -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/faces-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | org.springframework.web.jsf.el.SpringBeanFacesELResolver 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | index.jsf 5 | 6 | -------------------------------------------------------------------------------- /src/main/webapp/index.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 | 36 | 37 | 38 |
39 |
40 | 41 |
42 |
43 | 45 | 47 | 49 |
50 |
51 | 52 |
53 |
54 | 55 |
56 | 57 | 58 | #{value} 59 | 60 | 61 |
62 | 63 |
64 |
65 | 66 |
67 |
68 | 69 | 70 |
-------------------------------------------------------------------------------- /src/test/java/org/acme/sample/SpringBootFacesApplicationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2014 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.acme.sample; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertTrue; 21 | 22 | import java.util.Map; 23 | 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.springframework.beans.factory.annotation.Value; 27 | import org.springframework.boot.test.IntegrationTest; 28 | import org.springframework.boot.test.SpringApplicationConfiguration; 29 | import org.springframework.boot.test.TestRestTemplate; 30 | import org.springframework.http.HttpStatus; 31 | import org.springframework.http.ResponseEntity; 32 | import org.springframework.test.annotation.DirtiesContext; 33 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 34 | import org.springframework.test.context.web.WebAppConfiguration; 35 | 36 | /** 37 | * Basic integration tests. 38 | * 39 | * @author Phillip Webb 40 | */ 41 | @RunWith(SpringJUnit4ClassRunner.class) 42 | @SpringApplicationConfiguration(classes = SpringBootFacesApplication.class) 43 | @WebAppConfiguration 44 | @IntegrationTest("server.port:0") 45 | @DirtiesContext 46 | public class SpringBootFacesApplicationTests { 47 | 48 | @Value("${local.server.port}") 49 | private int port; 50 | 51 | @Value("${management.context-path:}") 52 | private String actuatorPath; 53 | 54 | @Test 55 | public void testJsfWelcomePage() throws Exception { 56 | ResponseEntity entity = new TestRestTemplate().getForEntity("http://localhost:" + this.port, String.class); 57 | assertEquals(HttpStatus.OK, entity.getStatusCode()); 58 | assertTrue("Wrong body:\n" + entity.getBody(), entity.getBody().contains("javax.faces.ViewState")); 59 | } 60 | 61 | @SuppressWarnings({"rawtypes", "unchecked"}) 62 | @Test 63 | public void testActuatorHealthPage() throws Exception { 64 | ResponseEntity entity = new TestRestTemplate().getForEntity("http://localhost:" + this.port + actuatorPath + "/health", Map.class); 65 | assertEquals(HttpStatus.OK, entity.getStatusCode()); 66 | 67 | Map body = entity.getBody(); 68 | assertTrue("Wrong body:\n" + entity.getBody(), body.containsKey("status")); 69 | } 70 | 71 | } 72 | --------------------------------------------------------------------------------