пятница, 26 ноября 2010 г.

WebSphere: Используем сервис планирования WebSphere 7.0 из EJB 2.1


В последнее время по своей работе мне приходится много общаться с WebSphere 7.0. Т.к. зверь это тяжелый и малоизученный - я буду временами публиковать интересные заметки из нашей с ним совместной жизни.

Сегодня я хочу рассказать о сервисе планирования, который есть в WebSphere с 6ой версии. Основой данной статьи является Using a WebSphere Scheduler.

Я адаптировал данную статью для WebSphere 7.0 под управлением ОС Windows, исправил ряд недочетов автора, описал несколько присыпанных листьями граблей и главное выложу исходный код классов, которые оригинальный автор выложил в виде картинок.

Данная статья промежуточная для стадии получения первого рабочего прототипа, в следующей статье я планирую написать продолжение с переходом на EJB 3.0 и примером из реальной жизни.

Нашей целью будет научиться использовать встроенный в WebSphere сервис планирования для планирования наших собственных задач.


1. Подготавливаем БД


Сервис планирования WebSphere хранит информацию о задачах в БД. В качестве БД можно использовать СУБД: DB2, Derby, Informix, MSSQL, Oracle, Sybase. С остальными СУБД тоже можно запустить, но как минимум DDL скрипт создания БД придется адаптировать самому.
В качестве БД мы будем использовать Apache Derby. Она простая и легкая, а именно это нам и надо в нашем примере. Я взял последнюю версию 10.6.2.1 с официального сайта.

1.1. Установка


Установка Derby просто до безобразия - просто распакуйте её в любую папку на жестком диске. Я установил её в папку D:\devel\derby\10.6.2.1

1.2. Настройка окружения


Чтобы использовать Derby было удобно надо немного настроить окружение Windows. Добавьте следующие переменные окружения:
DERBY_HOME = D:\devel\derby\10.6.2.1 В переменную CLASSPATH добавьте новые значения:
%DERBY_HOME\lib\derbyclient.jar;%DERBY_HOME\lib\derbytools.jar;

1.3. Создание схемы БД


Структуру БД планировщика вы можете найти в папке %WebSphere_HOME%/Scheduler/createSchemaMod1Derby.ddl В этом файле хранится структура БД, необходимо скопировать этот файлв derby.sql и заменить все вхождения @TABLE_QUALIFIER@@TABLE_PREFIX@ на sched_

1.4. Создание БД


После этого вы сможете запустить Derby с помощью команды: java -jar %DERBY_HOME%\lib\derbyrun.jar server start Создать структуру БД можно с помощью команды java -jar %DERBY_HOME%\lib\derbyrun.jar ij запущенной из другого терминала. Вам последовательно надо будет выполнить следующие команды:

 #Подключаться к Derby и создать новую БД
 connect 'jdbc:derby://localhost:1527/d:\temp\SchedulerDb;create=true';
 #Импортировать структуру БД из заранее подготовленной схемы
 run '/home/jsears/local/tmp3/derby.sql';
 #проверить результат
 show tables;
 
 #настроить параметры безопасности
 CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.user.user1', 'password1');
 CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.connection.requireAuthentication', 'true');

 #переподключиться к БД и проверить авторизацию
 disconnect;
 connect 'jdbc:derby://localhost:1527/d:\temp\SchedulerDb;user=user1;password=password1';
 exit;
После этого Derby необходимо перезапустить.

2. Создание JDBC ресурса в WebSphere


В WebSphere как и в других Application Server'ах приложения получают доступ к БД через JDBC Provide / DJBC Datasource. Эти ресурсы настроиваются прямо на сервере приложений и позволяют абстрагировать наше приложения от особенностей подключения к БД.
Необходимо подключить наше БД как JDBC resource в WebSphere. Для этого в Integrated Solutions Console выполняем следующие действия:

2.1. Сохраним credentials для доступа к нашей БД.


Для этого в разделе Security->Global security->JAAS - J2C authentication data нужно создать новый ресурс:
   - Alias = derby_user
   - User ID = user1
   - Password = password1
Логин и пароль должны совпадать с теми которые вы заводили в БД

2.2. Теперь создаем JDBC провайдера


В разделе Resources->JDBC providers создаем новый ресурс:
  Database type = Derby
  Provider type  = Derby JDBC Provider
  Implementation Type = Connection pool data source
  Name = Derby JDBC Provider Scheduler

2.3. Создаем новый DataSource:


В разделе Resources->JDBC providers -> Derby JDBC Provider Scheduler -> Data Sources создаем новую запись:
  Step 1:
    Data source name = Derby JDBC Driver DataSource
    JNDI name = jdbc/DerbyNSDS
 
  Step 2:
    Database name  = d:\temp\SchedulerDb
 
  Step 3:
    Component-managed authentication alias  = Node01/Derby user 
    Mapping-configuration alias  = none
    Container-managed authentication alias  = Node01/Derby user 
Проверьте соединение с помощью Test Connection. Если все хорошо - вы успешно подключили БД к WebSphere, в противном случае придется искать ошибку.

В моем случае я получил следующее сообщение:


"The test connection operation failed for data source Derby JDBC Driver DataSource on server server1 at node Node01 with the following exception: java.sql.SQLException: Failed to start database 'd:\temp\SchedulerDb' with class loader com.ibm.ws.bootstrap.ExtClassLoader@f5e0f5e, see the next exception for details.DSRA0010E: SQL State = XJ040, Error Code = 40,000. View JVM logs for further details."

2.4. Настройка classpath


Самый простой способ это вернуться в настройку JDBC providers и изменить значение полей:
  Classpath 
  с 
    "${DERBY_JDBC_DRIVER_PATH}/derby.jar"
  на 
    "D:\devel\derby\10.6.2.1\lib\derbyclient.jar"

  "Implementation class name" с
    "org.apache.derby.jdbc.EmbeddedConnectionPoolDataSource"
  на 
    "org.apache.derby.jdbc.ClientConnectionPoolDataSource"


Теперь сообщение изменилось на:


"The test connection operation failed for data source Derby JDBC Driver DataSource on server server1 at node Node01 with the following exception: java.lang.ClassNotFoundException: org.apache.derby.jdbc.ClientConnectionPoolDataSource. View JVM logs for further details."

Оказалось что значение classpath в JDBC providers не сохралилось.
После этого все успешно заработало и было получено сообщение:

InformationThe test connection operation for data source Derby JDBC Driver DataSource on server server1 at node Node01 was successful.

3. Создание Scheduler в WebSphere


3.1. Создание ресурса - планировщик


Перейдите в раздел Resources->Scheduler и в качестве scope выберите ваш instance сервера.
Выберите команду New и введите следующие параметры:
  - Name = MyScheduler
  - JNDI name = sched/MyScheduler
  - Data source JNDI name = jdbc/DerbyNSDS
  - Data source alias = Node01/Derby user
  - Table prefix = <имя схемы>.<префикс БД> в моем случае это было APP.sched_
  - Work manager JNDI name = ws/default
И нажмите кнопку "Ok".

4. Использование планировщика из приложений


Теперь пришло время реализовать работу с планировщиком из наших приложений. В оригинале статьи примеры используют EJB 2.1. Поэтому и мы для начала воспользуемся этой версией (а потом разберемся как это реализовать в EJB 3.0)

4.1. Создание EAR


Я рекомендую вам создать новый EAR проект и использовать его для компонентов планировщика, назовем его SchedulerEAR

4.2. Создание сервлета


Также нам нужен сервлет для вызова нашего планировщика, назовем проект SchedulerWeb и в проекте создадим сервлет SchedulerServlet
Class : SchedulerServlet
package ru.jdevel.websphere.scheduler.web;

import java.io.IOException;
import java.util.Calendar;
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.ibm.websphere.scheduler.BeanTaskInfo;
import com.ibm.websphere.scheduler.NotificationSinkHome;
import com.ibm.websphere.scheduler.Scheduler;
import com.ibm.websphere.scheduler.TaskHandlerHome;
import com.ibm.websphere.scheduler.TaskInfo;
import com.ibm.websphere.scheduler.TaskNotificationInfo;
import com.ibm.websphere.scheduler.TaskStatus;

/**
 * Servlet implementation class SchedulerServlet
 */
public class SchedulerServlet extends HttpServlet {
 private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public SchedulerServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

 /**
  * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
  */
 @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     System.out.println("Handle request");
     
  try {
      Hashtable env = new Hashtable();
      env.put(Context.INITIAL_CONTEXT_FACTORY, "com.ibm.websphere.naming.WsnInitialContextFactory");
      env.put(Context.PROVIDER_URL, "corbaloc:iiop:localhost:2809");
      Context initialContext = new InitialContext(env);
      
      //get scheduler reference which we register via admin console
      Scheduler scheduler = (Scheduler) initialContext.lookup("sched/MyScheduler");
      
      TaskInfo taskInfo = scheduler.getTask("01");
      
      if (taskInfo != null) {
          System.out.println("====== Deregister");
          int status = taskInfo.getStatus();
          System.out.println("====== Task status: " + status + " id: " + taskInfo.getTaskId());
          scheduler.purge("01");
      } else {
                System.out.println("====== Register new task");
                
          //Create new Task with TaskHandlerHome & NotificationSinkHome callbacks 
                Object object = new InitialContext().lookup("ejb/ejbs/HandlerHome");
                TaskHandlerHome taskHandlerHome = (TaskHandlerHome) PortableRemoteObject.narrow(object, TaskHandlerHome.class); 
          
                object = new InitialContext().lookup("ejb/ejbs/SinkHome");
                NotificationSinkHome notificationSinkHome = (NotificationSinkHome) PortableRemoteObject.narrow(object, NotificationSinkHome.class);
                
                BeanTaskInfo beanTaskInfo = (BeanTaskInfo) scheduler.createTaskInfo(BeanTaskInfo.class);
                beanTaskInfo.setTaskHandler(taskHandlerHome);
                beanTaskInfo.setName("01");
                
                beanTaskInfo.setNotificationSink(notificationSinkHome, TaskNotificationInfo.ALL_EVENTS);
                
                Calendar now = Calendar.getInstance();
                beanTaskInfo.setStartTime(now.getTime());
                beanTaskInfo.setNumberOfRepeats(5);
                beanTaskInfo.setRepeatInterval("10seconds");
                TaskStatus taskStatus = scheduler.create(beanTaskInfo);
                System.out.println("status: " + taskStatus.getStatus() + "; " + taskStatus.getTaskId());
      }
  } catch (Exception e) {
      e.printStackTrace();
        }
 }

 /**
  * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
  */
 @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  // TODO Auto-generated method stub
 }

}

4.3. Создание EJB


Нам надо создать 2 EJB (версии 2.1.) HandlerBean, SinkBean и демкриптор развертывания ejb-jar.xml
Class : HandlerBean
package ru.jdevel.websphere.scheduler;

import java.rmi.RemoteException;

import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

import com.ibm.websphere.scheduler.TaskStatus;

/**
 * Session Bean implementation class HandlerBean
 */
public class HandlerBean implements SessionBean {

    private SessionContext ctx;
        
    public SessionContext getSessionContext() {
        return ctx;
    }

    /**
     * Default constructor. 
     */
    public HandlerBean() {
        // TODO Auto-generated constructor stub
    }

    public void process (TaskStatus arg) {
        System.out.println("process ==============");
        System.out.println("name=" + arg.getName() + "; status=" + arg.getStatus() + "; repeatsLeft=" + arg.getRepeatsLeft());
    }

    @Override
    public void ejbActivate() throws EJBException, RemoteException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void ejbPassivate() throws EJBException, RemoteException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void ejbRemove() throws EJBException, RemoteException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void setSessionContext(SessionContext ctx) throws EJBException, RemoteException {
        this.ctx = ctx;        
    }
}

Class : SinkBean
package ru.jdevel.websphere.scheduler;

import java.rmi.RemoteException;

import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

import com.ibm.websphere.scheduler.TaskNotificationInfo;

/**
 * Session Bean implementation class SinkBean
 */
public class SinkBean implements SessionBean {  
    
    private SessionContext ctx;
    
    
    
    public SessionContext getSessionContext() {
        return ctx;
    }

    public void handleEvent(TaskNotificationInfo arg) { 
        int eventType = arg.getEventType();
        String eventName = "UNKNOWN";
        
        switch (eventType) {
        case 1: eventName = "SCHEDULED"; break;
        case 2: eventName = "PURGED"; break;
        }
        
        System.out.println("handleEvent: " + eventName);
    }
    
    
    /**
     * Default constructor. 
     */
    public SinkBean() {
        // TODO Auto-generated constructor stub
    }


    @Override
    public void ejbActivate() throws EJBException, RemoteException {
        // TODO Auto-generated method stub
        
    }


    @Override
    public void ejbPassivate() throws EJBException, RemoteException {
        // TODO Auto-generated method stub
        
    }


    @Override
    public void ejbRemove() throws EJBException, RemoteException {
        // TODO Auto-generated method stub
        
    }


    @Override
    public void setSessionContext(SessionContext ctx) throws EJBException, RemoteException {
        this.ctx = ctx;        
    }
}


File : ejb-jar.xml



  Scheduler
  
    
        Sink
        com.ibm.websphere.scheduler.NotificationSinkHome
        com.ibm.websphere.scheduler.NotificationSink
        ru.nixdev.websphere.scheduler.SinkBean
        Stateless
        Container
    
    
        Handler
        com.ibm.websphere.scheduler.TaskHandlerHome
        com.ibm.websphere.scheduler.TaskHandler
        ru.nixdev.websphere.scheduler.HandlerBean
        Stateless
        Container        
    
  
  SchedulerEJBClient.jar 
 


5. Профит


Теперь полученный EAR можно задеплоить на сервер приложений и получить доступ к планировщику через сервлет. Например у меня он был доступен по адресу http://localhost:9080/SchedulerWeb/SchedulerServlet/

2 комментария:

  1. Привет,

    я не пойму где ты ejb/ejbs/HandlerHome декларируешь.

    ОтветитьУдалить
    Ответы
    1. А! Я нашел. Это написано в ibm-ejb-jar-bnd.xmi

      Удалить