SystemMessage.java

package org.heigit.ors.api.util;

import org.apache.log4j.Logger;
import org.heigit.ors.api.SystemMessageProperties;
import org.heigit.ors.api.requests.isochrones.IsochronesRequest;
import org.heigit.ors.api.requests.matrix.MatrixRequest;
import org.heigit.ors.api.requests.routing.RouteRequest;
import org.heigit.ors.matrix.MatrixMetricsType;
import org.heigit.ors.routing.RoutingProfileType;
import org.heigit.ors.routing.RoutingRequest;
import org.heigit.ors.routing.WeightingMethod;

import java.sql.Timestamp;
import java.time.Instant;
import java.util.*;

public class SystemMessage {
    private static final Logger LOGGER = Logger.getLogger(SystemMessage.class.getName());
    private static List<Message> messages;

    private SystemMessage() {
    }

    public static String getSystemMessage(Object requestObj, SystemMessageProperties messageProperties) {
        if (messages == null) {
            loadMessages(messageProperties);
        }
        if (messages.isEmpty()) {
            return "";
        }
        if (requestObj == null) {
            requestObj = "";
        }
        RequestParams params = new RequestParams();
        // V1
        if (requestObj.getClass() == RoutingRequest.class) {
            extractParams((RoutingRequest) requestObj, params);
        } else if (requestObj.getClass() == org.heigit.ors.matrix.MatrixRequest.class) {
            extractParams((org.heigit.ors.matrix.MatrixRequest) requestObj, params);
        } else if (requestObj.getClass() == org.heigit.ors.isochrones.IsochroneRequest.class) {
            extractParams((org.heigit.ors.isochrones.IsochroneRequest) requestObj, params);
        }
        // V2
        else if (requestObj.getClass() == RouteRequest.class) {
            extractParams((RouteRequest) requestObj, params);
        } else if (requestObj.getClass() == MatrixRequest.class) {
            extractParams((MatrixRequest) requestObj, params);
        } else if (requestObj.getClass() == IsochronesRequest.class) {
            extractParams((IsochronesRequest) requestObj, params);
        }
        return selectMessage(params);
    }

    private static void extractParams(RoutingRequest req, RequestParams params) {
        params.setApiVersion("1");
        params.setApiFormat(req.getResponseFormat());
        params.setRequestService("routing");
        params.setRequestProfiles(RoutingProfileType.getName(req.getSearchParameters().getProfileType()));
        params.setRequestPreferences(WeightingMethod.getName(req.getSearchParameters().getWeightingMethod()));
    }

    private static void extractParams(org.heigit.ors.matrix.MatrixRequest req, RequestParams params) {
        params.setApiVersion("1");
        params.setApiFormat("json");
        params.setRequestService("matrix");
        params.setRequestProfiles(RoutingProfileType.getName(req.getProfileType()));
        params.setRequestPreference(MatrixMetricsType.getMetricsNamesFromInt(req.getMetrics()));
    }

    private static void extractParams(org.heigit.ors.isochrones.IsochroneRequest req, RequestParams params) {
        params.setApiVersion("1");
        params.setApiFormat("geojson");
        params.setRequestService("isochrones");
        params.setRequestProfile(req.getProfilesForAllTravellers());
        params.setRequestPreference(req.getWeightingsForAllTravellers());
    }

    private static void extractParams(RouteRequest req, RequestParams params) {
        params.setApiVersion("2");
        params.setApiFormat(req.getResponseType().toString());
        params.setRequestService("routing");
        params.setRequestProfiles(req.getProfile().toString());
        params.setRequestPreferences(req.hasRoutePreference() ? req.getRoutePreference().toString() : "");
    }

    private static void extractParams(MatrixRequest req, RequestParams params) {
        params.setApiVersion("2");
        params.setApiFormat("json");
        params.setRequestService("matrix");
        params.setRequestProfiles(req.getProfile() != null ? req.getProfile().toString() : "driving-car");
        params.setRequestPreference(req.getMetricsStrings().isEmpty() ? new HashSet<>(List.of("duration")) : req.getMetricsStrings());
    }

    private static void extractParams(IsochronesRequest req, RequestParams params) {
        params.setApiVersion("2");
        params.setApiFormat("geojson");
        params.setRequestService("isochrones");
        params.setRequestProfiles(req.getProfile() != null ? req.getProfile().toString() : "driving-car");
        params.setRequestPreferences(req.hasRangeType() ? req.getRangeType().name() : "TIME");
    }

    private static String selectMessage(RequestParams params) {
        for (Message message : messages) {
            if (message.applicableForRequest(params)) {
                return message.getText();
            }
        }
        return "";
    }

    private static void loadMessages(SystemMessageProperties messageProperties) {
        messages = new ArrayList<>();

        for (SystemMessageProperties.MessageObject message : messageProperties.getMessages()) {
            try {
                if (message.isActive()) {
                    List<SystemMessage.Condition> conditions = new ArrayList<>();
                    loadConditionsForMessage(message, conditions);
                    messages.add(new SystemMessage.Message(message.getText(), conditions));
                }
            } catch (Exception e) {
                // ignore otherwise incomplete messages entirely
                LOGGER.warn("Invalid SystemMessage object in ors config %s.".formatted(message.toString().substring(18)));
            }
        }
        AppConfigMigration.loadSystemMessagesfromAppConfig(messages);
        if (!messages.isEmpty())
            LOGGER.info("SystemMessage loaded %s messages.".formatted(messages.size()));
    }

    private static void loadConditionsForMessage(SystemMessageProperties.MessageObject message, List<Condition> conditions) {
        try {
            for (Map<String, String> conditionMap : message.getCondition()) {
                for (Map.Entry<String, String> condition : conditionMap.entrySet()) {
                    conditions.add(new Condition(condition.getKey(), condition.getValue()));
                }
            }
        } catch (Exception e) {
            // ignore missing condition block and keep message
            LOGGER.info("Invalid or missing condition in message object.");
        }
    }

    static class Message {
        private final String text;
        private final List<Condition> conditions;

        public Message(String text, List<Condition> conditions) {
            this.text = text;
            this.conditions = conditions;
        }

        public String getText() {
            return text;
        }

        public boolean applicableForRequest(RequestParams params) {
            for (Condition condition : conditions) {
                if (!condition.fulfilledBy(params)) {
                    return false;
                }
            }
            return true;
        }
    }

    static class Condition {
        private final String type;
        private final String[] values;

        public Condition(String type, String valuesCSV) {
            this.type = type;
            this.values = valuesCSV.split(",");
        }

        public boolean fulfilledBy(RequestParams params) {
            switch (type) {
                case "time_before" -> {
                    return new Timestamp(System.currentTimeMillis()).getTime() < Date.from(Instant.parse(this.values[0])).getTime();
                }
                case "time_after" -> {
                    return new Timestamp(System.currentTimeMillis()).getTime() > Date.from(Instant.parse(this.values[0])).getTime();
                }
                case "api_version" -> {
                    return matchApiVersion(params);
                }
                case "api_format" -> {
                    return matchApiFormat(params);
                }
                case "request_service" -> {
                    return matchRequestService(params);
                }
                case "request_profile" -> {
                    return matchRequestProfiles(params);
                }
                case "request_preference" -> {
                    return matchRequestPreferences(params);
                }
                default -> // unknown rule
                        LOGGER.warn("Invalid condition set in system_message.");
            }
            return false;
        }

        private boolean matchApiVersion(RequestParams params) {
            for (String val : this.values) {
                if (params.getApiVersion().equalsIgnoreCase(val)) return true;
            }
            return false;
        }

        private boolean matchApiFormat(RequestParams params) {
            for (String val : this.values) {
                if (params.getApiFormat().equalsIgnoreCase(val)) return true;
            }
            return false;
        }

        private boolean matchRequestService(RequestParams params) {
            for (String val : this.values) {
                if (params.getRequestService().equalsIgnoreCase(val)) return true;
            }
            return false;
        }

        private boolean matchRequestProfiles(RequestParams params) {
            for (String val : this.values) {
                for (String param : params.getRequestProfiles()) {
                    if (param.equalsIgnoreCase(val)) return true;
                }
            }
            return false;
        }

        private boolean matchRequestPreferences(RequestParams params) {
            for (String val : this.values) {
                for (String param : params.getRequestPreferences()) {
                    if (param.equalsIgnoreCase(val)) return true;
                }
            }
            return false;
        }
    }

    private static class RequestParams {
        private String apiVersion = "";
        private String apiFormat = "";
        private String requestService = "";
        private final Set<String> requestProfiles = new HashSet<>();
        private final Set<String> requestPreferences = new HashSet<>();

        public String getApiVersion() {
            return apiVersion;
        }

        public void setApiVersion(String apiVersion) {
            this.apiVersion = apiVersion;
        }

        public String getApiFormat() {
            return apiFormat;
        }

        public void setApiFormat(String apiFormat) {
            this.apiFormat = apiFormat;
        }

        public String getRequestService() {
            return requestService;
        }

        public void setRequestService(String requestService) {
            this.requestService = requestService;
        }

        public Set<String> getRequestProfiles() {
            return requestProfiles;
        }

        public void setRequestProfiles(String requestProfiles) {
            this.requestProfiles.add(requestProfiles);
        }

        public void setRequestProfile(Set<String> requestProfile) {
            this.requestProfiles.addAll(requestProfile);
        }

        public Set<String> getRequestPreferences() {
            return requestPreferences;
        }

        public void setRequestPreferences(String requestPreferences) {
            this.requestPreferences.add(requestPreferences);
        }

        public void setRequestPreference(Set<String> requestPreference) {
            this.requestPreferences.addAll(requestPreference);
        }
    }
}