Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Getting started Java EE action-based MVC with Thymeleaf3_en

Getting started Java EE action-based MVC with Thymeleaf3_en

Session in JJUG(Japan JUG) CCC 2016 Spring
"Getting started Java EE action-based MVC with Thymeleaf3"

5dbaf4015e7f249ab21b195ced8e9e46?s=128

Masatoshi Tada

May 25, 2016
Tweet

More Decks by Masatoshi Tada

Other Decks in Technology

Transcript

  1. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Getting started Java

    EE action-based MVC with Thymeleaf3 Masatoshi Tada (@suke_masa, Casareal) JJUG CCC 2016 Spring 2016-05-21(Sat) 1
  2. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. About this slides

    ! Answer to your question "Java EE is useful framework as next Struts?" ! Views are Thymeleaf3 ! Codes on GitHub ! https://github.com/MasatoshiTada/jjug-action-based- mvc ! In this slide, some codes are omitted. Codes in GitHub are correct. 2
  3. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. About me !

    Masatoshi Tada !Casareal, Inc. !GlassFish User Group Japan !Trainer (Java EE/Spring/Swift) !Twitter:@suke_masa 3
  4. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Agenda ᶃ What’s

    action-based MVC? ᶄ Getting started MVC 1.0(EE 8) ᶅ Using Jersey MVC and RESTEasy HTML in EE 7 ᶆ Other Java EE technologies you should know ᶇ Final conclusions 4
  5. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. ᶃWhat’s action-based MVC?

    5
  6. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. What’s action-based MVC?

    ! Web framework which focuses HTTP request and response ! Most of web framework are action-based ! Struts 1, Struts 2, Spring MVC… 6
  7. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. What’s action-based MVC?

    7 View View Controller Model request response Struts developer friendly
  8. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. What’s component-based MVC?

    8 View Backing Bean Model View and Bean are one-to-one -> Struts developer Un-friendly
  9. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Web framework of

    Java EE ! JSF ! Responses HTML ! Component base ! JAX-RS ! Responses JSON / XML ! Action base 9 There is no "action-based" and "HTML" framework
  10. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Java EE Standard

    action-based MVC is coming!! ! Model-View-Controller API (MVC 1.0) ! Java EE 8 (2017 H1) ! JAX-RS based 10
  11. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. ᶄGetting started MVC

    1.0 (EE 8) 11
  12. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. MVC 1.0 !

    MVC 1.0 is specification ! Set of interfaces, annotations, exceptions ! Implementation is Ozark ! Implementation classes of interfaces above ! Reference Implementation 12
  13. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Main feature of

    Struts ! Controller ! Validation ! Handling exception ! View technology ! Transaction token 13
  14. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. In MVC 1.0?

    ! Controller → ⭕ ! Validation → ⭕ ! Handling exception → ⭕ ! View technology → ❌ ! Transaction token → ❌ 14
  15. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Why no view

    technologies? ! In Non-Goal of MVC specification, 
 "Define a new view (template) language and processor." ! Instead, MVC provides integration with many view technologies! 15
  16. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. View technologies we

    can use in MVC 1.0 ! MVC 1.0 ! JSP, Facelets ! Ozark ! Thymeleaf, Mustache, Freemarker, 
 Handlerbars, Velocity, AsciiDoc, etc. 16 Implement ViewEngine interface if you want to use other view
  17. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Problems of JSP

    and Facelets 17 ! JSP ! Mix of view and logic ! Sometime occurs XSS ! Facelets ! Cannot use all features in MVC 1.0 ! Bad compatibility with JavaScript
  18. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Let’s use Thymeleaf!

    18 ! Pure HTML! ! Isolate view and logic absolutely! ! Good compatibility with JavaScript! ! Escape strings by default! ! Other useful feature ! Link expression, internationalization, …
  19. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. How to use

    Thymeleaf in MVC 1.0 19 <dependency> <groupId>org.glassfish.ozark.ext</groupId> <artifactId>ozark-thymeleaf</artifactId> <version>1.0.0-m02</version> <scope>compile</scope> </dependency> ! Just adding dependency in pom.xml Thymeleaf 2 at this moment. In GitHub, I created original ViewEngine for Thymeleaf 3
  20. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Why no transaction

    token? ! In expert group’s maling-list,
 "This is a client side concern" ! https://java.net/projects/mvc-spec/lists/jsr371- experts/archive/2015-07/message/2 ! Spring MVC doesn’t have this feature, either 20
  21. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. How to prevent

    double-submission ! TERASOLUNA Guideline ! http://terasolunaorg.github.io/guideline/ 5.1.0.RELEASE/en/ArchitectureInDetail/ DoubleSubmitProtection.html 21
  22. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Preparation : Enable

    JAX-RS 22 @ApplicationPath("/api") public class MyApplication extends Application { } Extend Application class Specify annotation jjug-mvc10/src/main/java/com/example/rest/MyApllication.java
  23. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Controller 23 @Path("/employee")

    public class EmployeeController { @GET @Path("/index") @Controller public String index() { return "employee/index.html"; } } Path of view (extention is REQUIRED) Indicate as controller method Mapping to URL and HTTP request method ※Italic is feature of MVC jjug-mvc10/src/main/java/com/example/rest/controller/EmployeeController.java
  24. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Form class 24

    public class EmployeeIdForm { @QueryParam("id") @NotBlank @Pattern(regexp = "[1-9][0-9]*") private String id; // setter/getter } Mapping to query parameter "id" Constraint annotation of Bean Validation jjug-mvc10/src/main/java/com/example/rest/form/EmployeeIdForm.java
  25. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Hand over value

    to view 25 public class EmployeeController { @Inject private Models models; @GET @Path("/result") @Controller @ValidateOnExecution(type=ExecutableType.NONE) public String result(…) { models.put("employee", employee); return "employee/result.html"; } Hold value (Map + Iterator) Value referenced by view jjug-mvc10/src/main/java/com/example/rest/controller/EmployeeController.java ※Italic is feature of MVC
  26. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Showing value 26

    <table th:if="${employee}"> <tr th:object="${employee}"> <td th:text="*{empId}">99999</td> <td th:text="*{name}">Taro Yamada</td> <td th:text="*{joinedDate}">2020-01-01</td> <td th:text="*{department.deptId}">99</td> <td th:text="*{department.name}">Admin</td> </tr> </table> View folder is WEB-INF/views jjug-mvc10/src/main/webapp/WEB-INF/views/employee/result.html Name put to Models
  27. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Validation 27 public

    class EmployeeController { @Inject private BindingResult br; @GET @Path("/result") @Controller @ValidateOnExecution(type=ExecutableType.NONE) public String result( @Valid @BeanParam EmployeeIdForm form) { if (br.isFailed()) { models.put("bindingResult", br); return "employee/index.html"; // To input view Holder of error message Enable validation Error processing jjug-mvc10/src/main/java/com/example/rest/controller/EmployeeController.java ※Italic is feature of MVC
  28. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Showing error message

    28 <ul th:if="${bindingResult}"> <li th:each="message : ${bindingResult.allMessages}" th:text="${message}"> This is dummy message </li> </ul> Get messages from BindingResult jjug-mvc10/src/main/webapp/WEB-INF/views/employee/index.html
  29. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Writing error messages

    ! Write message in src/main/resources/ ValidationMessages.properties ! Specify key of error message to "message" attribute of constraint annotation 29
  30. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Specify error message

    30 public class EmployeeIdForm { @QueryParam("id") @NotBlank(message = "{id.notblank}") @Pattern(regexp = "[1-9][0-9]*", message = "{id.pattern}") private String id; // setter/getter Key of message with "{}" jjug-mvc10/src/main/java/com/example/rest/form/EmployeeIdForm.java
  31. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Caution : @BeanParam

    is not form-binding ! @BeanParam just put together multiple parameters in one class ! Form-binding must be implemented by programmer 31
  32. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Implement form-binding 32

    <form action="./result" method="get"> <input type="text" name="id" value="" th:value="${param.id} ? ${param.id[0]} : '' "> <input type="submit" value="Search"> </form> jjug-mvc10/src/main/webapp/WEB-INF/views/employee/index.html It may be complex when selectbox…
  33. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Handling exception !

    Implement ExceptionMapper interface ! When exception occurs in controller methods, ExceptionMapper catches this exception ! You can create multiple ExceptionMappers according to exception types 33
  34. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. ExceptionMapper class 34

    @Provider public class ExceptionMapper implements javax.ws.rs.ext.ExceptionMapper<Exception> { public Response toResponse( Exception exception) { // to error view } } Don’t forget @Provider Catches exception as parameter Implement ExceptionMapper interface jjug-mvc10/src/main/java/com/example/rest/exception/mapper/ExceptionMapper.java
  35. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Other features !

    Security (CSRF, XSS) ! File upload and download (Upload is Jersey’s specific feature) ! MvcContext ! Mix of HTML app and REST app (Hybrid class) ! For more details, see GUGJ’s slides ! http://www.slideshare.net/masatoshitada7/java-ee-8mvc-10 35
  36. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Conclusion of this

    chapter ! MVC 1.0 + Thymeleaf cover most of features of Struts ! More easy, more secure! ! Need to implement transaction token and form-binding by yourself ! MVC 1.0 is simple, leverages existing Java EE features 36
  37. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. ᶅUsing Jersey MVC

    and
 RESTEasy HTML in EE 7 37
  38. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. How to use

    action-based MVC in Java EE 7? ! MVC 1.0 is part of Java EE 8 ! Java EE 8 will be released in 2017H1 38 Let’s use Jersey MVC and RESTEasy HTML!
  39. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. What’s Jersey MVC?

    ! Specific feature of Jersey(JAX-RS RI) ! Model of MVC 1.0 ! Included in GlassFish/Payara ! WebLogic doesn’t include 39
  40. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. What’s RESTEasy HTML?

    ! Specific feature of RESTEasy(Another implementation of JAX-RS) ! RESTEasy is included in WildFly/ JBoss,but RESTEasy HTML isn’t included. 40
  41. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Enable Jersey MVC

    ! Register MvcFeature to Application subclass ! ResourceConfig is useful 41 @ApplicationPath("/api") public class MyApplication extends ResourceConfig { public MyApplication() { register(MvcFeature.class); property(MvcFeature.TEMPLATE_BASE_PATH, "/WEB-INF/views/"); packages(true, "com.example.rest"); } } jjug-jersey-mvc/src/main/java/com/example/rest/MyApplication.java ※Italic is feature of Jersey MVC
  42. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Controller of Jersey

    MVC ! Return Viewable in controller methods 42 @Path("/employee") public class EmployeeController { @GET @Path("/index") public ThymeleafViewable index(…) { return new ThymeleafViewable( "employee/index.html"); } } My original Viewable’s subclass jjug-jersey-mvc/src/main/java/com/example/rest/controller/EmployeeControler.java ※Italic is feature of Jersey MVC
  43. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. How to hand

    over value to view in Jersey MVC ! Use 2nd parameter of Viewable constructor 43 @Path("/employee") public class EmployeeController { @GET @Path("/result") public ThymeleafViewable result(…) { Map<String, Object> models = … return new ThymeleafViewable( "employee/result.html", models); } Put value to Map jjug-jersey-mvc/src/main/java/com/example/rest/controller/EmployeeControler.java ※Italic is feature of Jersey MVC
  44. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. How to use

    Thymeleaf in Jersey MVC ! Implement TemplateProcessor interface ! AbstractTemplateProcessor class is useful (implementation of TemplateProcessor) 44
  45. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Thymeleaf3’s TemplateProcessor 45

    @Provider public class ThymeleafTemplateProcessor extends AbstractTemplateProcessor<String> { private TemplateEngine templateEngine; @Inject public ThymeleafTemplateProcessor( Configuration config, ServletContext sc) { super(config, sc, "html", "html"); ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(sc); … templateEngine = new TemplateEngine(); templateEngine.setTemplateResolver(resolver); } // to next slide jjug-jersey-mvc/src/main/java/com/example/rest /thymeleaf/ThymeleafTemplateProcessor.java ※Italic is feature of Jersey MVC
  46. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Thymeleaf3’s TemplateProcessor 46

    // from previous slide @Override public void writeTo(...) { WebContext webContext = new WebContext( request, response, servletContext, request.getLocale()); Map<String, Object> map = (Map) viewable.getModel(); webContext.setVariables(map); templateEngine.process( viewable.getTemplateName(), webContext, response.getWriter()); } } jjug-jersey-mvc/src/main/java/com/example/rest /thymeleaf/ThymeleafTemplateProcessor.java Thymeleaf processes response ※Italic is feature of Jersey MVC
  47. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Validation in Jersey

    MVC ! We can specify error view with @ErrorTemplate, but… ! Treats in the same way validation errors and exceptions ! Bad compatibility with MVC 1.0, because MVC 1.0 doesn’t have this feature ! Cannot use validation group and group sequence 47
  48. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Validate in controller

    methods 48 @Inject Validator validator; @GET @Path("/result") public ThymeleafViewable result( @BeanParam EmployeeIdForm form) { Set<ConstraintViolation<EmployeeIdForm>> violations = validator.validate(form); if (!violations.isEmpty()) { Map<String, Object> model = …; model.put("violations", violations); return new ThymeleafViewable( "employee/index.html", model); } Executes validation Don’t annotate @Valid Validate When error, return to input view
  49. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Τϥʔϝοηʔδͷදࣔ 49 <ul

    th:if="${violations}"> <li th:each="violation : ${violations}" th:text="${violation.message}"> This is dummy message </li> </ul>
  50. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. About this way

    ! Advantage ! Pure JAX-RS code ! Good compatibility with MVC 1.0 ! Prevent excess customize ! Disadvantage ! Take some time 50 I thought and tried many way, this way is better
  51. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Handling exception !

    Use ExceptionMapper ! Explanation omitted because same to MVC 1.0 51
  52. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. How to redirect

    ! JAX-RSͷίʔυͰॻ͚͹OK 52 @GET @Path("/redirect") public Response redirect( @Context UriInfo uriInfo) throws Exception { URI location = uriInfo.getBaseUriBuilder() .path(HelloResource.class) .path("redirect2") .build(); return Response.status(Response.Status.FOUND) .location(location).build(); } Has some methods to build URI Specify URI to Location header Status code is 3xx
  53. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Enable RESTEasy HTML

    ! Create Application subclass ! No need to register specific class ! JAX-RS’s "Providers" feature 53 @ApplicationPath("/api") public class MyApplication extends Application { } jjug-resteasy-html/src/main/java/com/example/rest/MyApplication.java
  54. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Controller in RESTEasy

    HTML ! Return Renderable in controller methods 54 <<interface>> Renderable View class (forward) Redirect class (redirect)
  55. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. How to use

    RESTEasy HTML in RESTEasy HTML ! Implement Renderable interface 55 <<interface>> Renderable View Redirect ThymeleafView
  56. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Thymeleaf3’s Renderable 56

    public class ThymeleafView implements Renderable { private String templateName; private Map<String, Object> models; private TemplateEngine templateEngine; public ThymeleafView(String templateName) { this(templateName, new HashMap<>()); } public ThymeleafView(String templateName, Map<String, Object> models) { this.templateName = templateName; this.models = models; } void setTemplateEngine(TemplateEngine templateEngine) { this.templateEngine = templateEngine; } Implement Renderable ※Italic is feature of RESTEasy HTML jjug-resteasy-html/src/main/java/com/example/rest/thymeleaf/ThymeleafView.java
  57. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Thymeleaf3’s Renderable 57

    // from previous slide @Override public void render(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException, WebApplicationException { response.setCharacterEncoding("UTF-8"); WebContext webContext = new WebContext( request, response, request.getServletContext(), request.getLocale()); webContext.setVariables(models); templateEngine.process(templateName, webContext, response.getWriter()); } } TemplateEngine#process() in render() ※Italic is feature of RESTEasy HTML jjug-resteasy-html/src/main/java/com/example/rest/thymeleaf/ThymeleafView.java
  58. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Create Interceptor which

    sets TemplateEngine to Renderable class ! JAX-RS’s WriterInterceptor ! Create original NameBinding annotation ! This is not only way. We can think and try other way, too. 58
  59. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Create annotation 59

    @NameBinding @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) @Documented public @interface ThymeleafController { } Annotated @NameBinding jjug-resteasy-html/src/main/java/com/example/rest/thymeleaf/ThymeleafController.java
  60. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. WriterInterceptor 60 @Provider

    @ThymeleafController public class ThymeleafWriterInterceptor implements WriterInterceptor { private TemplateEngine templateEngine; @Inject private ServletContext sc; @PostConstruct public void init() { ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(sc); resolver.setPrefix("/WEB-INF/views/"); resolver.setTemplateMode(TemplateMode.HTML); templateEngine = new TemplateEngine(); templateEngine.setTemplateResolver(resolver); } Original annotation (previous slide) Implement WriterInterceptor jjug-resteasy-html/src/main/java/com/example/rest/thymeleaf/ThymeleafWriterInterceptor.java
  61. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. WriterInterceptor 61 @Override

    public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { Object entity = context.getEntity(); if (ThymeleafView.class.isAssignableFrom(entity.getClass())) { ThymeleafView thymeleafView = (ThymeleafView) context.getEntity(); thymeleafView.setTemplateEngine(templateEngine); } context.proceed(); } } Set TemplateEngine before context.proceed() jjug-resteasy-html/src/main/java/com/example/rest/thymeleaf/ThymeleafWriterInterceptor.java
  62. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Controller 62 @GET

    @Path("result") @ThymeleafController public ThymeleafView result(…) { Map<String, Object> models = …; models.put("employee", employee); return new ThymeleafView( "employee/result.html", models); } Annotate -> applying WriterInterceptor View path and values ※Italic is feature of RESTEasy HTML jjug-resteasy-html/src/main/java/com/example/rest/controller/EmployeeController.java
  63. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Validation and ExceptionMapper

    ! Explanation omitted because same to Jersey MVC 63
  64. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Conclusion of this

    chapter ! In Java EE 7, use Jersey MVC or RESTEasy HTML! ! Most codes are same to MVC 1.0! ! No BindingResult feature, so validate in controller methods! 64
  65. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. ᶆOther Java EE

    technologies you should know 65
  66. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. JAX-RS ! Specific

    features of MVC 1.0/Jersey MVC/ RESTEasy HTML are not many ! To achieve your requirement and trouble- shooting, knowledge of JAX-RS is very important 66
  67. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. JAX-RS Reference !

    JSR-339 JAX-RS 2.0 rev.A ! You should learn Processing Pipeline(Appendix C) ! https://jcp.org/en/jsr/detail?id=339 ! Jersey Document ! https://jersey.java.net/documentation/latest/ user-guide.html ! RESTEasy Document ! http://docs.jboss.org/resteasy/docs/ 3.0.17.Final/userguide/html_single/index.html 67
  68. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Others ! Bean

    Validation ! http://docs.jboss.org/hibernate/validator/5.2/reference/en- US/html_single/ ! CDI ! http://docs.jboss.org/weld/reference/latest/en-US/ html_single/ ! JPA 68
  69. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. ᶇFinal conclusions 69

  70. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Final Conclusions !

    MVC 1.0 + Thymeleaf covers most features of Struts! ! In EE 7, Use Jersey MVC or RESTEasy HTML! ! Understanding JAX-RS is very important! 70
  71. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Caution ! Jersey

    MVC and RESTEasy HTML are 
 NOT "Java EE 7 standard" ! They will be out of range 
 of server vendors’ support ! In "Java EE 7 standard", 
 JSF is the ONLY HTML Framework 71
  72. #ccc_cd4 (C) CASAREAL, Inc. All rights reserved. Enjoy Java EE!!

    ! Thank you for reading! 72