JSP file: /WEB-INF/jsp/customers/edit.jsp

<%-- 

    Examples of:
    
    * Form.renderAsText to display the form as plain text.
    * Enum properties as radio buttons.
    * Many-to-one relationship as inputSelect
    * Input password with equals validation.
    * Multiple field as map (collection or array are also supported).
    * Multiple submit buttons

--%>
<tags:layout>

  <jsp:attribute name="js">
  
    <c:if test="${empty action.customer.id}">
    
        <script>
        
          // bind javascript validations
          $$('form')[0].bindValidations();
          
        </script>
        
    </c:if>
    
  </jsp:attribute>
  
  <jsp:body>

      <l:form action="Customers" event="save" separateErrors="true" renderAsText="${!empty action.customer.id}">
      
      <c:if test="${!empty action.customer.id}">
          <div class="important">This is the same form used for user input, but setting renderAsText=true</div>
      </c:if>
      
      <div class="buttonBar">
          <c:if test="${empty action.customer.id}">
              <l:button type="submit"/>
              <l:button type="submit" value="Refresh" event="edit"/>
          </c:if>
          <l:url title="goback" action="Customers" event="list"/>
    </div>
  
      <l:messages/>
    
      <c:if test="${!empty action.customer.id}">
          <l:inputHidden name="customer.id" />
      </c:if>
      <l:inputText name="customer.firstName"/>
      <l:inputText name="customer.lastName"/>
      <l:inputText name="customer.idCard"/>
      <l:inputText name="customer.email" autocomplete="false"/>
      <l:inputPassword name="customer.password" value=""/>
      <l:inputPassword name="confirmPassword"  value="" />
      <l:inputCheckbox name="customer.phoneSupport" labelPos="left"/>
      <l:inputSelect label="customerType" name="customer.customerType.id" options="${action.customerTypes}" />
      
      <c:forTokens items="EUROPE,ASIA,AMERICA" delims="," var="index">
          <l:inputText name="prices[${index}]" label="prices.${index}" />
      </c:forTokens>
      
    <hr/>
    
    <l:inputRadio name="customer.creditType"/>
          
      </l:form>
    
  </jsp:body>
    
</tags:layout>    

Class: Customer.java

/*
 * Copyright 2002-2006 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.loom.demo.model;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;

import org.loom.annotation.validation.RequiredValidation;
import org.loom.annotation.validation.StringValidation;

/**
 * This class uses one example of hibernate validations
 * @author Ignacio Coloma
 */
@Entity
public class Customer {

    public enum CreditType {
        GOOD, 
        FAIR, 
        NEEDS_IMPROVEMENT, 
        POOR
    }
    
    @Id @GeneratedValue
    private Integer id;
    
    @Basic(optional = false)
    @Column(length=40)
    private String firstName;
    
    @Column(length=80)
    @RequiredValidation(on="save")
    private String lastName;

    @Basic(optional = false)
    @Column(length=80)
    private String idCard;
    
    @StringValidation(mask=StringValidation.EMAIL_MASK)
    private String email;

    private boolean phoneSupport;
    
    @StringValidation(minLength=5, maxLength=10)
    private String password;
    
    @Enumerated(EnumType.STRING)
    private CreditType creditType = CreditType.FAIR;
    
    @ManyToOne
    @RequiredValidation(propertyPath="id")
    private CustomerType customerType;
    
    /** comment */
    @Lob
    @Basic(fetch=FetchType.LAZY)
    @Column(length=80)
    private String comments;
    
    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getIdCard() {
        return idCard;
    }

    public void setIdCard(String idCard) {
        this.idCard = idCard;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public CreditType getCreditType() {
        return creditType;
    }

    public void setCreditType(CreditType creditType) {
        this.creditType = creditType;
    }

    public CustomerType getCustomerType() {
        return customerType;
    }

    public void setCustomerType(CustomerType customerType) {
        this.customerType = customerType;
    }

    public boolean isPhoneSupport() {
        return phoneSupport;
    }

    public void setPhoneSupport(boolean phoneSupport) {
        this.phoneSupport = phoneSupport;
    }

    public String getComments() {
        return comments;
    }

    public void setComments(String comments) {
        this.comments = comments;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }


}

Class: CustomersAction.java

/*
 * Copyright 2002-2006 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.loom.demo.action;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.QueryParam;

import org.loom.action.AbstractAction;
import org.loom.annotation.Event;
import org.loom.annotation.RetrieveEntity;
import org.loom.annotation.validation.NestedAnnotations;
import org.loom.annotation.validation.NumberValidation;
import org.loom.annotation.validation.RequiredValidation;
import org.loom.annotation.validation.StringValidation;
import org.loom.demo.model.Customer;
import org.loom.demo.model.CustomerType;
import org.loom.exception.LocaleAwareException;
import org.loom.exception.MissingRequiredPropertyException;
import org.loom.paged.PagedListCriteria;
import org.loom.paged.PagedListCriteriaFactory;
import org.loom.paged.PagedListData;
import org.loom.paged.PagedListDataFactory;
import org.loom.persistence.ExtendedEntityManager;
import org.loom.resolution.Resolution;
import org.loom.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Example of:
 * 
 * Pagination done in the web layer
 * Validations indicated in the action class.
 * Throwing locale-aware exceptions
 * 
 * @author icoloma
 */
public class CustomersAction extends AbstractAction {

    /** service class */
    @Autowired
    private ExtendedEntityManager transactionalService;
    
    /** the paged list of customers */
    private PagedListData<Customer> customers;
    
    /** 
     * The instance that is being edited. 
     * This is an example of validations specified on the action class in addition of the model class 
     */
    @NestedAnnotations(on="save")
    @StringValidation(propertyPath="lastName", maxLength=80, minLength=5, on="save")
    @RetrieveEntity(on="edit")
    private Customer customer;
    
    /** example of a Map property */
    @NumberValidation(precision=11, scale=2)
    @RequiredValidation(on="save")
    private Map<String, BigDecimal> prices = new HashMap<String, BigDecimal>();
    
    /**
     * Edit an existing customer
     */
    public Resolution edit() {
        return forward();
    }

    /**
     * Example of returning a custom type (here, CustomerType) instead of Option
     * to fill a <select> tag
     */
    public Iterable<CustomerType> getCustomerTypes() {
        return transactionalService.findAll(CustomerType.class);
    }
    
    /**
     * Example of LocaleAwareThrowable-to-error conversion.
     * If the Exception gets thrown, it will be displayed as an input validation error.
     */
    public Resolution save(
            @StringValidation(maxLength=10, on="save") 
            @QueryParam("confirmPassword")
            String confirmPassword
            ) {
        if (StringUtils.isEmpty(customer.getPassword())) {
            throw new MissingRequiredPropertyException("customer.password");
        }
        if (!customer.getPassword().equals(confirmPassword)) {
            throw new LocaleAwareException("The values of password and confirm password must be the same", "error.custom.equalsFailed").addTranslatedArg("property1", "password").addTranslatedArg("property2", "confirmPassword");
        }
        if (customer.getId() == null) {
            if (!transactionalService.find("from Customer c where c.firstName=? and c.lastName=?", customer.getFirstName(), customer.getLastName()).isEmpty()) {    
                throw new LocaleAwareException("There is already a customer with name " + customer.getFirstName() + " " + customer.getLastName(), "error.custom.customerNameExists").addArg("customer", customer);
            }
        } 
        this.customer = transactionalService.merge(customer);
        return forward("edit.jsp");
    }
    
    /**
     * Example of manual conversion from List to PagedListData 
     * This is not recommended; if possible, pagination should be done by the database layer
     * (see the example  in MortgagesAction)
     */
    @Event(defaultEvent=true)
    public Resolution list() {
        PagedListCriteria criteria = PagedListCriteriaFactory.create(getRequest());
        criteria.setPageSize(5); // to showcase the pagination
        customers = PagedListDataFactory.create(criteria, transactionalService.findAll(Customer.class));
        return forward();
    }
    
    public PagedListData<Customer> getCustomers() {
        return customers;
    }

    public Customer getCustomer() {
        return customer;
    }

    public Map<String, BigDecimal> getPrices() {
        return prices;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
    
}