com.edduarte.vokter.job.JobManager.java Source code

Java tutorial

Introduction

Here is the source code for com.edduarte.vokter.job.JobManager.java

Source

/*
 * Copyright 2015 Eduardo Duarte
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.edduarte.vokter.job;

import com.edduarte.vokter.diff.Difference;
import com.edduarte.vokter.diff.DifferenceMatcher;
import com.edduarte.vokter.keyword.Keyword;
import com.edduarte.vokter.model.v1.SubscribeRequest;
import com.edduarte.vokter.util.Constants;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.quartz.*;
import org.quartz.impl.DirectSchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.simpl.SimpleThreadPool;
import org.quartz.spi.JobStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Calendar;
import java.util.*;

/**
 * @author Eduardo Duarte (<a href="mailto:hello@edduarte.com">hello@edduarte.com</a>)
 * @version 1.3.3
 * @since 1.0.0
 */
public class JobManager {

    private static final Logger logger = LoggerFactory.getLogger(JobManager.class);

    private static final String SCHEDULER_NAME = "Vokter_Scheduler";

    private static final Map<String, JobManager> activeManagers = new HashMap<>();

    private final String managerName;

    private final JobManagerHandler handler;

    private final int detectionInterval;

    private Scheduler scheduler;

    private JobManager(final String managerName, int detectionInterval, final JobManagerHandler handler) {
        this.managerName = managerName;
        this.handler = handler;
        this.detectionInterval = detectionInterval;
    }

    public static JobManager create(final String managerName, final int detectionInterval,
            final JobManagerHandler handler) {
        JobManager existingManager = get(managerName);
        if (existingManager != null) {
            existingManager.stop();
        }

        JobManager newManager = new JobManager(managerName, detectionInterval, handler);
        activeManagers.put(managerName, newManager);
        return newManager;
    }

    public static JobManager get(final String managerName) {
        return activeManagers.get(managerName);
    }

    public void initialize() throws SchedulerException {
        StdSchedulerFactory factory = new StdSchedulerFactory();
        scheduler = factory.getScheduler();
        factory = null;
        scheduler.start();
    }

    public void initialize(JobStore jobStore, int maxThreads) throws SchedulerException {
        DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
        factory.createScheduler(SCHEDULER_NAME, Constants.bytesToHex(Constants.generateRandomBytes()),
                new SimpleThreadPool(maxThreads, 5), jobStore);
        scheduler = factory.getScheduler(SCHEDULER_NAME);
        factory = null;
        scheduler.start();
    }

    public boolean createJob(final SubscribeRequest request) {

        String documentUrl = request.getDocumentUrl();
        String clientUrl = request.getClientUrl();

        try {
            // attempt creating a new DiffDetectorJob
            JobDetail detectionJob = JobBuilder.newJob(DetectionJob.class)
                    .withIdentity(documentUrl, "detection" + documentUrl)
                    .usingJobData(DetectionJob.PARENT_JOB_MANAGER, managerName)
                    .usingJobData(DetectionJob.FAULT_COUNTER, 0).build();

            Trigger detectionTrigger = TriggerBuilder.newTrigger()
                    .withIdentity(documentUrl, "detection" + documentUrl).withSchedule(SimpleScheduleBuilder
                            .simpleSchedule().withIntervalInSeconds(detectionInterval).repeatForever())
                    .build();

            try {
                scheduler.scheduleJob(detectionJob, detectionTrigger);
                logger.info("Started detection job for '{}'.", documentUrl);
            } catch (ObjectAlreadyExistsException ignored) {
                // there is already a job monitoring the request url, so ignore this
            }

            ObjectMapper mapper = new ObjectMapper();
            String keywordJson = mapper.writeValueAsString(request.getKeywords());

            JobDetail matchingJob = JobBuilder.newJob(MatchingJob.class)
                    .withIdentity(clientUrl, "matching" + documentUrl)
                    .usingJobData(MatchingJob.PARENT_JOB_MANAGER, managerName)
                    .usingJobData(MatchingJob.REQUEST_URL, documentUrl)
                    .usingJobData(MatchingJob.KEYWORDS, keywordJson)
                    .usingJobData(MatchingJob.IGNORE_ADDED, request.getIgnoreAdded())
                    .usingJobData(MatchingJob.IGNORE_REMOVED, request.getIgnoreRemoved())
                    .usingJobData(MatchingJob.HAS_NEW_DIFFS, false).build();

            GregorianCalendar cal = new GregorianCalendar();
            cal.add(Calendar.SECOND, request.getInterval());

            Trigger matchingTrigger = TriggerBuilder.newTrigger().withIdentity(clientUrl, "matching" + documentUrl)
                    .startAt(cal.getTime()).withSchedule(SimpleScheduleBuilder.simpleSchedule()
                            .withIntervalInSeconds(request.getInterval()).repeatForever())
                    .build();

            try {
                scheduler.scheduleJob(matchingJob, matchingTrigger);
            } catch (ObjectAlreadyExistsException ex) {
                return false;
            }

        } catch (SchedulerException | JsonProcessingException ex) {
            logger.error(ex.getMessage(), ex);
        }

        return true;
    }

    void timeoutDetectionJob(String documentUrl) {
        try {
            Set<JobKey> keys = scheduler.getJobKeys(GroupMatcher.groupEquals("matching" + documentUrl));
            for (JobKey k : keys) {
                String clientUrl = k.getName();
                sendTimeoutResponse(documentUrl, clientUrl);
                scheduler.interrupt(k);
                scheduler.deleteJob(k);
            }

            JobKey detectJobKey = new JobKey(documentUrl, "detection" + documentUrl);
            scheduler.interrupt(detectJobKey);
            scheduler.deleteJob(detectJobKey);
            handler.removeExistingDifferences(documentUrl);
            logger.info("Timed-out detection job for '{}'.", documentUrl);
        } catch (SchedulerException | JsonProcessingException ex) {
            logger.error(ex.getMessage(), ex);
        }
    }

    public boolean cancelMatchingJob(String documentUrl, final String clientUrl) {
        JobKey matchingJobKey = new JobKey(clientUrl, "matching" + documentUrl);
        return cancelMatchingJobAux(documentUrl, matchingJobKey);
    }

    private boolean cancelMatchingJobAux(String documentUrl, JobKey jobKey) {
        try {
            scheduler.interrupt(jobKey);
            boolean wasDeleted = scheduler.deleteJob(jobKey);

            if (wasDeleted) {
                // check if there are more match jobs for the same request url
                Set<JobKey> keys = scheduler.getJobKeys(GroupMatcher.groupEquals("matching" + documentUrl));
                if (keys.isEmpty()) {
                    // no more matching jobs for this document! interrupt the
                    // DiffDetectJob
                    JobKey detectJobKey = new JobKey(documentUrl, "detection" + documentUrl);
                    scheduler.interrupt(detectJobKey);
                    scheduler.deleteJob(detectJobKey);
                    handler.removeExistingDifferences(documentUrl);
                    logger.info("Canceled detection job for '{}'.", documentUrl);
                }
            }

            return wasDeleted;

        } catch (SchedulerException ex) {
            logger.error(ex.getMessage(), ex);
        }
        return false;
    }

    public void stop() {
        try {
            scheduler.shutdown();
        } catch (SchedulerException ex) {
            logger.error(ex.getMessage(), ex);
        }
    }

    final boolean callDetectDiffImpl(String documentUrl) {
        boolean wasSuccessful = handler.detectDifferences(documentUrl);

        // notify all matching jobs of that url that there are new differences to match
        if (wasSuccessful) {
            try {
                Set<JobKey> keys = scheduler.getJobKeys(GroupMatcher.groupEquals("matching" + documentUrl));
                for (JobKey k : keys) {
                    JobDetail jobDetail = scheduler.getJobDetail(k);
                    List<? extends Trigger> triggerList = scheduler.getTriggersOfJob(k);
                    if (!triggerList.isEmpty()) {
                        Trigger trigger = triggerList.get(0);

                        // update job to say that he has new diffs
                        JobDataMap dataMap = jobDetail.getJobDataMap();
                        dataMap.put(MatchingJob.HAS_NEW_DIFFS, true);

                        attemptRefreshJob(jobDetail, trigger, 10);

                    } else {
                        // invalid state, where there is still an active job
                        // without triggers
                        // unschedule it!
                        cancelMatchingJobAux(documentUrl, k);
                    }
                }
            } catch (SchedulerException ex) {
                logger.error(ex.getMessage(), ex);
            }
        }

        return wasSuccessful;
    }

    private void attemptRefreshJob(JobDetail jobDetail, Trigger trigger, int tries) {
        if (tries > 0) {
            try {
                scheduler.unscheduleJob(trigger.getKey());
                scheduler.scheduleJob(jobDetail, trigger);
            } catch (SchedulerException ignored) {
                // could not refresh job because the it did not unschedule properly
                int newCount = tries - 1;
                logger.info("Error while refreshing job, attempting " + newCount + " more times.");
                attemptRefreshJob(jobDetail, trigger, newCount);
            }
        }
    }

    final List<Difference> callGetDiffsImpl(String url) {
        return handler.getExistingDifferences(url);
    }

    final Keyword callBuildKeyword(String keywordInput) {
        return handler.buildKeyword(keywordInput);
    }

    final boolean responseOk(final String documentUrl, final String clientUrl,
            final Set<DifferenceMatcher.Result> diffs) throws JsonProcessingException {
        Map<String, Object> jsonResponseMap = new LinkedHashMap<>();
        jsonResponseMap.put("status", "ok");
        jsonResponseMap.put("url", documentUrl);
        jsonResponseMap.put("diffs", diffs);

        ObjectMapper mapper = new ObjectMapper();
        String input = mapper.writeValueAsString(jsonResponseMap);
        return sendResponse(clientUrl, input);
    }

    final boolean sendTimeoutResponse(final String documentUrl, final String clientUrl)
            throws JsonProcessingException {
        Map<String, Object> jsonResponseMap = new LinkedHashMap<>();
        jsonResponseMap.put("status", "timeout");
        jsonResponseMap.put("url", documentUrl);
        Set<DifferenceMatcher.Result> diffs = Collections.emptySet();
        jsonResponseMap.put("diffs", diffs);

        ObjectMapper mapper = new ObjectMapper();
        String input = mapper.writeValueAsString(jsonResponseMap);
        return sendResponse(clientUrl, input);
    }

    private boolean sendResponse(final String clientUrl, final String input) {
        try {
            URL targetUrl = new URL(clientUrl);

            HttpURLConnection httpConnection = (HttpURLConnection) targetUrl.openConnection();
            httpConnection.setDoOutput(true);
            httpConnection.setRequestMethod("POST");
            httpConnection.setRequestProperty("Content-Type", "application/json");

            OutputStream outputStream = httpConnection.getOutputStream();
            outputStream.write(input.getBytes());
            outputStream.flush();

            int responseCode = httpConnection.getResponseCode();
            httpConnection.disconnect();
            return responseCode == 200;

        } catch (IOException ex) {
            logger.error(ex.getMessage(), ex);
        }
        return false;
    }
}