package org.jpwh.web.jsf; import org.jpwh.web.dao.BidDAO; import org.jpwh.web.dao.ItemDAO; import org.jpwh.web.model.Bid; import org.jpwh.web.model.Item; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; import javax.persistence.EntityNotFoundException; import javax.transaction.Transactional; import java.math.BigDecimal; @Named /* We don't need to hold state across requests for this use case. A service instance will be created when the auction page view is rendered with a GET request, and JSF will bind the request parameter by calling setId(). The service instance is destroyed after rendering is complete. Your server does not hold any state between requests. When the auction form is submitted and processing of that POST request starts, JSF will call setId() to bind the hidden form field, and you can again initialize the state of the service. */ @RequestScoped public class AuctionService { @Inject ItemDAO itemDAO; @Inject BidDAO bidDAO; /* The state you hold for each request is the identifier value of the Item the user is working on, the actual Item after it was loaded, the currently highest bid amount for that item, and the new bid amount entered by the user. */ long id; Item item; BigDecimal highestBidAmount; BigDecimal newBidAmount; public void setId(long id) { this.id = id; if (item == null) { item = itemDAO.findById(id); if (item == null) throw new EntityNotFoundException(); highestBidAmount = itemDAO.getMaxBidAmount(item); } } // Other plain getters and setters... public long getId() { return id; } public Item getItem() { return item; } public BigDecimal getNewBidAmount() { return newBidAmount; } public void setNewBidAmount(BigDecimal newBidAmount) { this.newBidAmount = newBidAmount; } public BigDecimal getHighestBidAmount() { return highestBidAmount; } /* The @Transactional annotation is new in Java EE 7 (from JTA 1.2) and similar to @TransactionAttribute on EJB components. Internally, an interceptor will wrap the method call in a system transaction context, just like an EJB method. */ @Transactional public String placeBid() { /* In this method, you want to perform transactional work and store a new bid in the database, and prevent concurrent bids. You must join the persistence context with the transaction. It doesn't actually matter which DAO you call, as all of them share the same request-scoped EntityManager. */ itemDAO.joinTransaction(); /* If another transaction is committed for a higher bid, in the time our user was thinking and looking at the rendered auction page, fail and re-render the auction page with a message. */ if (!getItem().isValidBidAmount( getHighestBidAmount(), getNewBidAmount() )) { ValidationMessages.addFacesMessage("Auction.bid.TooLow"); return null; } /* You must force a version increment of the Item at flush time to prevent concurrent bids. If another transaction runs at the same time as this, and loads the same Item version and current highest bid from the database in setId(), one of the transactions must fail in placeBid(). */ itemDAO.checkVersion(getItem(), true); bidDAO.makePersistent(new Bid(getNewBidAmount(), getItem())); /* This is a simple redirect-after-POST in JSF, so users can safely reload the page after submitting a bid. */ return "auction?id=" + getId() + "&faces-redirect=true"; } }