Add a rules engine so I can be notified when I forget to close my garage door.

This commit is contained in:
MarkBryanMilligan
2021-07-15 23:34:15 -05:00
parent de50645a2c
commit 3d5cd6500f
81 changed files with 2044 additions and 231 deletions

View File

@@ -0,0 +1,73 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lanternsoftware.rules</groupId>
<artifactId>lantern-datamodel-rules</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>lantern-datamodel-rules</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-dao</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<index>true</index>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,43 @@
package com.lanternsoftware.datamodel.rules;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
@DBSerializable
public class Action {
private ActionType type;
private String description;
private String destinationId;
private double value;
public ActionType getType() {
return type;
}
public void setType(ActionType _type) {
type = _type;
}
public String getDescription() {
return description;
}
public void setDescription(String _description) {
description = _description;
}
public String getDestinationId() {
return destinationId;
}
public void setDestinationId(String _destinationId) {
destinationId = _destinationId;
}
public double getValue() {
return value;
}
public void setValue(double _value) {
value = _value;
}
}

View File

@@ -0,0 +1,7 @@
package com.lanternsoftware.datamodel.rules;
public enum ActionType {
SET_SWITCH,
MOBILE_ALERT_STATIC,
MOBILE_ALERT_EVENT_DESCRIPTION
}

View File

@@ -0,0 +1,23 @@
package com.lanternsoftware.datamodel.rules;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
@DBSerializable
public class Alert {
private String message;
public Alert() {
}
public Alert(String _message) {
message = _message;
}
public String getMessage() {
return message;
}
public void setMessage(String _message) {
message = _message;
}
}

View File

@@ -0,0 +1,172 @@
package com.lanternsoftware.datamodel.rules;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
@DBSerializable
public class Criteria {
private EventType type;
private String sourceId;
private Operator operator;
private double value;
private boolean or;
private List<Criteria> criteria;
public EventType getType() {
return type;
}
public void setType(EventType _type) {
type = _type;
}
public String getSourceId() {
return sourceId;
}
public void setSourceId(String _sourceId) {
sourceId = _sourceId;
}
public Operator getOperator() {
return operator;
}
public void setOperator(Operator _operator) {
operator = _operator;
}
public double getValue() {
return value;
}
public void setValue(double _value) {
value = _value;
}
public boolean isOr() {
return or;
}
public void setOr(boolean _or) {
or = _or;
}
public List<Criteria> getCriteria() {
return criteria;
}
public void setCriteria(List<Criteria> _criteria) {
criteria = _criteria;
}
public EventId toEventId() {
return new EventId(type, sourceId);
}
public Set<Integer> toJavaDays() {
if (type != EventType.TIME)
return Collections.emptySet();
return CollectionUtils.transformToSet(CriteriaDay.toEnumSet(sourceId), _d->_d.javaDay);
}
public boolean isMet(List<Event> _events, TimeZone _tz) {
Event e = CollectionUtils.filterOne(_events, this::triggers);
if (type == EventType.TIME) {
int day = DateUtils.toCalendar(new Date(), _tz).get(Calendar.DAY_OF_WEEK);
if (!toJavaDays().contains(day))
return false;
Date timeToday = timeOfDay(new Date(), DaoSerializer.toInteger(getValue()), day, _tz);
if (!e.getTime().equals(timeToday))
return false;
}
else if (operator != null) {
if (operator == Operator.GREATER) {
if (e.getValue() <= value)
return false;
}
else if (operator == Operator.GREATER_EQUAL) {
if (e.getValue() < value)
return false;
}
else if (operator == Operator.EQUAL) {
if (e.getValue() != value)
return false;
}
else if (operator == Operator.LESS_EQUAL) {
if (e.getValue() > value)
return false;
}
else if (operator == Operator.LESS) {
if (e.getValue() >= value)
return false;
}
}
if (CollectionUtils.isNotEmpty(criteria)) {
if (or)
return CollectionUtils.anyQualify(criteria, _c->_c.isMet(_events, _tz));
return CollectionUtils.allQualify(criteria, _c->_c.isMet(_events, _tz));
}
return true;
}
public boolean triggers(Event _event) {
if (_event.getType() != type)
return false;
if (NullUtils.isEmpty(_event.getSourceId()) || NullUtils.isEqual(_event.getSourceId(), "*") || NullUtils.isEqual(_event.getSourceId(), sourceId))
return true;
return CollectionUtils.anyQualify(criteria, _c->_c.triggers(_event));
}
public void addAllCriteria(List<Criteria> _criteria) {
_criteria.add(this);
CollectionUtils.edit(criteria, _c->_c.addAllCriteria(_criteria));
}
public Date getNextTriggerDate(TimeZone _tz) {
if (type != EventType.TIME)
return null;
Collection<Date> dates = CollectionUtils.transform(CriteriaDay.toEnumSet(getSourceId()), _s->nextTimeOfDay(new Date(), DaoSerializer.toInteger(getValue()), _s.javaDay, _tz));
return CollectionUtils.getSmallest(dates);
}
public Date timeOfDay(Date _now, int _time, int _day, TimeZone _tz) {
Calendar cal = DateUtils.toCalendar(_now, _tz);
cal.set(Calendar.DAY_OF_WEEK, _day);
cal.set(Calendar.HOUR_OF_DAY, hour(_time));
cal.set(Calendar.MINUTE, minute(_time));
cal.set(Calendar.SECOND, second(_time));
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
public Date nextTimeOfDay(Date _now, int _time, int _day, TimeZone _tz) {
Date time = timeOfDay(_now, _time, _day, _tz);
return time.before(_now)?DateUtils.addDays(time,7, _tz):time;
}
public int hour(int _timeOfDay) {
return _timeOfDay/3600;
}
public int minute(int _timeOfDay) {
return (_timeOfDay/60)%60;
}
public int second(int _timeOfDay) {
return _timeOfDay%60;
}
}

View File

@@ -0,0 +1,35 @@
package com.lanternsoftware.datamodel.rules;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import java.util.Calendar;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Set;
public enum CriteriaDay {
SUN(Calendar.SUNDAY),
MON(Calendar.MONDAY),
TUE(Calendar.TUESDAY),
WED(Calendar.WEDNESDAY),
THU(Calendar.THURSDAY),
FRI(Calendar.FRIDAY),
SAT(Calendar.SATURDAY);
public final int javaDay;
CriteriaDay(int _javaDay) {
javaDay = _javaDay;
}
public static String toString(Collection<CriteriaDay> _coll) {
return CollectionUtils.transformToCommaSeparated(_coll, Enum::name, false);
}
public static EnumSet<CriteriaDay> toEnumSet(String _days) {
String[] days = NullUtils.cleanSplit(_days, ",");
Set<CriteriaDay> setDays = CollectionUtils.transformToSet(CollectionUtils.asArrayList(days), _s->NullUtils.toEnum(CriteriaDay.class, _s));
return setDays.isEmpty()?EnumSet.allOf(CriteriaDay.class):EnumSet.copyOf(setDays);
}
}

View File

@@ -0,0 +1,73 @@
package com.lanternsoftware.datamodel.rules;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import com.lanternsoftware.util.dao.annotations.PrimaryKey;
import java.util.Date;
@DBSerializable
public class Event {
@PrimaryKey private String id;
private int accountId;
private EventType type;
private Date time;
private String eventDescription;
private String sourceId;
private double value;
public String getId() {
return id;
}
public void setId(String _id) {
id = _id;
}
public int getAccountId() {
return accountId;
}
public void setAccountId(int _accountId) {
accountId = _accountId;
}
public EventType getType() {
return type;
}
public void setType(EventType _type) {
type = _type;
}
public Date getTime() {
return time;
}
public void setTime(Date _time) {
time = _time;
}
public String getEventDescription() {
return eventDescription;
}
public void setEventDescription(String _eventDescription) {
eventDescription = _eventDescription;
}
public String getSourceId() {
return sourceId;
}
public void setSourceId(String _sourceId) {
sourceId = _sourceId;
}
public double getValue() {
return value;
}
public void setValue(double _value) {
value = _value;
}
}

View File

@@ -0,0 +1,34 @@
package com.lanternsoftware.datamodel.rules;
import java.util.Objects;
public class EventId {
private final EventType type;
private final String sourceId;
public EventId(EventType _type, String _sourceId) {
type = _type;
sourceId = _sourceId;
}
public EventType getType() {
return type;
}
public String getSourceId() {
return sourceId;
}
@Override
public boolean equals(Object _o) {
if (this == _o) return true;
if (_o == null || getClass() != _o.getClass()) return false;
EventId eventId = (EventId) _o;
return type == eventId.type && Objects.equals(sourceId, eventId.sourceId);
}
@Override
public int hashCode() {
return Objects.hash(type, sourceId);
}
}

View File

@@ -0,0 +1,8 @@
package com.lanternsoftware.datamodel.rules;
public enum EventType {
SWITCH_LEVEL,
TEMPERATURE,
TIME,
POWER
}

View File

@@ -0,0 +1,65 @@
package com.lanternsoftware.datamodel.rules;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import com.lanternsoftware.util.dao.annotations.PrimaryKey;
import java.util.Date;
@DBSerializable
public class FcmDevice {
@PrimaryKey private String id;
private int accountId;
private String token;
private String name;
private Date posted;
public FcmDevice() {
}
public FcmDevice(int _accountId, String _token, String _name, Date _posted) {
accountId = _accountId;
token = _token;
name = _name;
posted = _posted;
}
public String getId() {
return id;
}
public void setId(String _id) {
id = _id;
}
public int getAccountId() {
return accountId;
}
public void setAccountId(int _accountId) {
accountId = _accountId;
}
public String getToken() {
return token;
}
public void setToken(String _token) {
token = _token;
}
public String getName() {
return name;
}
public void setName(String _name) {
name = _name;
}
public Date getPosted() {
return posted;
}
public void setPosted(Date _posted) {
posted = _posted;
}
}

View File

@@ -0,0 +1,9 @@
package com.lanternsoftware.datamodel.rules;
public enum Operator {
GREATER,
GREATER_EQUAL,
EQUAL,
LESS_EQUAL,
LESS
}

View File

@@ -0,0 +1,80 @@
package com.lanternsoftware.datamodel.rules;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import com.lanternsoftware.util.dao.annotations.PrimaryKey;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
@DBSerializable
public class Rule {
@PrimaryKey private String id;
private int accountId;
private boolean or;
private List<Criteria> criteria;
private List<Action> actions;
public String getId() {
return id;
}
public void setId(String _id) {
id = _id;
}
public int getAccountId() {
return accountId;
}
public void setAccountId(int _accountId) {
accountId = _accountId;
}
public boolean isOr() {
return or;
}
public void setOr(boolean _or) {
or = _or;
}
public List<Criteria> getCriteria() {
return criteria;
}
public List<Criteria> getAllCriteria() {
List<Criteria> allCriteria = new ArrayList<>();
CollectionUtils.edit(criteria, _c->_c.addAllCriteria(allCriteria));
return allCriteria;
}
public void setCriteria(List<Criteria> _criteria) {
criteria = _criteria;
}
public List<Action> getActions() {
return actions;
}
public void setActions(List<Action> _actions) {
actions = _actions;
}
public boolean isMet(List<Event> _events, TimeZone _tz) {
if (or)
return CollectionUtils.anyQualify(criteria, _c->_c.isMet(_events, _tz));
return CollectionUtils.allQualify(criteria, _c->_c.isMet(_events, _tz));
}
public List<Criteria> getCriteriaNeedingData(Event _event) {
List<Criteria> allCriteria = getAllCriteria();
allCriteria.removeIf(_c->_c.triggers(_event));
return allCriteria;
}
public boolean triggers(Event _event) {
return CollectionUtils.anyQualify(criteria, _c-> _c.triggers(_event));
}
}

View File

@@ -0,0 +1,46 @@
package com.lanternsoftware.datamodel.rules.dao;
import com.lanternsoftware.datamodel.rules.Action;
import com.lanternsoftware.datamodel.rules.ActionType;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class ActionSerializer extends AbstractDaoSerializer<Action>
{
@Override
public Class<Action> getSupportedClass()
{
return Action.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(Action _o)
{
DaoEntity d = new DaoEntity();
d.put("type", DaoSerializer.toEnumName(_o.getType()));
d.put("description", _o.getDescription());
d.put("destination_id", _o.getDestinationId());
d.put("value", _o.getValue());
return d;
}
@Override
public Action fromDaoEntity(DaoEntity _d)
{
Action o = new Action();
o.setType(DaoSerializer.getEnum(_d, "type", ActionType.class));
o.setDescription(DaoSerializer.getString(_d, "description"));
o.setDestinationId(DaoSerializer.getString(_d, "destination_id"));
o.setValue(DaoSerializer.getDouble(_d, "value"));
return o;
}
}

View File

@@ -0,0 +1,39 @@
package com.lanternsoftware.datamodel.rules.dao;
import com.lanternsoftware.datamodel.rules.Alert;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class AlertSerializer extends AbstractDaoSerializer<Alert>
{
@Override
public Class<Alert> getSupportedClass()
{
return Alert.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(Alert _o)
{
DaoEntity d = new DaoEntity();
d.put("message", _o.getMessage());
return d;
}
@Override
public Alert fromDaoEntity(DaoEntity _d)
{
Alert o = new Alert();
o.setMessage(DaoSerializer.getString(_d, "message"));
return o;
}
}

View File

@@ -0,0 +1,51 @@
package com.lanternsoftware.datamodel.rules.dao;
import com.lanternsoftware.datamodel.rules.Criteria;
import com.lanternsoftware.datamodel.rules.EventType;
import com.lanternsoftware.datamodel.rules.Operator;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class CriteriaSerializer extends AbstractDaoSerializer<Criteria>
{
@Override
public Class<Criteria> getSupportedClass()
{
return Criteria.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(Criteria _o)
{
DaoEntity d = new DaoEntity();
d.put("type", DaoSerializer.toEnumName(_o.getType()));
d.put("source_id", _o.getSourceId());
d.put("operator", DaoSerializer.toEnumName(_o.getOperator()));
d.put("value", _o.getValue());
d.put("or", _o.isOr());
d.put("criteria", DaoSerializer.toDaoEntities(_o.getCriteria(), DaoProxyType.MONGO));
return d;
}
@Override
public Criteria fromDaoEntity(DaoEntity _d)
{
Criteria o = new Criteria();
o.setType(DaoSerializer.getEnum(_d, "type", EventType.class));
o.setSourceId(DaoSerializer.getString(_d, "source_id"));
o.setOperator(DaoSerializer.getEnum(_d, "operator", Operator.class));
o.setValue(DaoSerializer.getDouble(_d, "value"));
o.setOr(DaoSerializer.getBoolean(_d, "or"));
o.setCriteria(DaoSerializer.getList(_d, "criteria", Criteria.class));
return o;
}
}

View File

@@ -0,0 +1,53 @@
package com.lanternsoftware.datamodel.rules.dao;
import com.lanternsoftware.datamodel.rules.Event;
import com.lanternsoftware.datamodel.rules.EventType;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class EventSerializer extends AbstractDaoSerializer<Event>
{
@Override
public Class<Event> getSupportedClass()
{
return Event.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(Event _o)
{
DaoEntity d = new DaoEntity();
if (_o.getId() != null)
d.put("_id", _o.getId());
d.put("account_id", _o.getAccountId());
d.put("type", DaoSerializer.toEnumName(_o.getType()));
d.put("time", DaoSerializer.toLong(_o.getTime()));
d.put("event_description", _o.getEventDescription());
d.put("source_id", _o.getSourceId());
d.put("value", _o.getValue());
return d;
}
@Override
public Event fromDaoEntity(DaoEntity _d)
{
Event o = new Event();
o.setId(DaoSerializer.getString(_d, "_id"));
o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
o.setType(DaoSerializer.getEnum(_d, "type", EventType.class));
o.setTime(DaoSerializer.getDate(_d, "time"));
o.setEventDescription(DaoSerializer.getString(_d, "event_description"));
o.setSourceId(DaoSerializer.getString(_d, "source_id"));
o.setValue(DaoSerializer.getDouble(_d, "value"));
return o;
}
}

View File

@@ -0,0 +1,48 @@
package com.lanternsoftware.datamodel.rules.dao;
import com.lanternsoftware.datamodel.rules.FcmDevice;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class FcmDeviceSerializer extends AbstractDaoSerializer<FcmDevice>
{
@Override
public Class<FcmDevice> getSupportedClass()
{
return FcmDevice.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(FcmDevice _o)
{
DaoEntity d = new DaoEntity();
if (_o.getId() != null)
d.put("_id", _o.getId());
d.put("account_id", _o.getAccountId());
d.put("token", _o.getToken());
d.put("name", _o.getName());
d.put("posted", DaoSerializer.toLong(_o.getPosted()));
return d;
}
@Override
public FcmDevice fromDaoEntity(DaoEntity _d)
{
FcmDevice o = new FcmDevice();
o.setId(DaoSerializer.getString(_d, "_id"));
o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
o.setToken(DaoSerializer.getString(_d, "token"));
o.setName(DaoSerializer.getString(_d, "name"));
o.setPosted(DaoSerializer.getDate(_d, "posted"));
return o;
}
}

View File

@@ -0,0 +1,50 @@
package com.lanternsoftware.datamodel.rules.dao;
import com.lanternsoftware.datamodel.rules.Action;
import com.lanternsoftware.datamodel.rules.Criteria;
import com.lanternsoftware.datamodel.rules.Rule;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class RuleSerializer extends AbstractDaoSerializer<Rule>
{
@Override
public Class<Rule> getSupportedClass()
{
return Rule.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(Rule _o)
{
DaoEntity d = new DaoEntity();
if (_o.getId() != null)
d.put("_id", _o.getId());
d.put("account_id", _o.getAccountId());
d.put("or", _o.isOr());
d.put("criteria", DaoSerializer.toDaoEntities(_o.getCriteria(), DaoProxyType.MONGO));
d.put("actions", DaoSerializer.toDaoEntities(_o.getActions(), DaoProxyType.MONGO));
return d;
}
@Override
public Rule fromDaoEntity(DaoEntity _d)
{
Rule o = new Rule();
o.setId(DaoSerializer.getString(_d, "_id"));
o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
o.setOr(DaoSerializer.getBoolean(_d, "or"));
o.setCriteria(DaoSerializer.getList(_d, "criteria", Criteria.class));
o.setActions(DaoSerializer.getList(_d, "actions", Action.class));
return o;
}
}

View File

@@ -0,0 +1,6 @@
com.lanternsoftware.datamodel.rules.dao.ActionSerializer
com.lanternsoftware.datamodel.rules.dao.AlertSerializer
com.lanternsoftware.datamodel.rules.dao.CriteriaSerializer
com.lanternsoftware.datamodel.rules.dao.EventSerializer
com.lanternsoftware.datamodel.rules.dao.FcmDeviceSerializer
com.lanternsoftware.datamodel.rules.dao.RuleSerializer