Thursday, 18 April 2013

Spring MVC, Jackson and Filtering JSON Serialization



The default Spring JSON configuration will show all the fields of an object during serialization. Often we would like to return a subset of an entity’s properties and hide some which are only used internally such as the id and version.  Or perhaps return different properties depending on who/what is requesting the data.

As Spring uses Jackson for JSON serialization it supports the Jackson Json annotations. Jackson provides a number of options for “narrowing” or “filtering” the properties of an entity that gets serialized.

Jackson JsonViews (requires adding @JsonView to entity classes which pollutes them)
Jackson mix-in (requires extending the entity to specify @JsonIgnoreProperties)

Jackson Filtering options

Example of using Jackson views and mixin for filtering:

This one creates a new annotation to use with JsonViews but is a little too involved for my liking as it requires changing some Spring classes.

This link provides a good demo of Jackson Views and Mixins

Within my SearchController I was returning a SearchSummary object that referenced a Collector object. However, I didn’t want to return all the Collector details within the SearchSummary as its not relevant. Shown below is all I required:

@RequestMapping("/rest/loyalty")
@Controller
public class SearchController {

    @JsonIgnoreProperties({"loyaltyId","earnings","id","version"})
    private static class CollectorIdOnlyView extends Collector {
        // Empty by design ...
    } 
      
    private final ObjectMapper objectMapper = new ObjectMapper();

    private final MappingJacksonJsonView view = new MappingJacksonJsonView();      
    
    public SearchController() {         
        objectMapper.getSerializationConfig().addMixInAnnotations(Collector.class, CollectorIdOnlyView.class);
        view.setObjectMapper(objectMapper);
          
    }
    @Autowired
    private AccountService accountService;
      
    @Autowired
    private SearchService searchService;
      
    @RequestMapping(value = "/search/{id}", method = RequestMethod.GET)
    public View getSummary(@PathVariable("id") String id, Model uiModel) {
        Collector collector = accountService.findCollectorById(id);
        if(collector == null) {
              throw new RuntimeException("Collector Not Found");
        } else {
              SearchSummary searchSummary = searchService.getSearchSummary(collector);
              uiModel.addAttribute("summary",searchSummary);
              return view;
        }
       
    }
}

For the filtering I had to create a subclass of my entity to define the properties I wanted to ignore. (CustomerIdOnlyView).
This is then used to configure the ObjectMapper and View.
The change I had to make to the getSummary method was to change the return type from SearchSummary to View, add the SearchSummary to the Model and remove the @ResponseBody annotation.

Tuesday, 22 January 2013

Spring Roo Updating two entities from one View

Following on from the last post about Spring Roo, this post looks at how to customize the views and controller to update multiple entities from a single view.

Using the web mvc add-on Spring Roo generates views with crud functionality for each entity. For a given entity, Roo will typically generate views for create, show, list and update that map to methods on the generated Spring controller. By specifying attributes to the RooWebScaffold annotation, it is possible to control which views get generated.

However, there are some situations where it may be desirable to allow updating multiple entities (parent-child) from within the same view, perhaps to provide a better user experience. For example, consider a domain model with Business and User entities having a one-to-one relationship to an Address entity (managed from the Business/User). When creating a new Business or User entity, it would provide a better user experience to allow entering all the Business or User details and Address details from a single view, rather than having to create the Address from a separate view and then select the address identifier from a drop-down list within the create Business/User view (as would be the default behaviour with Roo generation).

To achieve this behaviour, the create.jspx view can be augmented with additional fields to enter the address details. Note the use of the dot-notation to navigate to the nested property of the address object.


    <form:create id="fc_com_changesoft_samples_domain_Business" modelAttribute="business" path="/businesses" render="${empty dependencies}" z="0qN2h7loptVj50JEP4Htm9WcQ2U=">
        <field:select field="businessType" id="c_com_changesoft_samples_domain_Business_businessType" items="${businesstypes}" path="businesstypes" z="lbHfbOekwe5M/bFdLTQsGY43NTs="/>
        <field:input field="businessName" id="c_com_changesoft_samples_domain_Business_businessName" z="H/Qzpi7gDgY45Ag3tLqLZiYL9wY="/>
        <field:input field="registrationNumber" id="c_com_changesoft_samples_domain_Business_registrationNumber" z="v0EWCe/zpM89BvaLz1O6+1p1ZEs="/>
        <field:input field="businessDescription" id="c_com_changesoft_samples_domain_Business_description" z="oly643Jm218C3sMXDHNnxbCaKC8="/>
        <field:input field="address.addressLine" id="c_com_changesoft_samples_domain_Address_addressLine" z="nDT/wm1FdsgfzEXLv9zJkmZybmg="/>
        <field:select field="address.addressType" id="c_com_changesoft_samples_domain_Address_addressType" items="${addresstypes}" path="addresstypes" z="aYI9UiQIlRB+F0Bwu93/eo4ZFAc="/>
        <field:input field="address.city" id="c_com_changesoft_samples_domain_Address_city" z="FVwmWn28G7JtqIfUIvAdZORMVkM="/>
        <field:input field="address.postcode" id="c_com_changesoft_samples_domain_Address_postcode" z="eezB9GBw9FiIb+ISiJosM7zXbi4="/>
    </form:create>


This results in the following rendered jsp view.

When the form is submitted, it correctly persists a new Business with a new Address entity (due to the address attribute of Business having cascading behaviour).

The update.jspx view requires slightly more work. When the update form of an entity is submitted, the id and version attributes are also present as hidden form fields and sent as request parameters to identify and retrieve the detached entity and update it. Therefore when updating 2 entities from a single view, it is also necessary to encapsulate the id and version attributes of the Address entity as hidden form fields; and subsequently submit them along with the Business attributes. The jsp tags that are provided by the web mvc add-on do not cater for hidden form fields. Therefore to fulfil this requirement it was necessary to create a custom tag that would generate an html hidden form field that could then be used within the update view jsp.


  <c:set value="hidden" var="type" />


  <c:choose>
    <c:when test="${disableFormBinding}">
      <input id="_${sec_field}_id" name="${sec_field}" type="${fn:escapeXml(type)}" />
    </c:when>
    <c:otherwise>
      <form:hidden id="_${sec_field}_id" path="${sec_field}" />
      <br />
      <form:errors cssClass="errors" id="_${sec_field}_error_id" path="${sec_field}" />
    </c:otherwise>
  </c:choose>



With the new hidden.tagx custom tag file, the update view for the Business entity is adjusted to mark any required fields as hidden:


    <field:hidden field="address.id" id="c_com_changesoft_samples_domain_Address_Id" z="user-managed"/>
    <field:hidden field="address.version" id="c_com_changesoft_samples_domain_Address_Version" z="user-managed"/>


Using the new hidden field tags, the update view renders as follows:

This view allows updating both the Business and Address entities by correctly sending the id and version properties for each entity as hidden form fields, shown below in the HTML source.

<div style="display: none;" id="_c_com_changesoft_samples_domain_Address_Id_id">
<input id="_address.id_id" name="address.id" type="hidden" value="1"/>
<br />
</div>
<div style="display: none;" id="_c_com_changesoft_samples_domain_Address_Version_id">
<input id="_address.version_id" name="address.version" type="hidden" value="2"/>
<br />
</div>
<div id="_c_com_changesoft_samples_domain_Address_addressLine_id">
<label for="_address.addressLine_id">
Address Line
:
</label>
<input id="_address.addressLine_id" name="address.addressLine" type="text" value="119 Middlesex Street"/>

This works quite well without requiring any custom code within the controller.

Spring Roo MVC Read-Only Inputs

I've recently been using Spring Roo quite regularly and also recommended it as part of the development tool stack at a client to boost productivity. It provides a lot of nice features and generates a lot of boiler-plate code for CRUD functionality. Obviously it has its limitations and when you start trying to do things differently then Roo will start having problems. Nevertheless, as long as you are willing to accept the limitations and customize where necessary, you can use it to your benefit.

Recently, I came across an issue where I wanted to update an entity from the web UI, but have some fields marked as readonly so that the user cannot modify them from the update view. Unfortunately, the custom jsp tags that the Spring Roo web mvc add-on provides doesn't support readonly input fields. The input.tagx provides a number of attributes such as render, disableFormBinding and disabled but none of these fulfilled the requirement. Using the disabled attribute, marks the input field as disabled which renders fine and doesn't allow editing of the field, but it also doesn't get submitted with the form and consequently the entity's validation fails or the field's value is replaced by an empty value.

The solution I found was to create a new inputreadonly tag by copying and customizing the input.tagx file.

I added a new jsp directive:

<jsp:directive.attribute name="readonly" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Specify if this field should be readonly" />

and modified the body where it generates the input field:

<c:otherwise>
<c:choose>
<c:when test="${readonly eq true}">
<form:input disabled="${disabled}" id="_${sec_field}_id" path="${sec_field}" readonly="${readonly}"></form:input></c:when>
<c:otherwise>
<form:input disabled="${disabled}" id="_${sec_field}_id" path="${sec_field}



Then I modified the update.jspx file to use the new jsp tag.

<field:inputreadonly field="businessType" id="c_com_changesoft_samples_domain_Business_businessType" z="user-managed>" readonly="true"/>


The result is an html read-only input field.

Wednesday, 10 October 2012

Custom Glassfish Security Realm

Following on from the last post, our client required an user management console that delegated authentication to the container to leverage the SSO capabilities that were available.

Glassfish allows creating a JDBC realm that authenticates users and retrieves their roles from a database. 
The above screenshot shows how a new JDBC realm can be created from the Glassfish administrative console. This JDBC realm however specifies that there needs to be an User table with username and password columns, and a Group table with group name column with a foreign key to the user table as shown below:

T_USERS
USERNAME (PK)
PASSWORD
T_GROUPS
USERNAME (FK)
GROUPNAME
This is fine if the tables do not exist, but what if the database already exists or you require a mapping table for the many-to-many relationship between users and groups? For example, as our application was using Spring Security we already had a database schema as follows:
T_USERS
USERNAME (PK)
PASSWORD
....
T_USER_AUTHORITIES
USERNAME (FK)
ROLE_ID (FK)
T_ROLES
ROLE_ID (PK)
ROLENAME
In such a scenario, the Glassfish provided JDBCRealm is insufficient and a custom realm needs to be created. The custom jdbc realm will allow Glassfish to authenticate using the same database as the application is using to manage the users.

Creating a custom realm requires providing a custom JAAS login module class and custom realm class.
  • The custom login module must extend com.sun.appserv.security.AppservPasswordLoginModule.This class implements the javax.security.auth.spi.LoginModule.
  • The custom realm class must extend the abstract com.sun.appserv.security.AppservRealm class. You may find it easier to view the source code of the com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm class to provide a base from which to develop the custom realm class. Unfortunately, the JDBCRealm class is final and so cannot be extended. The realm class must also contain the HK2 @Service annotation to allow integration with the Glassfish OSGI module system.
  • Update the <GF_HOME>/domains/<DOMAIN-NAME>/config/login.conf to add an entry for the custom realm referring to the custom LoginModule.
  • Finally there should be a file named javax.security.auth.spi.LoginModule in the META-INF/services directory with the fully qualified name of the LoginModule class.
Once these classes have been written they need to be packaged as an OSGI bundle to be activated by Glassfish. For this purpose, we used the maven-bundle-plugin
 <dependencies>

<dependency>
            <groupId>org.glassfish.security</groupId>
            <artifactId>security</artifactId>
            <version>3.1.1-b11</version>
        </dependency>            
       
</dependencies>
<build>
       <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Export-Package>                            ${project.groupId}.glassfish.auth;version=${project.version}
                        </Export-Package>
                        <Import-Package>
                            java.util,
                            javax.security.auth,
                            javax.security.auth.login,
                            javax.sql,                                                   
                            org.glassfish.security.common,
                            org.jvnet.hk2.annotations,
                            org.jvnet.hk2.component,
                            com.sun.appserv.connectors.internal.api,
                            com.sun.appserv.security,
                            com.sun.enterprise.security.common,
                            com.sun.enterprise.util,
                            com.sun.enterprise.util.i18n,                           
                            com.sun.enterprise.security.auth.realm,
                            com.sun.enterprise.security.auth.realm.jdbc,
                            com.sun.enterprise.security.auth.login.common,
                            com.sun.enterprise.security.auth.digest.api,
                            com.sun.enterprise.universal                           
                        </Import-Package>
                    </instructions>
                </configuration>
              </plugin>
</plugins>   
</build>
Note the export-package element should contain the name of the packages of the custom LoginModule class and custom AppservRealm classes.
Once the OSGI bundle has been created it can be loaded by copying it to <GF_HOME>/domains/<DOMAIN-NAME>/autodeploy/bundles.

The realm can now be created from the Glassfish administrative console. When an application that utilizes this realm is accessed the LoginModule will be initialized.