JEXL dynamic code execution engine

Previously on

  • The existing Internet of Things system has preliminarily connected some intelligent devices and collected and controlled the corresponding devices’ data. The next step is to realize the linkage function of the device.

Second, what to do?

The function we want to implement is the scene automation function.
  • When the temperature reaches 30 ° C and the humidity is less than 45% ==> Turn on the air conditioner, set the air conditioner to cooling mode, and set the temperature to 20 ° C
  • When the PM2.5 concentration in Jinan is greater than 23, ==> open and close the socket, and send the nail message reminder
  • When the weather state of Jinan is “rainy day” or the indoor light intensity is less than 250LUX ==> Turn on the air conditioning dehumidification of these tasks, can be uniformly set the execution time period, and set the execution state of the week.

Three, how to do?

1, the difficulties in
  • How to define the data structure of the task to ensure that the above functions can be achieved.
  • How to use code to dynamically determine whether comparisons of various data values meet the criteria.
  • How to perform each action dynamically, with different parameters required for each action.
  • How do you do it elegantly, you don’t have to write a bunch of if-else on every condition
  • How to trigger the execution of each task and define a unified trigger entry
  • In order to ensure the stability and expansibility of the service, Orange has 140+ stores. Assuming that each store has 5 routine tasks, the number of tasks to be carried out should be 700+ more.
2, table structure
CREATE TABLE 'scene_AUTOMATION' (' id 'int(11) NOT NULL AUTO_INCREMENT COMMENT' 增 increment ', 'scene_name' varchar(200) DEFAULT NULL COMMENT 'Scene name ',' store_code 'varchar(10) DEFAULT NULL COMMENT' store id ', 'match_type' varchar(10) DEFAULT NULL COMMENT 'match_type' varchar(10) DEFAULT NULL COMMENT 'match_id ', 'BACKGROUND' varchar(200) DEFAULT NULL COMMENT 'background ', 'scene_state' varchar(10) DEFAULT NULL COMMENT 'ON ',' conditions' text COMMENT ', 'actions' text COMMENT' conditional ', 'preconditions' text COMMENT' conditional ', 'create_no' varchar(50) DEFAULT NULL COMMENT 'founder ', 'create_time' datetime DEFAULT NULL COMMENT 'create_time ',' update_no 'varchar(50) DEFAULT NULL COMMENT' update_no ', 'update_time' datetime DEFAULT NULL COMMENT 'update time ', PRIMARY KEY (' id ') ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET= UTf8MB4 COMMENT=' 表 现 ';Copy the code
3. Data structure
{"sceneName": "Scene Automation test ", "storeCode": "0009", "matchType": "ALL"," BACKGROUND ": "# FFFFFF ", "sceneState": "ON", "conditionMessages": [ { "entityId": "6cb6a428adede6b580jqqb", "entityType": "DEVICE", "orderNum": 1, "display": { "code": "temperature", "operator": "MORE", "value": 25 } }, { "entityId": "6cb6a428adede6b580jqqb", "entityType": "DEVICE", "orderNum": 2, "display": { "code": "humidity", "operator": "MORE", "value": 30 } } ], "preconditionMessages": [ { "display": { "start": "00:00", "end": "23:59", "loops": "1111110" }, "type": "TIME_CHECK" } ], "actionMessages": [ { "executor": "socket", "entityId": "6c30fb6e4c962c96b2asgo", "property": {"switchSocket": true}}, {"executor": "sendDing", "property": {"message": "Message sending test"}}]}Copy the code
4. Introduce the JEXL logic engine
Mons < dependency > < groupId > org.apache.com < / groupId > < artifactId > Commons jexl - < / artifactId > < version > 2.1.1 < / version > </dependency>Copy the code

The Java Expression Language(JEXL) is a Jar from Apache that facilitates dynamic and scripting functionality in applications and frameworks written in Java. JEXL implements the expression language based on some extensions of the JSTL expression language, supporting most constructs in shell scripts or ECMAScript(JS) #####

Relational operators / * * * * * @ param conditionOperationMessage * @ return Boolean * / private static Boolean relationalCal(ConditionOperationMessage conditionOperationMessage) { JexlEngine engine = new JexlEngine(); JexlContext context = new MapContext(); / / build the expression String command = "if (a" + conditionOperationMessage. GetOperation () + > conditionOperationMessage. The getValue ()  + 20 " ) { return true } else { return false } "; / / set the context variable values. The set (" a ", conditionOperationMessage getParam ()); Boolean result = (Boolean) engine.createexpression (command).evaluate(context); The info (" relationship between computing results [{} {} {}] - > [{}] ", conditionOperationMessage. GetParam (), conditionOperationMessage.getOperation(), conditionOperationMessage.getValue(), result); return result; }Copy the code
Conditional – logical operation expression
/ * * * or core computation methods * * @ param conditionOperationMessageList * @ return Boolean * / private static Boolean LogicOrCoreCal (List < ConditionOperationMessage > conditionOperationMessageList) {the info (" = = = = = = = = start into the logic or the = = = = = "); for (ConditionOperationMessage conditionOperationMessage : ConditionOperationMessageList) {if (relationalCal (conditionOperationMessage)) {the info (" = = = = = = = = meet the logic or the = = = = = "); return true; }} log.info("======== does not satisfy logic or operation ====="); return false; } / * * * * * or core computation method @ param conditionOperationMessageList * @ return Boolean * / private static Boolean LogicAndCoreCal (List < ConditionOperationMessage > conditionOperationMessageList) {the info (" = = = = = = = = get on logic and arithmetic = = = = = "); for (ConditionOperationMessage conditionOperationMessage : conditionOperationMessageList) { if (! RelationalCal (conditionOperationMessage)) {log. The info (" = = = = = = = = does not meet the logic and arithmetic = = = = = "); return false; }} log.info("======== satisfies logic and operates ====="); Return true; }Copy the code
7. Action-dynamic method execution
@param actionMessageList */ public static void action(List<ActionMessage> actionMessageList) {JexlEngine  engine = new JexlEngine(); JexlContext context; if (! CollectionUtils.isEmpty(actionMessageList)) { for (ActionMessage actionMessage : actionMessageList) { context = new MapContext(); String command = "ActionTool." + actionMessage.getExecutor() + "(property)"; context.set("ActionTool", ActionTool.class); context.set("property", actionMessage.getProperty()); boolean result = (boolean) engine.createExpression(command).evaluate(context); The info (" method ({}) execution results ({}) ", actionMessage. GetExecutor (), result); Public static Boolean Socket (ActionPropertyMessage property) {public static Boolean socket(ActionPropertyMessage property) { Log.info (" Socket control [{}]", property.getSwitchSocket()); autoService.testService(); return true; } @param property */ public static Boolean sendDing(ActionPropertyMessage property) { Log.info (" Send pin message [{}]", property.getMessage()); return true; } /** * @param property */ public static Boolean delay(ActionPropertyMessage property) {log.info(" ActionPropertyMessage property) [{}] Minutes [{}] seconds [{}] ", Property.getDelayHour (), Property.getDelayMinutes (), Property.getDelaySeconds ()); return true; } /** * @param Property */ public static Boolean airCondition(ActionPropertyMessage Property) {log.info(" air conditioner control ") Type a value [{}] [{}] ", the property, getAirConditionType (.) getStr (), the property. GetAirConditionValue ()); return true; }Copy the code
8. Call entry
SimpleMessage (String code) {Override public SimpleMessage (String code) {Override public SimpleMessage (String code) Access is open the activity of the List < SceneAutomation > sceneAutomationList = sceneAutomationDao. GetSuitableScene (code); If (collectionUtils. isEmpty(sceneAutomationList)) {return SimpleMessage(ErrorCodeEnum.NO, "This scene is not found "); } // Go through the scene for (SceneAutomation SceneAutomation: List<PreconditionMessage> preconditionMessageList = JSON.parseArray(sceneAutomation.getPreconditions(), PreconditionMessage.class); if (! PreconditionTool. Judge (preconditionMessageList)) {the info (" task [{}] does not meet the precondition ", sceneAutomation. GetSceneName ()); continue; } / / analytical conditions List < ConditionMessage > conditionMessages = JSON. ParseArray (sceneAutomation. GetConditions (), ConditionMessage.class); / / conditional execution encapsulation List < ConditionOperationMessage > conditionOperationMessageList = new ArrayList < > (); / / get condition value conditionMessages. ForEach (conditionMessage - > {if / / equipment type (ConditionEntityTypeEnum. DEVICE. The equals (conditionMessage getEntityType ())) {/ / condition value Object param = sceneAutomationDao.getDeviceParam(conditionMessage.getDisplay().getCode(), conditionMessage.getEntityId()); The info (" equipment code [{}] [{}] type current value ({}) ", conditionMessage. GetEntityId (), conditionMessage. GetDisplay (). GetCode (), param); conditionOperationMessageList.add(ConditionOperationMessage.builder() .param(param) .operation(conditionMessage.getDisplay().getOperator().getStr()) .value(conditionMessage.getDisplay().getValue()) .build()); } else {// TODO to be determined}}); / / determine whether conditions completes the if (CollectionUtils. IsEmpty (conditionOperationMessageList) | |! ConditionTool. Judge (conditionOperationMessageList, sceneAutomation getMatchType ())) {the info (" task "[{}] does not meet the conditions, sceneAutomation.getSceneName()); continue; } / / to perform an action List < ActionMessage > actionMessages = JSON. ParseArray (sceneAutomation. GetActions (), ActionMessage. Class); / / execution ActionTool. Action (actionMessages); } return new SimpleMessage(ErrorCodeEnum.OK); }Copy the code

What else do you want to add?

Mysql > query data in JSON format
@return List<SceneAutomation> */ @select (" Select "+" id, "+" scene_name, ") " + " store_code, " + " match_type, " + " background, " + " scene_state, " + " conditions, " + " preconditions, " + " actions " + "FROM " + " `scene_automation` " + "WHERE " + " scene_state = 'ON' " + " AND JSON_CONTAINS( conditions, JSON_OBJECT( 'display', JSON_OBJECT( 'code', #{code} ) ) )") List<SceneAutomation> getSuitableScene(@Param("code") String code); @param deviceUid device id @return Object */ @select (" Select "+ "device_detail ->> '$.${key}' as 'param' " + "from store_devices WHERE " + "device_uid = #{deviceUid} ") Object getDeviceParam(@Param("key") String key, @Param("deviceUid") String deviceUid);Copy the code
2. Static Execute through service
/** * final AutoService innerAutoService; static AutoService autoService; public ActionTool(AutoService innerAutoService) { this.innerAutoService = innerAutoService; } /** * construct public void init() {autoService = innerAutoService; }Copy the code

What is the meaning of this thing?

1. When we encounter special problems, we should break our inherent programming ideas and find the optimal solution according to our business needs. 2. Don’t compromise. Make your program elegant.