Примеры динамического кода акшена и веб-сервисов

Материал из BiTel WiKi

(Различия между версиями)
Перейти к: навигация, поиск
(Динамические веб-сервисы)
 
(9 промежуточных версий не показаны.)
Строка 3: Строка 3:
== Динамические экшены ==
== Динамические экшены ==
-
В конфигурацию сервера прописываются динамические классы, и то, что они заменяют. Динамический класс позволяет получить, чтобы потом заменить (обернуть) вызов метода родного класса.
+
В случае старых экшенов в конфигурацию сервера прописываются динамические классы, и то, что они заменяют. Динамический класс позволяет получить, чтобы потом заменить (обернуть) вызов метода родного класса.
Параметр в конфиге примерно такой:
Параметр в конфиге примерно такой:
<source lang="text">dynaction:<модуль>.<режим.><экшенкласснаме>=дин.класс</source>
<source lang="text">dynaction:<модуль>.<режим.><экшенкласснаме>=дин.класс</source>
Строка 10: Строка 10:
dynaction:ru.bitel.bgbilling.plugins.crm.ActionUpdateRegisterTask=дин.класс
dynaction:ru.bitel.bgbilling.plugins.crm.ActionUpdateRegisterTask=дин.класс
dynaction:contract.web.ActionAdditionalAction=дин.класс</source>
dynaction:contract.web.ActionAdditionalAction=дин.класс</source>
-
Внутри дин.класса при необходимости надо сделать явным образом вызов родительского метода (super.doAction, например). При поиске реализации соответствующего экшена сначала ищутся динамические классы прописанные. Дальше ничего не проверяется, что нашлось то и вернулось, т.е. если прописан actionwrapper, но косячный, то дальше не ищется, незачем.
+
'''Обратите внимание: там указывается НЕ package класса, а строка по указанной схеме.''' Класс может быть простой кастомный, унаследованный от самого корневого класса, но удобнее наследовать от самого перегружаемого класса, если потенциально нужна хоть какая-то логика из него. Внутри дин.класса при необходимости надо сделать явным образом вызов родительского метода (super.doAction, например). Если нужно только вывести ошибку, например, то унаследованный метод не нужно вызывать, очевидно. При поиске реализации соответствующего экшена сначала ищутся динамические классы прописанные. Дальше ничего не проверяется, что нашлось то и вернулось, т.е. если прописан dynaction, но косячный, то дальше не ищется, незачем.
Для нашего примера прописать нужно следующее:
Для нашего примера прописать нужно следующее:
Строка 30: Строка 30:
  *  
  *  
  * В конфиг сервера прописывается:
  * В конфиг сервера прописывается:
-
  * dynaction:contract.ActionUpdateContractLimit=ru.specsvyaz.ActionUpdateContractLimit
+
  * dynaction:contract.ActionUpdateContractLimit=ru.xxx.ActionUpdateContractLimit
  * @author dimon
  * @author dimon
  */
  */
Строка 77: Строка 77:
  *  
  *  
  * В конфиг сервера прописывается:
  * В конфиг сервера прописывается:
-
  * dynaction:ru.bitel.bgbilling.plugins.crm.ActionUpdateRegisterTask=ru.specsvyaz.ActionUpdateRegisterTask
+
  * dynaction:ru.bitel.bgbilling.plugins.crm.ActionUpdateRegisterTask=ru.xxx.ActionUpdateRegisterTask
  * @author dimon
  * @author dimon
  */
  */
Строка 130: Строка 130:
}
}
</source>
</source>
 +
 +
----
 +
 +
В данном примере пользователям запрещено добавлять новый тарифный план датой ранее следующей за текущей, а так же запрещено закрывать тарифные планы датой ранее текущей.
 +
На пользователей входящих в группу администраторов  ADMIN_GROUP = 1 данные ограничения не распространяются.
 +
Если на договоре нет никаких тарифных планов - разрешено указывать любые даты (т.е. если добавляемый тариф будет единственным - можно выставлять произвольные интервалы. Так было необходимо)
 +
 +
<source lang="text">dynaction:contract.ActionUpdateContractTariffPlan=ru.xxx.updateContractTariffPlan</source>
 +
 +
<source lang="java">
 +
package ru.xxx;
 +
 +
import java.sql.ResultSet;
 +
import java.sql.SQLException;
 +
import java.util.Calendar;
 +
import java.util.Date;
 +
import java.util.List;
 +
 +
import bitel.billing.common.TimeUtils;
 +
import bitel.billing.server.admin.bgsecure.bean.UserGroupManager;
 +
 +
 +
import ru.bitel.bgbilling.common.BGException;
 +
import ru.bitel.bgbilling.common.BGMessageException;
 +
 +
 +
public class updateContractTariffPlan
 +
extends bitel.billing.server.contract.action.ActionUpdateContractTariffPlan
 +
{
 +
//ID группы администраторов
 +
private final int ADMIN_GROUP = 1;
 +
 +
@Override
 +
    public void doAction()
 +
    throws SQLException, BGException
 +
{
 +
UserGroupManager userGroupManager = new UserGroupManager(con);
 +
List<Integer> groups = userGroupManager.getUserGroups(userID);
 +
Calendar calendar = Calendar.getInstance();
 +
Date today = calendar.getTime();
 +
calendar.add(Calendar.DAY_OF_YEAR, 1);
 +
Date tomorrow = calendar.getTime();
 +
String id = getParameter("id", "");
 +
Date date2 = TimeUtils.convertStringToDate(getParameter( "date2", "" ));
 +
Date date1 = TimeUtils.convertStringToDate(getParameter( "date1", "" ));
 +
 +
if(!groups.contains(ADMIN_GROUP))
 +
{
 +
if(!id.equals("new") & TimeUtils.dateBefore(date2, today))
 +
{
 +
throw new BGMessageException( "Дата закрытия тарифного плана не может быть ранее текущей" );
 +
}
 +
if(id.equals("new") & TimeUtils.dateBefore(date1, tomorrow))
 +
{
 +
String query = " SELECT COUNT(id) FROM contract_tariff WHERE cid=? ";
 +
java.sql.PreparedStatement ps = con.prepareStatement( query );
 +
ps.setInt(1, cid);
 +
ResultSet rs = ps.executeQuery();
 +
while ( rs.next() )
 +
{
 +
int count = rs.getInt(1);
 +
if(count > 0 )
 +
{
 +
throw new BGMessageException( "Тарифный план должен устанавливаться датой следующей за текущей" );
 +
}
 +
}
 +
rs.close();
 +
ps.close();
 +
}
 +
}
 +
// иначе вызываем родительский метод
 +
super.doAction();
 +
}
 +
}
 +
 +
</source>
 +
--[[Участник:Phricker]] 11:23, 17 февраля 2014 (UTC)
== Динамические веб-сервисы ==
== Динамические веб-сервисы ==
-
Динамический класс позволяет заменить (обернуть) вызов метода вебсервиса.
+
Динамический класс позволяет заменить (обернуть) вызов метода вебсервиса. В отличие от старых методов экшенов в данном случае необходимо напрямую указать какой именно интерфейс вызова перегружается.
Параметр в конфиге такой:
Параметр в конфиге такой:
<source lang="text">dynservice:<модуль>.<интерфейс_сервиса>=<дин.класс></source>
<source lang="text">dynservice:<модуль>.<интерфейс_сервиса>=<дин.класс></source>
Например:
Например:
<source lang="text">dynservice:ru.bitel.bgbilling.kernel.contract.status.ContractStatusMonitorService=дин.класс</source>
<source lang="text">dynservice:ru.bitel.bgbilling.kernel.contract.status.ContractStatusMonitorService=дин.класс</source>
 +
Или если вызов, например, такой: <nowiki>http://.../bgbilling/executer/ru.bitel.bgbilling.modules.inet.api/666/InetServService?wsdl -> {http://service.common.api.inet.modules.bgbilling.bitel.ru/}InetServService:inetServTypeList</nowiki>
 +
<source lang="text">dynservice:ru.bitel.bgbilling.modules.inet.api.InetServService=дин.класс</source>
 +
 +
Точно также удобнее унаследовать от родного Impl-класса соответствующего сервиса, чем писать с нуля.
 +
 +
'''Обратите внимание: там указывается НЕ package класса, а строка по указанной схеме.'''
 +
 +
Обратите внимание, что в динамическом коде нужно ещё раз указать имплементацию интерфейса даже в этом случае наследования от старой имплементации (несмотря на то, что очевидно, родительский класс уже реализует указываемый интерфейс и по правилам ООП и так понятно что потомок тоже реализует, а по правилам Java не нужно указывать ещё раз implements ContractStatusMonitorService как в примере ниже).
 +
Внутри дин.класса при необходимости надо перегрузить нужный метод (или несколько), а после сделать (или не сделать, если обёртка отработала сама всё, например, вывела ошибку) явным образом вызов соответствующего родительского метода (super.blabla(foo,bar)).
Внутри дин.класса при необходимости надо перегрузить нужный метод (или несколько), а после сделать (или не сделать, если обёртка отработала сама всё, например, вывела ошибку) явным образом вызов соответствующего родительского метода (super.blabla(foo,bar)).
При поиске реализации сервиса сначала ищется среди динамических классов, но если в динкоде класс заявлен, но косячный, то дальше не ищем, незачем.
При поиске реализации сервиса сначала ищется среди динамических классов, но если в динкоде класс заявлен, но косячный, то дальше не ищем, незачем.
Строка 162: Строка 248:
  *  
  *  
  * В конфиг сервера прописывается:
  * В конфиг сервера прописывается:
-
  * dynservice:ru.bitel.bgbilling.kernel.contract.status.ContractStatusMonitorService=ru.specsvyaz.ContractStatusMonitorServiceImpl
+
  * dynservice:ru.bitel.bgbilling.kernel.contract.status.ContractStatusMonitorService=ru.xxx.ContractStatusMonitorServiceImpl
  * @author dimon
  * @author dimon
  */
  */

Текущая версия на 07:32, 16 мая 2016

Динамическим кодом в данном случае можно как писать новые экшены (практическое значение имеют больше веб-экшены, т.е. экшены веб-интерфейса) и веб-сервисы, так и обёртывать существующие, т.е. заменять их, а внутри вызывать родительский метод, или не вызывать в зависимости от обстоятельств. Этим можно полностью заменить функционал такой, как запуск скрипта до и после акшена. Помимо того, что область применения шире, ещё динамичемским кодом просто быстрее, удобнее и надёжнее. В данном разделе рассмотрим примеры скриптов, совершенно аналогичные упомянутым в разделе «примеры скриптов до и после акшена», за исключением задачи "проверка смены статуса", которую в старом механизме реализовать вообще невозможно, т.к. смена статуса реализована только в веб-сервисах и событий там никаких нету.

Динамические экшены

В случае старых экшенов в конфигурацию сервера прописываются динамические классы, и то, что они заменяют. Динамический класс позволяет получить, чтобы потом заменить (обернуть) вызов метода родного класса. Параметр в конфиге примерно такой:

dynaction:<модуль>.<режим.><экшенкласснаме>=дин.класс

Примеры:

dynaction:contract.ActionCheckContractLimitUpdate=дин.класс
dynaction:ru.bitel.bgbilling.plugins.crm.ActionUpdateRegisterTask=дин.класс
dynaction:contract.web.ActionAdditionalAction=дин.класс

Обратите внимание: там указывается НЕ package класса, а строка по указанной схеме. Класс может быть простой кастомный, унаследованный от самого корневого класса, но удобнее наследовать от самого перегружаемого класса, если потенциально нужна хоть какая-то логика из него. Внутри дин.класса при необходимости надо сделать явным образом вызов родительского метода (super.doAction, например). Если нужно только вывести ошибку, например, то унаследованный метод не нужно вызывать, очевидно. При поиске реализации соответствующего экшена сначала ищутся динамические классы прописанные. Дальше ничего не проверяется, что нашлось то и вернулось, т.е. если прописан dynaction, но косячный, то дальше не ищется, незачем.

Для нашего примера прописать нужно следующее:

dynaction:contract.ActionUpdateContractLimit=ru.xxx.ActionUpdateContractLimit
dynaction:ru.bitel.bgbilling.plugins.crm.ActionUpdateRegisterTask=ru.xxx.ActionUpdateRegisterTask
package ru.xxx;
 
import java.sql.SQLException;
 
import ru.bitel.bgbilling.common.BGException;
import ru.bitel.bgbilling.common.BGMessageException;
import ru.bitel.common.Utils;
 
/**
 * Перегруженный экшен для проверки некоторых параметров:
 * 2. Скрипт реализующий запрет изменения "лимита" договора в случае если поле комментарий пустое.
 * 
 * В конфиг сервера прописывается:
 * dynaction:contract.ActionUpdateContractLimit=ru.xxx.ActionUpdateContractLimit
 * @author dimon
 */
public class ActionUpdateContractLimit
	extends bitel.billing.server.contract.action.ActionUpdateContractLimit
{
	@Override
    public void doAction() 
    	throws SQLException, BGException
	{
		//[2]
		//module=contract
		//action=UpdateContractLimit
		//comment=%ED%E5%E3%E5%ED
		// получаем параметры как в экшене
		String comment = getParameter( "comment", "" );
		// если 1) комментарий пустой => ругаемся
		if( Utils.isBlankString( comment ) )
		{
			throw new BGMessageException( "Введите комментарий" );
		}
		// иначе вызываем родительский метод
		super.doAction();
	}
}
package ru.xxx;
 
import java.sql.SQLException;
import java.util.Date;
 
import bitel.billing.common.TimeUtils;
 
import ru.bitel.bgbilling.common.BGException;
import ru.bitel.bgbilling.common.BGMessageException;
import ru.bitel.bgbilling.plugins.crm.common.model.RegisterTask;
import ru.bitel.bgbilling.plugins.crm.server.dao.RegisterTaskManager;
import ru.bitel.common.Utils;
 
/**
 * Перегруженный экшен для проверки некоторых параметров:
 * 1. Скрипт реализующий запрет изменения "срока" задачи в плагине CRM всем кроме того кто задачу создал, а так же кроме отдельно обозначенного администратора.
 * 4. CRM: Нужно сделать так чтобы задачу нельзя было закрыть если поле "Резолюция" пустое.
 * 
 * В конфиг сервера прописывается:
 * dynaction:ru.bitel.bgbilling.plugins.crm.ActionUpdateRegisterTask=ru.xxx.ActionUpdateRegisterTask
 * @author dimon
 */
public class ActionUpdateRegisterTask
	extends ru.bitel.bgbilling.plugins.crm.server.action.ActionUpdateRegisterTask
{
	/** ид отдельно обозначенного администратора */
	private final int ADMIN_USER = 1;
 
	@Override
    public void doAction() 
    	throws SQLException, BGException
	{
		// [1]
		// module=ru.bitel.bgbilling.plugins.crm
		// action=UpdateRegisterTask
		// id=32
		// target_date_and_time=21.04.2010+00%3A00
		// получаем параметры как в экшене
		int id = getIntParameter( "id", -1 );
		Date targetDate = getDateParameter( "target_date_and_time", "dd.MM.yyyy HH:mm", null );
		if( targetDate == null )
		{
			targetDate = getDateParameter( "target_date", "dd.MM.yyyy", null  );
		}
		// получаем таск
		RegisterTaskManager manager = new RegisterTaskManager( con );
		RegisterTask task = manager.getTaskById( id );
		// если 1) юзер не админ, и 2) юзер не тот кто создал задачу, то 3) проверяем время и если оно не такое => ругаемся
		if( userID != ADMIN_USER && userID != task.getCreateUserId() && !TimeUtils.dateEqual( targetDate, task.getTargetDate() ) )
		{
			throw new BGMessageException( "Вам нельзя менять срок задачи" );
		}
		// [4]
		// module=ru.bitel.bgbilling.plugins.crm
		// action=UpdateRegisterTask
		// status=2
		// resolution=%F0%E5%E7%EE%EB%FE%F6%E8%FF
		// получаем параметры как в экшене
		int status = getIntParameter( "status", 0 );
		String resolution = getParameter( "resolution", "" );
		//System.out.println("status="+status);
		//System.out.println("resolution="+resolution);
		// если 1) статус "закрыто" и 2) резолюция пустая => ругаемся
		if( status == 2 && Utils.isBlankString( resolution ) )
		{
			throw new BGMessageException( "Для закрытия заполните резолюцию" );
		}
		// иначе вызываем родительский метод
		super.doAction();
	}
}

В данном примере пользователям запрещено добавлять новый тарифный план датой ранее следующей за текущей, а так же запрещено закрывать тарифные планы датой ранее текущей. На пользователей входящих в группу администраторов ADMIN_GROUP = 1 данные ограничения не распространяются. Если на договоре нет никаких тарифных планов - разрешено указывать любые даты (т.е. если добавляемый тариф будет единственным - можно выставлять произвольные интервалы. Так было необходимо)

dynaction:contract.ActionUpdateContractTariffPlan=ru.xxx.updateContractTariffPlan
package ru.xxx;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
 
import bitel.billing.common.TimeUtils;
import bitel.billing.server.admin.bgsecure.bean.UserGroupManager;
 
 
import ru.bitel.bgbilling.common.BGException;
import ru.bitel.bgbilling.common.BGMessageException;
 
 
public class updateContractTariffPlan 
	extends bitel.billing.server.contract.action.ActionUpdateContractTariffPlan
{
	//ID группы администраторов
	private final int ADMIN_GROUP = 1;
 
	@Override
    public void doAction() 
    	throws SQLException, BGException
	{
		UserGroupManager userGroupManager = new UserGroupManager(con);
		List<Integer> groups = userGroupManager.getUserGroups(userID);
		Calendar calendar = Calendar.getInstance();
		Date today = calendar.getTime();
		calendar.add(Calendar.DAY_OF_YEAR, 1);
		Date tomorrow = calendar.getTime();
		String id = getParameter("id", "");
		Date date2 = TimeUtils.convertStringToDate(getParameter( "date2", "" ));
		Date date1 = TimeUtils.convertStringToDate(getParameter( "date1", "" ));
 
		if(!groups.contains(ADMIN_GROUP))
		{
			if(!id.equals("new") & TimeUtils.dateBefore(date2, today))
			{
				throw new BGMessageException( "Дата закрытия тарифного плана не может быть ранее текущей" );
			}
			if(id.equals("new") & TimeUtils.dateBefore(date1, tomorrow))
			{
				String query = 	" SELECT COUNT(id) FROM contract_tariff WHERE cid=? ";
				java.sql.PreparedStatement ps = con.prepareStatement( query );
				ps.setInt(1, cid);
				ResultSet rs = ps.executeQuery();
				while ( rs.next() )
				{
					int count = rs.getInt(1);
					if(count > 0 )
					{
						throw new BGMessageException( "Тарифный план должен устанавливаться датой следующей за текущей" );
					}
				}
				rs.close();
				ps.close();
			}
		}
		// иначе вызываем родительский метод
		super.doAction();
	}
}

--Участник:Phricker 11:23, 17 февраля 2014 (UTC)

Динамические веб-сервисы

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

dynservice:<модуль>.<интерфейс_сервиса>=<дин.класс>

Например:

dynservice:ru.bitel.bgbilling.kernel.contract.status.ContractStatusMonitorService=дин.класс

Или если вызов, например, такой: http://.../bgbilling/executer/ru.bitel.bgbilling.modules.inet.api/666/InetServService?wsdl -> {http://service.common.api.inet.modules.bgbilling.bitel.ru/}InetServService:inetServTypeList

dynservice:ru.bitel.bgbilling.modules.inet.api.InetServService=дин.класс

Точно также удобнее унаследовать от родного Impl-класса соответствующего сервиса, чем писать с нуля.

Обратите внимание: там указывается НЕ package класса, а строка по указанной схеме.

Обратите внимание, что в динамическом коде нужно ещё раз указать имплементацию интерфейса даже в этом случае наследования от старой имплементации (несмотря на то, что очевидно, родительский класс уже реализует указываемый интерфейс и по правилам ООП и так понятно что потомок тоже реализует, а по правилам Java не нужно указывать ещё раз implements ContractStatusMonitorService как в примере ниже).

Внутри дин.класса при необходимости надо перегрузить нужный метод (или несколько), а после сделать (или не сделать, если обёртка отработала сама всё, например, вывела ошибку) явным образом вызов соответствующего родительского метода (super.blabla(foo,bar)). При поиске реализации сервиса сначала ищется среди динамических классов, но если в динкоде класс заявлен, но косячный, то дальше не ищем, незачем.

Для нашего примера прописать нужно следующее:

dynservice:ru.bitel.bgbilling.kernel.contract.status.ContractStatusMonitorService=ru.xxx.ContractStatusMonitorServiceImpl
package ru.xxx;
 
import java.util.Date;
 
import javax.jws.WebService;
 
import ru.bitel.bgbilling.common.BGException;
import ru.bitel.bgbilling.common.BGMessageException;
import ru.bitel.bgbilling.kernel.contract.status.common.ContractStatusMonitorService;
import ru.bitel.common.Utils;
 
/**
 * Перегруженный вебсервис для проверки некоторых параметров:
 * 3. Скрипт реализующий запрет изменения "Статуса" договора в случае если поле комментарий пустое.
 * 
 * В конфиг сервера прописывается:
 * dynservice:ru.bitel.bgbilling.kernel.contract.status.ContractStatusMonitorService=ru.xxx.ContractStatusMonitorServiceImpl
 * @author dimon
 */
@WebService(endpointInterface = "ru.bitel.bgbilling.kernel.contract.status.common.ContractStatusMonitorService")
public class ContractStatusMonitorServiceImpl
	extends ru.bitel.bgbilling.kernel.contract.status.server.service.ContractStatusMonitorServiceImpl
	implements ContractStatusMonitorService
{
	@Override
	public void changeContractStatus( int[] cids, int statusId, Date dateFrom, Date dateTo, String comment )
	    throws BGException
	{
		//System.out.println("\t!\tchangeContractStatus");
		// [3]
		// ContractStatusMonitorService:changeContractStatus
		// @WebParam( name = "comment" ) String comment
		if( Utils.isBlankString( comment ) )
		{
			throw new BGMessageException( "Введите комментарий" );
		}
		super.changeContractStatus( cids, statusId, dateFrom, dateTo, comment );
	}
}

--dimOn 12:00, 28 ноября 2012 (UTC)

Личные инструменты