Adding an MDB to the Agency Case Study

To illustrate the use of MDBs you will extend the Agency case study to utilize a Message-Driven bean to match advertised jobs to new applicants as they register with the system or when an applicant updates his or her skills or location.

As each applicant is created, modified, or deleted, a message is sent to the JMS queue jms/applicantQueue. The message includes the applicant's login and a flag to indicate if this is a new applicant or a change to an existing one. An MDB will receive this message and compare the applicant's details with all advertised jobs. The MDB will add an entry to the Matched table in the database if the applicant's location matches a job location and the applicant has at least one of the skills required by the job. If the applicant has all of the skills required for a job, the row added to the Matched table is tagged with an exact match flag.

Deleting an applicant deletes the rows in the Matched table for that applicant.

The steps required to add this functionality to the case study are as follows:

1.
Write a helper class called MessageSender that creates and sends a message to the jms/applicantQueue containing the applicant's login.

2.
Amend the Agency and Register Session beans to call this new method when a new applicant is registered or the applicant's location or skills are changed.

3.
Write an MDB called ApplicantMatch to

  • Consume a message on the jms/applicantQueue

  • Look up the applicant's location and skills information

  • Find all the jobs that match the applicant's location

  • For each of these jobs, find those that require the applicant's skills

  • Determine if the applicant has all or just some of the skills

  • Store applicant and job matches in the Matched table

4.
Deploy the new EJBs; run and test the application.

Write MessageSender Helper Class

This class contains a constructor for the class and two methods—sendApplicant() and close().

The constructor takes two parameters, which are strings representing the JNDI names of the JMS connection factory and the JMS queue.

The sendApplicant() method is called by the Agency Session bean when a new applicant registers with the system and the Register Session bean when an existing applicant changes his or her location or job skills. It has two parameters—the applicant's login string and a Boolean denoting if this is a new applicant.

The close() method is called before the application is terminated. It sends a message that lets the container know that no more messages will be sent to the queue and frees-up resources.

If you have worked through Day 9, the code for the MessageSender, shown in Listing 10.1, should be familiar to you.

Listing 10.1. MessageSender Helper Class
import javax.naming.*;
import javax.jms.*;

public class MessageSender {

    private Connection connection;
    private Session session;
    private MessageProducer producer;

    public MessageSender(String jndiFactory, String jndiQueue) throws JMSException,
 NamingException {
        Context context = new InitialContext();
        ConnectionFactory connectionFactory = (ConnectionFactory)context.lookup(jndiFactory);
        connection = connectionFactory.createConnection();
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Destination destination = (Destination)context.lookup(jndiQueue);
        producer = session.createProducer(destination);
    }

    public void sendApplicant(String applicant, boolean newApplicant) throws JMSException {
        TextMessage message = session.createTextMessage();
        message.setBooleanProperty ("NewApplicant", newApplicant);
        message.setText(applicant);
        producer.send(message);
    }

    public void close() throws JMSException {
        connection.close();
    }
}

Update Agency and Register Session Beans

The following changes are required to AgencyBean.java and RegisterBean.java to call the MessageSender.send() method when a new applicant is registered or the applicant's location or skills are changed.

1.
In both AgencyBean.java and RegisterBean.java create a MessageSender object in the setSessionContext() method.

private MessageSender messageSender;
public void setSessionContext(SessionContext ctx) {
    /* existing code */
messageSender = new MessageSender ("java:comp/env/jms/QueueConnectionFactory", "java:comp
/env/jms/applicantQueue");
}

2.
In the AgencyBean.java file, add code to send a message indicating that a new applicant has registered in the createApplicant() method. Add similar functionality to the deleteApplicant() method. The added lines are shown in bold in the following code:

public void createApplicant(String login, String name, String email) throws
 DuplicateException, CreateException{
    try {
        ApplicantLocal applicant = applicantHome.create(login,name,email);
        messageSender.sendApplicant(login,true);
    }
    catch (CreateException e) {
        error("Error adding applicant "+login,e);
    }
    catch (JMSException e) {
        error("Error sending applicant details to message bean "+login,e);
}

public void deleteApplicant (String login)
throws NotFoundException{
    try {
        applicantHome.remove(login);
        messageSender.sendApplicant(login,false);
    }
    catch (JMSException ex) {
        error ("Error sending remove applicant match message",ex);
    }
    catch (RemoveException e) {
        error("Error removing customer "+login,e);
    }
}

3.
In the RegisterBean.java file, change updateDetails() to send a message to indicate that the applicant's details have changed. The added lines are shown in bold in the following code:

public void updateDetails (String name, String email, String locationName, String summary,
 String[] skillNames) {

    List skillList;
    try {
        skillList = skillHome.lookup(Arrays.asList(skillNames));
    } catch(FinderException ex) {
        error("Invalid skill", ex); // throws an exception
        return;
    }

    LocationLocal location = null;
    if (locationName != null) {
        try {
            location = locationHome.findByPrimaryKey(locationName);
        } catch(FinderException ex) {
            error("Invalid location", ex);
            return;
        }
    }
    applicant.setName(name);
    applicant.setEmail(email);
    applicant.setLocation(location);
    applicant.setSummary(summary);
    applicant.setSkills( skillList );
    try {
									messageSender.sendApplicant(applicant.getLogin(),false);
									}
									catch (JMSException ex) {
									ctx.setRollbackOnly();
									error ("Error sending applicant match message",ex);
									}
}

4.
In both AgencyBean.java and RegisterBean.java, add the following to ejbRemove() to close down the MessageSender:

try {
        messageSender.close();
    }
    catch (JMSException ex) {
        error("Error closing down the message producer",ex);
    }

Write The ApplicantMatch MDB

As already stated, all Message-Driven beans must implement the MessageDrivenBean and MessageListener interfaces.

import javax.ejb.*;
import javax.jms.*;
public class ApplicantMatch implements MessageDrivenBean, MessageListener {

Just like the EntityBean and SessionBean interfaces, the MessageDrivenBean interface extends the javax.ejb.EnterpriseBean interface.

The MessageDrivenBean interface contains only two methods—setMessageDrivenContext() and ejbRemove(); see the class diagram in Figure 10.3.

Figure 10.3. The MessageDrivenBean class diagram.


You also need to supply an ejbCreate() method.

In the ApplicantMatch, MDBs save references to the Applicant, Job and Matched Entity beans in the setMessageDrivenContext() method (see Listing 10.2 later in this chapter).

This update introduces a new Entity bean. The MatchedBean creates rows in the Matched table to store the applicant and job matches. This code may be found in the MatchedBean.java file on the Web site.

The MDB container handles the association of the MDB with the JMS message queue or topic so there is no requirement to create and start a JMS connection. Consequently the ApplicantMatch.ejbCreate() method is blank.

public void ejbCreate(){}

The ApplicantMatch.ejbRemove() cleans up by setting all the references to the Entity beans to null. There are no other resources for you to deallocate—nor should this method stop the JMS connection.

public void ejbRemove(){
    applicantHome = null;
    jobHome = null;
    matchedHome = null;
}

The algorithm that matches an applicant to advertised jobs is in the onMessage() method. First, you need to check that onMessage() has received the expected text message from the Agency or Register Session beans. The message contains the applicant's login, which is the primary key on the Applicants table. This primary key is used to obtain the applicant's location and skills in subsequent finder methods.

if (!(message instanceof TextMessage)) {
    System.out.println("ApplicantMatch: bad message:" + message.getClass());
    return;
}

Next, check whether this applicant is a new one or the registration has been amended. If details about the applicant have changed, you need to delete the existing matches stored in the Matched table.

login = ((TextMessage)message).getText();
if (! message.getBooleanProperty("NewApplicant")) {
    matchedHome.deleteByApplicant(login);
}

Use the login primary key to find the applicant's location using the Applicant Entity bean's finder method.

ApplicantLocal applicant = applicantHome.findByPrimaryKey(login);
String location = applicant.getLocation().getName();

Next, obtain all the skills that the applicant has registered and store them in an array.

Collection skills = applicant.getSkills();
Collection appSkills = new ArrayList();
Iterator appIt = skills.iterator();
while (appIt.hasNext()) {
    SkillLocal as = (SkillLocal)appIt.next();
    appSkills.add(as.getName());
}

Now you have all the information you need about the applicant. The next step is to start matching the jobs. First, find the jobs that match the applicant's location from the Job bean and iterate over this collection finding the skills required for each job.

Collection col = jobHome.findByLocation(location);
Iterator jobsIter = col.iterator();
while (jobsIter.hasNext()) {
    JobLocal job = (JobLocal)jobsIter.next();
    Collection jobSkills = job.getSkills();

Now you have a appSkills array containing the applicant's skills and a jobSkills collection containing the skills required for the job. The next task is to find how many of these skills match. This is done by iterating over the jobSkills, and for each jobSkill, searching the appSkills array for a match. When a match is found, the skillMatch counter is incremented.

int skillMatch = 0;
Iterator jobSkillIter = jobSkills.iterator();
while (jobSkillIter.hasNext()) {
    SkillLocal jobSkill = (SkillLocal)jobSkillIter.next();
    if (appSkills.contains(jobSkill.getName()))
      skillMatch++;
}

Now see if you have a match. If there was a job skill to match (jobSkills.size() > 0) and the applicant did not have any of them (skillMatch == 0), get the next job (continue).

if (jobSkills.size() > 0 && skillMatch == 0)
    continue;

Otherwise, determine if the applicant has all or just some of the skills and update the Matched table.

    boolean exact = skillMatch == jobSkills.size();
    MatchedPK key = new MatchedPK(login,job.getRef(),job.getCustomer());
    try {
        matchedHome.create(key.getApplicant(),key.getJob(),key.getCustomer(), exact);
    }
    catch (CreateException ex) {
        System.out.println("ApplicantMatch: failed to create matched entry: "+key);
    }
}

That is all there is to the bean apart from the exception handling. The full listing of the ApplicantMatch Message-Driven bean is shown in Listing 10.2.

Listing 10.2. Full Listing on ApplicantMatch.java Message-Driven Bean Code
package data;

import java.util.*;
import javax.ejb.*;
import javax.jms.*;
import javax.naming.*;

public class ApplicantMatch implements MessageDrivenBean, MessageListener
{
    private ApplicantLocalHome applicantHome;
    private JobLocalHome jobHome;
    private MatchedLocalHome matchedHome;

    public void onMessage(Message message) {
        String login = null;
        if (!(message instanceof TextMessage)) {
            System.out.println("ApplicantMatch: bad message:" + message.getClass());
            return;
        }
        try {
            login = ((TextMessage)message).getText();
            if (! message.getBooleanProperty("NewApplicant")) {
                matchedHome.deleteByApplicant(login);
            }
        }
        catch (JMSException ex) {
            error ("Error getting JMS property: NewApplicant",ex);
        }
        try {
            ApplicantLocal applicant = applicantHome.findByPrimaryKey(login);
            LocationLocal applicantLocation = applicant.getLocation();
            String location = "";
            if (applicantLocation!=null)
                location = applicantLocation.getName();
            Collection skills = applicant.getSkills();
            Collection appSkills = new ArrayList();
            Iterator appIt = skills.iterator();
            while (appIt.hasNext()) {
                SkillLocal as = (SkillLocal)appIt.next();
                appSkills.add(as.getName());
            }
            Collection col = jobHome.findByLocation(location);
            Iterator jobsIter = col.iterator();
            while (jobsIter.hasNext()) {
                JobLocal job = (JobLocal)jobsIter.next();
                Collection jobSkills = job.getSkills();
                int skillMatch = 0;
                Iterator jobSkillIter = jobSkills.iterator();
                while (jobSkillIter.hasNext()) {
                    SkillLocal jobSkill = (SkillLocal)jobSkillIter.next();
                    if (appSkills.contains(jobSkill.getName()))
                       skillMatch++;
                }
                if (jobSkills.size() > 0 && skillMatch == 0)
                    continue;
                boolean exact = skillMatch == jobSkills.size();
                MatchedPK key = new MatchedPK(login,job.getRef(),job.getCustomer());
                try {
                    matchedHome.create(key.getApplicant(),key.getJob(),key.getCustomer(),
 exact);
                }
                catch (CreateException ex) {
                    System.out.println("ApplicantMatch: failed to create matched entry:
 "+key);
                }
            }
        }
        catch (FinderException ex) {
            System.out.println("ApplicantMatch: failed to find applicant data: "+login);
        }
        catch (RuntimeException ex) {
            System.out.println("ApplicantMatch: "+ex);
            ex.printStackTrace();
            throw ex;
        }
    }

    // EJB methods start here

    public void setMessageDrivenContext(MessageDrivenContext ctx) {
    InitialContext ic = null;
        try {
            ic = new InitialContext();
            applicantHome = (ApplicantLocalHome)ic.lookup("java:comp/env/ejb/ApplicantLocal");
        }
        catch (NamingException ex) {
            error("Error connecting to java:comp/env/ejb/ApplicantLocal:",ex);
        }
        try {
            jobHome = (JobLocalHome)ic.lookup("java:comp/env/ejb/JobLocal");
        }
        catch (NamingException ex) {
            error("Error connecting to java:comp/env/ejb/JobLocal:",ex);
        }
        try {
            matchedHome = (MatchedLocalHome)ic.lookup("java:comp/env/ejb/MatchedLocal");
        }
        catch (NamingException ex) {
            error("Error connecting to java:comp/env/ejb/MatchedLocal:",ex);
        }
    }

    public void ejbCreate(){
    }

    public void ejbRemove(){
        applicantHome = null;
        jobHome = null;
        matchedHome = null;
    }

    private void error (String msg, Exception ex) {
        String s = "ApplicantMatch: "+msg + "
" + ex;
        System.out.println(s);
        throw new EJBException(s,ex);
    }
}

Deploying an MDB Using J2EE RI

Yesterday, you were shown how to create JMS connection factories, queues, topics and destinations using the J2EE RI. Today, you will need to create an additional JMS queue called jms/applicantQueue for sending messages to the ApplicantMatch MDB.

Do this before you start to create the ApplicantMatch MDB by going to the Day10/examples directory provided on the accompanying Web site and running the command

asant create-jms

You are now ready to create the ApplicantMatch MDB as detailed in the following steps. If you do not want to create the MDB but simply look at a completed application, open the EAR file Day10/examples/j2ee-ri/examples.ear and study the results of each step described.

1.
Take a copy of the Day 6 Agency case study (the one with BMP Entity beans), start up deploytool and open the EAR file you created on that day (a suitable starting point is Day06/agency/j2ee-ri/agency.ear). Make sure you pick up your new class files from the Day10/examples/classes directory.

2.
Create and add a new BMP Entity bean (using local interfaces) called MatchedBean to represent the Matched database table. Refer to Day 6 for details on creating new BMP Entity beans if you are unsure how to do this. The information you need for the MatchedBean BMP Entity bean is summarized in Table 10.1.

Table 10.1. BMP MatchedBean Configuration.
EJB Configuration FieldSub-fieldValue
EJB Jar file Add to existing Entity JAR file
Class files data.MatchedBean, data.MatchedLocal, data.MatchedLocalHome, data.MatchedPK
Enterprise bean name MatchedBean
Bean-managed persistence n/a
Primary Key class data.MatchedPK
Resource References,Coded Namejdbc/Agency
Add new referenceTypejavax.sql.DataSource
 JNDI Namejdbc/Agency
 UsernamepbPublic
 PasswordpbPublic
General, Sun-specific SettingsJNDI Nameejb/MatchedLocal

3.
Before you create your new MDB for matching applicants you must define a Message Destination for the MDB. Do this for the Agency case study by selecting the JAR file for the data Entity beans and selecting the Message Destination dialogue page. Add a new message destination and set the Destination Name and Display name to ApplicantQueue and set the Sun-Specific JNDI name to jms/applicantQueue. You should have a screen similar to the one shown in Figure 10.4.

Figure 10.4. Adding Message Destinations in deploytool.


4.
Now you can add the new MDB using the File, New Enterprise Bean… menu option. Skip the introductory page if you have not suppressed display of this page and on the New Enterprise Bean Wizard page add the data.ApplicantMatch class file to the application.

5.
On the next page select the data.ApplicantMatch Enterprise Bean class and accept the default name of ApplicantMatch, as shown in Figure 10.5.

Figure 10.5. Adding a new MDB in deploytool.


6.
On the next screen, check the No option so you do not expose the bean as a web service endpoint and move onto the next page to define the MDB settings.

7.
On the MDB settings page, select

  • JMS as the Messaging Service.

  • javax.jms.Queue as the Destination Type.

  • ApplicantQueue from the list of Target Destination Names. If you do not have a list of targets, you have not created the JMS for this example as described at the start of this section—cancel the wizard, run “asant create-jms” from the Day10/examples directory and start again.

  • Set the Acknowledgement Mode to Auto-Acknowledge.

  • Type in the Connection Factory JNDI name as jms/QueueConnectionFactory—you do not get a popup list for this field so type your entry carefully.

You should now have a completed screen, as shown in Figure 10.6.

Figure 10.6. Defining JMS Resources for an MDB.


8.
Move onto the next page which summarizes your next steps and then click Finish to create the ApplicantMatch MDB.

9.
Select the ApplicantMatch MDB and use the EJB References page to create references to the Applicant, Job and Matched Entity beans as discussed on Day 6 when local EJB references to Entity beans were described. The Coded Names for each of the Entity beans are ejb/ApplicantLocal, ejb/JobLocal and ejb/MatchedLocal, respectively. The required EJB references are shown in Figure 10.7.

Figure 10.7. EJB References for the ApplicantMatch MDB.


10.
Select the General dialogue page for the ApplicantMatch MDB and select the Sun-specific Settings page. Set the JNDI Name field to jms/applicantQueue (it will have defaulted to the name of the bean: ApplicantQueue).

11.
Save your changes to the Enterprise Application and select the Tools, Verify J2EE Compliance to ensure you have created the MatchedBean Entity bean and ApplicantMatch MDB correctly.

NOTE

As discussed on Day 6, using deploytool from the November 2003 release of J2EE RI 1.4 reports spurious errors about missing JNDI names for the Entity beans with local interfaces. You can ignore these errors. If you are unsure you can run the command “asant verify-j2ee-ri” from the Day10/examples directory to verify your changes using the command line verifier, which does not report spurious errors. Make sure you save your changes before running the command line verifier.


The EJB DD entry you have created for the ApplicantMatch MDB (with the EJB reference omitted) is

<message-driven>
  <ejb-name>ApplicantMatch</ejb-name>
  <ejb-class>data.ApplicantMatch</ejb-class>
  <messaging-type>javax.jms.MessageListener</messaging-type>
  <transaction-type>Bean</transaction-type>
  <message-destination-type>javax.jms.Queue</message-destination-type>
  <message-destination-link>ApplicantQueue</message-destination-link>
...
</message-driven>

The DD also contains an entry for the Message destination at the end of the assembly-descriptor section:

<assembly-descriptor>
...
  <message-destination>
    <message-destination-name>ApplicantQueue</message-destination-name>
  </message-destination>
...
</assembly-descriptor>

Before you can deploy the updated case study code you must update the Agency and Register session beans to include the jms/ApplicantQueue message destination as discussed in the next section.

Adding JMS Message Destinations with J2EE RI

In order to send messages to an MDB you must define a resource reference for the JMS connection factory and a message destination reference for the JMS queue. For the Agency case study you do this as follows:

1.
Select the Agency JAR file in the case study application, select the General dialogue, click Edit to add the helper class (shown in Listing 10.1). In the popup Edit window add the class file for agency.MessageSender. Close the Edit window.

2.
Select the AgencyBean Session bean in the case study application and then select the Resource Refs page. There will already be an entry for the JDBC Resource (jdbc/Agency). Click on Add to add a new resource reference and set the following values

  • Code name to jms/QueueConnectionFactory.

  • Type to be javax.jms.QueueConnectionFactory.

  • Authentication to container.

  • Check the shareable option.

  • Set the Sun-Specific JNDI name to jms/QueueConnectionFactory with a username and password of j2ee and j2ee, respectively.

You should have a screen similar to Figure 10.8.

Figure 10.8. JMS Connection Factory Resource Reference.


3.
After defining the resource reference for the JMS connection factory select the Msg Dest Refs page and click on Add to add a new message destination reference. In the popup window, enter the following data:

  • jms/applicantQueue as the Coded Name.

  • javax.jms.Queue as the Destination Type.

  • Choose Produces from the list of Usage options.

  • In the Target Destination Name field select the entry for the data-entity-ejb.jar#ApplicantQueue defined for your application. If you do not have target destination names defined, you have not created the ApplicantQueue message destination as detailed in the previous section. You should now have a window similar to the one in Figure 10.9.

    Figure 10.9. Defining EJB Message Destination References.

Click OK to save your changes.

4.
Select the RegisterBean Session bean and perform steps 1 and 2 again to add the jms/QueueConnectionFactory resource reference and the ApplicantMatch message destination reference. Save your changes.

5.
Select the Agency application and run Tools, Verify J2EE Compliance to verify your changes.

Adding the JMS connection factory resource reference and the message destination adds the following entry to the DD:

<session>
...
 <ejb-name>AgencyBean</ejb-name>
 <resource-ref>
   <res-ref-name>jms/QueueConnectionFactory</res-ref-name>
   <res-type>javax.jms.QueueConnectionFactory</res-type>
   <res-auth>Container</res-auth>
   <res-sharing-scope>Shareable</res-sharing-scope>
 </resource-ref>
   <message-destination-ref>
   <message-destination-ref-name>jms/applicantQueue</message-destination-ref-name>
   <message-destination-type>javax.jms.Queue</message-destination-type>
   <message-destination-usage>Produces</message-destination-usage>
   <message-destination-link>data-entity-ejb.jar#ApplicantQueue</message-destination-link>
 </message-destination-ref>
...
</session>

You can now deploy your updated Agency application. Make sure you obtain the client JAR file for this new application as described on Day 4, “Introduction to Enterprise JavaBeans.” If you are not sure how to obtain the client JAR file, after deploying the updated Agency application (in deploytool), select the localhost:4848 server in the left window. From this list of applications in the right window select agency and click on Client Jar…. Save the returned JAR file to the Day10/examples/j2ee-ri directory. You can now run the example using

asant run-j2ee-ri

If you are working from the asant build files rather than using deploytool, to build and deploy the supplied code, simply enter the command

asant build deploy

The client JAR file is automatically returned to the Day12/examples/build directory. Run this version of the case study using

asant run

You are now ready to test the application.

Testing the ApplicantMatch Bean

Deploy and run the Agency application as described in the previous section. Use the Register screen to add a new applicant whose location is London and skills are Cigar Maker.

Use the Tables screen to view the contents of the Matched table and check that a row has been added for the new applicant with the following details:

  • Job— Cigar trimmer

  • Customer— winston

  • Exact— false

Add another applicant whose location is also London and whose skills are Cigar Maker and Critic. Check that this creates a row with the following details in the Matched table:

  • Job— Cigar trimmer

  • Customer— winston

  • Exact— true

Change the skills for this second applicant. Remove the Cigar Maker and Critic and add the skill Bodyguard.

Check that the row for this applicant has now been deleted from the Matched table.

If these checks are okay, congratulations! You have successfully deployed the ApplicantMatch MDB. Of course, you can add or amend other applicants to find other job matches in the system.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.138.177