JRules


Wykorzystanie

IBM WebSphere ILOG JRules oferuje funkcjonalność potrzebną do budowania i wdrażania aplikacji działających w oparciu o reguły w środowiskach Java, mainframe i SOA. Programiści mogą w prosty sposób budować i wdrażać aplikacje, które w oparciu o reguły automatyzują podejmowanie decyzji w ramach szczegółowych, zmiennych procesów używanych przez systemy biznesowe. Narzędzie przyspiesza tworzenie i serwisowanie takich aplikacji oraz ogranicza pracochłonność i koszty takich prac.

Architektura

Ogólny sposób wywoływania JRules za pomocą jPALIO przedstawia poniższy obrazek:

Przebieg wykonania reguły biznesowej z użyciem JRules wygląda następująco:

Integracja JRules z jPALIO

W procesie integracji JRules z jPALIO mogą brać udział osoby bez wiedzy programistycznej oraz ją posiadające nazywane dalej informatykami.

Ogólny zarys integracji JRules z jPALIO:

1. Informatyk tworzy projekt XOM w Rule Studio w którym definiuje:

  1. parametry wejściowe reguł biznesowych
  2. parametry wyjściowe reguł biznesowych
  3. nazwę przepływu. Przy braku definicji tego parametru nie będzie możliwe w jPALIO wybieranie przepływów dla reguł.

2. Informatyk tworzy projekt reguł. W tym projekcie możliwe jest:

  1. modyfikacja reguł: tworzenie, edycja, usunięcie
  2. modyfikacja przepływów reguł: tworzenie, edycja, usunięcie
  3. modyfikacja pakietów z przepływami oraz regułami

3. Informatyk tworzy projekt aplikacji z projektu reguł który będzie wysłany do serwera wykonawczego RES.

4. Informatyk tworzy projekt webservices z wcześniej zdefiniowanego projektu aplikacji.

  1. w wygenerowanym projekcie webservices udostępniającym JRules modyfikuje się fragment kodu, który wybieraja przepływ.
  2. w wygenerowanym projekcie webservices udostępniającym JRules modyfikuje się wygenerowane parametry wejściowe oraz wyjściowe w celu poprawnej komunikacji z serwerem jPALIO. Krok ten jest konieczny ponieważ w jPALIO jest wykorzystywana biblioteka CXF, która inaczej działa z klasami Holder niż domyślna biblioteka webservices będąca na serwerze WebSphere.
  3. wygenerowany projekt webservices klienta należy skopiować z modyfikacjami opisanymi poniżej do jDesignera.

5. Informatyk publikuje stworzone projekty na serwerze RTS oraz opcjonalnie umieszcza wykonywalne reguły na serwerze wykonawczym RES.

6. Informatyk lub osoba nie mająca wiedzy programistycznej projektuje reguły i przepływy.

7. Informatyk lub osoba nie mająca wiedzy programistycznej umieszcza zmiany na serwerze RTS oraz RES.

8. Programista jPALIO modyfikuje działanie portalu www w zależności od wyników reguł.

 

Podsumowując ogólny sposób projektowania systemów wykorzystujących JRules z pomocą jPALIO przedstawia poniższy rysunek:

Udostępnienie interfejsu JRules dla jPALIO

Aby udostępnić JRules dla JPalio przez webservices należy:

  1. zainstalować Eclipse IDE wraz pluginem Rule Studio
  2. stworzyć projekt typu XOM. Projekt ten będzie zawierał dane wymieniane poprzez webservices pomiędzy JPalio a JRules. Te same dane będą także podstawą werbalizacji w kolejnym projekcie typu "Rule project". Przykładowymi mapowaniami danych w tym projekcie mogą być:
    package pl.com.torn.sncm;
    
    import java.util.List;
    
    public class Event {
    	
    	private String eventDate;
    	private double amount;
    	private String organizationCode;
    	private String productCode;
    	private String eventTypeCode;
    	private String entityCode;
    	private List<Parameter> parameters;
    	
    	public String getEventDate() {
    		return eventDate;
    	}
    	public void setEventDate(String eventDate) {
    		this.eventDate = eventDate;
    	}
    	public double getAmount() {
    		return amount;
    	}
    	public void setAmount(double amount) {
    		this.amount = amount;
    	}
    	public String getOrganizationCode() {
    		return organizationCode;
    	}
    	public void setOrganizationCode(String organizationCode) {
    		this.organizationCode = organizationCode;
    	}
    	public String getProductCode() {
    		return productCode;
    	}
    	public void setProductCode(String productCode) {
    		this.productCode = productCode;
    	}
    	public String getEventTypeCode() {
    		return eventTypeCode;
    	}
    	public void setEventTypeCode(String eventTypeCode) {
    		this.eventTypeCode = eventTypeCode;
    	}
    	public String getEntityCode() {
    		return entityCode;
    	}
    	public void setEntityCode(String entityCode) {
    		this.entityCode = entityCode;
    	}
    	public List<Parameter> getParameters() {
    		return parameters;
    	}
    	public void setParameters(List<Parameter> parameters) {
    		this.parameters = parameters;
    	}
    
    }
    oraz
    package pl.com.torn.sncm;
    
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlType;
    
    /**
     * <p>Java class for parameter complex type.
     *
     * <p>The following schema fragment specifies the expected content
    contained within this class.
     *
     * <pre>
     * <complexType name="parameter">
     *   <complexContent>
     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
     *       <sequence>
     *         <element name="code"
    type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
     *         <element name="dateValue"
    type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
     *         <element name="fractionalValue"
    type="{http://www.w3.org/2001/XMLSchema}double"/>
     *         <element name="numericValue"
    type="{http://www.w3.org/2001/XMLSchema}double"/>
     *         <element name="textValue"
    type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
     *       </sequence>
     *     </restriction>
     *   </complexContent>
     * </complexType>
     * </pre>
     *
     *
     */
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "parameter", propOrder = {
       "code",
       "dateValue",
       "fractionalValue",
       "numericValue",
       "textValue"
    })
    public class Parameter {
    
       protected String code;
       protected String dateValue;
       protected double fractionalValue;
       protected double numericValue;
       protected String textValue;
    
       /**
        * Gets the value of the code property.
        *
        * @return
        *     possible object is
        *     {@link String }
        *
        */
       public String getCode() {
           return code;
       }
    
       /**
        * Sets the value of the code property.
        *
        * @param value
        *     allowed object is
        *     {@link String }
        *
        */
       public void setCode(String value) {
           this.code = value;
       }
    
       /**
        * Gets the value of the dateValue property.
        *
        * @return
        *     possible object is
        *     {@link String }
        *
        */
       public String getDateValue() {
           return dateValue;
       }
    
       /**
        * Sets the value of the dateValue property.
        *
        * @param value
        *     allowed object is
        *     {@link String }
        *
        */
       public void setDateValue(String value) {
           this.dateValue = value;
       }
    
       /**
        * Gets the value of the fractionalValue property.
        *
        */
       public double getFractionalValue() {
           return fractionalValue;
       }
    
       /**
        * Sets the value of the fractionalValue property.
        *
        */
       public void setFractionalValue(double value) {
           this.fractionalValue = value;
       }
    
       /**
        * Gets the value of the numericValue property.
        *
        */
       public double getNumericValue() {
           return numericValue;
       }
    
       /**
        * Sets the value of the numericValue property.
        *
        */
       public void setNumericValue(double value) {
           this.numericValue = value;
       }
    
       /**
        * Gets the value of the textValue property.
        *
        * @return
        *     possible object is
        *     {@link String }
        *
        */
       public String getTextValue() {
           return textValue;
       }
    
       /**
        * Sets the value of the textValue property.
        *
        * @param value
        *     allowed object is
        *     {@link String }
        *
        */
       public void setTextValue(String value) {
           this.textValue = value;
       }
    
    }
  3. stworzyć projekt typu "rules" z zależnością do poprzednio zdefiniowanego projektu XOM. Projekt ten będzie zawierał projektowane reguły i przepływy. W tym projekcie należy zdefiniować werbalizację oraz parametry wejściowe / wyjściowe dla przyszłego interfejsu webservices. Przykładem tego może być:
  4. zwrócić uwagę na zdefiniowanie parametru flow typu string z kierunikem "IN". Parametr ten będzie wykorzystany do zarządzania, wyboru przepływu w środowisku jPALIO.
  5. przed rozpoczęciem definiowania nowych reguł i przepływów warto zapoznać się z dokumentacją oraz krótkimi filmami instruktażowymi np.:
    ILOG JRules : My First Business Rule
    ,
    Create a Business Object Model in IBM ILOG JRules,
    Create a Rule Project in IBM ILOG JRules,
    Write an Action Rule in IBM ILOG JRules,
    Create a Rule Flow in IBM ILOG JRules,
    Write a Scoring Rule With a Decision Table in IBM ILOG JRules.
  6. zdefiniować podstawowe reguły i przepływy lub ich szablony
  7. stworzyć projekt typu "RuleApp" z wcześniej zdefiniowanych reguł i przepływów. Projekt ten zawiera wykonywalne reguły które muszą być umieszczone na serwerze wykonawczym RES. Dlatego też projekt ten musi być połączony z projektem konfiguracją serwera. Projekt konfiguracji serwera RES tworzy się przez "File-> New -> Other -> Rule Studio -> Rule Execution Server Configuration Project". Po konfiguracji tych dwóch projektów możliwe jest umieszczenie na serwerze RES zaprojektowanych reguł za pomocą kliknięcia prawym przyciskiem myszy na projekcie typu "RuleApp" a następnie wybraniu opcji "RuleApp -> Deploy". Wynikowym widokiem w projekcie typu "Rules" powinnien być projekt podobny do:
  8. poprawne umieszczenie reguł na serwerze wykonawczym powinno być widoczne na stronie http://adres:8080/res w sposób podobny do:
  9. wystawić usługę webservices. W tym celu generuje jednocześnie się dwa projekty klienta oraz serwera za pomocą "File -> New -> Other -> Rule Stydio -> Client Project for RuleApps". W kolejnych oknach wybieramy "Rule Execution Server -> Web Service", nazwę projektu, projekt typu "RuleApp" oraz zaznaczamy opcję "Specify an alternative ruleflow task to start the execution of a ruleset(s)". Dzięki tej opcji będziemy mogli przekazanym parametrem "flow" wybierać przepływ dla przekazanych parametrów w wywołaniu JRules. W nowo wygenerowanym projekcie działającym po stronie serwera należy dopisać w klasie z nazwą o suffixie "RunnerImpl" linię:
    request.setTaskName(rulesetrequest.getFlow());

    cała metoda powinna być podobna do: 
    public SncmRulesResult executeSncmRules(SncmRulesRequest rulesetrequest) throws RulesetExecutionException {
    	if (SncmRulesListener.getMBean().isActivated()) {
    		try {
    			// create the request
    			IlrSessionFactory factory = getFactory();
    			IlrSessionRequest request = factory.createRequest();
    			request.setRulesetPath(IlrPath.parsePath("/SncmRuleApp/SncmRules"));
    			request.setUserData("SncmRuleApp.SncmWSRunnerImpl.executeSncmRules");
    
    			// prepare trace options
    			request.setTraceEnabled(true);
    			request.setTaskName(rulesetrequest.getFlow());
    			request.setInputParameter("eventItem", rulesetrequest.getEventItem());
    			request.setInputParameter("flow", rulesetrequest.getFlow());
    
    			IlrSessionResponse response;
    			IlrStatelessSession session = factory.createStatelessSession();
    			ExecutionHook hook = new ExecutionHook();
    			request = hook.preProcessing(request);
    			// Get current time
    			long start = System.currentTimeMillis();
    			response = session.execute(request);
    			// Get the duration
    			long duration = System.currentTimeMillis() - start;
    			// Add measurement
    			SncmRulesListener.getMBeanManager().addMeasurement(
    					response.getCanonicalRulesetPath().toString(), start,
    					duration);
    			response = hook.postProcessing(request, response);
    			SncmRulesResult SncmRulesresult = new SncmRulesResult();
    
    			SncmRulesresult.provision = (java.lang.Double) response
    					.getOutputParameters().get("provision");
    
    			return SncmRulesresult;
    		} catch (Exception e) {
    			SncmRulesListener.getMBeanManager().addError(
    					System.currentTimeMillis());
    			throw new RulesetExecutionException("SncmRules", e);
    		}
    	} else {
    		throw new RulesetExecutionException("Execution is not authorized");
    	}
    }
    Dzięki nowo dodanej linii jest możliwe wybieranie przepływu w jPalio poprzez webservices. Zmodyfikowaną usługę webservices należy umieścić na serwerze za pomocą wygenerowanych tasków ant'a w pliku build.xml. Wystawiona usługa webservices powinna być widoczna na stronie serwera wykonawczego RES oraz powinnien być dostępny dokument wsdl przez przeglądarkę.

    Kolejny krok integracji z jPALIO dotyczy klienta webservices. I jest on opisany w dalszym rozdziale.

Tworzenie reguł JRules

Aby wywołać regułę i otrzymać z niej wynik należy:

  1. skonfigurować serwer RES (Rule Execution Server) oraz RTS (Rule Team Server)
  2. stworzyć projekt na stronie teamserver http://adres:8080/teamserver
  3. opublikować dokument RuleDoc w którym będą edytowane reguły oraz przepływy

  4. przykładową regułę oraz przepływ przeznaczoną do edycji w dokumenie typu RuleDoc przedstawiono na poniższych dwóch rysunkach:




  5. po zmodyfikowaniu oraz utworzeniu nowych reguł oraz przepływów należy przenieść dokument RuleDoc do projektu na stronie teamserver. Przedstawiono to na poniższym rysunku:




  6. aktualny projekt reguł należy umieścić na serwerze RES wykonującym reguły:


W tym momencie jest możliwe wywołanie reguły JRules. Należy zwrócić uwagę, że sposób tworzenia i zarządzania regułami za pomocą dokumentów pakietu Office jest możliwe dla klienta końcowego nie posiadającego wiedzy programistycznej. Użytkownik nie posiadający wiedzy programistycznej może także edytować reguły biznesowe bezpośrednio za pomocą strony teamserver. Jednak w tym podejściu nie możliwa jest edycja przepływów (ang. flow) między regułami.

Dla osób mających wiedzę informatyczną, możliwa jest edycja reguł i przepływów bezpośrednio w projektach programu eclipse z pluginem do JRules  - ILOG Business Rule Studio. To środowisko może być przez takie osoby preferowane ponieważ istnieje dodatkowo możliwość wpływu na webservices.

Wywoływanie reguł w jPALIO

Integracja JRules po stronie jPALIO wygląda następująco:

@WebServiceClient(name = "SncmWSRunnerImplService", targetNamespace = "http://SncmRuleApp/", 
wsdlLocation = "http://192.168.1.210:8080/jaxws-SncmRuleApp/SncmWS?wsdl") public class SncmWSRunnerImplService {....}
    static {
        URL url = null;
        try {
            url = new URL("http://192.168.1.210:8080/jaxws-SncmRuleApp/SncmWS?wsdl");
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        SNCMWSRUNNERIMPLSERVICE_WSDL_LOCATION = url;
    }

Wywołanie JRules w jDesignerze odbywa się przez wywołanie obiektu groovy. Przykładem takiego obiektu może być:

package application.jrules;

import java.util.LinkedList;
import java.util.List;

public class SncmWSRunnerClient {

    public static Double getProvisionFromJRules(final String flow, final Event event) {
        SncmWSRunner port = new SncmWSRunnerImplService().getSncmWSRunnerImplPort();
        return port.sncmRules(event, flow);
    }

    public static Event createEvent(
            final String entityCode, final double amount, final String eventDate,
            final String eventTypeCode, final String organizationCode, final String productCode,
            final List<String> parameterCodes, final List<String> parameterFieldType, final List<Object> parameterFieldValue) {
        Event eventItem = new Event();
        eventItem.setAmount(amount);
        eventItem.setEntityCode(entityCode);
        eventItem.setEventDate(eventDate);
        eventItem.setEventTypeCode(eventTypeCode);
        eventItem.setOrganizationCode(organizationCode);
        eventItem.setProductCode(productCode);
        List<Parameter> parameters = new LinkedList<Parameter>();
        if ((parameterCodes.size() == parameterFieldType.size()) &&
                (parameterCodes.size() == parameterFieldValue.size())) {
            for (int i = 0; i < parameterCodes.size(); ++i) {
                parameters.add(createParameter(parameterCodes.get(i), parameterFieldType.get(i), parameterFieldValue.get(i)));
            }
        } else {
            System.out.println("\nWrong parameter initialization");
            System.out.println("\n parameterCodessize= " + parameterCodes.size());
            System.out.println("\n parameterFieldType size= " + parameterFieldType.size());
            System.out.println("\n parameterFieldValue size= " + parameterFieldValue.size());
        }
        eventItem.setParameters(parameters);
        return eventItem;
    }

    private static Parameter createParameter(final String codeValue, final String field, Object value) {
        Parameter parameter = new Parameter();
        parameter.setCode(codeValue);
        if (field.equals("dateValue")) {
            parameter.setDateValue((String) value);
        } else if (field.equals("fractionalValue")) {
            parameter.setFractionalValue((Double) value);
        } else if (field.equals("numericValue")) {
            parameter.setNumericValue((Double) value);
        } else if (field.equals("textValue")) {
            parameter.setTextValue((String) value);
        } else {
            return null;
        }
        return parameter;
    }

    public Object run(String methodName, Object... methodArguments) {
        return this."$methodName"(*methodArguments)
    }

    public void run() {
    }
}

 

W tym groovy obiekcie metoda "getProvisionFromJRules" wywołuje reguły w przepływie o nazwie zdefiniowanej w atrybucie flow z parametrami zdefiniowanymi w parametrze event. Natomiast metody "run" pozwalają z poziomu jPALIO wywoływać groovy metodę "getProvisionFromJRules". Przykładem takiego wywołania z inicjacją parametrów może być:

 

$=(@parameterCodes, $util.emptyList())
$=(@parameterFieldType, $util.emptyList())
$=(@parameterFieldValue, $util.emptyList())

$util.add($@parameterCodes, "code1")
$util.add($@parameterFieldType, "dateValue")
$util.add($@parameterFieldValue, "2010-01-01 00:00:00")

$util.add($@parameterCodes, "code2")
$util.add($@parameterFieldType, "fractionalValue")
$util.add($@parameterFieldValue, 2.2)

$util.add($@parameterCodes, "code3")
$util.add($@parameterFieldType, "numericValue")
$util.add($@parameterFieldValue, 2)

$util.add($@parameterCodes, "code4")
$util.add($@parameterFieldType, "textValue")
$util.add($@parameterFieldValue, "Parameter->text value")

$try({
  $=(@commission, $toBigDecimal(
  	$toString($*application.jrules.SncmWSRunnerClient("getProvisionFromJRules", "multipliedFlow",
	  $*application.jrules.SncmWSRunnerClient("createEvent", "EntityCode", 100.0, 
	  "2010-01-01", "EventTypeCode", "OrganizationCode", "ProductCode", 
	  $@parameterCodes, $@parameterFieldType, $@parameterFieldValue)
	))
  ))	
  $@commission
}, {
	$error.stackTrace()
})