Chapter 5 Tutorials

Eliminating Hidden Fields

  1. Declare a bean with session scope in the main configuration file, SimpleBean.java. The bean needs a new qualifier, uses session scope and needs a new method to return an instance of the data bean.
        @Bean("sessionDefaultBean")
        @SessionScope
        RequestDataDefault getSessionDefaultBean() {
            return new RequestDataDefault();
        }
  2. Copy the controller to a new class named ControllerHelperP1AddModel in the controller/ch3/restructured/p1_addModel folder.
  3. Modify the request mapping annotation to include the URL pattern /ch3/restructured/p1_addModel/Controller.
  4. Change the qualifier for the bean to sessionDefaultBean.
  5. Annotate the getData method with ModelAttribute and the name data to allow access to the bean.
  6. Copy the JSPs to a new folder named ch3/restructured/twoconfirms.
  7. Update the return value from the viewLocation method to return the new location of the JSPs.
  8. Remove the hidden fields from each JSP.
  9. Modify the process view so that the name for the confirm button is confirmNoDataButton.
  10. Modify the doGet method.
    1. Remove the statement that calls setAttribute to add the bean to the session. This code is no longer needed, since Spring controls what is added to the session with the ModelAttribute annotation on the getData method.
    2. Move the statements that copy data into the bean to the code block for the confirm button.
    3. Add a test for when confirmNoDataButton is in the query string. Return the address for the confirm view.
  11. Enter the URL for the controller. The application has the same functionality as before but uses the session to store the request data.

Controller Logic

  1. Copy the controller from the last example to a new class named ControllerHelperP2Mappings in the controller/ch3/restructured/p2_mappings folder.
  2. Modify the request mapping annotation for the URL pattern /ch3/restructured/p2_mappings/Controller.
  3. Add a method for each button.
    1. Cut and paste the code from the if-clause for a button into the new method for that button. Instead of setting the value of the address variable, return that value from the method.
    2. Annotate each method with a GetMapping annotation. Set the params attribute with the name of the button that is associated with the method.
    3. Add a parameter of type HttpServletRequest to the method for the confirm button.
  4. Remove the HttpServletRequest parameter from the doGet method.
  5. Replace the body of the doGet method with return editMethod();
  6. The application has the same functionality as before but is better organized and is simpler.

Post Requests

  1. Copy the JSPs to a new location named ch3/restructured/post.
  2. Modify the edit view so the form uses the POST method.
  3. Modify the process view so the name of the button to return to the confirm page is confirmButton.
  4. Copy the controller from the last example to a new class named ControllerHelperP3Post in the controller/ch3/restructured/p3_post folder.
  5. Modify the request mapping annotation for the URL pattern /ch3/restructured/p3_post/Controller.
  6. Modify the viewLocation method for the new location of the JSPs.
  7. Change the annotation on the confirm method to PostMapping. Change the return value from the method to the string redirect:Controller?confirmButton=confirm.
  8. Change the button associated with the confirmNoData method to confirmButton.
  9. The application has improved functionality with minimal changes to the code. The data can only be posted once to the application.

Replacing the Request

  1. Copy the JSPs to a new location named ch3/restructured/model.
  2. Modify the edit view so it uses the Spring tags for the form and input tags. Do not use the input tag for the submit button.
  3. Add a new page for expired data. Include a link to the controller to start over.
  4. Copy the controller from the last example to a new class named ControllerHelperP4Model in the controller/ch3/restructured/p4_model folder.
  5. Modify the request mapping annotation for the URL pattern /ch3/restructured/p4_model/Controller.
  6. Modify the viewLocation method for the new location of the JSPs.
  7. Replace the current confirm method that handles post requests with
        @PostMapping(params="confirmButton")
        public String confirmMethod(
                @ModelAttribute("data") Optional dataForm) {
            if (! dataForm.isPresent()) return "redirect:expired";
            return "redirect:Controller?confirmButton=Confirm";
        }
  8. The functionality is the same as the last controller, but the code does not access the HTTP request or session directly. The data is automatically copied from the bean to the form data and from the query string back to the bean.

Navigation Without the Query String

  1. Copy the JSPs to a new location named ch3/restructured/path.
  2. Remove the forms from the confirm and process views. Add links that use path information to specify the next view to access.
  3. Modify the edit view so the data is sent to the confirm view.
  4. Copy the controller from the last example to a new class named ControllerHelperP5Path in the controller/ch3/restructured/p5_path folder.
  5. Modify the request mapping annotation for the URL pattern /ch3/restructured/p5_path/collect/.
  6. Modify the viewLocation method for the new location of the JSPs.
  7. Replace the params attribute for each mapping annotation with the path attribute for the button. Since the path attribute is the default attribute, the name of the attribute may be omitted.
  8. Modify the confirm method that handles post requests so it returns the string redirect:confirm.
  9. The application no longer uses the query string to navigate from one page to the next. Instead, it uses the path information. Path information is easier to read and to write.

Session Attributes

  1. Add a declaration for a prototype bean to the configuration file, SimpleBean.java
        @Bean("protoDefaultBean")
        @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        RequestDataDefault getProtoRequiredBean() {
            return new RequestDataDefault();
        }
  2. Copy the JSPs to a new location named ch3/restructured/sessattr.
  3. Add a restart link to the process view.
  4. Copy the controller from the last example to a new class named ControllerHelperP6SesAttr in the controller/ch3/restructured/p6_sessattr folder.
  5. Modify the request mapping annotation for the URL pattern /ch3/restructured/p6_sessattr/collect/.
  6. Annotate the class with SessionAttributes and include the name data, which is the same name in the ModelAttribute annotation in the getData method.
  7. Modify the viewLocation method for the new location of the JSPs.
  8. Remove the instance variable for the bean.
  9. Add an instance variable of type ObjectFactory which wraps the RequestData interface. Annotate it with a qualifier for the prototype bean that was added to the configuration file.
  10. Modify the getData method so it calls getObject on the ObjectFactory variable.
  11. The confirm method does not have to be modified. It does not required a parameter for the session attribute. The model attribute will parameter will access the session variable.

Adding a Logger

  1. Add the dependency to the groovy classes in the pom file. Do not include the groovy-all dependency, as that will prevent the JUnit tests from running.
            <dependency>
                <groupId>org.codehaus.groovy</groupId>
                <artifactId>groovy</artifactId>
                <version>2.5.11</version>
            </dependency>
  2. Add a file named logback.groovy to the src/main/resources folder.
    1. Start the file with the scan time and the location of the log files.
              scan("1 minute")
              String LOG_PATH = System.getenv("logpath") ?: "logs"
    2. Add the code for the console appender.
              appender ("Console-Appender", ConsoleAppender) {
                  encoder(PatternLayoutEncoder) {
                      pattern=String.format("%s%n%s%n", 
                        "highlight(%-5level\t%msg)",
                        "\t%gray(%date(ISO8601) - %logger{65})")
                  }
              }
    3. Add the code for the file appender.
              appender ("RollingFile-Appender", RollingFileAppender) {
                  file = "${LOG_PATH}/current.log"
                  rollingPolicy(TimeBasedRollingPolicy) {
                      fileNamePattern = "${LOG_PATH}/backup/log%d{yyyy-MM-dd}.zip"
                      maxHistory = 10
                      totalSizeCap = "50MB"
                  }
                  encoder(PatternLayoutEncoder) {
                      pattern = "%-5level\t%msg%n\t%date{ISO8601} - %logger%n"
                  }
              }
    4. Define the root logger and file logger. The first parameter to the file logger is a string that should match the beginning of the package for classes that can send messages to the log file. In these examples, all the classes descend from the com.bytesizebook package. Set each to a different level. General messages from Spring and Tomcat will not include DEBUG messages. Messages from the controller will include DEBUG messages.
              logger ("com.bytesizebook", DEBUG, ["Console-Appender", "RollingFile-Appender"], false)
              root(INFO, ["Console-Appender"])
  3. Copy the controller from the last example to a new class named ControllerHelperP7Logger in the controller/ch3/restructured/p7_logger folder.
  4. Modify the request mapping annotation for the URL pattern /ch3/restructured/p7_logger/collect/.
  5. Add each of the five error message types to different methods in the controller.
  6. Run the application and open the log file to see the results.
  7. Modify the logger configuration file and access the application again.
  8. View the log file to see that different level messages are written to the log file. EOF

Enhanced Controller

  1. Copy the interface and bean from the last example to a new folder src/main/java/data/ch5/enhanced
  2. Add a declaration for a prototype bean to the configuration file, SimpleBean.java
        @Bean("protoEnhancedBean")
        @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        com.bytesizebook.data.ch5.enhanced.RequestDataDefault getProtoEnhancedBean() {
            return new com.bytesizebook.data.ch5.enhanced.RequestDataDefault();
        }
  3. Copy the JSPs to a new location named ch5/enhanced.
  4. Copy the controller from the last example to a new class named ControllerEnhanced in the controller/ch5/enhanced folder.
  5. Change the import for the bean to com.bytesizebook.data.ch5.enhanced.
  6. Modify the request mapping annotation for the URL pattern /ch5/enhanced/collect/.
  7. Modify the instance variable of type ObjectFactory which wraps the RequestData interface. Annotate it with a qualifier for the prototype bean that was added to the configuration file for this example.
  8. Modify the viewLocation method for the new location of the JSPs.
  9. This example is referenced in future chapters

Testing Enhanced Controller

  1. Copy the test class from the last chapter to the src/test/java/com/bytesizebook/ch5/enhanced folder.
  2. Rename the class.
  3. Create constants for the controller mapping, view location, and session bean name.
  4. Remove the autowired bean instance.
  5. Autowire an instance of WebApplicationContext.
  6. Create an additional method that will be run before each test. Annotate it with BeforeEach.
    1. Initialize an instance of the Mock Mvc using MockMvcBuilders.
    2. Call the webAppContextSetup method for the instance with the web application context.
    3. Call apply with the sharedHttpSession method.
    4. Build the instance.
  7. Add an additional MultiValueMap that contains invalid parameters.
  8. Create additional instance variables for default values and invalid values.
  9. Remove references to adding buttons as parameters to query string.
  10. Modify the SetUpAll method to initialize the new instance variables.
  11. Modify the makeRequestTestContent method so it returns the results from the mock Mvc.
  12. Create a method that returns the MvcResult for when the application is called for the first time, without a button click.
  13. Call the method when testing for the first call to the application.
  14. Call the method when testing the confirm button, then make the call for the confirm button with the appropriate query string. The test does not have direct access to the session, but can only retrieve it after the fact.
  15. Create a helper method that retrieves the session from the MvcResult and tests that it has the correct values.
  16. Make similar tests for other transitions.

Contact the author