Friday, March 25, 2011

Jasperoo update

I have just completed some fairly significant enhancements to Jasperoo. It's starting to look like a pretty solid addon for Spring Roo. As far as I can tell, your Roo projects can now benefit from the power of Jasper Reports.

What has changed?
  • I've introduced "Detail" reports. These reports provide the contents of all the fields of a specific entity instance, all on one page.
  • I've abandoned the idea of a menu entry for each report. The reports are now accessed through icons on the "List" and "Show" page for each entity that has been added to Roo.
  • I've simplified the process of adding additional report formats. Previously, you had to choose which formats you wanted when you called "setup". With the addition of the "extend" method, you can now add support for other formats after setup.
  • I have spent a fair bit of time documenting jasperoo. Please check out the jasperoo project site at jasperoo.digitalface.ca, and click on API and How-To from the left-side navigation.
Many thanks for the feedback already received, I look forward to hearing more.

Here are some screen shots to whet your appetite.
A "show" screen with the report icons highlighted.

A "Detail" report

A "list" screen with the report icons highlighted.

A "List" report.
The "extend" command in use.

Friday, March 18, 2011

#JasperReports + #SpringRoo = #Jasperoo

For Immediate Release

DigitalFace has released the jasperoo code to the community under GPL3.

What? You say you've never heard of it before? Well, that's not too surprising, I only dusted off the last of the critical functionality last night. ;o)

There are still features missing, and a few little bugs, but I didn't want to work on it any longer without checking it into source control, and since I planned to make this a open source project, I figured that now is the time for its release.

What is jasperoo, and why should I care?
If you don't use Spring Roo, then you probably won't care... just carry on with your life.

If you have started using Spring Roo (or if you've been reading my posts lately), then you already know that it is very powerful for quickly stitching together the components needed for Enterprise Java applications. Jasperoo facilitates the integration of Jasper Reports into your Spring Roo applications.

Let's say that you have created the PetClinic application and would like to have a report that generates a list of Owners. Without jasperoo, you would need to:
  • Add the JasperReports dependencies to your pom.xml;
  • Add the jasperreports-maven-plugin to your pom.xml;
  • Create the ReportController to handle the report requests;
  • Create your jrxml template; and 
  • Wire it all together.
With jasperoo, after it is installed, you just have to call:
  • jasperoo setup
  • jasperoo add --type ~.domain.Owner
Do you want to try it out?
Ok... here we go. I will assume that you have already setup Spring Roo, and familiarized yourself with it. If not, go to the Spring Roo website for instructions.

Step 1 - Create a sample application.
mkdir clinic
cd clinic
roo
    ____  ____  ____
   / __ \/ __ \/ __ \
  / /_/ / / / / / / /
 / _, _/ /_/ / /_/ /
/_/ |_|\____/\____/ 1.1.2.RELEASE [rev fbc33bb]


Welcome to Spring Roo. For assistance press TAB or type "hint" then hit ENTER.
roo> script --file clinic.roo
Step 2 - If you already have an older version of jasperoo installed do the following, otherwise, skip to step 3.
roo> addon remove --bundleSymbolicName ca.digitalface.jasperoo
Step 3 - Install the latest version of jasperoo
roo> osgi start --url http://s.digitalface.ca/jasperoo-latest
Step 4 - Setup jasperoo
roo> jasperoo setup
Step 5 - Generate a report of the Pet Owners
roo> jasperoo add --type ~.domain.Owner
There... that wasn't so hard, was it?

Test your work
Now either exit out of roo, or open another command shell and start-up the Pet Clinic application.
mvn tomcat:run
Open your browser and go to localhost:8080/petclinic/
[click to enlarge]

Create a new Owner
[click to enlarge]

Select the PDF report
[click to enlarge]

Open the report in Adobe Acrobat or some other PDF viewer.
[click to enlarge]

Select the Excel Report
[click to enlarge]

Open the report in MS Excel or Open Office Calc
[click to enlarge]

The reports generated are very utilitarian. However they can be readily edited in iReport.

The same code produces pdf and xls files, and with a small change to the menu file you also get csvhtmlodtxml, and rtf formats.

So, I encourage you to download it and try it out. The project is hosted on Google Code: jasperoo.digitalface.ca.

I look forward to your feedback.

Wednesday, February 23, 2011

Typical Security in Roo (#springroo, #roo-typical-security)

Rohit Ghatol, the author of the typical security addon for Spring Roo has invited me to be a contributor.

So my first contribution was to fix the minor bugs I mentioned in my previous post.

The following are reflected in the 0.1.4.BUILD-SNAPSHOT of the typical security addon:

  1. Password encryption
  2. Fixed SimpleMailMessage Autowire bug in SignUpController.java and ForgotPasswordController.java
  3. Fixed activationDate
  4. Fixed the finders 
  5. Renamed UserModel to User and RoleModel to Role.
  6. Renamed the Typicalsecurity command to typicalsecurity
  7. Set the default entity package to ~.domain (to align with how the rest of roo works).
As always, both Rohit and I welcome your feedback on this tool.

Saturday, February 19, 2011

Typical Security in roo

Roo provides a basic Spring Security setup out of the box.
roo> security setup
But what if you want the typical security features like user registration and password changing etc.?

There is a project underway to provide this. The Typical security add-on for roo facilitates the generation of some typical security features.

The author introduced it to the community here: http://forum.springsource.org/showthread.php?t=100534 but basically, the following will be all that you need to do to gain all the typical security features.
roo> Typicalsecurity setup --controllerPackage ~.web --entityPackage ~.domain
There are some minor bugs in the current build (0.1.2.BUILD). After you run the setup, you will need to
roo> focus --class ~.domain.UserModel
~.domain.UserModel roo> finder add findUserModelsByEmailAddress
~.domain.UserModel roo> finder add findUserModelsByActivationKeyAndEmailAddress
~.domain.UserModel roo> field date --type java.util.Date --fieldName activationDate
~.domain.UserModel roo> focus --class ~.domain.UserRoleModel
~.domain.UserRoleModel roo> finder add findUserRoleModelsByUserEntry
and you will need to comment out the Autowired annotation for SimpleMailMessage in SignUpController.java and ForgotPasswordController.java
//@Autowired
private transient SimpleMailMessage simpleMailMessage;

I will be keeping a close eye on this project. I sure hope it finds its way into the standard roo distribution.

Saturday, February 5, 2011

In Context Creation of Related Objects

As I stated in my last blog post, I have been looking into solutions for ROO-207 "Allow in context creation of 1:n and 1:1 related objects". Well, I am happy to announce that I am getting there. What I have now can best be described as an early work-around. It's not a full solution, but rather a first kick at it.

The direction I'm currently going with is to change as little of the look and feel of ROO as possible. To that end, I will leave the drop down list of related items for now. I am also using Dojo for this since it is already included by ROO.

What I've done is always display the "add" icon beside the dropdown list of referenced items. Clicking on that icon launches a modal dialog box with all the fields required to create a new Object. When that modal form is submitted, the values are saved using JSON. Clicking the down arrow in the dropdown list fetches the latest Objects using JSON. There are some known issues with this solution, such as:
  1. There is no validation on the dialog form.
  2. Since it is extremely manual, it is still very error prone.
  3. If you change anything on the referenced Object, you will have to change the create page for the host Object.
  4. We fetch the latest objects every time the referenced Object list is opened. This may be an un-acceptable performance hit.
I'm publishing this work to gather the opinions of the community before going through the effort of turning this into a plugin.

A picture says a thousand words... so here are a couple thousand words.
Petclinic - Add Pet Add "Owner" from within "Add Pet" screen.

There were a few new files created

  • clinic/src/main/webapp/WEB-INF/tags/util/
    • json-util.tagx
      This file contains the JavaScript that handles the display of the dialog, the dynamic linking of the Owners select tag to the database, and the saving of the dialog form.
    • link-to-json.tagx
      This tag links the form element defined to the database though JavaScript calls to functions defined in the above file.
  • clinic/src/main/webapp/WEB-INF/tags/form
    • create-json.tagx
      This tag is based on the create.tagx in the same folder, but instead of a basic <form>, a modal dialog is created.

...and some others that just needed to be modified.

  • clinic/src/main/java/com/springsource/petclinic/domain
    • Owner.java
      Not absolutely necessary, but I added a "getName()" method so that the dropdown would display the full name of the owner.
  • clinic/src/main/webapp/WEB-INF/tags/util/
  • clinic/src/main/webapp/WEB-INF/tags/form/
    • create.tagx
      Modified since the host form was not getting submitted while the referred form was on the page.
  • clinic/src/main/webapp/WEB-INF/tags/form/fields/
    • select.tagx
      Modified to accommodate the selective inline creation of Objects.
    • textarea.tagx
      Modified to support the disabling of form binding.
  • clinic/src/main/webapp/WEB-INF/views/pets/
    • create.jspx
      Modified to include the Owner creation form.

The high level steps that are required to execute this in a clean application are:

  1. Generate the clean application.
  2. Enable JSON for the referenced objects.
  3. Copy the new and modified files.
  4. Modify the Create page for the host Objects.

Details of the changes

  1. Generate the clean application.

    roo> script --file clinic.roo
  2. Enable JSON for the referenced objects.

    roo> json add --class ~.domain.Owner
  3. Copy the new and modified files.

    Click on the file names above to download them from my server and save them into the source path indicated. (If you prefer, just download the new files and follow these instructions to modify the existing files.)

    Owner.java

    package com.springsource.petclinic.domain;

    import org.springframework.roo.addon.entity.RooEntity;
    import org.springframework.roo.addon.javabean.RooJavaBean;
    import org.springframework.roo.addon.tostring.RooToString;
    import java.util.Set;
    import com.springsource.petclinic.domain.Pet;
    import java.util.HashSet;
    import javax.persistence.OneToMany;
    import javax.persistence.CascadeType;
    import javax.persistence.Transient;

    import org.springframework.roo.addon.json.RooJson;

    @RooJavaBean
    @RooToString
    @RooEntity
    @RooJson
    public class Owner extends AbstractPerson {

        @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
        private Set<Pet> pets = new HashSet<Pet>();

        public String getName(){
         return getFirstName() + " " + getLastName();
        }

    }


    load-scripts.tagx (enable Dojo's "parseOnLoad")

    <jsp:root xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:fn="http://java.sun.com/jsp/jstl/functions" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:spring="http://www.springframework.org/tags" version="2.0">
      <jsp:output omit-xml-declaration="yes" />
      <spring:theme code="styleSheet" var="roo_css" />
      <spring:url value="/${roo_css}" var="roo_css_url" />
      <spring:url value="/resources/dojo/dojo.js" var="dojo_url" />
      <spring:url value="/resources/dijit/themes/tundra/tundra.css" var="tundra_url" />
      <spring:url value="/resources/spring/Spring.js" var="spring_url" />
      <spring:url value="/resources/spring/Spring-Dojo.js" var="spring_dojo_url" />
      <spring:url value="/resources/images/favicon.ico" var="favicon" />
      <link rel="stylesheet" type="text/css" media="screen" href="${roo_css_url}"><!-- required for FF3 and Opera --></link>
      <link rel="stylesheet" type="text/css" href="${tundra_url}"><!-- required for FF3 and Opera --></link>
      <link rel="SHORTCUT ICON" href="${favicon}" />
      <!-- Get the user local from the page context (it was set by Spring MVC's locale resolver) -->
      <c:set var="userLocale">
        <c:out value="${pageContext.response.locale}" default="en" />
      </c:set>
      <script type="text/javascript">var djConfig = {parseOnLoad: false, isDebug: false, locale: '${fn:toLowerCase(userLocale)}'};</script>

      <script src="${dojo_url}" djConfig="parseOnLoad: true, isDebug: true" type="text/javascript"><!-- required for FF3 and Opera --></script>
      <script src="${spring_url}" type="text/javascript"><!-- /required for FF3 and Opera --></script>
      <script src="${spring_dojo_url}" type="text/javascript"><!-- required for FF3 and Opera --></script>
      <script language="JavaScript" type="text/javascript">dojo.require("dojo.parser");</script>
    </jsp:root>

    create.tagx

    <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:form="http://www.springframework.org/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:spring="http://www.springframework.org/tags" version="2.0">
      <jsp:output omit-xml-declaration="yes"/>
      <jsp:directive.attribute name="id" type="java.lang.String" required="true" description="The identifier for this tag (do not change!)"/>
      <jsp:directive.attribute name="modelAttribute" type="java.lang.String" required="true" description="The name of the model attribute for form binding"/>
      <jsp:directive.attribute name="path" type="java.lang.String" required="true" description="Specify the relative URL path (wit leading /)" />
      <jsp:directive.attribute name="label" type="java.lang.String" required="false" description="The label used for this object, will default to a message bundle if not supplied"/>
      <jsp:directive.attribute name="render" type="java.lang.Boolean" required="false" description="Indicate if the contents of this tag and all enclosed tags should be rendered (default 'true')" />
      <jsp:directive.attribute name="openPane" type="java.lang.Boolean" required="false" description="Control if the title pane is opened or closed by default (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:if test="${empty label}">
          <spring:message code="label_${fn:toLowerCase(fn:substringAfter(id,'_'))}" var="label" htmlEscape="false" />
        </c:if>
        <spring:message arguments="${label}" code="entity_create" var="title_msg"/>
        <util:panel id="${id}" title="${title_msg}" openPane="${openPane}">
          <spring:url value="${path}" var="form_url"/>
          <form:form action="${fn:escapeXml(form_url)}" method="POST" modelAttribute="${modelAttribute}">
            <form:errors cssClass="errors" delimiter="&lt;p/&gt;"/>
            <jsp:doBody />
            <div class="submit" id="${fn:escapeXml(id)}_submit">
              <spring:message code="button_save" var="save_button"/>
              <script type="text/javascript">Spring.addDecoration(new Spring.ValidateAllDecoration({elementId:'proceed', event:'onclickonsubmit'}));</script>
              <input id="proceed" type="submit" value="${fn:escapeXml(save_button)}"/>
            </div>
          </form:form>
        </util:panel>
      </c:if>
    </jsp:root>

    select.tagx

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

      <jsp:directive.attribute name="id" type="java.lang.String" required="true" description="The identifier for this tag (do not change!)" />
      <jsp:directive.attribute name="field" type="java.lang.String" required="true" description="The field exposed from the form backing object" />
      <jsp:directive.attribute name="path" type="java.lang.String" required="true" description="The relative path to the referenced resource" />
      <jsp:directive.attribute name="items" type="java.util.Collection" required="true" description="The name of the collection displayed in the select box" />
      <jsp:directive.attribute name="label" type="java.lang.String" required="false" description="The label used for this field, will default to a message bundle if not supplied" />
      <jsp:directive.attribute name="itemValue" type="java.lang.String" required="false" description="The identifier used as value in the select box (defaults to 'id' for non enum types)" />
      <jsp:directive.attribute name="required" type="java.lang.Boolean" required="false" description="Indicates if this field is required (default false)" />
      <jsp:directive.attribute name="disabled" type="java.lang.Boolean" required="false" description="Specify if this field should be enabled" />
      <jsp:directive.attribute name="multiple" type="java.lang.Boolean" required="false" description="Specify if the select box should allow multiple selections" />
      <jsp:directive.attribute name="disableFormBinding" type="java.lang.Boolean" required="false" description="Set to true to disable Spring form binding" />
      <jsp:directive.attribute name="render" type="java.lang.Boolean" required="false" 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)" />
      <jsp:directive.attribute name="inlineCreate" type="java.lang.Boolean" required="false" description="Indicate if this object can be created inline (default 'false')" />

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

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

        <c:if test="${empty label}">
          <spring:message code="label_${fn:toLowerCase(fn:substringAfter(id,'_'))}" var="label" htmlEscape="false" />
        </c:if>

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

        <c:if test="${empty multiple}">
          <c:set value="false" var="multiple" />
        </c:if>
      
        <c:set var="sec_field">
          <spring:escapeBody javaScriptEscape="true" >${field}</spring:escapeBody>
        </c:set>

        <div id="_${fn:escapeXml(id)}_id">
          <c:choose>
            <c:when test="${not empty items or inlineCreate}">
              <label for="_${sec_field}_id">
                <c:out value="${fn:escapeXml(label)}" />
                :
              </label>
              <c:choose>
                <c:when test="${empty itemValue}">
                  <c:choose>
                    <c:when test="${disableFormBinding}">
                      <select id="_${sec_field}_id" name="${sec_field}" multiple="${multiple}">
                        <c:forEach items="${items}" var="item">
                          <option value="${item}">
                            <spring:eval expression="item" />
                          </option>
                        </c:forEach>
                      </select>
                    </c:when>
                    <c:otherwise>
                      <form:select id="_${sec_field}_id" items="${items}" path="${sec_field}" disabled="${disabled}" multiple="${multiple}" />
                      <br />
                      <form:errors cssClass="errors" id="_${sec_field}_error_id" path="${sec_field}" />
                    </c:otherwise>
                  </c:choose>
                </c:when>
                <c:otherwise>
                  <c:choose>
                    <c:when test="${disableFormBinding}">
                      <select id="_${sec_field}_id" name="${sec_field}" multiple="${multiple}">
                        <c:forEach items="${items}" var="item">
                          <option value="${item[fn:escapeXml(itemValue)]}">
                            <spring:eval expression="item" />
                          </option>
                        </c:forEach>
                      </select>
                    </c:when>
                    <c:otherwise>
                      <form:select id="_${sec_field}_id" items="${items}" path="${sec_field}" disabled="${disabled}" multiple="${multiple}" itemValue="${fn:escapeXml(itemValue)}" />
                      <br />
                      <form:errors cssClass="errors" id="_${sec_field}_error_id" path="${sec_field}" />
                    </c:otherwise>
                  </c:choose>
                </c:otherwise>
              </c:choose>
              <c:if test="${inlineCreate}">
    <spring:url value="/resources/images/add.png" var="create_img_url" />
    <spring:message arguments="${label}" code="global_menu_new" var="add_message" />
    <img onclick="showCreateDialog('${sec_field}');" alt="${fn:escapeXml(add_message)}" src="${fn:escapeXml(create_img_url)}" title="${fn:escapeXml(add_message)}" />
     </c:if>
     <br />

              <c:choose>
                <c:when test="${multiple == false}">
                  <script type="text/javascript">Spring.addDecoration(new Spring.ElementDecoration({elementId : '_${sec_field}_id', widgetType: 'dijit.form.FilteringSelect', widgetAttrs : {hasDownArrow : true}})); </script>
                </c:when>
                <!-- disabled due to http://jira.springframework.org/browse/ROO-909 <c:otherwise> <script type="text/javascript">Spring.addDecoration(new Spring.ElementDecoration({elementId : '_${field}_id', widgetType: 'dijit.form.MultiSelect', widgetAttrs : {}})); </script> </c:otherwise> -->
              </c:choose>
            </c:when>
            <c:otherwise>
              <field:reference field="${label}" id="${id}" path="${path}" required="${required}" />
            </c:otherwise>
          </c:choose>
        </div>
        <br />

      </c:if>
    </jsp:root>

    textarea.tagx

    <jsp:root xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:fn="http://java.sun.com/jsp/jstl/functions" xmlns:spring="http://www.springframework.org/tags" xmlns:form="http://www.springframework.org/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0">
      <jsp:output omit-xml-declaration="yes" />

      <jsp:directive.attribute name="id" type="java.lang.String" required="true" description="The identifier for this tag (do not change!)" />
      <jsp:directive.attribute name="field" type="java.lang.String" required="true" description="The field exposed from the form backing object" />
      <jsp:directive.attribute name="label" type="java.lang.String" required="false" description="The label used for this field, will default to a message bundle if not supplied" />
      <jsp:directive.attribute name="required" type="java.lang.Boolean" required="false" description="Indicates if this field is required (default false)" />
      <jsp:directive.attribute name="disabled" type="java.lang.Boolean" required="false" description="Specify if this field should be enabled" />
      <jsp:directive.attribute name="validationRegex" type="java.lang.String" required="false" description="Specify regular expression to be used for the validation of the input contents" />
      <jsp:directive.attribute name="validationMessageCode" type="java.lang.String" required="false" description="Specify the message (message property code) to be displayed if the regular expression validation fails" />
      <jsp:directive.attribute name="validationMessage" type="java.lang.String" required="false" description="Specify the message to be displayed if the regular expression validation fails" />
      <jsp:directive.attribute name="render" type="java.lang.Boolean" required="false" 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)" />
      <jsp:directive.attribute name="disableFormBinding" type="java.lang.Boolean" required="false" description="Set to true to disable Spring form binding" />

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

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

        <c:if test="${empty label}">
          <spring:message code="label_${fn:toLowerCase(fn:substringAfter(id,'_'))}" var="label" htmlEscape="false" />
        </c:if>

        <c:if test="${empty required}">
          <c:set value="false" var="required" />
        </c:if>
      
        <c:set var="sec_field">
          <spring:escapeBody javaScriptEscape="true" >${field}</spring:escapeBody>
        </c:set>

        <script type="text/javascript">dojo.require("dijit.form.SimpleTextarea");</script>
        <div id="_${fn:escapeXml(id)}_id">
          <label for="_${sec_field}_id">
            <c:out value="${fn:escapeXml(label)}" />
            :
          </label>
          <c:choose>
            <c:when test="${disableFormBinding}">
         <textarea id="_${sec_field}_id" name="${sec_field}">${sec_field}</textarea>
            </c:when>
            <c:otherwise>

         <form:textarea id="_${sec_field}_id" path="${sec_field}" disabled="${disabled}" />
       </c:otherwise>
     </c:choose>

          <br />
          <form:errors cssClass="errors" id="_${sec_field}_error_id" path="${sec_field}" />
          <script type="text/javascript">Spring.addDecoration(new Spring.ElementDecoration({elementId : '_${sec_field}_id', widgetType : 'dijit.form.SimpleTextarea', widgetAttrs : {disabled : ${disabled}}})); </script>
        </div>
        <br />

      </c:if>
    </jsp:root>

  4. Modify the Create page for the host Objects.

    This last step has three sub-steps. Again in our example, we want to create an Owner from within the page that we use to create a Pet.
    1. Copy the creation form from the "create Owner" page. (clinic/src/main/webapp/WEB-INF/views/owners/create.jspx)
    2. Paste it into the "create Pets" page. (clinic/src/main/webapp/WEB-INF/views/pets/create.jspx)
    3. We will then modify it.

    create.jspx (Owners - copy)

    ...
        <form:create id="fc_com_springsource_petclinic_domain_Owner" modelAttribute="owner" path="/owners" render="${empty dependencies}" z="JVL6ZvtbnTyvO3flv+5gP4Lf98o=">
            <field:input field="firstName" id="c_com_springsource_petclinic_domain_Owner_firstName" max="30" min="3" z="iFQbgU5/QXNtac+P4ovg8kEV7ek="/>
            <field:input field="lastName" id="c_com_springsource_petclinic_domain_Owner_lastName" max="30" min="3" required="true" z="njvl6BTK6olS0lu0xpz2JAljl/w="/>
            <field:textarea field="address" id="c_com_springsource_petclinic_domain_Owner_address" required="true" z="F58lrymQYAb+vI2R18H+u5MV3co="/>
            <field:input field="city" id="c_com_springsource_petclinic_domain_Owner_city" max="30" required="true" z="KeFguNGWsgzbj3W5t5xAQ67nB8w="/>
            <field:input field="telephone" id="c_com_springsource_petclinic_domain_Owner_telephone" required="true" z="UArcYWJhCMGb8fdNhd+tPR7lGZA="/>
            <field:input field="homePage" id="c_com_springsource_petclinic_domain_Owner_homePage" max="30" z="/vNZCeUDVvKka/dick16UkqxCeM="/>
            <field:input field="email" id="c_com_springsource_petclinic_domain_Owner_email" max="30" min="6" validationMessageCode="field_invalid_email" z="OBipoi4fQlmRIdM78qa1WWEIIwU="/>
            <field:datetime dateTimePattern="${owner_birthday_date_format}" field="birthDay" id="c_com_springsource_petclinic_domain_Owner_birthDay" required="true" z="hePNmv60SdPIC9BHED+b/A3wvL8="/>
            <field:simple field="pets" id="c_com_springsource_petclinic_domain_Owner_pets" messageCode="entity_reference_not_managed" messageCodeAttribute="Pet" z="mFNdxgNw45XzY+L48hMYPWM8B4E="/>
        </form:create>
    ...

    create.jspx (Pets - paste)

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <div xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:fn="http://java.sun.com/jsp/jstl/functions" xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields" xmlns:form="urn:jsptagdir:/WEB-INF/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:spring="http://www.springframework.org/tags" version="2.0">

        <jsp:directive.page contentType="text/html;charset=UTF-8"/>
        <jsp:output omit-xml-declaration="yes"/>
        <form:create id="fc_com_springsource_petclinic_domain_Pet" modelAttribute="pet" path="/pets" render="${empty dependencies}" z="lgvEyAlAYOudDmaPjwU0ABseTIk=">
            <field:checkbox field="sendReminders" id="c_com_springsource_petclinic_domain_Pet_sendReminders" z="uPpMX+IWb0KONpvd11fpG8x4/4Q="/>
            <field:input field="name" id="c_com_springsource_petclinic_domain_Pet_name" min="1" required="true" z="ZY+k75JeSo9RmejYZRFNIvs2aBg="/>
            <field:input field="weight" id="c_com_springsource_petclinic_domain_Pet_weight" min="0" required="true" validationMessageCode="field_invalid_number" z="cOD5zE/z7gy+RZu5kVSPuxCa+/I="/>
            <field:select field="owner" inlineCreate="true" id="c_com_springsource_petclinic_domain_Pet_owner" itemValue="id" items="${owners}" path="/owners" z="fGzswAP4XXvhPhowJKsRVve929c="/>
            <field:select field="type" id="c_com_springsource_petclinic_domain_Pet_type" items="${pettypes}" path="pettypes" required="true" z="+hDCnUp+Y+A1RlT+AjH07sgipOo="/>
        </form:create>
        <form:dependency dependencies="${dependencies}" id="d_com_springsource_petclinic_domain_Pet" render="${not empty dependencies}" z="kThDNIW+69h9nI/69ynY1WyUieo="/>
      

        <form:create id="fc_com_springsource_petclinic_domain_Owner" modelAttribute="owner" path="/owners" render="${empty dependencies}" z="JVL6ZvtbnTyvO3flv+5gP4Lf98o=">
            <field:input field="firstName" id="c_com_springsource_petclinic_domain_Owner_firstName" max="30" min="3" z="iFQbgU5/QXNtac+P4ovg8kEV7ek="/>
            <field:input field="lastName" id="c_com_springsource_petclinic_domain_Owner_lastName" max="30" min="3" required="true" z="njvl6BTK6olS0lu0xpz2JAljl/w="/>
            <field:textarea field="address" id="c_com_springsource_petclinic_domain_Owner_address" required="true" z="F58lrymQYAb+vI2R18H+u5MV3co="/>
            <field:input field="city" id="c_com_springsource_petclinic_domain_Owner_city" max="30" required="true" z="KeFguNGWsgzbj3W5t5xAQ67nB8w="/>
            <field:input field="telephone" id="c_com_springsource_petclinic_domain_Owner_telephone" required="true" z="UArcYWJhCMGb8fdNhd+tPR7lGZA="/>
            <field:input field="homePage" id="c_com_springsource_petclinic_domain_Owner_homePage" max="30" z="/vNZCeUDVvKka/dick16UkqxCeM="/>
            <field:input field="email" id="c_com_springsource_petclinic_domain_Owner_email" max="30" min="6" validationMessageCode="field_invalid_email" z="OBipoi4fQlmRIdM78qa1WWEIIwU="/>
            <field:datetime dateTimePattern="${owner_birthday_date_format}" field="birthDay" id="c_com_springsource_petclinic_domain_Owner_birthDay" required="true" z="hePNmv60SdPIC9BHED+b/A3wvL8="/>
            <field:simple field="pets" id="c_com_springsource_petclinic_domain_Owner_pets" messageCode="entity_reference_not_managed" messageCodeAttribute="Pet" z="mFNdxgNw45XzY+L48hMYPWM8B4E="/>
        </form:create>


    </div>


    create.jspx (Pets - modify)


    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <div xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:util="urn:jsptagdir:/WEB-INF/tags/util" xmlns:fn="http://java.sun.com/jsp/jstl/functions" xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields" xmlns:form="urn:jsptagdir:/WEB-INF/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:spring="http://www.springframework.org/tags" version="2.0">

        <util:json-util />
        <util:link-to-json field="owner" itemValue="id" path="/owners" />

        <jsp:directive.page contentType="text/html;charset=UTF-8"/>
        <jsp:output omit-xml-declaration="yes"/>
        <form:create id="fc_com_springsource_petclinic_domain_Pet" modelAttribute="pet" path="/pets" render="${empty dependencies}" z="lgvEyAlAYOudDmaPjwU0ABseTIk=">
            <field:checkbox field="sendReminders" id="c_com_springsource_petclinic_domain_Pet_sendReminders" z="uPpMX+IWb0KONpvd11fpG8x4/4Q="/>
            <field:input field="name" id="c_com_springsource_petclinic_domain_Pet_name" min="1" required="true" z="ZY+k75JeSo9RmejYZRFNIvs2aBg="/>
            <field:input field="weight" id="c_com_springsource_petclinic_domain_Pet_weight" min="0" required="true" validationMessageCode="field_invalid_number" z="cOD5zE/z7gy+RZu5kVSPuxCa+/I="/>
            <field:select field="owner" inlineCreate="true" id="c_com_springsource_petclinic_domain_Pet_owner" itemValue="id" items="${owners}" path="/owners" z="fGzswAP4XXvhPhowJKsRVve929c="/>
            <field:select field="type" id="c_com_springsource_petclinic_domain_Pet_type" items="${pettypes}" path="pettypes" required="true" z="+hDCnUp+Y+A1RlT+AjH07sgipOo="/>
        </form:create>
        <form:dependency dependencies="${dependencies}" id="d_com_springsource_petclinic_domain_Pet" render="${not empty dependencies}" z="kThDNIW+69h9nI/69ynY1WyUieo="/>
      
        <form:create-json id="fc_com_springsource_petclinic_domain_Owner" modelAttribute="owner" path="/petclinic/owners/jsonArray" render="${empty dependencies}" z="JVL6ZvtbnTyvO3flv+5gP4Lf98o=">
            <field:input field="firstName" disableFormBinding="true" id="c_com_springsource_petclinic_domain_Owner_firstName" max="30" min="3" z="iFQbgU5/QXNtac+P4ovg8kEV7ek="/>
            <field:input field="lastName" disableFormBinding="true" id="c_com_springsource_petclinic_domain_Owner_lastName" max="30" min="3" required="true" z="njvl6BTK6olS0lu0xpz2JAljl/w="/>
            <field:textarea field="address" disableFormBinding="true" id="c_com_springsource_petclinic_domain_Owner_address" required="true" z="F58lrymQYAb+vI2R18H+u5MV3co="/>
            <field:input field="city" disableFormBinding="true" id="c_com_springsource_petclinic_domain_Owner_city" max="30" required="true" z="KeFguNGWsgzbj3W5t5xAQ67nB8w="/>
            <field:input field="telephone" disableFormBinding="true" id="c_com_springsource_petclinic_domain_Owner_telephone" required="true" z="UArcYWJhCMGb8fdNhd+tPR7lGZA="/>
            <field:input field="homePage" disableFormBinding="true" id="c_com_springsource_petclinic_domain_Owner_homePage" max="30" z="/vNZCeUDVvKka/dick16UkqxCeM="/>
            <field:input field="email" disableFormBinding="true" id="c_com_springsource_petclinic_domain_Owner_email" max="30" min="6" validationMessageCode="field_invalid_email" z="OBipoi4fQlmRIdM78qa1WWEIIwU="/>
            <field:datetime dateTimePattern="MM/dd/yyyy" field="birthDay" disableFormBinding="true" id="c_com_springsource_petclinic_domain_Owner_birthDay" required="true" z="hePNmv60SdPIC9BHED+b/A3wvL8="/>
            <field:simple field="pets" id="c_com_springsource_petclinic_domain_Owner_pets" messageCode="entity_reference_not_managed" messageCodeAttribute="Pet" z="mFNdxgNw45XzY+L48hMYPWM8B4E="/>
        </form:create-json>

    </div>


Monday, January 24, 2011

Accessing ROO with JSON and Prototype.js

I decided to play around a little this week-end with JSON and Roo. The main reason is that I wanted to look into contributing to a solution for ROO-207, and it seems to me that JSON will probably factor greatly into it. Initially, I was testing against the PetClinic example (clinic.roo), but when I started to see the errors I am describing here, I decided to go for a more basic example. I decided to use prototype.js to facilitate the ajax calls from within JavaScript since I will be building a test UI completely from scratch without anything from ROO.

Setup
Create a directory called jsonlist. The following is the contents of my roo.log.
// Spring Roo 1.1.1.RELEASE [rev 156ccd6]
project --topLevelPackage ca.digitalface.jsonlist
persistence setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE
entity --class ~.Item --testAutomatically
field string --fieldName name --notNull
controller all --package ~.web
json all
Create a basic html page (I called mine testJSONPrototype.html) to test the JavaScript access.
Put it in src/main/webapp.
<html>
<head>
<title>Accessing ROO with JSON and Prototype.js</title>
<script type="text/javascript" src="./prototype.js"></script>
<script type="text/javascript">
  document.observe('dom:loaded', function() {
    var url = 'http://localhost:8080/jsonlist/items';
      new Ajax.Request(url, {
        method: 'POST', method: 'GET',
        contentType: 'application/json',
        requestHeaders: ["Accept","application/json"],
        onLoaded: function(){alert("sent");},
        onSuccess: function(transport) {
          var val = transport.responseText;
          var target = $('target');
          if (transport != null)
            target.update('Response: '+val);
          else
            target.update('Transport is null!');
        }
    });
  });
</script>
</head>
<body>
<h1>Accessing ROO with JSON and Prototype.js</h1>
<div id="target">List of Items.</div>
</body>
</html>
Download the prototype.js file from www.prototypejs.org and put it in src/main/webapp.


Execute
Run the application with mvn tomcat:run and then open http://localhost:8080/jsonlist in your browser.
Add some items to the list.

Here's where the confusion comes in.
If you use curl at the command line, you get the JSON text as you would expect,
curl -i -H "Accept: application/json" http://localhost:8080/jsonlist/items

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 92
Date: Mon, 24 Jan 2011 12:57:32 GMT

[{"id":1,"name":"aaaaaaaaaaa","version":0},{"id":2,"name":"bbbbbbbbbbbbbbbbbb","version":0}]
however if you go to the test page (localhost:8080/jsonlist/testJSONPrototype.html) we created above it fails with the following error and stack trace.
Stepping back two steps is not supported
flexjson.JSONTokener.back(JSONTokener.java:83)
flexjson.JSONTokener.nextValue(JSONTokener.java:378)
flexjson.JSONDeserializer.deserialize(JSONDeserializer.java:150)
ca.digitalface.jsonlist.Item_Roo_Json.fromJsonToItem_aroundBody0(Item_Roo_Json.aj:21)
ca.digitalface.jsonlist.Item_Roo_Json.ajc$interMethod$ca_digitalface_jsonlist_Item_Roo_Json$ca_digitalface_jsonlist_Item$fromJsonToItem(Item_Roo_Json.aj:1)
ca.digitalface.jsonlist.Item.fromJsonToItem(Item.java:1)
ca.digitalface.jsonlist.Item_Roo_Json.ajc$interMethodDispatch1$ca_digitalface_jsonlist_Item_Roo_Json$ca_digitalface_jsonlist_Item$fromJsonToItem(Item_Roo_Json.aj)
ca.digitalface.jsonlist.web.ItemController_Roo_Controller.ajc$interMethod$ca_digitalface_jsonlist_web_ItemController_Roo_Controller$ca_digitalface_jsonlist_web_ItemController$createFromJson(ItemController_Roo_Controller.aj:107)
ca.digitalface.jsonlist.web.ItemController.createFromJson(ItemController.java:1)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
java.lang.reflect.Method.invoke(Method.java:597)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:176)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:426)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:414)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.doFilterInternal(OpenEntityManagerInViewFilter.java:113)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
java.lang.Thread.run(Thread.java:662)

Here are the headers that were captured by the "Live HTTP Headers" plugin for Firefox.
http://localhost:8080/jsonlist/items

POST /jsonlist/items HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13
Accept: application/json
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
X-Requested-With: XMLHttpRequest
X-Prototype-Version: 1.7
Content-Type: application/json; charset=UTF-8
Referer: http://localhost:8080/jsonlist/testJSONPrototype.html
Content-Length: 0
Cookie: JSESSIONID=2F82F1236EA5EFDC0C0464A87F64EB67
Pragma: no-cache
Cache-Control: no-cache

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=1BE83EB4104405124F75B91A652197D5; Path=/jsonlist
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Content-Length: 7304
Date: Mon, 24 Jan 2011 14:21:51 GMT
I have not yet figured out the solution to this, but I thought I'd open a JIRA Ticket and see what comes of it. Watch this space for updates.

Update
Well, thanks to Stefan's comment the solution was easy.  8^)
I only had to change the method from POST to GET, and everything went as planned. I was sure that I had tried that, but I guess not.

There is no denying the value of a second set of eyes!

Thursday, August 5, 2010

DataOnDemand and Number Fields - Unit tests fail on Short and @Min(1L)

I just opened the following bug (ROO-1176). Though one could look at this and say "what junk", I look at this and marvel at the ease of the work-around. I think this is another positive check for ROO.

This bug covers two separate issues, I have placed them together because they seem to stem from the same code.

FIRST:
Creating an entity with number type fields and applying an @Min constraint greater than zero will fail. It seems that either the getNewTransient_() method or the init() method should take the @Min constraint into account. The init() method sends indexes that are from 0 to 9, but if you have an @Min of 1, the getNewTransient_() will try to populate your field with a zero, which will fail when the object persist() method is called.

SECOND:
Creating an entity with number type fields that are smaller than an int fail when the testPersist() test is called, because it calls getNewTransient__(Integer.MAX_VALUE) which cannot be squeezed into a Short (for example).

WORK AROUND:
Override the getNewTransient__() method and add this to the beginning
if(index == 0){
index = 1;
} else if(index <>
index = index * -1;
}
if(index > Short.MAX_VALUE){
Random rnd = new Random(new Integer(index).longValue());
index = rnd.nextInt(Short.MAX_VALUE);
}

LOG.ROO:
The following is a log.roo for a test.
// Spring Roo 1.1.0.M1 [rev 3a0b8a3] log opened at 2010-08-05 11:46:45
project --topLevelPackage com.things
persistence setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE
entity --class ~.domain.ShortThings --testAutomatically
field string --fieldName name field number --fieldName thingValue --type java.lang.Short --min 1 --notNull
entity --class ~.domain.LongThings --testAutomatically
field number --fieldName thingValue --type java.lang.Long --min 1 --notNull
perform tests
// Spring Roo 1.1.0.M1 [rev 3a0b8a3] log opened at 2010-08-05 11:54:26