org.jberet.vertx.rest.JBeretRouterConfig.java Source code

Java tutorial

Introduction

Here is the source code for org.jberet.vertx.rest.JBeretRouterConfig.java

Source

/*
 * Copyright (c) 2017 Red Hat, Inc. and/or its affiliates.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Cheng Fang - Initial API and implementation
 */

package org.jberet.vertx.rest;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;

import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.VertxException;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.LocalMap;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import org.jberet.rest.entity.JobEntity;
import org.jberet.rest.entity.JobExecutionEntity;
import org.jberet.rest.entity.JobInstanceEntity;
import org.jberet.rest.entity.StepExecutionEntity;
import org.jberet.rest.service.JobService;

/**
 * This class is responsible for configuring the vert.x router for
 * servicing JBeret REST API. The follow shows how to configure it in your application:
 *
 * <pre>
 *   Router router = Router.router(vertx);
 *   JBeretRouterConfig.config(router);
 *   vertx.createHttpServer().requestHandler(router::accept).listen(8080);
 </pre>
 *
 * @since 1.3.0.Beta7
 */
public enum JBeretRouterConfig {
    ;

    /**
     * Default schedule delay is 5 minutes.
     */
    private static final int DEFAULT_SCHEDULE_DELAY = 5;

    /**
     * Name of the local map used to store job schedules
     */
    private static final String TIMER_LOCAL_MAP_NAME = "timer-local-map";

    /**
     * Configures REST API routes based on request URI.
     *
     * <ul>
     *     <li>
     *         Default mapping
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /
     *             <li>Query Params: None
     *             <li>Return: default return value
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/</li>
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get jobs
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /jobs
     *             <li>Query Params: None
     *             <li>Return: JSON array of jobs
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobs</li>
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Start a job execution with a job name
     *         <ul>
     *             <li>HTTP Method: POST
     *             <li>URI Pattern: /jobs/:jobXmlName/start
     *             <li>Query Params: 0 or more job parameters
     *             <li>Return: job execution as JSON
     *             <li>Examples:
     *                  <ul>
     *                     <li>http://localhost:8080/jobs/simple/start
     *                     <li>http://localhost:8080/jobs/simple/start?sleepSeconds=4&amp;foo=bar
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Schedule a job execution with a job name
     *         <ul>
     *             <li>HTTP Method: POST
     *             <li>URI Pattern: /jobs/:jobXmlName/schedule
     *             <li>Query Params:
     *                 <ul>
     *                     <li>delay: required, int number indicating the number of minutes to delay before starting job execution
     *                     <li>periodic: flag to control whether the schedule is recurring or not, optional and defaults to false
     *                 </ul>
     *             </li>
     *             <li>Return: job schedule as JSON
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobs/simple/schedule?delay=1
     *                     <li>http://localhost:8080/jobs/simple/schedule?delay=1&amp;periodic=true
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Restart the most recently failed or stopped job execution belonging to the job name
     *         <ul>
     *             <li>HTTP Method: POST
     *             <li>URI Pattern: /jobs/:jobXmlName/restart
     *             <li>Query Params: 0 or more job parameters to override the corresponding original job parameters
     *             <li>Return: job execution as JSON
     *             <li>Examples:
     *                  <ul>
     *                     <li>http://localhost:8080/jobs/simple/restart
     *                     <li>http://localhost:8080/jobs/simple/restart?sleepSeconds=5&amp;foo=bar
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get job instances
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /jobinstances
     *             <li>Query Params:
     *                 <ul>
     *                     <li>jobName: the job name used to get the associated job instances, required unless jobExecutionId is present
     *                     <li>start: the offset position in the list of all eligible job instances to include, optional and defaults to 0
     *                     <li>count: the number of job instances to return, optional and defaults to all job instances
     *                     <li>jobExecutionId: the job execution id used to get the associated job instance.
     *                         This param should not be used along with jobName, start, or count.
     *                 </ul>
     *             </li>
     *             <li>Return: JSON array of job instances
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobinstances?jobName=simple&amp;count=2
     *                     <li>http://localhost:8080/jobinstances?jobExecutionId=1
     *                     <li>http://localhost:8080/jobinstances?jobName=simple&amp;count=2&amp;start=1
     *                     <li>http://localhost:8080/jobinstances/count?jobName=simple
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Count the number of job instances
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /jobinstances/count
     *             <li>Query Params:
     *                 <ul>
     *                     <li>jobName: the job name used to get the associated job instances, required.
     *                 </ul>
     *             </li>
     *             <li>Return: number of job instances
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobinstances/count?jobName=simple
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get job executions
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /jobexecutions
     *             <li>Query Params:
     *                 <ul>
     *                     <li>count: the number of job executions to return, optional and defaults to all matching job executions
     *                     <li>jobExecutionId1: the job execution id whose sibling job executions will be returned, optional and defaults to unspecified
     *                 </ul>
     *             </li>
     *             <li>Return: JSON array of job executions
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobexecutions
     *                     <li>http://localhost:8080/jobexecutions?count=5
     *                     <li>http://localhost:8080/jobexecutions?jobExecutionId1=2
     *                     <li>http://localhost:8080/jobexecutions?jobExecutionId1=1&amp;count=10
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get running job executions associated with a job name
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /jobexecutions/running
     *             <li>Query Params:
     *                 <ul>
     *                     <li>jobName: the job name used to get the associated job instances, required.
     *                 </ul>
     *             </li>
     *             <li>Return: JSON array of job executions
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobexecutions/running?jobName=simple'
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get job execution by id
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /jobexecutions/:jobExecutionId
     *             <li>Query Params: None
     *             <li>Return: job execution as JSON
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobexecutions/1
     *                     <li>http://localhost:8080/jobexecutions/2
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get step executions belonging to a particular job execution
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /jobexecutions/:jobExecutionId/stepexecutions
     *             <li>Query Params: None
     *             <li>Return: JSON array of step executions
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobexecutions/1/stepexecutions
     *                     <li>http://localhost:8080/jobexecutions/15/stepexecutions
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get the step execution by job execution id and step execution id
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /jobexecutions/:jobExecutionId/stepexecutions/:stepExecutionId
     *             <li>Query Params: None
     *             <li>Return: step execution as JSON
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobexecutions/1/stepexecutions/1
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Abandon a job execution
     *         <ul>
     *             <li>HTTP Method: POST
     *             <li>URI Pattern: /jobexecutions/:jobExecutionId/abandon
     *             <li>Query Params: None
     *             <li>Return: void
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobexecutions/1/abandon
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Stop a job execution
     *         <ul>
     *             <li>HTTP Method: POST
     *             <li>URI Pattern: /jobexecutions/:jobExecutionId/stop
     *             <li>Query Params: None
     *             <li>Return: void
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobexecutions/2/stop
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Restart a failed or stopped job execution
     *         <ul>
     *             <li>HTTP Method: POST
     *             <li>URI Pattern: /jobexecutions/:jobExecutionId/restart
     *             <li>Query Params: 0 or more job parameters to override the corresponding original job parameters
     *             <li>Return: job execution as JSON
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/jobexecutions/1/restart
     *                     <li>http://localhost:8080/jobexecutions/1/restart?sleepSeconds=3&amp;foo=buzz
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get all job schedules
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /schedules
     *             <li>Query Params: None
     *             <li>Return: JSON array of job schedules
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/schedules
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get job schedule by id
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /schedules/:scheduleId
     *             <li>Query Params: None
     *             <li>Return: job schedule as JSON
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/schedules/2
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get all timezone values available to job schedules
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /schedules/timezones
     *             <li>Query Params: None
     *             <li>Return: JSON array of all timezone values
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/schedules/timezones
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Get supported extra features as a string array, currently return []
     *         <ul>
     *             <li>HTTP Method: GET
     *             <li>URI Pattern: /schedules/features
     *             <li>Query Params: None
     *             <li>Return: JSON array, currently return empty array
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/schedules/features
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Cancel a job schedule
     *         <ul>
     *             <li>HTTP Method: POST
     *             <li>URI Pattern: /schedules/:scheduleId/cancel
     *             <li>Query Params: None
     *             <li>Return: true if the job schedule is cancelled successfully; false otherwise
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/schedules/0/cancel
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     *     <li>
     *         Delete a job schedule
     *         <ul>
     *             <li>HTTP Method: DELETE
     *             <li>URI Pattern: /schedules/:scheduleId
     *             <li>Query Params: None
     *             <li>Return: void
     *             <li>Examples:
     *                 <ul>
     *                     <li>http://localhost:8080/schedules/0
     *                 </ul>
     *             </li>
     *         </ul>
     *     </li>
     * </ul>
     *
     * @param router the vert.x router for the current request
     */
    public static void config(final Router router) {
        router.route().handler(BodyHandler.create());
        router.get("/").handler(JBeretRouterConfig::getDefault);
        router.get("/jobs").handler(JBeretRouterConfig::getJobs);

        router.post("/jobs/:jobXmlName/start").handler(JBeretRouterConfig::startJob);
        router.post("/jobs/:jobXmlName/schedule").handler(JBeretRouterConfig::scheduleJob);
        router.post("/jobs/:jobXmlName/restart").handler(JBeretRouterConfig::restartJob);

        router.get("/jobinstances").handler(JBeretRouterConfig::getJobInstances);
        router.get("/jobinstances/count").handler(JBeretRouterConfig::getJobInstanceCount);

        router.get("/jobexecutions").handler(JBeretRouterConfig::getJobExecutions);
        router.get("/jobexecutions/running").handler(JBeretRouterConfig::getRunningExecutions);
        router.get("/jobexecutions/:jobExecutionId").handler(JBeretRouterConfig::getJobExecution);
        router.get("/jobexecutions/:jobExecutionId/stepexecutions").handler(JBeretRouterConfig::getStepExecutions);
        router.get("/jobexecutions/:jobExecutionId/stepexecutions/:stepExecutionId")
                .handler(JBeretRouterConfig::getStepExecution);

        router.post("/jobexecutions/:jobExecutionId/abandon").handler(JBeretRouterConfig::abandonJobExecution);
        router.post("/jobexecutions/:jobExecutionId/stop").handler(JBeretRouterConfig::stopJobExecution);
        router.post("/jobexecutions/:jobExecutionId/restart").handler(JBeretRouterConfig::restartJobExecution);

        router.get("/schedules").handler(JBeretRouterConfig::getJobSchedules);
        router.get("/schedules/timezones").handler(JBeretRouterConfig::getJobSchedulesTimezone);
        router.get("/schedules/features").handler(JBeretRouterConfig::getJobSchedulesFeatures);
        router.get("/schedules/:scheduleId").handler(JBeretRouterConfig::getJobSchedule);
        router.post("/schedules/:scheduleId/cancel").handler(JBeretRouterConfig::cancelJobSchedule);
        router.delete("/schedules/:scheduleId").handler(JBeretRouterConfig::deleteJobSchedule);
    }

    private static void getDefault(final RoutingContext routingContext) {
        routingContext.response().end("JBeret Vert.x REST API");
    }

    private static void getJobs(final RoutingContext routingContext) {
        final JobEntity[] jobEntities = JobService.getInstance().getJobs();
        final JsonArray jsonArray = new JsonArray();
        for (JobEntity jobEntity : jobEntities) {
            jsonArray.add(JsonObject.mapFrom(jobEntity));
        }
        sendJsonResponse(routingContext, jsonArray.encodePrettily());
    }

    private static void startJob(final RoutingContext routingContext) {
        final String jobXmlName = routingContext.pathParam("jobXmlName");
        final Properties jobParams = getJobParameters(routingContext);
        final JobExecutionEntity jobExecutionEntity = JobService.getInstance().start(jobXmlName, jobParams);

        setJobExecutionEntityHref(routingContext, jobExecutionEntity);
        final JsonObject jsonObject = JsonObject.mapFrom(jobExecutionEntity);
        sendJsonResponse(routingContext, jsonObject.encodePrettily());
    }

    private static void scheduleJob(final RoutingContext routingContext) {
        final String jobXmlName = routingContext.pathParam("jobXmlName");
        final HttpServerRequest request = routingContext.request();
        final String delayString = request.getParam("delay");
        final long delay = delayString == null ? DEFAULT_SCHEDULE_DELAY : Long.parseLong(delayString);
        final String periodicString = request.getParam("periodic");
        final boolean periodic = periodicString != null && Boolean.parseBoolean(periodicString);

        final Properties jobParams = getJobParameters(routingContext);
        final JobSchedule jobSchedule = new JobSchedule();
        jobSchedule.setDelay(delay);
        jobSchedule.setJobName(jobXmlName);
        jobSchedule.setJobParameters(jobParams);

        final long delayMillis = delay * 60 * 1000;
        final long timerId;
        final Vertx vertx = routingContext.vertx();
        if (!periodic) {
            timerId = vertx.setTimer(delayMillis, timerId1 -> {
                final JobExecutionEntity jobExecutionEntity = JobService.getInstance().start(jobXmlName, jobParams);
                setJobExecutionEntityHref(routingContext, jobExecutionEntity);
                jobSchedule.addJobExecutionIds(jobExecutionEntity.getExecutionId());
                jobSchedule.setStatus(JobSchedule.Status.DONE);
            });
        } else {
            timerId = vertx.setPeriodic(delayMillis, timerId1 -> {
                final JobExecutionEntity jobExecutionEntity = JobService.getInstance().start(jobXmlName, jobParams);
                setJobExecutionEntityHref(routingContext, jobExecutionEntity);
                jobSchedule.addJobExecutionIds(jobExecutionEntity.getExecutionId());

                //                 since this is periodic timer, there may be more in the future
                //                jobSchedule.setStatus(JobSchedule.Status.DONE);
            });
        }
        jobSchedule.setId(timerId);
        final LocalMap<String, JobSchedule> timerLocalMap = getTimerLocalMap(vertx);
        timerLocalMap.put(String.valueOf(timerId), jobSchedule);
        final JsonObject jsonObject = JsonObject.mapFrom(jobSchedule);
        sendJsonResponse(routingContext, jsonObject.encodePrettily());
    }

    private static void restartJob(final RoutingContext routingContext) {
        final String jobXmlName = routingContext.pathParam("jobXmlName");
        final Properties jobParams = getJobParameters(routingContext);

        final JobInstanceEntity[] jobInstances = JobService.getInstance().getJobInstances(jobXmlName, 0, 1);
        if (jobInstances.length > 0) {
            final long latestJobExecutionId = jobInstances[0].getLatestJobExecutionId();
            final JobExecutionEntity jobExecutionEntity = JobService.getInstance().restart(latestJobExecutionId,
                    jobParams);
            setJobExecutionEntityHref(routingContext, jobExecutionEntity);
            final JsonObject jsonObject = JsonObject.mapFrom(jobExecutionEntity);
            sendJsonResponse(routingContext, jsonObject.encodePrettily());
        } else {
            throw new VertxException(routingContext.normalisedPath());
        }
    }

    private static void getJobExecution(final RoutingContext routingContext) {
        final long jobExecutionId = getIdAsLong(routingContext, "jobExecutionId");
        final JobExecutionEntity jobExecutionEntity = JobService.getInstance().getJobExecution(jobExecutionId);
        setJobExecutionEntityHref(routingContext, jobExecutionEntity);
        final JsonObject jsonObject = JsonObject.mapFrom(jobExecutionEntity);
        sendJsonResponse(routingContext, jsonObject.encodePrettily());
    }

    private static void getStepExecutions(final RoutingContext routingContext) {
        final long jobExecutionId = getIdAsLong(routingContext, "jobExecutionId");
        final StepExecutionEntity[] stepExecutions = JobService.getInstance().getStepExecutions(jobExecutionId);

        final JsonArray jsonArray = new JsonArray();
        for (StepExecutionEntity e : stepExecutions) {
            jsonArray.add(JsonObject.mapFrom(e));
        }
        sendJsonResponse(routingContext, jsonArray.encodePrettily());
    }

    private static void getStepExecution(final RoutingContext routingContext) {
        final long jobExecutionId = getIdAsLong(routingContext, "jobExecutionId");
        final long stepExecutionId = getIdAsLong(routingContext, "stepExecutionId");
        StepExecutionEntity stepExecutionFound = null;
        final StepExecutionEntity[] stepExecutions = JobService.getInstance().getStepExecutions(jobExecutionId);
        for (StepExecutionEntity e : stepExecutions) {
            if (e.getStepExecutionId() == stepExecutionId) {
                stepExecutionFound = e;
            }
        }

        final JsonObject jsonObject = JsonObject.mapFrom(stepExecutionFound);
        sendJsonResponse(routingContext, jsonObject.encodePrettily());
    }

    private static void abandonJobExecution(final RoutingContext routingContext) {
        final long jobExecutionId = getIdAsLong(routingContext, "jobExecutionId");
        JobService.getInstance().abandon(jobExecutionId);
        routingContext.response().end();
    }

    private static void stopJobExecution(final RoutingContext routingContext) {
        final long jobExecutionId = getIdAsLong(routingContext, "jobExecutionId");
        JobService.getInstance().stop(jobExecutionId);
        routingContext.response().end();
    }

    private static void restartJobExecution(final RoutingContext routingContext) {
        final long jobExecutionId = getIdAsLong(routingContext, "jobExecutionId");
        final Properties jobParams = getJobParameters(routingContext);
        final JobExecutionEntity jobExecutionEntity = JobService.getInstance().restart(jobExecutionId, jobParams);
        setJobExecutionEntityHref(routingContext, jobExecutionEntity);
        final JsonObject jsonObject = JsonObject.mapFrom(jobExecutionEntity);
        sendJsonResponse(routingContext, jsonObject.encodePrettily());
    }

    private static void getRunningExecutions(final RoutingContext routingContext) {
        final String jobName = routingContext.request().getParam("jobName");
        final JobExecutionEntity[] runningExecutions = JobService.getInstance().getRunningExecutions(jobName);
        final JsonArray jsonArray = new JsonArray();
        for (JobExecutionEntity e : runningExecutions) {
            setJobExecutionEntityHref(routingContext, e);
            jsonArray.add(JsonObject.mapFrom(e));
        }
        sendJsonResponse(routingContext, jsonArray.encodePrettily());
    }

    private static void getJobExecutions(final RoutingContext routingContext) {
        final String jobExecutionId1String = routingContext.request().getParam("jobExecutionId1");
        final long jobExecutionId1 = jobExecutionId1String == null ? 0 : Long.parseLong(jobExecutionId1String);
        final String countString = routingContext.request().getParam("count");
        final int count = countString == null ? 0 : Integer.parseInt(countString);

        //jobExecutionId1 is used to retrieve the JobInstance, from which to get all its JobExecution's
        //jobInstanceId param is currently not used.
        final JobExecutionEntity[] jobExecutionEntities = JobService.getInstance().getJobExecutions(count, 0,
                jobExecutionId1);
        final JsonArray jsonArray = new JsonArray();
        for (JobExecutionEntity e : jobExecutionEntities) {
            setJobExecutionEntityHref(routingContext, e);
            jsonArray.add(JsonObject.mapFrom(e));
        }
        sendJsonResponse(routingContext, jsonArray.encodePrettily());
    }

    private static void getJobInstances(final RoutingContext routingContext) {
        final HttpServerRequest request = routingContext.request();

        final String jobName = request.getParam("jobName");

        final String startString = request.getParam("start");
        final int start = startString == null ? 0 : Integer.parseInt(startString);

        final String countString = request.getParam("count");
        final int count = countString == null ? 0 : Integer.parseInt(countString);

        final String jobExecutionIdString = request.getParam("jobExecutionId");
        final long jobExecutionId = jobExecutionIdString == null ? 0 : Long.parseLong(jobExecutionIdString);

        if (jobExecutionId > 0) {
            final JobInstanceEntity jobInstanceData = JobService.getInstance().getJobInstance(jobExecutionId);
            final JsonObject jsonObject = JsonObject.mapFrom(jobInstanceData);
            sendJsonResponse(routingContext, jsonObject.encodePrettily());
        } else {
            final JobInstanceEntity[] jobInstanceData = JobService.getInstance().getJobInstances(
                    jobName == null ? "*" : jobName, start, count == 0 ? Integer.MAX_VALUE : count);
            final JsonArray jsonArray = new JsonArray();
            for (JobInstanceEntity e : jobInstanceData) {
                jsonArray.add(JsonObject.mapFrom(e));
            }
            sendJsonResponse(routingContext, jsonArray.encodePrettily());
        }
    }

    private static void getJobInstanceCount(final RoutingContext routingContext) {
        final String jobName = routingContext.request().getParam("jobName");
        final int jobInstanceCount = JobService.getInstance().getJobInstanceCount(jobName);
        routingContext.response().end(String.valueOf(jobInstanceCount));
    }

    private static void getJobSchedules(final RoutingContext routingContext) {
        final LocalMap<String, JobSchedule> timerLocalMap = getTimerLocalMap(routingContext.vertx());
        final JsonArray jsonArray = new JsonArray();
        for (JobSchedule jobSchedule : timerLocalMap.values()) {
            jsonArray.add(JsonObject.mapFrom(jobSchedule));
        }
        sendJsonResponse(routingContext, jsonArray.encodePrettily());
    }

    private static void getJobSchedule(final RoutingContext routingContext) {
        final JobSchedule jobSchedule = lookupJobScheduleWithPathParam(routingContext);
        final JsonObject jsonObject = JsonObject.mapFrom(jobSchedule);
        sendJsonResponse(routingContext, jsonObject.encodePrettily());
    }

    private static void cancelJobSchedule(final RoutingContext routingContext) {
        final JobSchedule jobSchedule = lookupJobScheduleWithPathParam(routingContext);
        boolean cancelled = false;

        if (jobSchedule != null) {
            cancelled = routingContext.vertx().cancelTimer(jobSchedule.getId());
            if (cancelled) {
                jobSchedule.setStatus(JobSchedule.Status.CANCELLED);
            }
        }
        routingContext.response().end(String.valueOf(cancelled));
    }

    private static void getJobSchedulesFeatures(final RoutingContext routingContext) {
        routingContext.response().end(new JsonArray().encode());
    }

    private static void getJobSchedulesTimezone(final RoutingContext routingContext) {
        final String[] availableIDs = TimeZone.getAvailableIDs();
        Arrays.sort(availableIDs);
        final int i = Arrays.binarySearch(availableIDs, TimeZone.getDefault().getID());
        final String[] result = new String[availableIDs.length];
        result[0] = availableIDs[i];
        System.arraycopy(availableIDs, 0, result, 1, i);
        System.arraycopy(availableIDs, i + 1, result, i + 1, availableIDs.length - (i + 1));

        final JsonArray jsonArray = new JsonArray();
        Arrays.stream(result).forEach(jsonArray::add);
        sendJsonResponse(routingContext, jsonArray.encodePrettily());
    }

    private static void deleteJobSchedule(final RoutingContext routingContext) {
        final String idString = routingContext.pathParam("scheduleId");
        final LocalMap<String, JobSchedule> timerLocalMap = getTimerLocalMap(routingContext.vertx());
        final JobSchedule removedItem = timerLocalMap.remove(idString);

        if (removedItem != null) {
            routingContext.vertx().cancelTimer(removedItem.getId());
        }
        routingContext.response().end();
    }

    // Helper methods

    private static Properties getJobParameters(final RoutingContext routingContext) {
        final MultiMap params = routingContext.request().params();
        final List<Map.Entry<String, String>> entries = params.entries();
        final Properties jobParams = new Properties();
        for (Map.Entry<String, String> entry : entries) {
            final String key = entry.getKey();
            final String value = entry.getValue();
            jobParams.setProperty(key, value);
        }
        return jobParams;
    }

    private static long getIdAsLong(final RoutingContext routingContext, final String pathParamKey) {
        final String idString = routingContext.pathParam(pathParamKey);
        return Long.parseLong(idString);
    }

    private static void sendJsonResponse(final RoutingContext routingContext, String body) {
        routingContext.response().putHeader("content-type", "application/json").end(body);
    }

    private static LocalMap<String, JobSchedule> getTimerLocalMap(Vertx vertx) {
        return vertx.sharedData().getLocalMap(TIMER_LOCAL_MAP_NAME);
    }

    private static JobSchedule lookupJobScheduleWithPathParam(final RoutingContext routingContext) {
        final LocalMap<String, JobSchedule> timerLocalMap = getTimerLocalMap(routingContext.vertx());
        final String idString = routingContext.pathParam("scheduleId");
        return timerLocalMap.get(idString);
    }

    /**
     * Sets the href field for each {@code org.jberet.rest.entity.JobExecutionEntity} passed in.
     *
     * @param routingContext io.vertx.ext.web.RoutingContext
     * @param entities 1 or more {@code org.jberet.rest.entity.JobExecutionEntity}
     */
    private static void setJobExecutionEntityHref(final RoutingContext routingContext,
            final JobExecutionEntity... entities) {
        final HttpServerRequest request = routingContext.request();
        final String absoluteURI = request.absoluteURI();
        final String uri = request.uri();
        final String baseURI = absoluteURI.substring(0, absoluteURI.length() - uri.length());
        for (final JobExecutionEntity e : entities) {
            e.setHref(baseURI + "/jobexecutions/" + e.getExecutionId());
        }
    }
}