DRL rule language for Drools records

2, DRL (Drools rule language) Rules

DRL files can contain single or multiple rules, queries, and functions, and can define resource declarations, such as imports, globals, and properties assigned and used by rules and queries. DRL packages must be listed at the top of the DRL file, and rules are usually listed at the end. All other DRL components can follow any order.

Each rule must have a unique name in the rule package. If the same rule name is used more than once in any DRL file in the package, the rule cannot be compiled. Always enclose the rule name in double quotes (rule "rule name") to prevent possible compilation errors, especially if spaces are used in the rule name.

All data objects related to DRL rules must be in the same package as DRL files in Business Central. By default, resources in the same package are imported. You can use DRL rules to import existing resources from other packages.

1. Package name definition in DRL

Packages are folders of related assets in Drools, such as data objects, DRL files, decision tables, and other asset types. The package also acts as a unique namespace for each group of rules. A rule library can contain multiple packages. Typically, all the rules of a package are stored in the same file as the package declaration so that the package is independent. However, you can import objects from other packages that you want to use in rules.


package org.mortgages;

Please note that,

  • The package must have a namespace and use the standard Java convention to declare the package name, that is, no spaces.
  • Must be at the top of the file and the semicolon is optional.

2. Import statement in DRL

Similar to the import statement in Java, you can use the format to specify the package and data object packageName.objectName. The Drools engine automatically imports the class with the same name as the import statement in the DRL file from the java package, and java.lang automatically imports it.


import org.mortgages.LoanApplication;

3. Function in DRL (method function definition)

Functions in the DRL file place semantic code in the rules source file, not in Java classes. Function can be defined to avoid code redundancy in the repeated methods used after then. Above the rules in the DRL file, you can declare a function or import a static method from the helper class as a function, and then use the function by name in the action () section of the rule.

Sample function declaration with rules (option 1)

function String hello(String applicantName) {
    return "Hello " + applicantName + "!";

rule "Using a function"
    // Empty
    System.out.println( hello( "James" ) );

Sample function import with rules (option 2)

import function my.package.applicant.hello;

rule "Using a function"
    // Empty
    System.out.println( hello( "James" ) );

4. query in DRL

Queries in the DRL file search the working memory of the Drools engine for facts related to rules in the DRL file.
Add a query definition to the DRL file and get the matching results in the application code. The query searches for a set of defined criteria without when or specifying then.
The name of query is required to be unique because it is global.
You can define the query and query result parameters above the rules in the DRL file.

Example query definition in DRL file

query "people under the age of 21"
    $person : Person( age < 21 )

Sample application code to get query results

QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age  of 21" );

Sample application code to get and iterate query results

QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age of 21" );

System.out.println( "These people are under the age of 21:" );

for ( QueryResultsRow row : results ) {
    Person person = ( Person ) row.get( "person" );
    System.out.println( person.getName() + "\n" );

5. Type declaration and metadata in DRL

When a new fact type is declared, the Drools engine generates a Java class that represents that fact type at compile time. The generated Java class is a one-to-one JavaBeans mapping of the type definition.

(1) No metadata type declaration in DRL

An example of using rules to declare a new fact type

declare Person
  name : String
  dateOfBirth : java.util.Date
  address : Address

rule "Using a declared type"
    $p : Person( name == "James" )
  then   // Insert Mark, who is a customer of James.
    Person mark = new Person();
    mark.setName( "Mark" );
    insert( mark );

In this case, the new face type Person has three properties: name, dateOfBirth, and address. The type of each property can be any valid Java type, including another class created or a previously declared fact type. The dateOfBirth property has a type in the java.util.DateJava API, and the address property has a previously defined fact type address.

To avoid writing a fully qualified name every time a class is declared, you can define the full name as part of the import clause:

import java.util.Date

declare Person
    name : String
    dateOfBirth : Date
    address : Address

Java classes generated for Person fact type declaration

public class Person implements Serializable {
    private String name;
    private java.util.Date dateOfBirth;
    private Address address;

    // Empty constructor
    public Person() {...}

    // Constructor with all fields
    public Person( String name, Date dateOfBirth, Address address ) {...}

    // If keys are defined, constructor with keys
    public Person( ...keys... ) {...}

    // Getters and setters
    // `equals` and `hashCode`
    // `toString`

Example rules for using the declared Person fact type

rule "Using a declared type"
    $p : Person( name == "James" )
  then   // Insert Mark, who is a customer of James.
    Person mark = new Person();
    mark.setName( "Mark" );
    insert( mark );

(2) Enumeration type declaration in DRL

Example enumeration type declaration with scheduling rules

declare enum DaysOfWeek
   fullName : String

rule "Using a declared Enum"
   $emp : Employee( dayOff == DaysOfWeek.MONDAY )

(3) Extension type declaration in DRL

Extension type declaration example

import org.people.Person

declare Person end

declare Student extends Person
    school : String

declare LongTermStudent extends Student
    years : int
    course : String

(4) Type declaration with metadata in DRL

In the following example, two metadata attributes @ author and @ dateOfCreation are declared as Person fact types, and two metadata items @ key and @ maxLength are declared as name attributes. The @ key metadata property of does not have the required value, so parentheses and values are omitted.

import java.util.Date

declare Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )

    name : String @key @maxLength( 30 )
    dateOfBirth : Date
    address : Address

Sample metadata declaration for import type

import org.drools.examples.Person

declare Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )

Metadata declaration example of declaration type

declare org.drools.examples.Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )

(5) Metadata label of fact type and attribute declaration in DRL

metadata tag Explain Example
@role( fact | event ) Determine that during complex event processing, the given fact type is as follows
Regular facts are still event handling in the Drools engine.
Default parameter: fact
Supported parameters: fact, event
declare VoiceCall
 @role( event )
@timestamp(<attributeName>) Custom timestamp properties.
Default parameter: increase time of session clock of Drools engine
Supported parameters: session clock time or custom timestamp property
declare VoiceCall
 @role( event )
 @timestamp( callDateTime )
@duration( <attributeName> ) This tab determines the duration of events in the Drools engine.
Events can be interval based or point in time events.
Interval based events have a duration and persist in the Drools engine
Until its duration has passed.
Default parameter: null (zero)
Supported parameters: Custom duration properties
declare VoiceCall
 @role( event )
 @duration( callDuration )
@expires( <timeOffset> ) This label determines the duration of the event before it expires in the working memory of the Drools engine.
This tag is only available when the Drools engine is running in streaming mode.
Default parameter: Null (event expires after event cannot match and activation rule)
Supported parameters: custom attribute in timeOffset format [ාd] [ාh] [ාm] [ාs] [MS]]
declare VoiceCall
 @role( event )
 @expires( 1h35m )
@typesafe( <boolean> ) Determines whether the given fact type is compiled using type security.
Default parameter: true
Supported parameters: true, false
declare VoiceCall
 @role( event )
 @typesafe( false )
@serialVersionUID(<integer>) Class serialization.
Default parameter: empty
Supported parameters: Custom serialVersionUID integer
declare VoiceCall
 @serialVersionUID( 42 )
<attributeDefinition> @key The key identifier of the generated class will automatically implement the equals() and hashCode() methods,
To determine if two instances of the type are equal to each other. – please go to the bottom of the form for details (Appendix 1)
Default parameter: None
Supported parameters: None
declare VoiceCall
 firstName : String @key
 lastName : String @key
 age : int
<attributeDefinition> @position ( <integer> ) Annotate the attribute order of the defined class. If @ position is not added, put it last,
The order in the example is LastName firstname age occupation.
– please go to the bottom of the form for details (Appendix 2)
Default parameter: None
Supported parameters: any integer
declare VoiceCall
 firstName : String @position( 1 )
 lastName : String @position( 0 )
 age : int @position( 2 )
 occupation: String

Attached 1
The Drools engine checks the firstName and lastName properties to determine if the two instances are equal to each other, but does not check the age property. The Drools engine also implicitly generates three constructors: one without parameters, one with @ key field, and one with all fields:

Person() // Empty constructor
Person( String firstName, String lastName )
Person( String firstName, String lastName, int age )
//An instance of this type can be created based on the key constructor
Person person = new Person( "John", "Doe" );

Attached 2
In the location parameter, you do not need to specify a field name because the location maps to a known named field. For example, the parameter person (lastName = = "Doe") is the same Person("Doe";), where the lastName field has the highest position comment in the DRL declaration. Semicolon; indicates that everything before is a location parameter. You can use semicolons to separate positional and named parameters in a pattern to separate them. All variables in a location parameter that are not yet bound are bound to fields that map to that location.

The following example pattern illustrates different ways to construct positional and named parameters. The pattern has two constraints and a binding, and the semicolon distinguishes the location part from the named parameter part. Position parameters support only text variables, text, and expressions, but not variables alone.

Sample pattern with location and named parameters

Person( "Doe", "John", $a; )

Person( "Doe", "John"; $a : age )

Person( "Doe"; firstName == "John", $a : age )

Person( lastName == "Doe"; firstName == "John", $a : age )

(6) Access the type of DRL declaration in the application code

Sample application code for handling declared fact types through the FactType API

import java.util.Date;

import org.kie.api.definition.type.FactType;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;


// Get a reference to a KIE base with the declared type:
KieBase kbase = ...

// Get the declared fact type:
FactType personType = kbase.getFactType("org.drools.examples", "Person");

// Create instances:
Object bob = personType.newInstance();

// Set attribute values:
personType.set(bob, "name", "Bob" );
personType.set(bob, "dateOfBirth", new Date());
personType.set(bob, "address", new Address("King's Road","London","404"));

// Insert the fact into a KIE session:
KieSession ksession = ...

// Read attributes:
String name = (String) personType.get(bob, "name");
Date date = (Date) personType.get(bob, "dateOfBirth");

6. Global variables in DRL

Global variables in DRL files usually provide data or services for rules, such as application services used in rule results, and return data from rules, such as adding logs or values to rule results. You can set the global value in the working memory of the Drools engine through the KIE session configuration or REST operation, declare the global variable above the rule in the DRL file, and then use it in the action (then) section of the rule. For multiple global variables, separate lines are used in the DRL file.

Sample global list configuration for Drools engine

List<String> list = new ArrayList<>();
KieSession kieSession = kiebase.newKieSession();
kieSession.setGlobal( "myGlobalList", list );

Sample global variable definition with rules

global java.util.List myGlobalList;

rule "Using a global"
    // Empty
    myGlobalList.add( "My global list" );

7. Rule properties in DRL

attribute value
salience An integer that defines the priority of the rule. When sorting in the activation queue, rules with a higher significance value will have a higher priority.
Example: salience 10
enabled Boolean values. When selected, rules are enabled. If this option is not selected, the rule is disabled.
Example: enabled true
date-effective A string containing the date and time definitions. The rule can only be activated if the current date and time are after the date effective attribute.
Example: date effective "4-Sep-2018"
date-expires A string containing the date and time definitions. The rule cannot be activated if the current date and time are after the date expires property.
Example: Date expires "4-Oct-2018"
no-loop Boolean values. When this option is selected, the rule cannot be reactivated (looped) if the result of the rule triggers a previously satisfied condition. If no conditions are selected, you can loop the rules in these cases.
Example: no loop true
agenda-group A string identifying the agenda group to which you want to assign rules. Through the agenda group, you can partition the agenda to provide more execution control over the rule group. Only rules in the focused agenda group can be activated.
Example: Agenda group "GroupName"
activation-group A string identifying the activation (or XOR) group to which you want to assign rules. Only one rule can be activated in an activation group. The first rule triggered will deactivate all pending activations of all rules in the activation group.
Example: activation group "GroupName"
duration A long integer value that defines the duration (in milliseconds) for which a rule can be activated if the rule condition is still met.
Example: duration 10000
timer A string identifying the int (interval) or cron timer definition to schedule rules.
Example: (timer (cron: * 0 / 15 * *?) every 15 minutes)
calendar A quartz scheduling rule calendar definition.
Example: (calendar "* * 0-7,18-23" * * "does not include non business hours)
auto-focus A Boolean value that applies only to rules in an agenda group. When this option is selected, the next time the rule is activated, the focus will automatically focus on the agenda group to which the rule is assigned.
Example: auto focus true
lock-on-active A Boolean value that applies only to rules in a rule flow group or agenda group. When this option is selected, the next time the rule flow group of the rule becomes active or the agenda group of the rule gets focus, the rule flow group will no longer be active or the rule cannot be activated again until the agenda group loses focus. This is a stronger version of the no loop attribute because the activation of the matching rule will be ignored regardless of the source of the update (not just the rule itself). This property is an ideal choice for calculation rules, in which you have many rules that modify facts, and you do not want any rules to be re matched and triggered again.
Example: lock on active true
ruleflow-group String identifying the rule flow group. In a rule flow group, rules can only be triggered when the group is activated by the related rule flow.
Example: ruleflow group "GroupName"
dialect A string that identifies the code expression in the rule that JAVA or MVEL uses as the language. By default, the rule uses the dialect specified at the package level. Any dialect specified here overrides the packaged dialect settings of the rule.
Example: dialect "JAVA"

8. Rule condition in DRL (when)

when is part of the DRL rule (also known as the LHS left hand side) and contains the conditions that must be met to perform the operation.
If the when part is empty, it is considered to be true, and then will be executed when findAllRules(), which is applicable to setting the Drools engine state.

Sample rules without conditions

rule "Always insert applicant"
    // Empty
  then   // Actions to be executed once
    insert( new Applicant() );

// The rule is internally rewritten in the following way:

rule "Always insert applicant"
    eval( true )
    insert( new Applicant() );

If multiple patterns are used for rule conditions, and keyword conjunctions (such as and, or or not) are not defined, the default combination is and:

Example rule with no keyword connection

rule "Underage"
    application : LoanApplication()
    Applicant( age < 21 )
    // Actions

// The rule is internally rewritten in the following way:

rule "Underage"
    application : LoanApplication()
    and Applicant( age < 21 )
    // Actions

(1) Patterns and constraints

Patterns can potentially match every fact inserted into the working memory of the Drools engine. Patterns can also contain constraints to further define the facts to match.

  • Matches the fact of the specified type, such as: Person()

  • Filter conditions can be added in brackets, such as: Person(age==18)

  • The type is not necessarily the actual class of a fact object, it can be a superclass or an interface, such as: Object() matches all facts

  • Constraints are essentially Java expressions and some enhanced expressions. Note: person (firstname = = "John") is similar to java.util.Objects.equals(person.getFirstName(), "John")

Here is the DRL constraint syntax with JavaBeans properties

Person( age == 50 )

// This is the same as the following getter format:
// The efficiency of the top is higher than that of the bottom

Person( getAge() == 50 )

Sample pattern with nested property access

Person( address.houseNumber == 50 )

// This is the same as the following format:

Person( getAddress().getHouseNumber() == 50 )
  • You can use any Java expression that returns a boolean value as a constraint within the pattern brackets. Java expressions can be mixed with other expression enhancements, such as property access.

Sample patterns using property access and Java expression constraints

Person( age == 50 )

You can use parentheses to change the evaluation priority, just as in any logical or mathematical expression

Person( age > 100 && ( age % 10 == 0 ) )

Example constraints with Java methods

Person( Math.round( weight / ( height * height ) ) < 25.0 )
  • For constraint groups, you can use delimited commas to use implicit and join semantics

Sample pattern with multiple constraints

// Person is at least 50 years old and weighs at least 80 kilograms:
Person( age > 50, weight > 80 )

// Person is at least 50 years old, weighs at least 80 kilograms, and is taller than 2 meters:
Person( age > 50, weight > 80, height > 2 )

Note here that although the semantics of commas and & & are the same, the execution order is different

(2) Binding variables in patterns and constraints

You can bind variables to patterns and constraints to reference matching objects in other parts of the rule. Binding variables can define rules more efficiently or consistently, and how to annotate fact s in the data model. To make it easier to distinguish variables and fields in rules, variables are usually defined in the standard format of $variable, especially in complex rules. Although useful, it is not necessary.

Patterns with bound variables

rule "simple rule"
    $p : Person()
    System.out.println( "Person " + $p );

Similarly, you can bind variables to attributes in schema constraints

// Two persons of the same age:
Person( $firstAge : age ) // Binding
Person( age == $firstAge ) // Constraint expression

(3) Nested constraints and inline casts

Sample patterns for accessing multiple properties

Person( name == "mark", address.city == "london", address.country == "uk" )
//the same as 
Person( name == "mark", address.( city == "london", country == "uk") )

You can use the syntax to convert its objects to subtypes

Sample pattern for inline cast to subtype

// Inline casting with subtype name:
Person( name == "mark", address#LongAddress.country == "uk" )

// Inline casting with fully qualified class name:
Person( name == "mark", address#org.domain.LongAddress.country == "uk" )

// Multiple inline casts:
Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )

You can use the instanceof operator to infer the subsequent use and pattern of this type of field, as shown in the following example

Person( name == "mark", address instanceof LongAddress, address.country == "uk" )

(4) Date text in constraints

The Drools engine supports the date format DD MMM yyyy.

Sample mode with date text restrictions

Person( bornBefore < "27-Oct-2009" )

(5) Date text in constraints

  • .(), #


// Inline casting with subtype name:
Person( name == "mark", address#LongAddress.country == "uk" )

// Inline casting with fully qualified class name:
Person( name == "mark", address#org.domain.LongAddress.country == "uk" )

// Multiple inline casts:
Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )
  • ! (equivalent to! = null)

Examples of constraints with empty security dereference

Person( $streetName : address!.street )

// This is internally rewritten in the following way:

Person( address != null, $streetName : address.street )
  • [] (use this operator to access values by List index or Map key. )

Sample constraints for List and Map access

// The following format is the same as `childList(0).getAge() == 18`:
Person(childList[0].age == 18)

// The following format is the same as `credentialMap.get("jdoe").isValid()`:
  • <,<=,>,>=

Example constraint of before operator

Person( birthDate < $otherBirthDate )

Person( firstName < $otherFirstName )
  • ==, !=

Examples of constraints with null security equality

Person( firstName == "John" )

// This is similar to the following formats:

java.util.Objects.equals(person.getFirstName(), "John")
  • &&, || (these operators can create a simplified combination of relational conditions)

Example constraints with simplified composition relationships

// Simple abbreviated combined relation condition using a single `&&`:
Person(age > 30 && < 40)

// Complex abbreviated combined relation using groupings:
Person(age ((> 30 && < 40) || (> 20 && < 25)))

// Mixing abbreviated combined relation with constraint connectives:
Person(age > 30 && < 40 || location == "london")
  • Matches, not matches (can indicate that the field matches or does not match the specified Java regular expression)

Example constraints for matching or mismatching regular expressions

Person( country matches "(USA)?\\S*UK" )

Person( country not matches "(USA)?\\S*UK" )
  • Contains, not contains (you can verify whether the Array or field Collection contains or does not contain the specified value)

Use the limitation contains of the embodiment, and not contains is a set

// Collection with a specified field:
FamilyTree( countries contains "UK" )

FamilyTree( countries not contains "UK" )

// Collection with a variable:
FamilyTree( countries contains $var )

FamilyTree( countries not contains $var )
  • memberOf, not memberOf (to verify that the field is an Array or member collection defined as a variable)

Limiting memberOf with the embodiment, and not collecting

FamilyTree( person memberOf $europeanDescendants )

FamilyTree( person not memberOf $europeanDescendants )
  • str (fields that can verify that a String begins or ends with a specified value, including length)

Constraint sample str

// Verify what the String starts with:
Message( routingValue str[startsWith] "R1" )

// Verify what the String ends with:
Message( routingValue str[endsWith] "R2" )

// Verify the length of the String:
Message( routingValue str[length] 17 )
  • In, not in (more than one possible value can be specified to match the constraint (compound value limit))

Constraint in and notin of the embodiment

Person( $color : favoriteColor )
Color( type in ( "red", "blue", $color ) )

Person( $color : favoriteColor )
Color( type notin ( "red", "blue", $color ) )

(6) Operator priority in DRL schema constraints

Highest to lowest priority

(7) Rule condition elements supported in DRL (key)

  • and or

Example mode and

//Infix `and`:
Color( colorType : type ) and Person( favoriteColor == colorType )

//Infix `and` with grouping:
(Color( colorType : type ) and (Person( favoriteColor == colorType ) or Person( favoriteColor == colorType ))

// Prefix `and`:
(and Color( colorType : type ) Person( favoriteColor == colorType ))

// Default implicit `and`:
Color( colorType : type )
Person( favoriteColor == colorType )
  • Exists not (specifies facts and constraints that must or do not exist. Trigger this option only on the first match, not on subsequent matches. If you use this element with multiple patterns, parentheses () should be added between the patterns.)

Example mode exists

exists Person( firstName == "John")

exists (Person( firstName == "John", age == 42 ))

exists (Person( firstName == "John" ) and
        Person( lastName == "Doe" ))
  • For all (determines whether all fact s matching the first pattern satisfy other patterns)

Rule example for all

rule "All full-time employees have red ID badges"
    forall( $emp : Employee( type == "fulltime" )
                   Employee( this == $emp, badgeColor = "red" ) )
    // True, all full-time employees have red ID badges.
  • from (specify data source for pattern)

Sample rules for binding from to patterns

rule "Validate zipcode"
    Person( $personAddress : address )
    Address( zipcode == "23920W" ) from $personAddress
    // Zip code is okay.
  • Entry point (defines the entry point or event flow corresponding to the data source of the schema. This element is usually used with the fromcondition element)

Rule example from entry point

rule "Authorize withdrawal"
    WithdrawRequest( $ai : accountId, $am : amount ) from entry-point "ATM Stream"
    CheckingAccount( accountId == $ai, balance > $am )
    // Authorize withdrawal.

Sample Java application code with EntryPoint objects and insert facts

import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.EntryPoint;

// Create your KIE base and KIE session as usual:
KieSession session = ...

// Create a reference to the entry point:
EntryPoint atmStream = session.getEntryPoint("ATM Stream");

// Start inserting your facts into the entry point:
  • collect
  • accumulate
  • eval (execute specified code)
p1 : Parameter()
p2 : Parameter()
eval( p1.getList().containsKey( p2.getItem() ) )
p1 : Parameter()
p2 : Parameter()
// call function isValid in the LHS
eval( isValid( p1, p2 ) )

9. Rule operation in DRL (THEN)

Part of the then rule (also known as the rule's (RHS)) contains the actions to be performed when the conditional part of the rule is satisfied.

(1) Rules operation methods supported in DRL

  • set (use the value of this setting field)

Example of rule operation for setting the value of loan application approval

$application.setApproved ( false );
$application.setExplanation( "has been bankrupt" );
  • Modify (specify the fields to modify for the fact and notify the Drools engine of the changes)

Example of rule operation for modifying loan application amount and approval

modify( LoanApplication ) {
        setAmount( 100 ),
        setApproved ( true )
  • update (specify the fields and the whole related facts to be updated, and notify the Drools engine of the changes)

Example of rule operation for updating loan application amount and approval

LoanApplication.setAmount( 100 );
update( LoanApplication );
  • Insert (insert the new fact into the working memory of the Drools engine and define the required result fields and values based on the fact)

Example of rule operation for inserting new loan applicant object

insert( new Applicant() );
  • Insert logical (insert facts into the Drools engine in new logic. The Drools engine is responsible for making logical decisions on the insertion and withdrawal of facts.)

Example rule operation of inserting new loan applicant object logically

insertLogical( new Applicant() );
  • Delete (to delete an object from the Drools engine. The keyword retract is also supported in DRL and performs the same operation, but delete is usually preferred in DRL code to use the keyword to keep consistent with the keyword insert)

Example of rule operation for deleting loan applicant object

delete( Applicant );

(2) Advanced rule operations with conditional and naming consequences

Using nested if and else if constructs to evaluate different rule conditions

rule "Give free parking and 10% discount to over 60 Golden customer and 5% to Silver ones"
    $customer : Customer( age > 60 )
    if ( type == "Golden" ) do[giveDiscount10]
    else if ( type == "Silver" ) break[giveDiscount5]
    $car : Car( owner == $customer )
    modify($car) { setFreeParking( true ) };
    modify($customer) { setDiscount( 0.1 ) };
    modify($customer) { setDiscount( 0.05 ) };

10. Comments in DRL

Comments are the same as java, / / or/****/

Published 3 original articles, won praise 1, visited 91
Private letter follow

Tags: Java Attribute Session REST

Posted on Thu, 20 Feb 2020 04:49:09 -0500 by Garcia