weka.server.JSONProtocol.java Source code

Java tutorial

Introduction

Here is the source code for weka.server.JSONProtocol.java

Source

/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    JSONProtocol.java
 *    Copyright (C) 2016 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.server;

import org.apache.commons.codec.binary.Base64;
import org.boon.json.JsonFactory;
import org.boon.json.ObjectMapper;
import weka.core.WekaException;
import weka.experiment.TaskStatusInfo;
import weka.server.knowledgeFlow.ScheduledNamedKnowledgeFlowTask;
import weka.server.knowledgeFlow.UnscheduledNamedKnowledgeFlowTask;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
 * Routines for encoding/decoding data to/from JSON
 *
 * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
 * @version $Revision: $
 */
public class JSONProtocol {

    public static final String JSON_MIME_TYPE = "application/json";
    public static final String CHARACTER_ENCODING = "UTF-8";

    public static final String JSON_CLIENT_KEY = "clientJ";

    public static final String RESPONSE_TYPE_KEY = "response";
    public static final String RESPONSE_MESSAGE_KEY = "message";
    public static final String RESPONSE_PAYLOAD_KEY = "payload";

    public static final String PAYLOAD_TYPE_KEY = "payloadType";

    // TASK stuff
    public static final String TASK_NAME_KEY = "taskName";
    public static final String TASK_TYPE_KEY = "taskType";

    public static final String TASK_TYPE_KF = "knowledgeFlow";
    public static final String TASK_TYPE_EXPLORER_CLASSIFIER_CV = "classifierCV";

    // Schedule stuff
    public static final String SCHEDULE_DATE_FORMAT_KEY = "dateFormat";
    public static final String SCHEDULE_START_DATE_KEY = "startDate";
    public static final String SCHEDULE_END_DATE_KEY = "endDate";
    public static final String SCHEDULE_REPEAT_UNIT_KEY = "reapeatUnit";
    public static final String SCHEDULE_REPEAT_VALUE_KEY = "repeatValue";
    public static final String SCHEDULE_DAY_OF_WEEK_KEY = "dayOfWeek";
    public static final String SCHEDULE_MONTH_OF_YEAR_KEY = "monthOfYear";
    public static final String SCHEDULE_DAY_OF_MONTH_KEY = "dayOfMonth";
    public static final String SCHEDULE_OCCURRENCE_KEY = "occurrence";

    // TaskStatusInfo stuff
    public static final String TASK_STATUS_INFO_EXECUTION_STATUS_KEY = "executionStatus";
    public static final String TASK_STATUS_INFO_STATUS_MESSAGE_KEY = "statusMessage";
    public static final String TASK_STATUS_INFO_TASK_RESULT_KEY = "taskResult";

    // KF task stuff
    public static final String KF_FLOW_PAYLOAD_ID = "kfFlow";
    public static final String KF_SEQUENTIAL_PROPERTY = "kfSequential";
    public static final String ENV_PAYLOAD_ID = "envVars";

    // Schedule
    public static final String SCHEDULE_PAYLOAD_ID = "schedule";

    /**
     * Creates pre-configured error response
     *
     * @param errorMessage the error message to include in the response
     * @return the response
     */
    public static Map<String, Object> createErrorResponseMap(String errorMessage) {
        Map<String, Object> response = new HashMap<String, Object>();
        response.put(RESPONSE_TYPE_KEY, WekaServlet.RESPONSE_ERROR);
        response.put(RESPONSE_MESSAGE_KEY, errorMessage);

        return response;
    }

    /**
     * Creates a pre-configured OK response
     *
     * @param message the message to include int eh response
     * @return the response
     */
    public static Map<String, Object> createOKResponseMap(String message) {
        Map<String, Object> response = new HashMap<String, Object>();
        response.put(RESPONSE_TYPE_KEY, WekaServlet.RESPONSE_OK);
        response.put(RESPONSE_MESSAGE_KEY, message);

        return response;
    }

    /**
     * Creates and adds a payload map to the supplied response map. Overwrites any existing
     * payload map in the supplied response
     *
     * @param responseMap the response map to add payload to
     * @param payloadTypeIdentifier the type of payload (added to the payload map
     *          under the {@code PAYLOAD_TYPE_KEY})
     * @return the payload map that was added
     */
    public static Map<String, Object> addPayloadMap(Map<String, Object> responseMap, String payloadTypeIdentifier) {
        Map<String, Object> payload = new HashMap<String, Object>();
        payload.put(PAYLOAD_TYPE_KEY, payloadTypeIdentifier);
        responseMap.put(RESPONSE_PAYLOAD_KEY, payload);

        return payload;
    }

    /**
     * Adds a supplied payload map to the supplied response map. Overwrites any
     * existing payload map.
     *
     * @param responseMap the response map to add the payload to
     * @param payloadMap the payload map to add
     * @param payloadIdentifier the type of payload (added to the payload map
     *          under the {@code PAYLOAD_TYPE_KEY})
     */
    public static void addPayloadMap(Map<String, Object> responseMap, Map<String, Object> payloadMap,
            String payloadIdentifier) {

        payloadMap.put(PAYLOAD_TYPE_KEY, payloadIdentifier);
        responseMap.put(RESPONSE_PAYLOAD_KEY, payloadMap);
    }

    /**
     * Encode a map to JSON
     *
     * @param toEncode the map to encode
     * @return the encoded map
     */
    public static String encodeToJSONString(Map<String, Object> toEncode) {
        ObjectMapper mapper = JsonFactory.create();
        return mapper.writeValueAsString(toEncode);
    }

    /**
     * Convert a JSON encoded {@code NamedTask} to a {@code NamedTask} object
     *
     * @param jsonTask the task in JSON format
     * @return a {@code NamedTask} instance
     * @throws WekaException if a problem occurs
     */
    @SuppressWarnings("unchecked")
    public static NamedTask jsonToNamedTask(String jsonTask) throws WekaException {
        if (jsonTask == null || jsonTask.length() == 0) {
            throw new WekaException("JSON task has size 0!");
        }

        ObjectMapper mapper = JsonFactory.create();
        Map<String, Object> taskMap = mapper.readValue(jsonTask, Map.class);

        String taskType = taskMap.get(TASK_TYPE_KEY).toString();
        NamedTask result = null;
        if (taskType.equals(TASK_TYPE_KF)) {
            result = jsonMapToKFTask(taskMap);
        } else {
            // TODO - other Task implementations supported by the JSON protocol and the
            // server
        }

        return result;
    }

    /**
     * Construct an unscheduled or scheduled Knowledge Flow task from the supplied
     * taskMap. <br>
     * <br>
     *
     * <pre>
     *         {
     *             taskName: name,
     *             taskType: type,
     *             schedule: [schedule json | null]
     *             kfFlow: kf json flow (as a sub map)
     *             sequential: [true | false]
     *             envVars: [environment variables json | null]
     *         }
     * </pre>
     *
     *
     *
     * @param taskMap map representation of the json source
     * @return a Knowledge Flow task
     * @throws WekaException if a problem occurs
     */
    @SuppressWarnings("unchecked")
    protected static NamedTask jsonMapToKFTask(Map<String, Object> taskMap) throws WekaException {
        NamedTask result = null;

        String name = taskMap.get(TASK_NAME_KEY).toString();
        // Object base64Flow = taskMap.get(KF_FLOW_PAYLOAD_ID);
        /*
         * if (base64Flow == null || base64Flow.toString().length() == 0) { throw
         * new WekaException("No flow resent in task!"); }
         */

        Map<String, String> envMap = (Map<String, String>) taskMap.get(ENV_PAYLOAD_ID);

        // have to transfer over to a new map because
        // org.boon.core.value.LazyValueMap
        // is not serializable
        Map<String, String> envMapNew = new HashMap<String, String>();
        envMapNew.putAll(envMap);

        Boolean sequential = (Boolean) taskMap.get(KF_SEQUENTIAL_PROPERTY);

        Object scheduleMap = taskMap.get(SCHEDULE_PAYLOAD_ID);

        // get flow as a string
        String jsonFlow = null;
        Map<String, Object> flowMap = (Map<String, Object>) taskMap.get(KF_FLOW_PAYLOAD_ID);
        if (flowMap == null) {
            throw new WekaException("No flow present in task!");
        }
        ObjectMapper mapper = JsonFactory.create();
        jsonFlow = mapper.writeValueAsString(flowMap);

        result = new UnscheduledNamedKnowledgeFlowTask(name, jsonFlow, sequential, envMapNew);
        if (scheduleMap != null) {
            Schedule schedule = jsonMapToSchedule((Map<String, Object>) scheduleMap);
            ScheduledNamedKnowledgeFlowTask newTask = new ScheduledNamedKnowledgeFlowTask(
                    (UnscheduledNamedKnowledgeFlowTask) result, schedule);
            result = newTask;
        }

        return result;
    }

    /**
     * Convert a supplied Knowledge Flow named task (scheduled or unscheduled)
     * into a json map
     * 
     * @param task the task to convert
     * @return the map representation of the task
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> kFTaskToJsonMap(NamedTask task) {
        Schedule schedule = task instanceof ScheduledNamedKnowledgeFlowTask
                ? ((ScheduledNamedKnowledgeFlowTask) task).getSchedule()
                : null;

        Map<String, Object> result = new HashMap<String, Object>();
        result.put(TASK_TYPE_KEY, TASK_TYPE_KF);
        String taskName = task.getName();
        result.put(TASK_NAME_KEY, taskName);

        Map<String, String> envMap = task instanceof ScheduledNamedKnowledgeFlowTask
                ? ((ScheduledNamedKnowledgeFlowTask) task).getParameters()
                : ((UnscheduledNamedKnowledgeFlowTask) task).getParameters();
        if (envMap != null) {
            result.put(ENV_PAYLOAD_ID, envMap);
        }
        boolean sequential = task instanceof ScheduledNamedKnowledgeFlowTask
                ? ((ScheduledNamedKnowledgeFlowTask) task).getSequentialExecution()
                : ((UnscheduledNamedKnowledgeFlowTask) task).getSequentialExecution();
        result.put(KF_SEQUENTIAL_PROPERTY, sequential);
        if (schedule != null) {
            Map<String, Object> scheduleMap = scheduleToJsonMap(schedule);
            result.put(SCHEDULE_PAYLOAD_ID, scheduleMap);
        }

        String jsonFlow = task instanceof UnscheduledNamedKnowledgeFlowTask
                ? ((UnscheduledNamedKnowledgeFlowTask) task).getFlowJSON()
                : ((ScheduledNamedKnowledgeFlowTask) task).getFlowJSON();

        ObjectMapper mapper = JsonFactory.create();
        Map<String, Object> flowMap = mapper.readValue(jsonFlow, Map.class);
        result.put(KF_FLOW_PAYLOAD_ID, flowMap);

        return result;
    }

    /**
     * Construct a Schedule object from the supplied json map.
     *
     * @param scheduleMap map representation of a schedule
     * @return a {@code Schedule} object
     * @throws WekaException if a problem occurs
     */
    public static Schedule jsonMapToSchedule(Map<String, Object> scheduleMap) throws WekaException {
        Schedule schedule = new Schedule();
        if (scheduleMap.containsKey(SCHEDULE_DATE_FORMAT_KEY)) {
            schedule.m_dateFormat = scheduleMap.get(SCHEDULE_DATE_FORMAT_KEY).toString();
        }

        try {
            schedule.setStartDate(schedule.parseDate(scheduleMap.get(SCHEDULE_START_DATE_KEY)));

            schedule.setEndDate(schedule.parseDate(scheduleMap.get(SCHEDULE_END_DATE_KEY)));

            if (scheduleMap.containsKey(SCHEDULE_REPEAT_UNIT_KEY)) {
                Schedule.Repeat r = Schedule.Repeat
                        .stringToValue(scheduleMap.get(SCHEDULE_REPEAT_UNIT_KEY).toString());
                if (r != null) {
                    schedule.setRepeatUnit(r);
                }
            }

            if (scheduleMap.containsKey(SCHEDULE_REPEAT_VALUE_KEY)) {
                schedule.setRepeatValue((Integer) scheduleMap.get(SCHEDULE_REPEAT_VALUE_KEY));
            }

            if (scheduleMap.containsKey(SCHEDULE_DAY_OF_WEEK_KEY)) {
                String csvDays = scheduleMap.get(SCHEDULE_DAY_OF_WEEK_KEY).toString();
                String[] days = csvDays.split(",");
                List<Integer> dowl = new ArrayList<Integer>();
                for (String dow : days) {
                    dowl.add(Schedule.stringDOWToCalendarDOW(dow));
                }
                schedule.setDayOfTheWeek(dowl);
            }

            if (scheduleMap.containsKey(SCHEDULE_MONTH_OF_YEAR_KEY)) {
                schedule.setMonthOfTheYear(Schedule
                        .stringMonthToCalendarMonth(scheduleMap.get(SCHEDULE_MONTH_OF_YEAR_KEY).toString()));
            }

            if (scheduleMap.containsKey(SCHEDULE_DAY_OF_MONTH_KEY)) {
                schedule.setDayOfTheMonth(Integer.parseInt(scheduleMap.get(SCHEDULE_DAY_OF_MONTH_KEY).toString()));
            }

            if (scheduleMap.containsKey(SCHEDULE_OCCURRENCE_KEY)) {
                Schedule.OccurrenceWithinMonth occ = Schedule.OccurrenceWithinMonth
                        .stringToValue(scheduleMap.get(SCHEDULE_OCCURRENCE_KEY).toString());
                if (occ != null) {
                    schedule.setOccurrenceWithinMonth(occ);
                }
            }
        } catch (ParseException e) {
            throw new WekaException(e);
        }

        return schedule;
    }

    /**
     * Convert a schedule object to a map (ready for encoding to a json string)
     *
     * @param schedule the schedule to convert
     * @return a map
     */
    protected static Map<String, Object> scheduleToJsonMap(Schedule schedule) {
        Map<String, Object> scheduleMap = new HashMap<String, Object>();

        scheduleMap.put(SCHEDULE_DATE_FORMAT_KEY, schedule.m_dateFormat);
        SimpleDateFormat sdf = new SimpleDateFormat(schedule.m_dateFormat);
        if (schedule.getStartDate() != null) {
            scheduleMap.put(SCHEDULE_START_DATE_KEY, sdf.format(schedule.getStartDate()));
        }
        if (schedule.getEndDate() != null) {
            scheduleMap.put(SCHEDULE_END_DATE_KEY, sdf.format(schedule.getEndDate()));
        }

        scheduleMap.put(SCHEDULE_REPEAT_UNIT_KEY, schedule.getRepeatUnit());
        scheduleMap.put(SCHEDULE_REPEAT_VALUE_KEY, schedule.getRepeatValue());
        if (schedule.getDayOfTheWeek() != null && schedule.getDayOfTheWeek().size() > 0) {
            String dowList = "";
            for (int dow : schedule.getDayOfTheWeek()) {
                dowList += "," + Schedule.calendarDOWToString(dow);
            }
            dowList = dowList.substring(1);
            scheduleMap.put(SCHEDULE_DAY_OF_WEEK_KEY, dowList);
        }

        if (schedule.getMonthOfTheYear() >= Calendar.JANUARY && schedule.getMonthOfTheYear() <= Calendar.DECEMBER) {
            scheduleMap.put(SCHEDULE_MONTH_OF_YEAR_KEY,
                    Schedule.calendarMonthToString(schedule.getMonthOfTheYear()));
        }

        scheduleMap.put(SCHEDULE_DAY_OF_MONTH_KEY, schedule.getDayOfTheMonth());
        if (schedule.getOccurrenceWithinMonth() != null) {
            scheduleMap.put(SCHEDULE_OCCURRENCE_KEY, schedule.getOccurrenceWithinMonth().toString());
        }

        return scheduleMap;
    }

    /**
     * Convert a {@code TaskStatusInfo} object to a map (ready for encoding to a
     * json string)
     *
     * @param statusInfo the status info object to convert
     * @param serializeResult true if the result object from the task status info
     *          should be serialized to base64
     * @return a map containing the parts of the task status info
     * @throws Exception if a problem occurs
     */
    protected static Map<String, Object> taskStatusInfoToJsonMap(TaskStatusInfo statusInfo, boolean serializeResult)
            throws Exception {
        Map<String, Object> statusMap = new HashMap<String, Object>();

        statusMap.put(TASK_STATUS_INFO_EXECUTION_STATUS_KEY, statusInfo.getExecutionStatus());
        statusMap.put(TASK_STATUS_INFO_STATUS_MESSAGE_KEY, statusInfo.getStatusMessage());
        if (serializeResult) {
            String base64Compressed = encodeToBase64(statusInfo.getTaskResult());
            statusMap.put(TASK_STATUS_INFO_TASK_RESULT_KEY, base64Compressed);
        }

        return statusMap;
    }

    /**
     * Construct a {@code TaskStatusInfo} object from the supplied map
     *
     * @param statusInfo map representation of a {@code TaskStatusInfo}
     * @return the reconstructed {@code TaskStatusInfo} object
     */
    public static TaskStatusInfo jsonMapToStatusInfo(Map<String, Object> statusInfo) {
        TaskStatusInfo result = new TaskStatusInfo();
        result.setExecutionStatus((Integer) statusInfo.get(TASK_STATUS_INFO_EXECUTION_STATUS_KEY));
        result.setStatusMessage(statusInfo.get(TASK_STATUS_INFO_STATUS_MESSAGE_KEY).toString());
        Object base64Compressed = statusInfo.get(TASK_STATUS_INFO_TASK_RESULT_KEY);
        if (base64Compressed != null && base64Compressed.toString().length() > 0) {
            result.setTaskResult(base64Compressed.toString());
        }

        return result;
    }

    /**
     * Encode an object to a base64 string of compressed bytes
     *
     * @param toEncode the object to encode
     * @return the encoded string
     * @throws IOException if a problem occurs
     */
    protected static String encodeToBase64(Object toEncode) throws IOException {
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(bao);
        ObjectOutputStream oo = new ObjectOutputStream(bos);
        oo.writeObject(toEncode);
        oo.flush();
        byte[] val = bao.toByteArray();

        String string;
        if (val == null) {
            string = null;
        } else {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            GZIPOutputStream gzos = new GZIPOutputStream(baos);
            bos = new BufferedOutputStream(gzos);
            bos.write(val);
            bos.flush();
            bos.close();

            string = new String(Base64.encodeBase64(baos.toByteArray()));
        }
        return string;
    }

    /**
     * Decode a base64 string (containing compressed bytes) back into an object
     *
     * @param base64 the base64 string
     * @return an decoded and deserialized object
     * @throws Exception if a problem occurs
     */
    public static Object decodeBase64(String base64) throws Exception {
        byte[] bytes = Base64.decodeBase64(base64.getBytes());

        byte[] result = null;
        Object resultObject = null;
        if (bytes != null && bytes.length > 0) {
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            GZIPInputStream gzip = new GZIPInputStream(bais);
            BufferedInputStream bi = new BufferedInputStream(gzip);

            result = new byte[] {};

            byte[] extra = new byte[1000000];
            int nrExtra = bi.read(extra);

            while (nrExtra >= 0) {
                // add it to bytes...
                int newSize = result.length + nrExtra;
                byte[] tmp = new byte[newSize];
                for (int i = 0; i < result.length; i++) {
                    tmp[i] = result[i];
                }
                for (int i = 0; i < nrExtra; i++) {
                    tmp[result.length + i] = extra[i];
                }

                // change the result
                result = tmp;
                nrExtra = bi.read(extra);
            }
            bytes = result;
            gzip.close();
        }

        if (result != null && result.length > 0) {
            ByteArrayInputStream bis = new ByteArrayInputStream(result);
            ObjectInputStream ois = new ObjectInputStream(bis);
            resultObject = ois.readObject();
            ois.close();
        }

        return resultObject;
    }
}