Setup a local smtp server for JBoss

In my Seam application I needed a local smtp server for testing the email facilities Seam offers. After some research on the internet I came up with a configuration which uses Ubuntu’s Mail Transfer Agent postfix with Gmail as the so called smarthost. Postfix will act as an intermediate smtp server which will dispatch the mails it receives to Gmail‘s smtp server.
This blog will summarize the steps needed for this setup.

Postfix installation and configuration

First, let’s install postfix.

sudo apt-get install postfix

Next, add additional configuration.

sudo dpkg-reconfigure postfix

Choose the following settings. For settings not listed below, you can keep the default settings.

  • General type of mail configuration: Internet with smarthost
  • SMTP relay host: smtp.gmail.com:587
  • Internet protocols to use: all

Next add settings necessary for authentication against the Gmail smtp server.

sudo postconf -e 'smtp_use_tls = yes'
sudo postconf -e 'smtp_sasl_auth_enable = yes'
sudo postconf -e 'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd'
sudo postconf -e 'smtp_sasl_security_options = noanonymous'
sudo postconf -e 'smtp_sasl_tls_security_options = noanonymous'

Next add the password file /etc/postfix/sasl_passwd containing the username and password of your Gmail account.

smtp.gmail.com username@gmail.com:PASSWORD

Now, hash the password file (this will create the hashed sasl_passwd.db file), throw away the original file and restart the postfix daemon.

sudo postmap /etc/postfix/sasl_passwd
sudo rm /etc/postfix/sasl_passwd
sudo /etc/init.d/postfix restart

Testing postfix

You can now test the postfix configuration via telnet.

telnet localhost 25
>mail from:noreply@gmail.com
>rcpt to:username@gmail.com
>data
>Testmail
>.
>quit

This should send a small email to username@gmail.com.

Troubleshooting

If the mail didn’t arrive, you can check a couple of things:
Check if it’s in the postfix queue:

sudo mailq

Flush the queue:

sudo postfix -f

Check the log file:

sudo tail /var/log/mail.log

JBoss configuration

Next we’re gonna add the mail server configuration to JBoss, so it van be accessed via JNDI. Edit the file $JBOSS_HOME/server/default/deploy/mail-service.xml.

<?xml version="1.0" encoding="UTF-8"?>
<server>
  <mbean code="org.jboss.mail.MailService"
         name="jboss:service=Mail">
    <attribute name="JNDIName">java:/Mail</attribute>
    <attribute name="Configuration">
      <configuration>
        <!-- Change to your mail server prototocol -->
        <property name="mail.transport.protocol" value="smtp"/>
        <!-- Change to the SMTP gateway server -->
        <property name="mail.smtp.host" value="localhost"/>
        <!-- The mail server port -->
        <property name="mail.smtp.port" value="25"/>
        <!-- Change to the address mail will be from  -->
        <property name="mail.from" value="noreply@gmail.com"/>
        <!-- Enable debugging output from the javamail classes -->
        <property name="mail.debug" value="false"/>
      </configuration>
    </attribute>
    <depends>jboss:service=Naming</depends>
  </mbean>
</server>

Seam configuration

Finally we need to configure seam so it can communicate with our new smtp server. This is done in the component descriptor components.xml.

<?xml version="1.0" encoding="UTF-8"?>
<components
  xmlns="http://jboss.com/products/seam/components"
  xmlns:mail="http://jboss.com/products/seam/mail"
  xsi:schemaLocation="http://jboss.com/products/seam/mail http://jboss.com/products/seam/mail-2.2.xsd
    http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd">
  ....
  <mail:mail-session session-jndi-name="java:/Mail" />
  ....
</components>

If you’re also using jBPM for business processes within your seam application and need email facilities, you can configure the smtp server in the jBPM configuration file.

<?xml version="1.0" encoding="UTF-8"?>
<jbpm-configuration>
  ....
  <string name="jbpm.mail.smtp.host" value="localhost"/>
  <string name="jbpm.mail.from.address" value="noreply@open18.org"/>
</jbpm-configuration>
Advertisements

Autosuggesting in Seam

Today I implemented an autosuggest textfield in a search page. Although technically not a Seam but a Richfaces feature, I baked it into a default Seam page. I added it to a default Seam search page backed up by an EntityQuery<E> subclass. Below you’ll find a screenshot of the Search Form containing the autosuggest field. It’s a search form for querying the collection of golfers in the database.

Autosuggest Search Form

Let’s take a walk through the most important code to get this example working.

Model classes

The golfer data in the database is contained in 2 tables MEMBER and GOLFER. These are mapped to 2 JPA classes called org.open18.model.Member and org.open18.model.Golfer respectively.

org.open18.model.Member

package org.open18.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "MEMBER", uniqueConstraints = { @UniqueConstraint(columnNames = "username") })
public abstract class Member implements Serializable {
	private Long id;
	private String userName;

	@Id
	@GeneratedValue
	public Long getId() {
		return id;
	}

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

	@Column(name = "username", nullable = false)
	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

}

org.open18.model.Golfer

package org.open18.model;

package org.open18.model;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.jboss.seam.annotations.Name;

@Entity
@PrimaryKeyJoinColumn(name = "MEMBER_ID")
@Table(name = "GOLFER")
@Name("golfer")
public class Golfer extends Member {
	private String firstName;
	private String lastName;
	private Date dateJoined;

	@Column(name = "last_name", nullable = false)
	public String getLastName() {
		return lastName;
	}

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

	@Column(name = "first_name", nullable = false)
	public String getFirstName() {
		return firstName;
	}

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

	@Transient
	public String getName() {
		return (firstName + ' ' + lastName);
	}

	@Temporal(TemporalType.TIMESTAMP)
	@Column(name = "joined", nullable = false, updatable = false)
	public Date getDateJoined() {
		return dateJoined;
	}

	public void setDateJoined(Date dateJoined) {
		this.dateJoined = dateJoined;
	}

}

Action class

The page is backed up by the action class org.open18.action.GolferList. This class contains an action method for providing the data for the autosuggest field.

org.open18.action.GolferList

package org.open18.action;

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

import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.framework.EntityQuery;
import org.open18.model.Golfer;

@Name("golferList")
public class GolferList extends EntityQuery<Golfer> {
	private static final String EJBQL = "select golfer from Golfer golfer";

	private static final String[] RESTRICTIONS = { "lower(golfer.lastName) like lower(concat(#{golferList.golfer.lastName},'%'))", };

	private Golfer golfer = new Golfer();

	public GolferList() {
		setEjbql(EJBQL);
		setRestrictionExpressionStrings(Arrays.asList(RESTRICTIONS));
		setMaxResults(25);
	}

	public Golfer getGolfer() {
		return golfer;
	}

	@SuppressWarnings("unchecked")
	@Transactional
	public List<Golfer> autocomplete(Object suggest) {

		String pref = (String) suggest + "%";

		List<Golfer> golferSuggestList;
		golferSuggestList = getEntityManager()
				.createQuery(
						"select golfer from Golfer golfer where golfer.lastName like :golfer")
				.setParameter("golfer", pref).getResultList();

		return golferSuggestList;

	}
}

Page descriptor

The page descriptor contains page parameters for storing some search and navigation criteria.

golferList.page.xml

<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd">

  <param name="from" />

  <param name="lastName" value="#{golferList.golfer.lastName}" />

</page>

Page

Finally the page itself. After the code, I’ll explain some of the elements/attributes on the page.

GolferList.xhtml

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
  xmlns:s="http://jboss.com/products/seam/taglib"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:a="http://richfaces.org/a4j"
  xmlns:rich="http://richfaces.org/rich"
  template="layout/template.xhtml">

  <ui:define name="body">

    <h:form id="courseSearch" styleClass="edit">

      <rich:simpleTogglePanel label="Course Search Filter"
        switchType="ajax">

        <s:decorate template="layout/display.xhtml">
          <ui:define name="label">Last Name</ui:define>
          <h:inputText id="lastName"
            value="#{golferList.golfer.lastName}">
          </h:inputText>
          <rich:suggestionbox ajaxSingle="true" for="lastName"
            var="_golfer" minChars="2"
            suggestionAction="#{golferList.autocomplete}">
            <h:column>
              <h:outputText value="#{_golfer.lastName}" />
            </h:column>
            <a:support event="onselect" reRender="golferSearchResults" />
          </rich:suggestionbox>
        </s:decorate>

      </rich:simpleTogglePanel>

      <div class="actionButtons">
        <h:commandButton id="search"
          value="Search" action="/golferList.xhtml">
        </h:commandButton> <s:button
          id="reset" value="Reset" includePageParams="false" />
      </div>
    </h:form>

    <rich:panel id="golferSearchResults">
      <f:facet name="header">Golfers</f:facet>

      <div class="results">
        <h:outputText
          value="There are no golfers registered."
          rendered="#{empty golferList.resultList}" />
        <rich:dataTable
          id="golferList" var="_golfer" value="#{golferList.resultList}"
          rendered="#{not empty golferList.resultList}">
          <h:column>
            <f:facet name="header">Username</f:facet>
            <s:link id="view" value="#{_golfer.userName}"
              view="/profile.xhtml" propagation="none">
              <f:param name="golferId" value="#{_golfer.id}" />
            </s:link>
          </h:column>
          <h:column>
            <f:facet name="header">Name</f:facet>
            #{_golfer.name}
          </h:column>
          <h:column>
            <f:facet name="header">Date Joined</f:facet>
            <h:outputText value="#{_golfer.dateJoined}">
              <s:convertDateTime pattern="MMMM dd, yyyy" />
            </h:outputText>
          </h:column>
        </rich:dataTable>
      </div>

    </rich:panel>

  </ui:define>

</ui:composition>
  • layout/template.xhtml, layout/display.xhtml: these are standard facelets generated by seam-gen;
  • rich:suggestionbox: this element will take care of the suggestion data for the input textfield
  • for=”lastName”: points to the if of the h:inputText component for which the autosuggest data will be shown;
  • minChars=”2″: After 2 characters are entered the suggestion list will appear
  • suggestionAction=”#{golferList.autocomplete}”: Calls the autocomplete action method on the GolferList action class, which will return a list of golfers;
  • h:column: The result will be shown as one column and will contain a suggestion list of the golfers last names via the h:outputText component;
  • a:support: This element takes care of rerendering the golferSearchResults panel when a user has selected a last name in the suggestion list.
  • Performance note

    Hitting the database everytime a user is rumbling in the autosuggest field is probably not the best solution. As an alternative you could create a page scoped factory which captures the autosuggest data in a List<Golfer> variable. This factory could then be injected into the golferList component. In this case the autosuggest query will only be executed once per page visit. See the scriptlets below.

    components.xml

    	....
    	<framework:entity-query name="golferSuggestQuery"
    		ejbql="select golfer from Golfer golfer" >
    	</framework:entity-query>
    	<factory name="golferSuggest" value="#{golferSuggestQuery.resultList}" scope="page" />
    	....
    

    org.open18.action.GolferList

    @Name("golferList")
    public class GolferList extends EntityQuery<Golfer> {
    	@In(create=true,value="golferSuggest")
    	List<Golfer> golferSuggest;
    	....
    	public List<Golfer> autocomplete(Object suggest) {
    		String pref = (String) suggest;
    
    		List<Golfer> golferSuggestList = new ArrayList<Golfer>(0);
    
    		for (Golfer g : golferSuggest) {
    			if (g.getLastName().startsWith(pref)) {
    				golferSuggestList.add(g);
    			}
    		}
    
    		return golferSuggestList;
    	}
    	....
    }
    

    Starting a jBPM pageflow

    This week i struggled a bit to get a jBPM pageflow running from my seam-gen application. It’s one of the examples in the Seam in Action book from Manning. The goal is to kick off a wizard containing a couple of screens for entering a new entity (in this case a golf course) in the database. As the wizard adds a new record in the database, one of the requirements in the application is that the user doing this is logged in.

    There are basically 2 ways to accomplish this. You can start the pageflow in the first page of the wizard during its Render Response phase by making use of a Seam factory. Your other option is to kick off the pageflow from an action method. The second method seems the more natural to me and is the one that gave me some headaches. I’ll shortly describe both the options and provide a simple solution to the issues regarding the second option.

    Example

    A golf facility can contain multiple golf courses. To add a golf course to a facility you can launch a course wizard which is linked to a jBPM pageflow.

    Launching the course wizard from the Facility page.

    In this flow we have a facility page /Facility.xhtml which is bound to the Seam component facilityHome and the first page of the course wizard /coursewizard/BasicCourseInfo.xhtml which is bound to the Seam components courseWizard and course (which contains the actual course being added). The pageflow is defined in the jPDL file courseWizard-pageflow.jpdl.xml.

    Adding a course to the database requires a user that’s logged in. This restriction is applied in the general page descriptor pages.xml by the following XML stanza

    <page view-id="/coursewizard/*" login-required="true"/>
    

    Starting the wizard on its first page

    In this option, the Add Course… button on the facility page contains navigation to the first page of the wizard and passes the id of the current facility:

    <s:button id="courseWizard" value="Add course..."
     propagation="none" view="/coursewizard/BasicCourseInfo.xhtml">
    	<f:param name="facilityId" value="#{facilityHome.instance.id}" />
    </s:button>
    

    The input components on the /coursewizard/BasicCourseInfo.xhtml page are all bound to a conversation scoped Seam component named course. This component is created by a factory in the courseWizard component. The facility is weaved in by a request parameter:

    @Name("courseWizard")
    @Scope(ScopeType.CONVERSATION)
    public class CourseWizard implements Serializable {
        @In
        protected EntityManager entityManager;
        @RequestParameter
        protected Long facilityId;
        @Out
        protected Course course;
        ....
    
        @Begin(pageflow = "Course Wizard", flushMode = FlushModeType.MANUAL)
        @Factory("course")
        public void initCourse() {
            course = new Course();
            course.setFacility(entityManager.find(Facility.class, facilityId));
            // setup some defaults
            course.setNumHoles(18);
            course.setFairways("BENT");
            course.setGreens("BENT");
        }
        ....
    }
    

    Finally the pageflow is started in the jPDL file

    <?xml version="1.0" encoding="UTF-8"?>
    <pageflow-definition xmlns="http://jboss.com/products/seam/pageflow"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://jboss.com/products/seam/pageflow http://jboss.com/products/seam/pageflow-2.0.xsd"
    	name="Course Wizard">
    	<start-page name="basicCourseInfo" view-id="/coursewizard/basicCourseInfo.xhtml"
    		no-conversation-view-id="/CourseList.xhtml">
    		<redirect />
    		<transition name="cancel" to="cancel" />
    		<transition name="next" to="description" />
    	</start-page>
    	....
    </pageflow-definition>
    

    And that’s all there is to it. You click on the Add course… button, the login screen appears (if you’re not logged in) and after successfully logging into the application the first page of the course wizard appears.

    Starting the wizard from an action method

    Now for this second option the Add course… button will be coupled to an action method which will launch the pageflow.
    Once again the code behind the button, which now contains the action method but doesn’t contain navigation anymore. The facilityid parameter isn’t needed anymore as it’s weaved in before the wizard is launched.

    <s:button id="courseWizard" value="Add course..."
    	action="#{courseWizard.addCourse}" propagation="none">
    </s:button>
    

    Now the action method will take care of launching the pageflow. It also weaves the facility into the course to be created.

    
    @Name("courseWizard")
    @Scope(ScopeType.CONVERSATION)
    public class CourseWizard implements Serializable {
    
    	@In
    	protected EntityManager entityManager;
    	@RequestParameter
    	protected Long facilityId;
    	@Out
    	protected Course course;
    	....
    
    	@Begin(join = true, pageflow = "Course Wizard", flushMode = FlushModeType.MANUAL)
    	public void addCourse() {
    		course = new Course();
    		course.setFacility(entityManager.find(Facility.class, facilityId));
    		// setup some defaults
    		course.setNumHoles(18);
    		course.setFairways("BENT");
    		course.setGreens("BENT");
    	}
    	....
    }
    

    Because you start the pageflow from an action method, you have to define a start-state element in your jPDL file as opposed to the start-page element in the previous example.

    <?xml version="1.0" encoding="UTF-8"?>
    <pageflow-definition xmlns="http://jboss.com/products/seam/pageflow"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://jboss.com/products/seam/pageflow http://jboss.com/products/seam/pageflow-2.0.xsd"
    	name="Course Wizard">
    	<start-state>
    		<transition to="basicCourseInfo" />
    	</start-state>
    
    	<page name="basicCourseInfo" view-id="/coursewizard/basicCourseInfo.xhtml"
    		no-conversation-view-id="/CourseList.xhtml">
    		<redirect />
    		<transition name="cancel" to="cancel" />
    		<transition name="next" to="description" />
    	</page>
    	....
    </pageflow-definition>
    

    Now, when clicking the Add course… button when you’re not logged in yet, the login screen will NOT be displayed. Instead you’ll get the following error when using firefox

    The page isn't redirecting properly
    Firefox has detected that the server is redirecting the request for
    this address in a way that will never complete.
    

    Redirect error

    If  firebug is enabled, you can see that the system gets into some sort of redirect loop. I tried a lot of things to fix this issue and eventually stumbled upon a solution which is really very simple. You just have to make sure that the user is logged in before the pageflow gets called. This is done by securing the action method which calls the pageflow. Just add a Restrict annotation.

    	@Begin(join = true, pageflow = "Course Wizard", flushMode = FlushModeType.MANUAL)
    	@Restrict("#{identity.loggedIn}")
    	public void addCourse() {
    		course = new Course();
    		course.setFacility(entityManager.find(Facility.class, facilityId));
    		// setup some defaults
    		course.setNumHoles(18);
    		course.setFairways("BENT");
    		course.setGreens("BENT");
    	}
    

    And that’s it. Now the solution with the action method will behave exactly the same as the solution without it. You click the Add course… button, the login screen appears and after successfully logging in, the first page of the course wizard appears.

    Seam-gen and groovy

    I recently added a groovy action class to my seam-gen project. After a while eclipse’s auto-build kept giving me these misleading ant errors in the groovy.compilemodel task:

    groovyc doesn't support the "srcdir" attribute
    

    It took me a while to fix this issue. When i ran the compile target manually there was no problem, so eventually i figured it had to do with the explode launcher that was generated by seam-gen. You need to let it run in a separate JVM.
    First, right-click on the project and select “Properties”.

    Properties for open18

    Next, select “Builders” and Edit the explode builder.


    Edit Configuration

    Edit configuration

    Finally, click the “JRE” tab en select “Separate JRE” as the runtime JRE.

    After this the auto-build error should be gone.

    %d bloggers like this: