Embrace the JPA 2 Criteria API

A bit of history and a problem statement of sorts

I’ve been a big fan of criteria queries for writing dynamic JPA queries for a long time. Sure I could make the same thing happen with a bit of logic and a whole lot of string concatenation but that gets old really quick. My first experience with criteria queries was with Hibernate 3 so lets start there and we will follow a query from SQL to JPA 2 Criteria.

We are all probably all most familiar with SQL so lets make that our starting point.

Assuming we have table in the database of tasks lets select those which are assigned to a specific user which have not been closed.

select * from Task t where t.assignee=1 and t.dateClosed is null

However, we are living in an ORM world though so lets see it in JPQL (same syntax in HQL)

List<Task> tasks = entityManager.createQuery("select t from Task t where t.assignee=:user"
    + " and t.dateClosed is null",Task.class).setParameter("user", myUser).getResultList();

Now lets migrate our query to use the Hibernate 3 Criteria API

Session session = (Session) entityManager.getDelegate();
    Criteria criteria = session.createCriteria(Task.class);
    criteria.add(Restrictions.eq("assignee", myUser));
    criteria.add(Restrictions.isNull("dateClosed"));
    List<Task> tasks = criteria.list();

Obviously this is more verbose but it also has some readability advantages in my opinion. Even if you disagree with regard to readability you can see that adding restrictions to the query in a programmatic way is much simpler than with JPQL/HQL.

However the one thing always bugged with with JPQL/HQL and Hibernate’s Criteria API is that everything is done with strings and doing things with strings makes refactoring difficult even with good tooling. This is Java and we want something type-safe.

JPA 2 Criteria API to the rescue.

CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Task> cq = cb.createQuery(Task.class);
    Root<Task> root = cq.from(Task.class);
    List<Predicate> predicates = new ArrayList<>();
    predicates.add(cb.equal(root.get(Task_.assignee), myUser));
    predicates.add(cb.isNull(root.get(Task_.dateClosed));
    cq.where(predicates.toArray(new Predicate[predicates.size()]));
    List<Task> tasks = em.createQuery(cq).getResultList();

I will readily admit that upon first taking a look at the JPA 2.0 Criteria API I was really turned off by its verbosity and complexity. I was turned off so much that I flat out ignored it for the first six months it was available to me. I still however desired a type-safe way to write my queries so that they would hold up better during refactoring.

Finally I blocked out a day to tinker with the API and see if I could convert a few of my more simple queries. After only a few minutes I was off and running and haven’t looked back. Here are a couple things I’ve learned during my time tinkering.

  • The code is longer but I can read and understand it much faster than a JPQL/HQL query.

  • I can write a criteria query faster due to IDE code completion and not having to review my entity structure to find the name of that specific column I’m trying to reference.

  • As the complexity of the query goes up the benefits of the criteria query grow, but you will be forced to do some learning. I have yet to find a query that I have not been able to convert. The API was very well thought out from this perspective.

  • My speed of development is faster almost 100% of the time as my criteria queries execute and return the desired results on the first try. I can’t say the same for my JPQl/HQL which are parsing a potentially very long string with lots of opportunities for syntax issues.

It isn’t however all sunshine and lollipops

This isn’t to say the API is perfect. It actually fights programmatic creation of queries a little bit. The API is designed so that the base of your query can reused. For example each time you call the criteriaQuery.where(Predicate... predicates) it replaces what was previously set. To work around this you need to store your predicates in a separate list and then add them all at once in array form (varargs). It would be nice if a criteriaQuery.where(List<Predicate> predicates) was exposed like it is for groupBy and orderBy. Additionally here are some other pain points.

  • Why is a Fetch<Z,X> not a Path like Join<Z,X> is? This means I need to define my "join" twice it I want it "join fetched". Hopefully this is fixed in JPA 2.1

  • Metamodel generation is broken in Eclipse. It doesn’t handle the cyclic nature of the metamodel classes. Thus when I do a project clean I will end up with 20 or so import failed errors. These are easily resolved by making a meaningless edit to the class to trigger an incremental build but it shouldn’t be this way.

Just do it

Hopefully I’ve inspired you to take another look if you’ve previous dismissed the API as I had. Block out a few hours and give it a shot. I’ll try to share my utility classes that I’ve written in an upcoming post that have made things a it easier for me, but in the meantime you really should get down and dirty with the api to understand it fully.

You’ll be glad you did.

Cody Lerum - August 08, 2013

CDI and JSF aren't Best Friends Yet

Where I started banging my head into the table.

This specific issue has actually has jumped up and bit me a few times so I’ll just try explain the most recent one. Basically I have a new object that I am creating through a JSF form page, and that object needs to have a default value set at creation which is determined at runtime. For the sake of exposing this little issue lets assume that this value needs to be used multiple times during the request and thus we will make the producer @RequestScoped to avoid duplicate database calls.

To the code!

@ConversationScoped
    @Named
    public class CarPricerManager implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        @Inject
        @Active
        private PriceBook activePriceBook;
    
        private CarPricer carPricer;
    
        public CarPricer getCarPricer() {
            if(carPricer == null) {
    
                carPricer = new CarPricer();
                carPricer.setPriceBook(activePriceBook);
            }
            return carPricer;
        }
    }
    
    public class PriceBooks {
    
        @Produces
        @RequestScoped
        @Active
        public PriceBook findActivePriceBook() {
            return em.createQuery(.....).getSingleResult();
        }
    
        @Produces
        @RequestScoped
        @Named
        public List<PriceBook> priceBooks() {
            return em.createQuery(.....).getResultList();
        }
    }

Now lets show the JSF snippet to select our price book when creating our new CarPricer

<h:selectOneMenu value="\#{carPricerManager.carPricer.priceBook}" required="true"
        converter="PriceBookConverter">
        <f:selectItem itemLabel="Choose" />
        <f:selectItems value="\#{priceBooks}" var="_apb" itemLabel="\#{_apb.name}" />
    </h:selectOneMenu>

Where it goes wrong.

What we would expect to happen is the selectOneMenu would initially be selected with the value of our active price book. However this isn’t going to be the case because the injection of our active price book does not give us an object of PriceBook.java it gives us an proxy of PriceBook.java and the equals(...) that JSF is going to call will not return true.

Why it goes wrong.

The CDI EG has determined that there is not meaningful way to implement .equals() and .hashCode() on a proxy that can delegate to the underlying bean (See OWB-458 & WELD-695). What this means is that two proxies of the same underlying bean will be equal. However, two proxies to underlying beans which are equal according to .equals will not be equal.

How can we fix this?

One solution would be to execute the .equals() on the converted value if a converter exists. However, the JSF RI Mojarra feels (JAVASERVERFACES-2393) that any proxy must should delegate equals, hashCode and toString to the proxied instance per java.lang.reflect.Proxy. In the case of CDI the underlying bean can change during the lifecycle of the proxy and thus delegating isn’t realistic. This appears to be one of those places where the JSF and CDI expert groups need to step in and find a solution so that developers can enjoy consistent and predictable behavior in their applications.

A solutions that work now.

By making the bean with the producers @RequestScoped and storing the values in the bean after initial population we can have default dependent scoped producers which don’t produce proxies and hit the database only once.

@RequestScoped
    public class PriceBooks {
    
        private PriceBook activeBook;
        private List<PriceBook> books;
    
        @Produces
        @Active
        public PriceBook findActivePriceBook() {
        if (activeBook == null)
            activeBook = em.createQuery(.....).getSingleResult();
            return activeBook;
        }
    
        @Produces
        @Named
        public List<PriceBook> priceBooks() {
        if (books == null) {
            books = em.createQuery(.....).getResultList();
            return books;
        }
    }
tags: cdi jsf java
Cody Lerum - September 11, 2012

Seam Mail 3.1.1.Final Released

I’m happy to announce that I’ve released a minor update to Seam 3 Mail versioned 3.1.1.Final. This is a minor release which is compatible with Seam 3.1.0.Final.

Whats new?

The main goal of this release was to fill in some gaps in documentation as well as some minor bug fixes which popped up since the 3.1.0.Final release. I’ve also added in a utility class called MessageConverter which allows you to read a MimeMessage back into the internal EmailMessage object used by Seam Mail. This opens up the option for Seam Mail to receive messages in addition to sending them. Check out Closed Issues on the JIRA tracker for details.

What does future look like?

Seam 3 Mail will continue to provide releases in its current format until the time comes for it to transition to Apache DeltaSpike. In the meantime if you have features for fixes to contribute just open a JIRA and I’ll be happy to work them in.

How about an example?

POP3+SSL to receive an MimeMessage and convert it. It’s basically for gmail but with values changed to protect the innocent.

import java.util.Properties;
    
    import javax.mail.Folder;
    import javax.mail.Message;
    import javax.mail.Session;
    import javax.mail.Store;
    import javax.mail.URLName;
    
    import org.jboss.seam.mail.core.EmailMessage;
    import org.jboss.seam.mail.util.MessageConverter;
    
    import com.sun.mail.pop3.POP3SSLStore;
    
    public class Popper {
    
        public static void main(String[] args) throws Exception {
    
            String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
    
            Properties pop3Props = new Properties();
    
             pop3Props.setProperty("mail.pop3.socketFactory.class", SSL_FACTORY);
             pop3Props.setProperty("mail.pop3.socketFactory.fallback", "false");
             pop3Props.setProperty("mail.pop3.port", "995");
             pop3Props.setProperty("mail.pop3.socketFactory.port", "995");
    
             URLName url = new URLName("pop3", "pop.example.com", 995, "", "foo@example.com", "mypass");
    
             Session session = Session.getInstance(pop3Props, null);
             Store store = new POP3SSLStore(session, url);
             store.connect();
    
             Folder inbox = store.getFolder("INBOX");
             inbox.open(Folder.READ_ONLY);
    
             Message[] messages = inbox.getMessages();
    
             if (messages.length == 0) {
                 System.out.println("No Messages Found");
             }
    
             List<EmailMessage> emailMessages = new ArrayList<EmailMessage>();
    
             for (Message m : messages) {
                 emailMessages.add(MessageConverter.convert(m));
             }
    
             inbox.close(false);
             store.close();
        }
    }
tags: java seam email
Cody Lerum - July 15, 2012

Sending Queued Mail with Seam Mail

The Problem

One of the more common questions that I see by users coming to "Seam 3 Mail":http://www.seamframework.org/Seam3/MailModule is about sending email asynchronously like was possible in "Seam 2":http://docs.jboss.org/seam/2.2.2.Final/reference/en-US/html_single/#d0e21609. This usually stems from "slowness" problems that arise when sending mail through an interactive application. The problem is that when your "user" submits that order for his fancy new widgets, the confirmation email sent is using the same thread that handled the processing of the web page. Thus the user is left with their browser in a blocked "loading" state thinking your application is slow when in reality it’s the mail server that is taking its sweet time.

The Knee-jerk Reaction

A lot of people will see this blocked thread and think the solution is to fire the email asynchronously. This way the email with be dispatched via separate thread and control will be instantly returned to the user . However the problem with asynchronous methods is that they are fire and forget. If an exception occurs during the send process, then the email is lost and there is no notification that it failed. Realistically, outside of a demo application you generally want some guarantee that the email was, or will be sent as having all those order confirmation emails silently lost will not be pretty.

You Really Want

What is needed is a system which allows you to send email from your application without blocking the execution on the current thread, while at the same time providing a guarantee that the email will be reliably sent (at least from your application to your "SMTP":http://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol server).

The Solution

Seam Mail provides a way to replace out the default "send" implementation with one of your own via the "MailTransporter":https://github.com/seam/mail/blob/develop/api/src/main/java/org/jboss/seam/mail/core/MailTransporter.java interface. That way when you call @msg.send()@ you can control how and when the email is "sent". What follows is a basic implementation.

In the code that follows I’m skipping the import statements for the sake of readability, but everything is standard EE6 + Seam 3 with exception of the Files which comes from Google Guava.

The default MailTransporterImpl is very simple and looks as follows.

public class MailTransporterImpl implements MailTransporter {
    
        private Session session;
    
        public MailTransporterImpl(Session session) {
            this.session = session;
        }
    
        public EmailMessage send(EmailMessage emailMessage) {
            MailUtility.send(emailMessage, session);
            return emailMessage;
        }
    }

The MailUtility.send(emailMessage, session) sends the email using the configured javax.mail.Session. This is a blocking implementation and will not release the thread until the destination mail server has acknowledged receipt of the message. This might take a couple milliseconds or a few minutes depending on message size, server performance and network bandwidth. If this doesn’t work for you then it’s easy to swap out.

First lets create a new MailTransporter called MailQueueTransporter

public class MailQueueTransporter implements Serializable, MailTransporter {
    
        private static final long serialVersionUID = 1L;
    
        @Inject
        @QueuedEmail
        private Event<MimeMessage> queueEvent;
    
        @Inject
        @ExtensionManaged
        private Instance<Session> session;
    
        @Override
        public EmailMessage send(EmailMessage emailMessage) {
    
            MimeMessage msg = MailUtility.createMimeMessage(emailMessage, session.get());
            queueEvent.fire(msg);
    
            return emailMessage;
        }
    }

Starting from the top we see that we are injecting a new Event qualified with @QueuedMail and typed to MimeMessage. @QueuedMail is a simple qualifier, and if you have never written one before here is what it looks like.

@Qualifier
    @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface QueuedEmail { }

Next we inject the javax.mail.Session as managed by Seam Mail and implement the public EmailMessage send(EmailMessage emailMessage) as mandated by the interface. Inside this method we use the Seam MailUtility class to convert our EmailMessage as built by Seam Mail to a standard MimeMessage and then fire it as a CDI event.

A CDI event however is still blocking in its execution so lets see what is observing this event.

public class MailQueueReceiver {
    
        @Inject
        private Logger log;
    
        public static final String mailQueueFolder = "/var/data/mailQueue/";
    
        public void receiveMessage(@Observes @QueuedEmail MimeMessage msg) {
            log.debugf("Writing queued message to disk");
            OutputStream os;
            try {
                os = Files.newOutputStreamSupplier(
                  new File(mailQueueFolder + UUID.randomUUID())).getOutput();
                msg.writeTo(os);
                os.close();
            } catch (IOException e) {
                throw new SendFailedException("Failed to Write Message to Queue", e);
            } catch (MessagingException e) {
                throw new SendFailedException(e);
            }
        }
    }

What we have done here is to receive the CDI event and immediately write the message to persistent storage, in this case to a queue folder on the application server. Now our thread is returned as fast as we can write the message to disk. You could also write these bytes out to the database or a in memory store if the loss of queued email during a server shutdown/crash is not an issue.

All that is needed now is a process to send these stored messages. I’m using a EJB 3.1 scheduled timer, but the options are endless.

@Singleton
    public class MailQueueSender {
    
        @Inject
        private Logger log;
    
        @Inject
        @ExtensionManaged
        private Session session;
    
        private String mailQueueFolder;
    
        @PostConstruct
        public void postConstruct() {
            mailQueueFolder = MailQueueReceiver.mailQueueFolder;
            File queueFolder = new File(mailQueueFolder);
            session.getProperties().put("mail.smtp.connectiontimeout", 15000);
            session.getProperties().put("mail.smtp.timeout", 15000);
            log.infof("Initialized Mail Queue with %s messages in queue", queueFolder.listFiles().length);
        }
    
        @Schedule(second = "15,45", minute = "*", hour = "*", persistent = false)
        public void sendQueue() throws InterruptedException {
    
            File queueFolder = new File(mailQueueFolder);
            LinkedList<File> files = new LinkedList<File>(Arrays.asList(queueFolder.listFiles()));
    
            while (!files.isEmpty()) {
                File file = files.peek();
                InputStream is;
                try {
                    is = Files.newInputStreamSupplier(file).getInput();
                    RootMimeMessage msg = new RootMimeMessage(session, is);
                    msg.setMessageId(generateMessageId());
                    Transport.send(msg);
                    log.debug("Sent Mail Message: " + msg.getMessageID());
                    is.close();
                    files.pop();
                    file.delete();
                }
                catch (IOException e) {
                    throw new SendFailedException("Failed to read message from Disk: " + file.getName(), e);
                }
                catch (MessagingException e) {
                    throw new SendFailedException("Send Failed for Message: " + file.getName(), e);
                }
            }
        }
    
        private String generateMessageId() {
            String mailerDomainName = session.getProperty("mail.seam.domainName");
            if (mailerDomainName != null && mailerDomainName.length() > 0) {
                return UUID.randomUUID().toString() + "@" + mailerDomainName;
            }
            else {
                return UUID.randomUUID().toString() + "@" + MailUtility.getHostName();
            }
        }
    }

One thing to note is that you have to reset the javax.mail.Session as that is lost when the MimeMessage is converted to a OutputStream. There is a little added complexity going on here as I like to control the domain part of a Message-ID, but this is a simple reliable way to handle sending mail in a fast and reliable fashion.

tags: java seam email
Cody Lerum - July 07, 2012

Fighting with JSF Request charset: JBoss or Tomcat

After just spending a good amount of time troubleshooting an issue with character encoding in JSF + JBoss AS7 I thought I would throw up a few notes on the subject as there are lots of scattered bits floating around but no real clear explanation. Hopefully this does more than contribute to the scattered bits :-)

First lets start off with a little background on how servers and clients determine how to encode data for transfer

  • Server response to Client:

    • Server Encode: When a server is sending a response to a client with JSF it encodes the response based on the defined encoding of the file which is usually set at the top of a JSF XHTML file <?xml version="1.0" encoding="UTF-8"?> The server will also include a Content-Type in the response header sent to the client.

    • Client Decode: When the client receives a response it checks the Content-Type defined in the response headers to decide which charset to use for decode. However If the Content-Type is not listed in the response header then the client may check the page meta tags for a hint or default to ISO-8955-1

  • Client Request (POST) to Server:

    • Client Encode: The client will encode it’s request based on the encoding of the response it received from the server.

    • Server Decode: This is where things get a little tricky.

      • The request received from the client will likely lack any hint as to how things were encoded.

        • It may have a Accept-Charset defined, but that is simply informing the server which charset’s are valid for it’s response encoding.

        • It will also have a Content-Type which reflects the enctype set on the form element submitting this post. By default it is application/x-www-form-urlencoded. Notice no reference to the charset this request was encoded in.

        • Some requests like ajax requests will look like this application/x-www-form-urlencoded;charset=utf-8; This does work and Tomcat/JbossWeb will decode as utf-8 but I have not figured out how to force this on a non-ajax h:form

      • The catch here is that if there is not a charset defined in the request Content-Type then the request parameters will be decoded using ISO-8859-1.

So you’ve configured for pages for UTF-8 and ensured that your server responses are including a charset in the Content-Type. Everything is great until you submit the form and your UTF-8 encoded client request is decoded as ISO-8859-1. Depending on the data your users submit you may not even notice this in your app for weeks/months/years/never. But for example if user may submit something like I’m and you will get I’m

Once you accept that this is how things work, there is an easy way to fix this by adding the following snippet to your web.xml

<web-app>
      ...
    
        <filter>
          <filter-name>forceUTF8CharSet</filter-name>
          <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
          <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
          </init-param>
        </filter>
    
        <filter-mapping>
          <filter-name>forceUTF8CharSet</filter-name>
          <url-pattern>*</url-pattern>
        </filter-mapping>
      ...
    </web-app>

This filter currently is in Tomcat 7.0.20 and up. If you are running JBoss 7 you can just grab the class from Github import it into your project. I’ve opened JBWEB-225 with JBoss Web to have this filter added.

tags: java jsf jboss
Cody Lerum - January 18, 2012
About outjected.com

Outjected is an infrequent blog about the frequent issues and annoyances that pop up while coding.

These are living posts and will be updated as errors or improvements are found.
About Me
Google+