Saturday, April 27, 2013

Restful URLs For Managing Entity Relationships

Web applications created by the scaffolding of Spring Roo's Web MVC add on map URLs using restful design principles.  Eventually as a developer you will need to add functionality that involve new request mappings.  So how should one create new mappings while maintaining consistency with both restful design principles and that provided by the scaffolding?

Controllers are generated by Spring Roo's scaffolding on a per entity basis.  For each entity a controller is generated for the following seven operations and URL request mappings are defined;

Operation Request Type URL
list GET /<app-name>/<entity>
show GET /<app-name>/<entity>/{id}
create form GET /<app-name>/<entity>?form
create POST /<app-name>/<entity>
update form GET /<app-name>/<entity>/{id}?form
update PUT /<app-name>/<entity>
delete DELETE /<app-name>/<entity>/{id}

This set of request mappings provide basic CRUD operations (show, create, update, and delete) along with ability to list entities, and display create and update forms.  For an independent entity this is all the operations that are typically needed.

In real life entities rarely exist independently of each other.  To have a consistent restful design for our applications, we need to develop a taxonomy to describe various roles that entities can assume in their interactions with each other.  We then need to develop a scheme to map those relationships into request mappings.  Typically when talking about entity relationships most the focus of the discussion is usually on the cardinality (1:1, 1:m, m:m) between entities, but here we are really more concerned about the nature of those relationships.

In considering the relationship between entities we must recognize entities can be either independent or dependent.  Independent entities do not dependent on other entities for their identity.  Such entities can exist by themselves, and may or may not be aware of dependent entities around them.  Dependent entities on the other hand rely on other or parent entities to establish their identity.  Dependent entities may also be existence dependent, meaning the entities existence is ;inked to its parents existence.  The role a dependent entity plays in regards to its parent can be characterized further into the following types;
  • Subtype -- entities of this type embellish another entity.  We don't have any of this type in the muti-tenant security model.
  • Characteristic -- entities of this type represent a group of attributes that can occur multiple times for another entity.
  • Associative -- entities of this type record relationships between other entities.
Consider the following entity relationship model for a multi-tenant security application;

In this model we do have independent entities like AppUser, Permission, and Organization.  But a majority of the entities in this model define relationships between the independent entities and are dependent.  Of the dependent entities here there are none that establish a subtype relationship.  But the AppRole entity is a characteristic of Organization.  The remaining entities OrganizationUser, OrganizationUserRole, and RolePermission are associative.

Since Roo provides the means to handle independent entities we need not be concerned about them.  And while we could manipulate the dependent and reference entities using Roo's default generated code, typically uses require a less technical and more intuitive interface.

To provide an intuitive interface we are going to have to extend our web application to allow users to manage dependent entities in a implicit manner.  For example we need to provide the ability for users to add and remove AppUsers from Organizations, but without exposing the concept of OrganizationUser.  But how do we want to define the request mappings such that they are restful, complement those that already exist in the application and hide the mechanics of managing relationships?

So lets look at the types of entity relationships and the type of functionality that we would expose to the user.

The functionality for subtypes is fairly easy to describe since both the subtype entity and its parent are locked together,  So when a operation is performed on a subtype we need to do the same to its parent.  For example when updating a subtype we should allow the user to update fields of the parent entity also.  And when that update is submitted both the subtype and its parent should be updated.  We can provide that linkage in code, but it shouldn't affect the subtypes restful URLs.

Functionality of characteristic relationships should be restricted to the scope of the entities parent.  So when listing such entities that list would be for a particular parent.  And when creating an entity the parent should already be known.

With associative entities the typical operations will be focused on adding and removing associations between independent entities. To create associations we will need to provide a form allowing multiple selection of candidate entities for inclusion into a relationship with another entity, along with a response service to handle the form submission.  For removing associations we will need to provide another form to allow multiple selection of current related entities, along with a subsequent service to handle the submission of that form.

Now that we have categorized the nature of the relationships that we could have in a entity model.  And we also have a good idea of type of functionality we want to expose to manage those relationships.  The next step now is to determine how we want to map that into functional units that will ultimately become request mappings.  To do this we will use the functionality associated with the request mappings that the Roo scaffolding already uses as the framework.

Functionality Subtype Characteristic Associative
list Roo default mapping applies. Should be restricted to those belonging to a parent. Not applicable.
show Default mapping applies, but need to include parent entity attributes. Default mapping and functionality applies. Show a list of the other associated entities. 
create form Default mapping applies, but need to include form elements of parent entity. Need to hide parent entity id. Show a multi-select list form with candidate entities for association.
create Default mapping applies, but need to create parent entity along with subtype entity. Default functionality and mapping applies. Provide ability to create multiple association entities.
update form Default mapping applies, but need to include form elements of parent entity. Default functionality and mapping applies. Not applicable.
update Default mapping applies, but need to update parent entity along with subtype entity. Default functionality and mapping applies. Not applicable.
delete form Not applicable. Not applicable. Show a multi-select list form with associated entities.
delete Default mapping applies, but need to delete parent entity along with subtype entity. Default mapping and functionality applies. Provide ability to delete multiple associating entities. 

As an astute reader you probably noticed the functionality we needed to map didn't align perfectly with what the scaffolding provides.  Principally for associative entities, we want to provide a multiple selection form to allow for the bulk deletion of associations.  To handle this we added delete form to our list of functionality.  Since the scaffolding treats all entities as independent entities and the concept of a delete form is not applicable to independent entities it doesn't appear in the controllers generated   But we need to add the operation to handle removing associative relationships.  Additionally adding it completes the symmetry that data modification functions (create, update, and delete) have corresponding form functions.

Finally we have arrived at the stage where we can determine a set of URL patterns that we can apply consistently throughout a web application.  With subtype entities we don't have to provide anything more in terms of URLs then what the scaffolding provides already.  For characteristic entities recall that only two of the operations required new URLs.  Just adding what is needed is going to make an inconsistent interface so lets extend the pattern to be consistent.  The URL patterns for characteristic entities are;

Operation Method URL Pattern
list GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>
Returns the list of entities that are characteristics of the parent entity with an id of entity-id.

show GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>/{characteristic-entity-id}
Shows the characteristic for the supplied characteristic-entity-id.  Provided for consistency.
Redirects to;

/<app-name>/<characteristic-entity>/{characteristic-entitty-id}

create form GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>?form
Returns a form for creating a new characteristic for the parent entity.

create PUT /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>
Create a new characteristic for the parent entity.

update form GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>/{characteristic-entity-id}?form
Returns an update form for the characteristic entity.  Redirects to;

/<app-name>/<characteristic-entity>/{characteristic-entity-id}?form

update POST /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>

Returns the list of entities that are characteristics of the parent entity with an id of entity-id.

delete form GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>
Returns the list of entities that are characteristics of the parent entity with an id of entity-id.

delete DELETE /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>/{characteristic-entity-id}
Deletes the charachteristic entity with the supplied id.  Redirects to;

/<app-name>/<characteristic-entity>/{characteristic-entity-id}


Then for associative entities we have the following URL patterns;

Operation Method URL Pattern
show GET /<app-name>/<entity-a>/{entity-a-id}/<entity-b>
Returns the list of entity-b's that are associated of entity-a with an id of entity-a-id.

add-form GET /<app-name>/<entity-a>/{entity-a-id}/<entity-b>?form=add

Returns a multi-select form to allow the selection of entity-b entities NOT associated to entity-a with a id of entity-a-id.


add POST /<app-name>/<entity-a>/{entity-a-id}/<entity-b>

Creates one or more association entities between entity-a-id and entity-b ids found in the request body.

delete-form GET /<app-name>/<entity-a>/{entity-a-id}/<entity-b>?form=delete
Returns a form allowing selection of entity-b entities associated with entity-a for deletion.

delete DELETE /<app-name>/<entity-a>/{entity-a-id}/<entity-b>

Deletes one for more association entities between entity-a-id and entity-b ids found in the request body.

We now have a road map to provide consistent restful URL's for Spring Roo based web applications.  The restful URL's that spring provides works well handling independent entities.  By doing some research and analysis we now have a logical and consistent design for the development of restful URL's to manage relations between entities.


Sunday, April 21, 2013

Building Multiple Selection Lists

The web application that Spring Roo builds by default is restricted to modifications of a single entity.  This interface treats all objects independently, but real life objects have associations with each other.  Effectively managing those associations for the user then is essential for the applications usability.  One part of doing this is to support multiple selection of entities in the user interface, allowing the application to manage many to many relationships for the user.

Adding multiple selection of entities can be done by making a few modifications to the existing table tag.  The scope of the modifications include;
  • Add a check box for each table row.  
  • Provide the ability to turn off the show links.
  • Provide a master select box.
Adding Multi-Select Check Boxes

Adding a check box to each row of the table is central feature of the modifications being made. To do this a new optional attribute to the tag is added.  When this attribute is omitted the tag operates as it normally does, to maintain compatibility with existing usage.  However when present the tag will present a check box in a table cell at the beginning of each row.  The value of the attribute will be used as the name for the check box and the entities id will be the value.

To do this first we need to add the attribute specification to the tag;

<jsp:directive.attribute name="multiselect" 
 type="java.lang.String" required="false" rtexprvalue="false" 
 description="The name for multiselection checkboxes." />

The attribute is named 'multiselect' and when it's not empty a check box is added to the beginning of each row in the table.  But of course to keep things aligned we must also add a table cell to the heading.  This table cell should also contain a check box to function as a master select box.

<c:if test="${not empty multiselect}">
 <th >
  <input type="checkbox" name="masterSelectBox_${multiselect}" />
  <spring:eval var="colCounter" expression="colCounter  + 1" />
 </td>
</c:if>

We will use the checkbox here 'masterSelectBox_' appended with the multiselect attribute value to provide a unique name in case a page contains more then one table.  All of this of course is placed inside a conditional such that if the 'multiselect' attribute is not empty then we add the table cell.  Next we need to add the check boxes to each tow;

<c:if test="${not empty multiselect}">
 <td class="selectBox">
  <input type="checkbox" 
   name="${multiselect}" value="${item.id}" />
 </td>
</c:if>

Like with the column heading this block of JSP is placed inside a conditional, so it only appears if the multiselect attribute is defined.  The table cell is given the class 'selectBox', that will be used by Javascript later to find the cells.  And finally we have the input checkbox itself, here the name is value of the 'multiselect' attribute.

Provide the Ability to Turn Off Show Links


While the existing table tag allows turning off the update and delete links, it doesn't provide the same for the show (view) links.  We are going to need to able to remove these links to prevent the user from exiting the page during the middle of making selections.

To implement this first we need to add another attribute directive to the tag.

<jsp:directive.attribute name="show" 
 type="java.lang.Boolean" required="false" rtexprvalue="true" 
 description="Include 'show' link into table (default true)" />


Then place a conditional expression around the existing table heading cell.

<c:if test="${show}">
 <th></th>
 <spring:eval var="colCounter" expression="colCounter  + 1" />
</c:if>

And finally place a conditional around the existing table cell containing the show link.

<c:if test="${show}">
 <td class="utilbox">
  <spring:url value="${path}/${itemId}" var="show_form_url" />
  <spring:url value="/resources/images/show.png" var="show_image_url" />
  <spring:message arguments="${typeName}" code="entity_show" var="show_label" htmlEscape="false" />
  <a href="${show_form_url}" alt="${fn:escapeXml(show_label)}" title="${fn:escapeXml(show_label)}">
   <img alt="${fn:escapeXml(show_label)}" class="image" src="${show_image_url}" title="${fn:escapeXml(show_label)}" />
  </a>
 </td>
</c:if>     

Provide a Master Select


Most applications that support multiple selection of list also provide a means to select or deselect all the entries on the list.  In the first section, 'Adding Multi-Select Check Boxes' we added a master select check box to the table heading cell.  So all that is left to do is adding some Javascript to provide that functionality.

<c:if test="${not empty multiselect}">
 <script>
  require( [ "dojo/query", "dojo/on", "dojo/domReady!" ], function( query, on ) {
   var masterSelect = null ;
       
   query( "table#${id} input.masterSelectBox_${multiselect}" ).forEach( function( node ){
    masterSelect = node ;
    on( node, "click", function( e ) {
     query( "table#${id} td.selectBox input[type=checkbox]").forEach( function( node ) {
      node.checked = masterSelect.checked ;
     }) ;
    })
   })
  }) ;
  
 </script>
</c:if>

Here a Dojo query is used to find the master select box that was added to the table in the first stage.  A 'chick' event handler is added to that check box.  When clicked another Dojo query is executed to find the check box associated with each entity.  The check boxes discovered from that query are then set to the current state of the master select check box.

Putting It All Together


Now that all the pieces are in place, here's the complete modified table tag code;

<jsp:root xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:fn="http://java.sun.com/jsp/jstl/functions" 
  xmlns:util="urn:jsptagdir:/WEB-INF/tags/util" xmlns:spring="http://www.springframework.org/tags" xmlns:form="http://www.springframework.org/tags/form" xmlns:fmt="http://java.sun.com/jsp/jstl/fmt" xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0">
  <jsp:directive.tag import="java.util.ArrayList" />
  <jsp:output omit-xml-declaration="yes" />

  <jsp:directive.attribute name="id" type="java.lang.String" required="true" rtexprvalue="true" description="The identifier for this tag (do not change!)" />
  <jsp:directive.attribute name="data" type="java.util.Collection" required="true" rtexprvalue="true" description="The collection to be displayed in the table" />
  <jsp:directive.attribute name="path" type="java.lang.String" required="true" rtexprvalue="true" description="Specify the URL path" />
  <jsp:directive.attribute name="typeIdFieldName" type="java.lang.String" required="false" rtexprvalue="true" description="The identifier field name for the type (defaults to 'id')" />
  <jsp:directive.attribute name="multiselect" type="java.lang.String" required="false" rtexprvalue="false" description="The name for multiselection checkboxes." />
  <jsp:directive.attribute name="show" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'show' link into table (default true)" />
  <jsp:directive.attribute name="create" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'create' link into table (default true)" />
  <jsp:directive.attribute name="update" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'update' link into table (default true)" />
  <jsp:directive.attribute name="delete" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'delete' link into table (default true)" />
  <jsp:directive.attribute name="render" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Indicate if the contents of this tag and all enclosed tags should be rendered (default 'true')" />
  <jsp:directive.attribute name="z" type="java.lang.String" required="false" description="Used for checking if element has been modified (to recalculate simply provide empty string value)" />

  <c:if test="${empty render or render}">

    <c:set var="columnProperties" scope="request" />
    <c:set var="columnLabels" scope="request" />
    <c:set var="columnMaxLengths" scope="request" />
    <c:set var="columnTypes" scope="request" />
    <c:set var="columnDatePatterns" scope="request" />

    <jsp:doBody />

    <c:if test="${empty typeIdFieldName}">
      <c:set var="typeIdFieldName" value="id" />
    </c:if>

    <c:if test="${empty update}">
      <c:set var="update" value="true" />
    </c:if>

    <c:if test="${empty delete}">
      <c:set var="delete" value="true" />
    </c:if>

    <spring:message var="typeName" code="menu_item_${fn:toLowerCase(fn:split(id,'_')[fn:length(fn:split(id,'_')) - 1])}_new_label" htmlEscape="false" />
    <c:set var="lengths" value="${fn:split(columnMaxLengths, '✏')}" scope="request" />
    <c:set var="types" value="${fn:split(columnTypes, '✏')}" scope="request" />
    <c:set var="patterns" value="${fn:split(columnDatePatterns, '✏')}" scope="request" />
    
    <c:if test="${not empty multiselect}">
     <script>
      require( [ "dojo/query", "dojo/on", "dojo/domReady!" ], function( query, on ) {
       var masterSelect = null ;
       
       query( "table#${id} input.masterSelectBox_${multiselect}" ).forEach( function( node ){
        masterSelect = node ;
        on( node, "click", function( e ) {
         query( "table#${id} td.selectBox input[type=checkbox]").forEach( function( node ) {
          node.checked = masterSelect.checked ;
         }) ;
        })
       })
      }) ;
      
     </script>
    </c:if>

    <spring:eval var="colCounter" expression="0" />

    <table id="${id}">
      <thead>
        <tr>
          <c:if test="${not empty multiselect}">
            <th >
    <input class="masterSelectBox_${multiselect}" type="checkbox" name="${column}" />
            </th>
            <spring:eval var="colCounter" expression="colCounter  + 1" />
          </c:if>
          <c:forTokens items="${columnLabels}" delims="${'✏'}" var="columnHeading">
            <th>
             <c:out value="${columnHeading}" />
               <spring:eval var="colCounter" expression="colCounter  + 1" />
            </th>
          </c:forTokens>
          <c:if test="${show}">
            <th></th>
            <spring:eval var="colCounter" expression="colCounter  + 1" />
          </c:if>
          <c:if test="${update}">
            <th></th>
            <spring:eval var="colCounter" expression="colCounter  + 1" />
          </c:if>
          <c:if test="${delete}">
            <th></th>
            <spring:eval var="colCounter" expression="colCounter  + 1" />
          </c:if>
        </tr>
      </thead>
      <c:forEach items="${data}" var="item">
        <tr>
          <c:if test="${not empty multiselect}">
            <td class="selectBox">
           <input type="checkbox" name="${multiselect}" value="${item.id}" />
          </td>
          </c:if>
          <c:forTokens items="${columnProperties}" delims="${'✏'}" var="column" varStatus="num">
            <c:set var="columnMaxLength" value="${lengths[num.count-1]}" />
            <c:set var="columnType" value="${types[num.count-1]}" />
            <c:set var="columnDatePattern" value="${patterns[num.count-1]}" />
            <td>
          <c:choose>
            <c:when test="${columnType eq 'date'}">
               <spring:escapeBody>
                 <fmt:formatDate value="${item[column]}" pattern="${fn:escapeXml(columnDatePattern)}" var="colTxt" />
               </spring:escapeBody>
            </c:when>
            <c:when test="${columnType eq 'calendar'}">
               <spring:escapeBody>
                 <fmt:formatDate value="${item[column].time}" pattern="${fn:escapeXml(columnDatePattern)}" var="colTxt"/>
               </spring:escapeBody>
            </c:when>
            <c:otherwise>
               <c:set var="colTxt">
                 <spring:eval expression="item[column]" htmlEscape="false" />
               </c:set>
            </c:otherwise>
          </c:choose>
          <c:if test="${columnMaxLength ge 0}">
            <c:set value="${fn:substring(colTxt, 0, columnMaxLength)}" var="colTxt" />
          </c:if>
          <c:out value="${colTxt}" />
            </td>
          </c:forTokens>
          <c:set var="itemId"><spring:eval expression="item[typeIdFieldName]"/></c:set>
          <c:if test="${show}">
           <td class="utilbox">
             <spring:url value="${path}/${itemId}" var="show_form_url" />
             <spring:url value="/resources/images/show.png" var="show_image_url" />
             <spring:message arguments="${typeName}" code="entity_show" var="show_label" htmlEscape="false" />
             <a href="${show_form_url}" alt="${fn:escapeXml(show_label)}" title="${fn:escapeXml(show_label)}">
               <img alt="${fn:escapeXml(show_label)}" class="image" src="${show_image_url}" title="${fn:escapeXml(show_label)}" />
             </a>
           </td>
   </c:if>     
          <c:if test="${update}">
            <td class="utilbox">
              <spring:url value="${path}/${itemId}" var="update_form_url">
                <spring:param name="form" />
              </spring:url>
              <spring:url value="/resources/images/update.png" var="update_image_url" />
              <spring:message arguments="${typeName}" code="entity_update" var="update_label" htmlEscape="false" />
              <a href="${update_form_url}" alt="${fn:escapeXml(update_label)}" title="${fn:escapeXml(update_label)}">
                <img alt="${fn:escapeXml(update_label)}" class="image" src="${update_image_url}" title="${fn:escapeXml(update_label)}" />
              </a>
            </td>
          </c:if>
          <c:if test="${delete}">
            <td class="utilbox">
              <spring:url value="${path}/${itemId}" var="delete_form_url" />
              <spring:url value="/resources/images/delete.png" var="delete_image_url" />
              <form:form action="${delete_form_url}" method="DELETE">
                <spring:message arguments="${typeName}" code="entity_delete" var="delete_label" htmlEscape="false" />
                <c:set var="delete_confirm_msg">
                  <spring:escapeBody javaScriptEscape="true">
                    <spring:message code="entity_delete_confirm" />
                  </spring:escapeBody>
                </c:set>
                <input alt="${fn:escapeXml(delete_label)}" class="image" src="${delete_image_url}" title="${fn:escapeXml(delete_label)}" type="image" value="${fn:escapeXml(delete_label)}" onclick="return confirm('${delete_confirm_msg}');" />
                <c:if test="${not empty param.page}">
                  <input name="page" type="hidden" value="1" />
                </c:if>
                <c:if test="${not empty param.size}">
                  <input name="size" type="hidden" value="${fn:escapeXml(param.size)}" />
                </c:if>
              </form:form>
            </td>
          </c:if>
        </tr>
      </c:forEach>
      <tr class="footer">
        <td colspan="${colCounter}">
          <c:if test="${empty create or create}">
            <span class="new">
              <spring:url value="${path}" var="create_url">
                <spring:param name="form" />
              </spring:url>
              <a href="${create_url}">
                <spring:url value="/resources/images/add.png" var="create_img_url" />
                <spring:message arguments="${typeName}" code="global_menu_new" var="add_message" htmlEscape="false" />
                <img alt="${fn:escapeXml(add_message)}" src="${create_img_url}" title="${fn:escapeXml(add_message)}" />
              </a>
            </span>
            <c:out value=" " />
          </c:if>
          <c:if test="${not empty maxPages}">
            <util:pagination maxPages="${maxPages}" page="${param.page}" size="${param.size}" />
          </c:if>
        </td>
      </tr>
    </table>

  </c:if>

</jsp:root>


Using the Modified Table Tag


To use the modified table tag we just need to provide values for the 'multiselect' and 'show' optional attributes.

<table:table data="${organizations}" id="l_com_repik_multitenant_security_domain_Organization" 
  path="/organizations" create="false" update="false" delete="false" 
  multiselect="organizationId" show="false" 
  z="5U3sseAfezAakwxsdptI+SXks8o=">
 <table:column id="c_com_repik_multitenant_security_domain_Organization_name" property="name" z="8FaKf+BmDqidSHXdwfn1tV1TnQo="/>
 <table:column id="c_com_repik_multitenant_security_domain_Organization_description" property="description" z="2tWVqb2uDhtkEVsFS0M2anZ68vg="/>
</table:table>

In this snippet of JSP we have a multi-select table showing the organizations that a user is associated with.  Elsewhere in the code there is a JPQL query that is building that list for use here.  Here the ability for the use to modify or view individual organizations have been turned off by setting the 'create', 'update', 'delete' and 'show' attributes to false.

And now here is the mutli-select table embedded in a form;