Tuesday 6 December 2011

Remove all .svn folders in Windows XP

It has been a tedious job to remove all the .svn folders in a project checked out from SVN repository. Thanks to http://www.iamatechie.com/remove-all-svn-folders-in-windows-xp-vista/, we now have an easy way.

Create a blank svnresistry.reg file (can be any name with .reg extension) on your desktop, edit it, and copy the following code snippet as its content.

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN]
@="Delete SVN Folders"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN\command]
@="cmd.exe /c \"TITLE Removing SVN Folders in %1 && COLOR 9A && FOR /r \"%1\" %%f IN (.svn) DO RD /s /q \"%%f\" \""

Double click the reg file to execute it.

Then you can right click on the project folder, you will see a 'Delete SVN Folders' menu.


Just clicking on this menu will remove all the 'svn' folders under this directory recursively.

Saturday 3 December 2011

JPA one-to-many many-to-one relationship

many-to-one unidirectional mapping

@Entity
@Table(name="T_MANAGER")
public class Manager {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="MANA_ID")
    private int id;
    
    @Column(name="MANA_NAME")
    private String name;
}

@Entity
@Table(name="T_EMPLOYEE")
public class Employee {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="EMPL_ID")
    private int id;
    
    @Column(name="EMPL_NAME")
    private String name;

    @ManyToOne(cascade=CascadeType.PERSIST)
    private Manager manager;
}

Domain model


T_EMPLOYEE.MANAGER_MANA_ID is the foreign key referenced to T_MANAGER.MANA_ID

MANAGER_MANA_ID is the default foreign key column name. It is derived from class name + ‘_’ + primary key name (‘MANAGER’ + ‘_’+’MANA_ID’)

Test:

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

private void save(){
    Manager manager1 = new Manager("John");
    Employee employee1 = new Employee("Bob");
    Employee employee2 = new Employee("Tim");
    Employee employee3 = new Employee("Mary");
    employee1.setManager(manager1);
    employee2.setManager(manager1);
    employee3.setManager(manager1);
    
    Manager manager2 = new Manager("Robert");
    Employee employee4 = new Employee("Tom");
    Employee employee5 = new Employee("Alex");
    employee4.setManager(manager2);
    employee5.setManager(manager2);
    em.persist(employee1);
    em.persist(employee2);
    em.persist(employee3);
    em.persist(employee4);
    em.persist(employee5);
}
Result:

T_MANAGER


T_EMPLOYEE


Specify foreign key column name

@OneToOne(cascade={CascadeType.PERSIST, 
    CascadeType.MERGE, CascadeType.REMOVE}, 
    optional=false, fetch=FetchType.EAGER)
@JoinColumn(name="F_ADDR_ID", referencedColumnName="ADDR_ID")
private Address address;

Domain model


The name attribute of @JoinColumn (‘F_MANA_ID’) specifies the foreign key column name.

many-to-one bidirectional mapping

@Entity
@Table(name="T_MANAGER")
public class Manager {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="MANA_ID")
    private int id;
    
    @Column(name="MANA_NAME")
    private String name;
    
    @OneToMany(cascade={CascadeType.ALL}, mappedBy="manager")
    private Set<Employee> employees;
}

@Entity
@Table(name="T_EMPLOYEE")
public class Employee {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="EMPL_ID")
    private int id;
    
    @Column(name="EMPL_NAME")
    private String name;

    @ManyToOne(cascade=CascadeType.PERSIST)
    @JoinColumn(name="F_MANA_ID")
    private Manager manager;
}

Domain model

Same as unidirectional mapping


Test:

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

private void save(){
    Manager manager1 = new Manager("John");
    Employee employee1 = new Employee("Bob");
    Employee employee2 = new Employee("Tim");
    Employee employee3 = new Employee("Mary");
    Set<Employee> employees1 = new HashSet<Employee>();
    employee1.setManager(manager1);
    employee2.setManager(manager1);
    employee3.setManager(manager1);
    employees1.add(employee1);
    employees1.add(employee2);
    employees1.add(employee3);
    manager1.setEmployees(employees1);
    
    Manager manager2 = new Manager("Robert");
    Employee employee4 = new Employee("Tom");
    Employee employee5 = new Employee("Alex");
    Set<Employee> employees2 = new HashSet<Employee>();
    employees2.add(employee4);
    employees2.add(employee5);
    employee4.setManager(manager2);
    employee5.setManager(manager2);
    manager2.setEmployees(employees2);
    
    em.persist(manager1);
    em.persist(manager2);
}

Result:

Same as unidirectional mapping

T_MANAGER


T_EMPLOYEE


Retrieving the employees of a manager returns null.

Manager manager = em.find(Manager.class, 1);
System.out.println(manager.getEmployees());

This is because in a one-to-many relationship, the default fetch type at ONE side is LAZY.

@OneToMany(cascade={CascadeType.ALL}, mappedBy="manager", 
           fetch=FetchType.EAGER)
private Set<Employee> employees;

Change the fetch type to EAGER, and we are able to retrieve the employees of a manager.

Add a new employee to a manager

Manager manager = em.find(Manager.class, 1);
Employee emp = new Employee("Adam");
emp.setManager(manager);
manager.getEmployees().add(emp);
em.merge(manager);

Result:


Update an existing employee of a manager

Manager manager = em.find(Manager.class, 1);
Employee employee = manager.getEmployees().iterator().next();
employee.setName("Josh");
em.merge(manager);

Result:



Delete an existing employee of a manager

Manager manager = em.find(Manager.class, 1);
Set<Employee> employees= manager.getEmployees();
for (Employee emp: employees){
    if ("Bob".equalsIgnoreCase(emp.getName())){
        employees.remove(emp);
        break;
    }
}
em.merge(manager);

Result:


Bob is not deleted! We can see that simply removing the object from the set won’t actually remove the row from the database. So in this case, we need to invoke em.remove() to delete the row.

Manager manager = em.find(Manager.class, 1);
Set<Employee> employees= manager.getEmployees();
for (Employee emp: employees){
    if ("Bob".equalsIgnoreCase(emp.getName())){
        employees.remove(emp);
        em.remove(emp);
        break;
    }
}
em.merge(manager);

Result:

Saturday 26 November 2011

JPA one-to-one relationship

Basic one-to-one unidirectional mapping

@Entity
@Table(name="T_CUSTOMER")
public class Customer {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="CUST_ID")
    private int id;
    
    @Column(name="CUST_NAME")
    private String name;
    
    @OneToOne
    private Address address;
    
}

@Entity
@Table(name="T_ADDRESS")
public class Address {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="ADDR_ID")
    private int id;
    
    @Column(name="STREET")
    private String street;
    
    @Column(name="POSTCODE")
    private String postcode;
    
}

Domain model


T_CUSTOMER.ADDRESS_ADDR_ID is the foreign key referenced to T_ADDRESS.ADDR_ID

ADDRESS_ADDR_ID is the default foreign key column name. It is derived from class name + ‘_’ + primary key name (‘ADDRESS’ + ‘_’+’ADDR_ID’)

Since by default, no operations are cascaded, Address entity needs to be saved before Customer entity does.

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

public void save(){
    Address address = new Address("10 test st", "2912");
    Customer customer = new Customer("Sun", address);
    //Save address to database
    em.save(address);
    //Save customer to database
    em.save(customer);
    System.out.println("Customer saved");
}

The result:

T_CUSTOMER


T_ADDRESS


Add cascade attribute

@OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE, 
        CascadeType.REMOVE})
private Address address;

Now Address entity does not need to be saved before Customer entity does.

public void save(){
    Address address = new Address("10 test st", "2912");
    Customer customer = new Customer("Sun", address);
    //Save customer to database
    em.save(customer);
    System.out.println("Customer saved");
} 

Same for update and delete.

Specify whether association is optional, fetch type

@OneToOne(cascade={CascadeType.PERSIST, 
        CascadeType.MERGE, CascadeType.REMOVE}, 
        optional=false, fetch=FetchType.EAGER)
private Address address;

Default value of optional attribute is true
Default value of fetch attribute is EAGER

Now if trying to save a customer without setting his address, an exception will be thrown.

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

public void save(){
    Address address = new Address("10 test st", "2912");
    Customer customer = new Customer("Sun", address);
    //Save customer to database
    em.save(customer);
    System.out.println("Customer saved");
}

Specify foreign key column name

@OneToOne(cascade={CascadeType.PERSIST, 
        CascadeType.MERGE, CascadeType.REMOVE}, 
        optional=false, fetch=FetchType.EAGER)
@JoinColumn(name="F_ADDR_ID", referencedColumnName="ADDR_ID")
private Address address;

Domain model


The name attribute of @JoinColumn (‘F_ADDR_ID’) specifies the foreign key column name.

The referenceColumnName attribute of @JoinColumn (‘ADDR_ID’) specifies the primary key of the table referenced to. It can be omitted.

One-to-one bidirectional mapping

@Entity
@Table(name="T_CUSTOMER")
public class Customer {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="CUST_ID")
    private int id;
    
    @Column(name="CUST_NAME")
    private String name;
    
    @OneToOne(cascade={CascadeType.ALL}, optional=false)
    @JoinColumn(name="F_ADDR_ID")
    private Address address;
    
}

@Entity
@Table(name="T_ADDRESS")
public class Address {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="ADDR_ID")
    private int id;
    
    @Column(name="STREET")
    private String street;
    
    @Column(name="POSTCODE")
    private String postcode;
    
    @OneToOne(mappedBy="address")
    private Customer customer;    
}

Domain model

Same as unidirectional mapping


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

public void save(){
    Address address = new Address("10 test st", "2912");
    Customer customer = new Customer("Sun", address);
    address.setCustomer(customer);
    //Save customer to database
    em.save(customer);
    System.out.println("Customer saved");
}

Friday 25 November 2011

Basic JPA annotations

The simplest entity bean:

@Entity
public class Student {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
    private String name;
    
}

The table “STUDENT” generated:


Specify table name, column name, nullable, length and unique:

@Entity
@Table(name="T_STUDENT")
public class Student {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="STU_ID")
    private int id;
    
    @Column(name="STU_NAME", nullable=false, 
            unique=true, length=30)
    private String name;    
}


Constraints:

CREATE UNIQUE INDEX SQL110902022422451 ON T_STUDENT(STU_NAME);

ColumnDefinition overwrites length

@Entity
@Table(name="T_STUDENT")
public class Student {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="STU_ID")
    private int id;
    
    @Column(name="STU_NAME", nullable=false, 
            unique=true, columnDefinition="VARCHAR(50)", length=30)
    private String name;    
}

The table “T_STUDENT” generated:


Integer column and BigDecimal column

@Entity
@Table(name="T_STUDENT")
public class Student {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="STU_ID")
    private int id;
    
    @Column(name="STU_NAME")
    private String name;
    
    @Column(name="AGE")
    private Integer age;
    
    @Column(name="MONEY")
    private BigDecimal money;    
}

The table “T_STUDENT” generated:


Specify precision and scale of BigDecimal

(The number 123.45 has a precision of 5 and a scale of 2)

@Entity
@Table(name="T_STUDENT")
public class Student {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="STU_ID")
    private int id;
    
    @Column(name="STU_NAME")
    private String name;
    
    @Column(name="AGE")
    private Integer age;
    
    @Column(name="MONEY", precision=5, scale=2)
    private BigDecimal money;
}

The table “T_STUDENT” generated:


Date column

@Entity
@Table(name="T_STUDENT")
public class Student {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="STU_ID")
    private int id;
    
    @Column(name="STU_NAME")
    private String name;
    
    @Column(name="AGE")
    private Integer age;
    
    @Column(name="MONEY", precision=5, scale=2)
    private BigDecimal money;
    
    @Column(name="START_DATE")
    private Date start;
}

The table “T_STUDENT” generated:


Temporal annotation to specify the date type (date, time, timestamp)

@Column(name="START_DATE")
@Temporal(TemporalType.DATE)
private Date start;

The column “START_DATE” generated:


The value:


@Column(name="START_DATE")
@Temporal(TemporalType.TIME)
private Date start;

The column “START_DATE” generated:


The value:


@Column(name="START_DATE")
@Temporal(TemporalType.TIMESTAMP) //This is the default
private Date start;

The column “START_DATE” generated:


The value:


Specify default date, not insertable, not updateable

@Entity
@Table(name="T_STUDENT")
public class Student {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="STU_ID")
    private int id;
    
    @Column(name="STU_NAME")
    private String name;
    
    @Column(name="START_DATE", 
            columnDefinition="DATE DEFAULT CURRENT_DATE", 
            insertable=false, updatable=false)
    private Date start;
}

The table “T_STUDENT” generated:


Blob and Clob

@Entity
@Table(name="T_STUDENT")
public class Student {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="STU_ID")
    private int id;
    
    @Column(name="STU_NAME")
    private String name;
    
    @Lob
    @Column(name="REPORT")
    private String report;
    
    @Lob
    @Column(name="IMAGE")
    private byte[] image;
}

The table “T_STUDENT” generated:

Monday 21 November 2011

EJB Security configuration in Glassfish 3.1

Assume the MySQL data source has been set up. (If not, check Configure MySQL datasource in Glassfish 3.1)

The JNDI name is jdbc/mysql

Start Glassfish, go to Admin Console, http://localhost:4848

Configurations -> server-config -> Security -> Realms


Click on 'New' button

Name: jdbcRealm
Class Name: Select com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm
JAAS Context: jdbcRealm
JNDI: jdbc/mysql
User Table: T_USER
User Name Column: username
Password Column: password
Group Table: T_GROUP
Group Name Column: groupname
Digest Algorithm: none


 

In the MySQL data source, create two tables: T_USER and T_GROUP

CREATE TABLE T_USER (
  `username` VARCHAR(30) NOT NULL,
  `password` VARCHAR(30) NOT NULL,
  PRIMARY KEY (`username`)
)

CREATE TABLE T_GROUP (
  `username` VARCHAR(30) NOT NULL,
  `groupname` VARCHAR(30) NOT NULL,
  PRIMARY KEY (`username`)
)

Insert data

insert into T_USER values (‘sun’, ‘123’);
insert into T_USER values (‘ming’, ‘456’);
insert into T_GROUP values (‘sun’, ‘adminGroup’);
insert into T_GROUP values (‘ming’, ‘userGroup’);

Create a stateless session bean

@Stateless(name="securityManager")
@Local(SecurityManager.class)

public class SecurityManagerBean implements SecurityManager {

    @Resource
    private EJBContext context;
    
    @RolesAllowed({"admin"})
    public void save() {
        System.out.println("User: "
                +context.getCallerPrincipal().getName());
        System.out.println("Save");
    }

}

Create a servlet

public class SecurityServlet extends HttpServlet {

    @EJB(beanName="securityManager")
    private SecurityManager securityManager;
    
    @Override
    protected void doGet(HttpServletRequest req, 
            HttpServletResponse resp)
            throws ServletException, IOException {
        securityManager.save();
    }
}

Edit web.xml under WEB-INF

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <security-role>
        <role-name>user</role-name>
    </security-role>
    
    <security-role>
        <role-name>admin</role-name>
    </security-role>
    
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>all resources</web-resource-name>
            <url-pattern>/se</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
            <http-method>HEAD</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>user</role-name>
            <role-name>admin</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    
    <login-config>
        <auth-method>BASIC</auth-method>
           <realm-name>jdbcRealm</realm-name>
    </login-config>

    <servlet>
        <servlet-name>se</servlet-name>
        <servlet-class>web.SecurityServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>se</servlet-name>
        <url-pattern>/se</url-pattern>
    </servlet-mapping>
</web-app> 

Note: The value of <realm-name> (jdbcRealm) must match the Name field in Admin Console.


Edit sun-web.xml under WEB-INF

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 8.1
Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd">

<sun-web-app>
    <security-role-mapping>
       <role-name>user</role-name>
       <group-name>userGroup</group-name>
    </security-role-mapping>

    <security-role-mapping>
       <role-name>admin</role-name>
       <group-name>adminGroup</group-name>
    </security-role-mapping>
</sun-web-app>


Note: The value of <role-name> must match the value of <role-name> under <security-role> in web.xml. The value of <group-name> must match the value of groupname column in T_GROUP table.

Test the servlet

http://localhost:8080/security-web/se


The console prints

User: sun
Saved


Close and open the browser again, login with the user 'ming'

The console prints

javax.ejb.EJBAccessException

Then try to login with an nonexistent user

The console prints

java.lang.SecurityException

Saturday 19 November 2011

EJB use third party libraries

Assume the EJB project (containing the session bean, entity bean) is named ‘ejb3-jpa’, the EAR project is named ‘ejb3-ear’

An EJB session bean needs to use a third party library (e.g. Log4j)

import org.apache.log4j.Logger;

@Stateless(name="stateless")
@Local(Session.class)
public class StatelessSessionBean implements Session {

    private int result;
    private Logger logger = Logger.getLogger(StatelessSessionBean.class);
    
    @Override
    public void add() {
        logger.info("add");
        result++;
    }

    @Override
    public int get() {
        return result;
    }
}

Without any more changes, we will get a NoClassDefFoundError:

Caused by: java.lang.NoClassDefFoundError: org/apache/log4j/Logger
at session.StatelessSessionBean.<init>(StatelessSessionBean.java:13)

This is because the dependent jar file (Log4j.jar) won’t be automatically deployed to the server. So we need to manually add the dependent jar file to the EAR project.

Right click on ‘ejb3-ear’, and click on ‘Properties’



Click on ‘Add External JARs’



Add the ‘Log4j.jar’ file into the Java EE modules list

In ‘ejb3-jpa’ project, open the MANIFEST.MF file under META-INF



Enter the Log4j jar name following the Class-Path:

Manifest-Version: 1.0
Class-Path: log4j-1.2.14.jar

Now the StatelessSessionBean class is able to find the Log4j classes it is dependent on.

Appendix

Class loading rule

Friday 18 November 2011

Access environment properties in EJB3

Create a stateless session bean

@Stateless(name="personBean")
@Local(PersonManager.class)
public class PersonManagerBean implements PersonManager {

    @PersistenceContext(unitName="unit")
    private EntityManager em;
    
    @Resource
    private String defaultName;
    
    @Override
    public void save(Person person) {
        em.persist(new Person(defaultName));
    }
}

The defaultName is to be injected

In ejb-jar.xml

<enterprise-beans>
    <session>
        <ejb-name>personBean</ejb-name>
        <env-entry>
            <env-entry-name>
                session.PersonManagerBean/defaultName
            </env-entry-name>
            <env-entry-type>java.lang.String</env-entry-type>
            <env-entry-value>Tom</env-entry-value>
        </env-entry>
    </session>
</enterprise-beans>

Now the defaultName is initialised with the value ‘Tom’.

The xml equivalence of @Resource annotation:

<enterprise-beans>
    <session>
        <ejb-name>personBean</ejb-name>
        <env-entry>
            <env-entry-name>
                session.PersonManagerBean/defaultName
            </env-entry-name>
            <env-entry-type>java.lang.String</env-entry-type>
            <env-entry-value>Tom</env-entry-value>
            <injection-target>
                <injection-target-class>
                    session.PersonManagerBean
                </injection-target-class>
                <injection-target-name>
                    defaultName
                </injection-target-name>
            </injection-target>
        </env-entry>
    </session>
</enterprise-beans> 

Thursday 17 November 2011

Credit Card Validation --- Luhn Check

/**
 * Checks if the value is a valid credit card number
 * @param value a string
 * @return true if the value is a valid credit card number
 */
public static boolean isValid(String value){
    return luhnCheck(value.replaceAll("\\D", "")); //remove non-digits
}
 
 /**
 * Checks if a cardNumber passes LUHN check
 * @param cardNumber A string contains only digits
 * @return true if the cardNumber passes LUHN check
 */
private static boolean luhnCheck(String cardNumber){
    int sum=0;
    for (int i=cardNumber.length()-1; i>=0; i-=2){
    sum+=Integer.parseInt(cardNumber.substring(i, i+1));
        if (i>0){
            int d=2*Integer.parseInt(cardNumber.substring(i-1, i));
            if (d>9) d-=9;
            sum+=d;
        }
    }
    return sum%10==0;
}

Configure JMS Resources and create Message Driven Bean in Websphere 7 (Step 8 and 9)

Step 8: Publish a Topic JMS message

Similar to step 4, omit the steps for context lookup, use dependency injection only.

8.1 Access the JMS resources in EJB

In ibm-ejb-jar-bnd.xml under META-INF

<session name="jmsService">
    <resource-ref name="jms/MyTopicConnectionFactory" 
        binding-name="jms/TopicConnectionFactory">
    </resource-ref>
    <message-destination-ref name="jms/MyTopic" 
            binding-name="jms/Topic"/>
</session> 

Note: The binding-name attribute of <resource-ref> and <message-destination-ref> element should match the jndi name specified in Step 6 and Step 7.

Create a stateless session bean

@Stateless(name = "jmsService")
@Local(JMSService.class)
public class JMSServiceBean implements JMSService {

    @Resource(name = "jms/MyTopicConnectionFactory")
    private TopicConnectionFactory tcf;

    @Resource(name = "jms/MyTopic")
    private Topic topic;
    

    @Override
    public void broadcast() {
        try {
            TopicConnection connection = tcf.createTopicConnection();
            TopicSession session 
                = connection.createTopicSession
                (false, TopicSession.AUTO_ACKNOWLEDGE);
            TopicPublisher publisher 
                = session.createPublisher(topic);
            TextMessage message = session.createTextMessage();
            message.setText("This is a broadcast");
            publisher.send(message);
        } catch (JMSException e) {
            throw new EJBException(e);
        }
        System.out.println("Broadcasted");
    }
}

Note: the name attribute of @Resource annotation should match name attribute of <resource-ref> and <message-destination-ref> element respectively in ibm-ejb-jar-bnd.xml, not the binding-name attribute.

Write a servlet to test

public class JMSTopicPublishServlet extends HttpServlet {

    @EJB(beanName="jmsService")
    private JMSService jmsService;
    
    protected void doGet(HttpServletRequest req, 
            HttpServletResponse resp)
            throws ServletException, IOException {
        jmsService.broadcast();
    }
} 

Result

[26/08/11 15:18:00:625 EST] 0000008f SystemOut O Broadcasted

8.2 Access the JMS resources in servlet

In ibm-web-bnd.xml under WEB-INF

<resource-ref name="jms/MyTopicConnectionFactory" 
        binding-name="jms/TopicConnectionFactory">
</resource-ref>
<message-destination-ref name="jms/MyTopic" 
            binding-name="jms/Topic"/>

Note: The binding-name attribute of <resource-ref> and <message-destination-ref> element should match the jndi name specified in Step 6 and Step 7.

Write a servlet to test

public class JMSTopicPublishServlet extends HttpServlet {
    
    @Resource(name = "jms/MyTopicConnectionFactory")
    private TopicConnectionFactory tcf;

    @Resource(name = "jms/MyTopic")
    private Topic topic;
    
    protected void doGet(HttpServletRequest req, 
            HttpServletResponse resp)
            throws ServletException, IOException {
        try {
            TopicConnection connection = tcf.createTopicConnection();
            TopicSession session 
                = connection.createTopicSession
                (false, TopicSession.AUTO_ACKNOWLEDGE);
            TopicPublisher publisher 
                = session.createPublisher(topic);
            TextMessage message = session.createTextMessage();
            message.setText("This is a broadcast");
            publisher.send(message);
        } catch (JMSException e) {
            throw new EJBException(e);
        }
        System.out.println("Broadcasted");
    }
} 

Result

[26/08/11 15:23:36:218 EST] 0000008f SystemOut O Broadcasted

Step 9: Create Topic Message Driven Beans

9.1 Create a Topic Activation Specification

Similar to 5.1



Type TopicActSpec for Name
jms/TopicActSpec for JNDI name
Select Topic as Destination type
Type jms/Topic for Destination JNDI name
Select InternalJMS as Bus name

Click ‘OK’ and save the changes.

9.2 Create two Topic Message Driven Beans

@MessageDriven(name="topicMessageDrivenBean1")
public class TopicMessageDrivenBean1 implements MessageListener {

    @Override
    public void onMessage(Message message) {
        try {
                    TextMessage txtMessage = (TextMessage) message;
                    System.out.println(txtMessage.getText()
                        + " processed by bean 1");
            } catch (JMSException ex) {
                throw new EJBException(ex);
            }
    }
}

@MessageDriven(name="topicMessageDrivenBean2")
public class TopicMessageDrivenBean2 implements MessageListener {

    @Override
    public void onMessage(Message message) {
        try {
                    TextMessage txtMessage = (TextMessage) message;
                    System.out.println(txtMessage.getText()
                            + " processed by bean 2");
            } catch (JMSException ex) {
                throw new EJBException(ex);
            }
    }
}

In ibm-ejb-jar-bnd.xml under META-INF

<message-driven name="topicMessageDrivenBean1">
    <jca-adapter activation-spec-binding-name="jms/TopicActSpec"/>
</message-driven>
<message-driven name="topicMessageDrivenBean2">
    <jca-adapter activation-spec-binding-name="jms/TopicActSpec"/>
</message-driven>

Test the servlet created in step 8 and check the result

[26/08/11 15:23:36:218 EST] 0000008f SystemOut O Broadcasted
[26/08/11 15:23:36:218 EST] 000000a3 SystemOut O This is a broadcast processed by bean 2
[26/08/11 15:23:36:218 EST] 000000a6 SystemOut O This is a broadcast processed by bean 1

Saturday 12 November 2011

Configure JMS Resources and create Message Driven Bean in Websphere 7 (Step 6 and 7)

Step 6: Create a JMS Topic connection factory

Similar to step 2



Type TopicConnection for Name
jms/TopicConnectionFactory for JNDI name

Select InternalJMS as Bus name

Click ‘OK’ and save the changes

Step 7: Create a JMS Topic destination

Similar to step 3




Type Topic for name
jms/Topic for JNDI name
Select InternalJMS as Bus name
Type q for topic space identifier

Click ‘OK’ and save the changes

Configure JMS Resources and create Message Driven Bean in Websphere 7 (Step 5)

Step 5: Create a Queue Message Driven Bean

5.1 Create a Queue Activation Specification

Log into Websphere Application Server 7 Admin Console

Resources -> JMS -> Activation specifications



Click on ‘New’ button



Click on ‘OK’ button


Type QueueActSpec for Name
jms/QueueActSpec for JNDI name
Select Queue as Destination type
Type jms/Queue for Destination JNDI name
Select InternalJMS as Bus name

Click on ‘OK’ button (not shown in the screenshot)



Click on ‘Save’ link.

5.2 Create a queue message driven bean

@MessageDriven(name="queueMessageDrivenBean")
public class QueueMessageDrivenBean 
    implements MessageListener {

    @Override
    public void onMessage(Message message) {
        try {
            TextMessage txtMessage = (TextMessage) message;
            System.out.println(txtMessage.getText()
                    + " processed....Orz");
        } catch (JMSException ex) {
            throw new EJBException(ex);
        }
    }
}

In ibm-ejb-jar-bnd.xml

<message-driven name="queueMessageDrivenBean">
    <jca-adapter activation-spec-binding-name="jms/QueueActSpec"/>
</message-driven>

Test the servlet created in step 4 and check the result

[26/08/11 11:11:33:812 EST] 0000008f SystemOut O Message Sent
[26/08/11 11:11:33:812 EST] 000000a4 SystemOut O Hello World processed....Orz

Binding another message driven bean to the same queue destination won’t cause exceptions, but only one message driven bean is able to process the message.

Friday 11 November 2011

Configure JMS Resources and create Message Driven Bean in Websphere 7 (Step 4)

Step 4: Send a queue JMS message

4.1 Access the JMS resources in EJB

4.1.1 Access the JMS resources through Context Lookup

In ibm-ejb-jar-bnd.xml under META-INF


<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar-bnd
        xmlns="http://websphere.ibm.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee 
        http://websphere.ibm.com/xml/ns/javaee/ibm-ejb-jar-bnd_1_0.xsd"
        version="1.0">
    <session name="jmsService">
        <resource-ref name="jms/MyQueueConnectionFactory" 
            binding-name="jms/QueueConnectionFactory">
        </resource-ref>
        <message-destination-ref name="jms/MyQueue" 
            binding-name="jms/Queue"/>
    </session>
</ejb-jar-bnd>

Note: The binding-name attribute of <resource-ref> and <message-destination-ref> element should match the jndi name specified in Step 2 and Step 3.

In ejb-jar.xml under META-INF

<session>
    <ejb-name>jmsService</ejb-name>
    <resource-ref>
        <res-ref-name>jms/MyQueueConnectionFactory</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/MyQueue
        </message-destination-ref-name>
        <message-destination-type>
            javax.jms.Queue
        </message-destination-type>
        <message-destination-usage>
            ConsumesProduces
        </message-destination-usage>
        <message-destination-link>
            jms/Queue
        </message-destination-link>
    </message-destination-ref>
</session> 

Note: The value of <res-ref-name> and <message-destination-ref-name> (jms/MyQueueConnectionFactory and jms/MyQueue) should match the name attribute of < resource-ref> and <message-destination-ref> element respectively in ibm-ejb-jar-bnd.xml.

Create a stateless session bean

@Stateless(name="jmsService")
@Local(JMSService.class)
public class JMSServiceBean implements JMSService {

    @Override
    public void sendMessage() {
        try{
            Context ctx = new InitialContext();
            QueueConnectionFactory cf = 
                (QueueConnectionFactory)
                ctx.lookup
                ("java:comp/env/jms/MyQueueConnectionFactory");
            Queue dest = 
                (Queue)ctx.lookup("java:comp/env/jms/MyQueue");
            QueueConnection connection = cf.createQueueConnection();
            QueueSession session = 
                connection.createQueueSession
                (false, javax.jms.Session.AUTO_ACKNOWLEDGE);
            QueueSender queueSender = session.createSender(dest);
            TextMessage message = session.createTextMessage();
            message.setText("Hello World");
            queueSender.send(message);
            System.out.println("Message Sent");
        }catch (Exception e) {
            throw new EJBException(e);
        }
    }
}

Note: The lookup string (jms/MyQueueConnectionFactory and jms/MyQueue) should match the value of <res-ref-name> and <message-destination-ref-name> element respectively in ejb-jar.xml.

4.1.2 Access the JMS resources through Dependency Injection

In ibm-ejb-jar-bnd.xml under META-INF, same as 4.1.1

<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar-bnd
        xmlns="http://websphere.ibm.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee 
        http://websphere.ibm.com/xml/ns/javaee/ibm-ejb-jar-bnd_1_0.xsd"
        version="1.0">
    <session name="jmsService">
        <resource-ref name="jms/MyQueueConnectionFactory" 
            binding-name="jms/QueueConnectionFactory">
        </resource-ref>
        <message-destination-ref name="jms/MyQueue" 
            binding-name="jms/Queue"/>
    </session>
</ejb-jar-bnd>

Note: The binding-name attribute of and element should match the jndi name specified in Step 2 and Step 3.

No changes needed in ejb-jar.xml

Create a stateless session bean


@Stateless(name = "jmsService")
@Local(JMSService.class)
public class JMSServiceBean implements JMSService {

    @Resource(name = "jms/MyQueueConnectionFactory")
    private QueueConnectionFactory qcf;

    @Resource(name = "jms/MyQueue")
    private Queue queue;

    @Override
    public void sendMessage() {
        try {
            QueueConnection connection = qcf.createQueueConnection();
            QueueSession session 
                = connection.createQueueSession(false,
                    QueueSession.AUTO_ACKNOWLEDGE);
            QueueSender queueSender = session.createSender(queue);
            TextMessage message = session.createTextMessage();
            message.setText("Hello World");
            queueSender.send(message);
        } catch (JMSException e) {
            throw new EJBException(e);
        }
        System.out.println("Message Sent");
    } 
}

Note: the name attribute of @Resource annotation should match name attribute of <resource-ref> and <message-destination-ref> element, not the binding-name attribute.

4.1.3 Write a servlet to test

public class JMSSenderServlet extends HttpServlet {

    @EJB(beanName="jmsService")
    private JMSService jmsService;
    
    @Override
    protected void doGet(HttpServletRequest req, 
            HttpServletResponse resp)
            throws ServletException, IOException {
        jmsService.sendMessage();
    }
}

The result:

[25/08/11 14:07:40:109 EST] 0000001a SystemOut O Message Sent

4.2 Access the JMS resources in servlet

4.2.1 Access the JMS resources through Context Lookup

In ibm-web-bnd.xml under WEB-INFO


<?xml version="1.0" encoding="UTF-8"?>
<web-bnd 
    xmlns="http://websphere.ibm.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee 
    http://websphere.ibm.com/xml/ns/javaee/ibm-web-bnd_1_0.xsd"
    version="1.0">

    <virtual-host name="default_host" />
    <resource-ref name="jms/ConnectionFactory" 
            binding-name="jms/QueueConnectionFactory" />
    <message-destination-ref name="jms/Queue" 
            binding-name="jms/Queue"/>
</web-bnd>

Note: The binding-name attribute of <resource-ref> and <message-destination-ref> element should match the jndi name specified in Step 2 and Step 3.

In web.xml under WEB-INFO

<resource-ref>
    <res-ref-name>jms/MyQueueConnectionFactory</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/MyQueue
    </message-destination-ref-name>
    <message-destination-type>
        javax.jms.Queue
    </message-destination-type>
    <message-destination-usage>
        ConsumesProduces
    </message-destination-usage>
</message-destination-ref>

Note: The value of <res-ref-name> and <message-destination-ref-name> (jms/MyQueueConnectionFactory and jms/MyQueue) should match the name attribute of < resource-ref> and <message-destination-ref> element respectively in ibm-web-bnd.xml.

Create the servlet

public class JMSQueueSenderServlet extends HttpServlet {
    
    protected void doGet(HttpServletRequest req, 
            HttpServletResponse resp)
            throws ServletException, IOException {
        
        try{
            Context ctx = new InitialContext();
            QueueConnectionFactory qcf = 
                (QueueConnectionFactory)
                ctx.lookup
                ("java:comp/env/jms/MyQueueConnectionFactory");
            Queue queue = 
                (Queue)ctx.lookup("java:comp/env/jms/MyQueue");
            QueueConnection connection = qcf.createQueueConnection();
            QueueSession session = 
                connection.createQueueSession
                (false, QueueSession.AUTO_ACKNOWLEDGE);
            QueueSender queueSender = session.createSender(queue);
            TextMessage message = session.createTextMessage();
            message.setText("Hello World");
            queueSender.send(message);
            System.out.println("Message Sent");
        }catch (Exception e) {
            throw new EJBException(e);
        }
    }
}

4.2.2 Access the JMS resources through Dependency Injection

In ibm-web-bnd.xml under META-INF, same as 4.2.1

<?xml version="1.0" encoding="UTF-8"?>
<web-bnd 
    xmlns="http://websphere.ibm.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee 
    http://websphere.ibm.com/xml/ns/javaee/ibm-web-bnd_1_0.xsd"
    version="1.0">

    <virtual-host name="default_host" />
    <resource-ref name="jms/ConnectionFactory" 
            binding-name="jms/QueueConnectionFactory" />
    <message-destination-ref name="jms/Queue" 
            binding-name="jms/Queue"/>
</web-bnd>

No changes in web.xml

Create the servlet

public class JMSQueueSenderServlet extends HttpServlet {

    @Resource(name = "jms/MyQueueConnectionFactory")
    private QueueConnectionFactory qcf;
    
    @Resource(name = "jms/MyQueue")
    private Queue queue;
    
    protected void doGet(HttpServletRequest req, 
            HttpServletResponse resp)
            throws ServletException, IOException {
        try {
            QueueConnection connection = qcf.createQueueConnection();
            QueueSession session 
                = connection.createQueueSession(false,
                    QueueSession.AUTO_ACKNOWLEDGE);
            QueueSender queueSender = session.createSender(queue);
            TextMessage message = session.createTextMessage();
            message.setText("Hello World");
            queueSender.send(message);
        } catch (JMSException e) {
            throw new EJBException(e);
        }
        System.out.println("Message Sent");
    }

}

Note: the name attribute of @Resource annotation should match name attribute of <resource-ref> and <message-destination-ref> element respectively in ibm-web-bnd.xml, not the binding-name attribute.

The result:

[25/08/11 14:20:55:218 EST] 00000020 SystemOut O Message Sent