Session Beans

In JumpStart, the business logic is in Java EE Session Beans, such as PersonFinderService and PersonManagerService.

Here we've used PersonFinderService to find entity Person with id = 1. The @EJB annotation is explained in the next example.
Id
1
Version
2
First Name
Humpty
Last Name
Dumpty2
Region
East Coast
Start Date
Dec 28, 2007
For those of you who do not want to use Session Beans, and who do not mind the open-session-in-view pattern, Tapestry has JPA Integration and Hibernate Integration.

References: "Why EJB3?" in JumpStart FAQ, What is an Enterprise Bean?.

Home


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like &nbsp; 
     We use a "strict" DTD to make IE follow the alignment rules. -->
     
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
<body class="container">

    <h3>Session Beans</h3>
    
    In JumpStart, the business logic is in Java EE Session Beans, such as PersonFinderService and PersonManagerService.<br/><br/>

    Here we've used PersonFinderService to find entity Person with id = 1. 
    The <code>@EJB</code> annotation is explained in the next example.

    <div class="eg">
        <t:beandisplay object="person"/>
    </div>

    For those of you who do not want to use Session Beans, and who do not mind the open-session-in-view pattern, Tapestry has 
    <a href="http://tapestry.apache.org/integrating-with-jpa.html">JPA Integration</a> and 
    <a href="http://tapestry.apache.org/hibernate.html">Hibernate Integration</a>.<br/><br/>

    References: <a href="http://jumpstart.doublenegative.com.au/faq.html">"Why EJB3?" in JumpStart FAQ</a>, 
    <a href="http://docs.oracle.com/javaee/7/tutorial/doc/ejb-intro001.htm#GIPMB">What is an Enterprise Bean?</a>.<br/><br/>
    
    <t:pagelink page="Index">Home</t:pagelink><br/><br/>

    <t:tabgroup>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/state/SessionBeans.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/state/SessionBeans.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/plain.css"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/iface/IPersonFinderServiceLocal.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/iface/IPersonFinderServiceRemote.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/PersonFinderService.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/iface/IPersonManagerServiceLocal.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/iface/IPersonManagerServiceRemote.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/PersonManagerService.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/Person.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/Regions.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/ValidationException.java"/>
    </t:tabgroup>
</body>
</html>


package jumpstart.web.pages.examples.state;

import javax.ejb.EJB;

import jumpstart.business.domain.person.Person;
import jumpstart.business.domain.person.iface.IPersonFinderServiceLocal;

import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Property;

@Import(stylesheet = "css/examples/plain.css")
public class SessionBeans {

    // Screen fields

    @Property
    private Person person;

    // Generally useful bits and pieces

    @EJB
    private IPersonFinderServiceLocal personFinderService;

    // The code

    void setupRender() throws Exception {
        person = personFinderService.findPerson(1L);

        if (person == null) {
            throw new IllegalStateException("Database data has not been set up!");
        }
    }

}


.eg {
                margin: 20px 0;
                padding: 14px;
                border: 1px solid #ddd;
                border-radius: 6px;
                -webkit-border-radius: 6px;
                -mox-border-radius: 6px;
}


package jumpstart.business.domain.person.iface;

/**
 * The <code>IPersonServiceLocal</code> bean exposes the business methods in the interface.
 */
public interface IPersonFinderServiceLocal extends IPersonFinderServiceRemote {
}


package jumpstart.business.domain.person.iface;

import java.util.List;

import jumpstart.business.domain.person.Person;
import jumpstart.business.domain.person.Regions;
import jumpstart.util.query.SortCriterion;

/**
 * The <code>IPersonFinderServiceRemote</code> bean exposes the business methods in the interface.
 */
public interface IPersonFinderServiceRemote {

    // Person

    Person findPerson(Long id);

    long countPersons();
    
    List<Person> findPersons(int maxResults);

    List<Person> findPersons(String partialName, int maxResults);

    List<Person> findPersonsByFirstName(String firstName);

    List<Person> findPersonsByLastName(String lastName);

    long countPersons(String partialName);
    
    List<Person> findPersons(String partialName, int startIndex, int maxResults);

    List<Person> findPersons(int startIndex, int maxResults, List<SortCriterion> sortCriteria);

    long countPersons(String firstNameStartsWith, String lastNameStartsWith, Regions region);
    
    List<Person> findPersons(String firstNameStartsWith, String lastNameStartsWith, Regions region, int startIndex,
            int maxResults, List<SortCriterion> sortCriteria);
    
}


package jumpstart.business.domain.person;

import java.util.Arrays;
import java.util.List;

import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import jumpstart.business.domain.person.iface.IPersonFinderServiceLocal;
import jumpstart.business.domain.person.iface.IPersonFinderServiceRemote;
import jumpstart.util.query.SortCriterion;

@Stateless
@Local(IPersonFinderServiceLocal.class)
@Remote(IPersonFinderServiceRemote.class)
public class PersonFinderService implements IPersonFinderServiceLocal, IPersonFinderServiceRemote {

    @PersistenceContext(unitName = "jumpstart")
    private EntityManager em;

    public Person findPerson(Long id) {
        return em.find(Person.class, id);
    }

    public long countPersons() {
        return (Long) em.createQuery("select count(p) from Person p").getSingleResult();
    }

    @SuppressWarnings("unchecked")
    public List<Person> findPersons(int maxResults) {
        return em.createQuery("select p from Person p order by lower(p.firstName), lower(p.lastName)")
                .setMaxResults(maxResults).getResultList();
    }

    @SuppressWarnings("unchecked")
    public List<Person> findPersons(String partialName, int maxResults) {
        String searchName = partialName == null ? "" : partialName.toLowerCase();

        StringBuilder buf = new StringBuilder();
        buf.append("select p from Person p");
        buf.append(" where lower(firstName) like :firstName");
        buf.append(" or lower(lastName) like :lastName");
        buf.append(" order by lower(p.firstName), lower(p.lastName)");

        Query q = em.createQuery(buf.toString());
        q.setParameter("firstName", "%" + searchName + "%");
        q.setParameter("lastName", "%" + searchName + "%");

        List<Person> l = q.setMaxResults(maxResults).getResultList();
        return l;
    }

    @SuppressWarnings("unchecked")
    public List<Person> findPersonsByFirstName(String firstName) {
        String searchName = firstName == null ? "" : firstName.trim().toLowerCase();

        StringBuilder buf = new StringBuilder();
        buf.append("select p from Person p");
        buf.append(" where lower(p.firstName) = :searchName");
        buf.append(" order by lower(p.firstName), lower(p.lastName)");

        Query q = em.createQuery(buf.toString());
        q.setParameter("searchName", searchName);

        List<Person> l = q.getResultList();
        return l;
    }

    @SuppressWarnings("unchecked")
    public List<Person> findPersonsByLastName(String lastName) {
        String searchName = lastName == null ? "" : lastName.trim().toLowerCase();

        StringBuilder buf = new StringBuilder();
        buf.append("select p from Person p");
        buf.append(" where lower(p.lastName) = :searchName");
        buf.append(" order by lower(p.lastName), lower(p.firstName)");

        Query q = em.createQuery(buf.toString());
        q.setParameter("searchName", searchName);

        List<Person> l = q.getResultList();
        return l;
    }

    public long countPersons(String partialName) {
        return (Long) findPersons(true, partialName, 0, 0);
    }

    @SuppressWarnings("unchecked")
    public List<Person> findPersons(String partialName, int startIndex, int maxResults) {
        return (List<Person>) findPersons(false, partialName, startIndex, maxResults);
    }

    @SuppressWarnings("unchecked")
    private Object findPersons(boolean counting, String partialName, int startIndex, int maxResults) {
        String searchName = partialName == null ? "" : partialName.toLowerCase();

        StringBuilder buf = new StringBuilder();

        if (counting) {
            buf.append("select count(p) from Person p");
        }
        else {
            buf.append("select p from Person p");
        }
        buf.append(" where lower(firstName) like :firstName");
        buf.append(" or lower(lastName) like :lastName");

        if (!counting) {
            buf.append(" order by lower(p.firstName), lower(p.lastName)");
        }

        Query q = em.createQuery(buf.toString());
        q.setParameter("firstName", "%" + searchName + "%");
        q.setParameter("lastName", "%" + searchName + "%");

        if (counting) {
            Long qty = (Long) q.getSingleResult();
            return qty;
        }
        else {
            List<Person> l = q.setFirstResult(startIndex).setMaxResults(maxResults).getResultList();
            return l;
        }
    }

    @SuppressWarnings("unchecked")
    public List<Person> findPersons(int startIndex, int maxResults, List<SortCriterion> sortCriteria) {
        final List<String> PROPERTIES_TO_LOWER_FOR_SORT = Arrays.asList("firstName", "lastName");

        // Here we use JPQL. An alternative is to use javax.persistence.criteria.CriteriaQuery. For an example see
        // Tapestry's JpaGridDataSource.

        StringBuilder buf = new StringBuilder();
        buf.append("select p from Person p");
        buf.append(" order by ");

        boolean firstOrderByItem = true;
        boolean orderByIncludesId = false;

        for (SortCriterion sortCriterion : sortCriteria) {
            String propertyName = sortCriterion.getPropertyName();

            // Append an "order by" item, eg. "startDate", or ", lower(firstName) desc".

            if (!firstOrderByItem) {
                buf.append(", ");
            }
            if (PROPERTIES_TO_LOWER_FOR_SORT.contains(propertyName)) {
                buf.append("lower(").append(propertyName).append(")");
            }
            else {
                buf.append(propertyName);
            }
            buf.append(sortCriterion.getSortDirection().toStringForJpql());

            // We need to know later whether the "order by" includes id.

            if (propertyName.equals("id")) {
                orderByIncludesId = true;
            }
            firstOrderByItem = false;
        }

        // Ensure sequence is predictable by ensuring a unique property, id, is in the "order by".

        if (!orderByIncludesId) {
            if (!firstOrderByItem) {
                buf.append(", ");
            }
            buf.append("id");
        }

        Query q = em.createQuery(buf.toString());

        List<Person> l = q.setFirstResult(startIndex).setMaxResults(maxResults).getResultList();
        return l;
    }

    public long countPersons(String firstNameStartsWith, String lastNameStartsWith, Regions region) {
        return (Long) findPersons(true, firstNameStartsWith, lastNameStartsWith, region, 0, 0, null);
    }

    @SuppressWarnings("unchecked")
    public List<Person> findPersons(String firstNameStartsWith, String lastNameStartsWith, Regions region,
            int startIndex, int maxResults, List<SortCriterion> sortCriteria) {
        return (List<Person>) findPersons(false, firstNameStartsWith, lastNameStartsWith, region, startIndex,
                maxResults, sortCriteria);
    }

    /**
     * Finds persons who match the criteria.
     * If counting == true, returns the count of persons found, as a Long.
     * Else, returns the persons found, as a List<Person>.
     */
    @SuppressWarnings("unchecked")
    private Object findPersons(boolean counting, String firstNameStartsWith, String lastNameStartsWith, Regions region,
            int startIndex, int maxResults, List<SortCriterion> sortCriteria) {
        final List<String> PROPERTIES_TO_LOWER_FOR_SORT = Arrays.asList("firstName", "lastName");

        String searchFirstName = firstNameStartsWith == null ? "" : firstNameStartsWith.toLowerCase();
        String searchLastName = lastNameStartsWith == null ? "" : lastNameStartsWith.toLowerCase();

        StringBuilder buf = new StringBuilder();

        if (counting) {
            buf.append("select count(p) from Person p");
        }
        else {
            buf.append("select p from Person p");
        }

        buf.append(" where lower(firstName) like :firstName");
        buf.append(" and lower(lastName) like :lastName");
        if (region != null) {
            buf.append(" and region = :region");
        }

        if (!counting) {
            buf.append(" order by ");

            boolean firstOrderByItem = true;
            boolean orderByIncludesId = false;

            for (SortCriterion sortCriterion : sortCriteria) {
                String propertyName = sortCriterion.getPropertyName();

                // Append an "order by" item, eg. "startDate", or ", lower(firstName) desc".

                if (!firstOrderByItem) {
                    buf.append(", ");
                }
                if (PROPERTIES_TO_LOWER_FOR_SORT.contains(propertyName)) {
                    buf.append("lower(").append(propertyName).append(")");
                }
                else {
                    buf.append(propertyName);
                }
                buf.append(sortCriterion.getSortDirection().toStringForJpql());

                // We need to know later whether the "order by" includes id.

                if (propertyName.equals("id")) {
                    orderByIncludesId = true;
                }
                firstOrderByItem = false;
            }

            // Ensure sequence is predictable by ensuring a unique property, id, is in the "order by".

            if (!orderByIncludesId) {
                if (!firstOrderByItem) {
                    buf.append(", ");
                }
                buf.append("id");
            }
        }

        Query q = em.createQuery(buf.toString());
        q.setParameter("firstName", searchFirstName + "%");
        q.setParameter("lastName", searchLastName + "%");
        if (region != null) {
            q.setParameter("region", region);
        }

        if (counting) {
            Long qty = (Long) q.getSingleResult();
            return qty;
        }
        else {
            List<Person> l = q.setFirstResult(startIndex).setMaxResults(maxResults).getResultList();
            return l;
        }
    }

}


package jumpstart.business.domain.person.iface;

/**
 * The <code>IPersonManagerServiceLocal</code> bean exposes the business methods in the interface.
 */
public interface IPersonManagerServiceLocal extends IPersonManagerServiceRemote {
}


package jumpstart.business.domain.person.iface;

import java.util.List;

import jumpstart.business.commons.IdVersion;
import jumpstart.business.domain.person.Person;

/**
 * The <code>IPersonManagerServiceRemote</code> bean exposes the business methods in the interface.
 */
public interface IPersonManagerServiceRemote {

    // Person

    Person createPerson(Person person);

    void createPersons(List<Person> persons);

    Person changePerson(Person person);

    void changePersons(List<Person> persons);

    void changePersonsByDTOs(List<PersonDTO> persons);

    void bulkEditPersons(List<Person> personsToCreate, List<Person> personsToChange, List<IdVersion> personsToDelete);

    void bulkEditPersonsByDTOs(List<PersonDTO> personsToCreate, List<PersonDTO> personsToChange,
            List<IdVersion> personsToDelete);

    void deletePerson(Long id, Integer version);

}


package jumpstart.business.domain.person;

import java.util.List;

import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceContext;

import jumpstart.business.commons.IdVersion;
import jumpstart.business.domain.person.iface.IPersonManagerServiceLocal;
import jumpstart.business.domain.person.iface.IPersonManagerServiceRemote;
import jumpstart.business.domain.person.iface.PersonDTO;

@Stateless
@Local(IPersonManagerServiceLocal.class)
@Remote(IPersonManagerServiceRemote.class)
public class PersonManagerService implements IPersonManagerServiceLocal, IPersonManagerServiceRemote {

    @PersistenceContext(unitName = "jumpstart")
    public EntityManager em;

    public Person createPerson(Person person) {
        em.persist(person);
        return person;
    }

    public void createPersons(List<Person> persons) {
        for (Person person : persons) {
            em.persist(person);
        }
    }

    public Person changePerson(Person person) {
        Person p = em.merge(person);
        // Flush to work around OPENEJB issue https://issues.apache.org/jira/browse/OPENEJB-782
        em.flush();

        // If id is different it means the person did not exist so merge has created a new one.
        if (!p.getId().equals(person.getId())) {
            throw new EntityNotFoundException("Person no longer exists.");
        }
        
        return p;
    }

    public void changePersons(List<Person> persons) {
        for (Person person : persons) {
            Person p = em.merge(person);

            // If id is different it means the person did not exist so merge has created a new one.
            if (!p.getId().equals(person.getId())) {
                throw new EntityNotFoundException("Person no longer exists.");
            }
        }
    }

    public void changePersonsByDTOs(List<PersonDTO> persons) {
        for (PersonDTO person : persons) {
            Person p = em.find(Person.class, person.getId());

            if (p == null) {
                throw new EntityNotFoundException("Person no longer exists.");
            }

            if (!p.getVersion().equals(person.getVersion())) {
                throw new OptimisticLockException();
            }

            p.setFirstName(person.getFirstName());
        }
    }

    public void bulkEditPersons(List<Person> personsToCreate, List<Person> personsToChange,
            List<IdVersion> personsToDelete) {
        for (Person person : personsToCreate) {
            em.persist(person);
        }
        for (Person person : personsToChange) {
            Person p = em.merge(person);

            // If id is different it means the person did not exist so merge has created a new one.
            if (!p.getId().equals(person.getId())) {
                throw new EntityNotFoundException("Person no longer exists.");
            }
        }
        for (IdVersion idVersion : personsToDelete) {
            Person p = em.find(Person.class, idVersion.getId());

            if (p == null) {
                throw new EntityNotFoundException("Person no longer exists.");
            }

            if (!p.getVersion().equals(idVersion.getVersion())) {
                throw new OptimisticLockException();
            }

            em.remove(p);
        }
    }

    public void bulkEditPersonsByDTOs(List<PersonDTO> personsToCreate, List<PersonDTO> personsToChange,
            List<IdVersion> personsToDelete) {
        for (PersonDTO p : personsToCreate) {
            Person person = new Person(p.getFirstName(), p.getLastName(), p.getRegion(), p.getStartDate());
            em.persist(person);
        }
        for (PersonDTO person : personsToChange) {
            Person p = em.find(Person.class, person.getId());

            if (p == null) {
                throw new EntityNotFoundException("Person no longer exists.");
            }

            if (!p.getVersion().equals(person.getVersion())) {
                throw new OptimisticLockException();
            }

            p.setFirstName(person.getFirstName());
            p.setLastName(person.getLastName());
            p.setRegion(person.getRegion());
            p.setStartDate(person.getStartDate());
        }
        for (IdVersion idVersion : personsToDelete) {
            Person p = em.find(Person.class, idVersion.getId());

            if (p == null) {
                throw new EntityNotFoundException("Person no longer exists.");
            }

            if (!p.getVersion().equals(idVersion.getVersion())) {
                throw new OptimisticLockException();
            }

            em.remove(p);
        }
    }

    public void deletePerson(Long id, Integer version) {
        Person p = em.find(Person.class, id);

        if (p == null) {
            throw new EntityNotFoundException("Person no longer exists.");
        }

        if (!p.getVersion().equals(version)) {
            throw new OptimisticLockException();
        }

        em.remove(p);
        // Flush to work around OPENEJB issue https://issues.apache.org/jira/browse/OPENEJB-782
        em.flush();
    }

}


package jumpstart.business.domain.person;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;


/**
 * The Person entity.
 */
@Entity
@SuppressWarnings("serial")
public class Person implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false)
    private Long id;

    @Version
    @Column(nullable = false)
    private Integer version;

    @Column(length = 10, nullable = false)
    @NotNull
    @Size(max = 10)
    private String firstName;

    @Column(length = 10, nullable = false)
    @NotNull
    @Size(max = 10)
    private String lastName;
    
    @Enumerated(EnumType.STRING)
    @NotNull
    private Regions region;

    @Temporal(TemporalType.DATE)
    @NotNull
    private Date startDate;

    public String toString() {
        final String DIVIDER = ", ";
        
        StringBuilder buf = new StringBuilder();
        buf.append(this.getClass().getSimpleName() + ": ");
        buf.append("[");
        buf.append("id=" + id + DIVIDER);
        buf.append("version=" + version + DIVIDER);
        buf.append("firstName=" + firstName + DIVIDER);
        buf.append("lastName=" + lastName + DIVIDER);
        buf.append("region=" + region + DIVIDER);
        buf.append("startDate=" + startDate);
        buf.append("]");
        return buf.toString();
    }

    // Default constructor is required by JPA.
    public Person() {
    }

    public Person(String firstName, String lastName, Regions region, Date startDate) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.region = region;
        this.startDate = startDate;
    }

    // The need for an equals() method is discussed at http://www.hibernate.org/109.html
    
    @Override
    public boolean equals(Object obj) {
        return (obj == this) || (obj instanceof Person) && id != null && id.equals(((Person) obj).getId());
    }

    // The need for a hashCode() method is discussed at http://www.hibernate.org/109.html

    @Override
    public int hashCode() {
        return id == null ? super.hashCode() : id.hashCode();
    }

    @PrePersist
    @PreUpdate
    public void validate() throws ValidationException {

    }

    public Long getId() {
        return id;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public String getFirstName() {
        return firstName;
    }

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

    public String getLastName() {
        return lastName;
    }

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

    public Regions getRegion() {
        return region;
    }

    public void setRegion(Regions region) {
        this.region = region;
    }

    public Date getStartDate() {
        return startDate;
    }

    public void setStartDate(Date startDate) {
        this.startDate = startDate;
    }

}


package jumpstart.business.domain.person;

public enum Regions {
    EAST_COAST, WEST_COAST;
}


package jumpstart.business.domain.person;

import javax.ejb.ApplicationException;

@ApplicationException(rollback = true)
@SuppressWarnings("serial")
public class ValidationException extends Exception {

    public ValidationException(String message) {
        super(message);
    }

}