пятница, 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/

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

  1. Привет,

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

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

      Удалить
  2. I am curious to find out what blog system you're working with? I'm experiencing some minor security issues with my latest website and I'd like to find something more secure. Do you have any suggestions? netflix login

    ОтветитьУдалить