rapture.dp.invocable.CheckPrerequisiteStep.java Source code

Java tutorial

Introduction

Here is the source code for rapture.dp.invocable.CheckPrerequisiteStep.java

Source

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2011-2016 Incapture Technologies LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package rapture.dp.invocable;

import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalTime;
import org.joda.time.format.DateTimeFormat;

import rapture.common.CallingContext;
import rapture.common.RaptureURI;
import rapture.common.Scheme;
import rapture.common.SeriesPoint;
import rapture.common.USCalendar;
import rapture.common.dp.AbstractInvocable;
import rapture.common.exception.RaptureExceptionFactory;
import rapture.common.impl.jackson.JacksonUtil;
import rapture.kernel.Kernel;

public class CheckPrerequisiteStep extends AbstractInvocable {

    public static final String CONFIG_URI = "prerequisiteConfigUri";
    private static final Logger log = Logger.getLogger(CheckPrerequisiteStep.class);
    private Set<String> isDataReady;

    public CheckPrerequisiteStep(String workerURI, String stepName) {
        super(workerURI, stepName);
        isDataReady = new HashSet<String>();
    }

    @Override
    public String invoke(CallingContext ctx) {
        PrerequisiteConfig config = getPrerequisiteConfig(ctx);
        log.info("config = " + JacksonUtil.jsonFromObject(config));
        DateTime cutoffTime = null;
        if (StringUtils.trimToNull(config.getCutoffTime()) == null) {
            log.warn("No timeout/cutoff time defined - Will wait forever");
        } else {
            cutoffTime = getDateTime(config.getCutoffTime());
        }
        String cutoffAction = getCutoffAction(config.getCutoffAction());

        while (true) {
            // check if reached cutoff time
            if ((cutoffTime != null) && !cutoffTime.isAfterNow()) {
                log.info("Reached cut off time, return " + cutoffAction);
                return cutoffAction;
            }
            // check last data point
            if (isDataReady(ctx, config)) {
                log.info("Data is ready, return next");
                return NEXT;
            }
            // data not ready, get next retry time
            DateTime now = DateTime.now();
            DateTime nextRetryTime = now.plusMillis(config.getRetryInMillis());
            if ((cutoffTime != null) && nextRetryTime.isAfter(cutoffTime)) {
                log.info("Next retry is after cutoff time, set it to " + cutoffTime);
                nextRetryTime = cutoffTime;
            }
            // sleep and retry later
            long sleepTime = nextRetryTime.getMillis() - now.getMillis();
            log.info("Sleep for " + sleepTime + " ms");
            try {
                if (sleepTime > 0)
                    Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                log.error("Interrupted, check if data is ready", e);
            }
        }
    }

    private PrerequisiteConfig getPrerequisiteConfig(CallingContext ctx) {
        String configUri = Kernel.getDecision().getContextValue(ctx, getWorkerURI(), CONFIG_URI);
        log.info("getPrequisiteConfig, configURI: " + configUri);
        String content = Kernel.getDoc().getDoc(ctx, configUri);
        return JacksonUtil.objectFromJson(content, PrerequisiteConfig.class);
    }

    // check if all required data is ready
    private boolean isDataReady(CallingContext ctx, PrerequisiteConfig config) {
        outer: for (PrerequisiteConfig.RequiredData requiredData : config.getRequiredData()) {
            if (log.isDebugEnabled())
                log.debug("Check requirement " + requiredData.toString());
            String uri = requiredData.getUri();
            int chevron = uri.indexOf('<');
            RaptureURI ruri = new RaptureURI((chevron > 0) ? uri.substring(0, chevron - 1) : uri);
            String keyFormat = requiredData.getKeyFormat();
            if (StringUtils.isEmpty(keyFormat))
                keyFormat = "yyyyMMdd";
            String specificDate = StringUtils.trimToNull(requiredData.getSpecificDate());
            if ((specificDate != null) && specificDate.startsWith("$")) {
                specificDate = Kernel.getDecision().getContextValue(ctx, getWorkerURI(), specificDate.substring(1));
            }

            // For series we look for a data point with the valid date
            if (ruri.getScheme().equals(Scheme.SERIES)) {
                if (specificDate != null)
                    log.warn("specificDate not supported for series data");
                DateTime acceptableTime = getDateTime(DateTime.now().withTimeAtStartOfDay(),
                        requiredData.getDateWithin(), requiredData.getTimeNoEarlierThan());

                if (!isDataReady.contains(uri)) {
                    if (!Kernel.getSeries().seriesExists(ctx, uri)) {
                        log.debug("Series not found " + uri);
                        return false;
                    }
                    SeriesPoint lastDataPoint = Kernel.getSeries().getLastPoint(ctx, uri);
                    DateTime lastDataPointTime = DateTime.parse(lastDataPoint.getColumn(),
                            DateTimeFormat.forPattern(keyFormat));
                    log.debug("Last data point is at " + lastDataPointTime);
                    if (lastDataPointTime.isBefore(acceptableTime)) {
                        log.debug("data point is outside acceptable range");
                        return false;
                    } else {
                        log.debug("data point is valid");
                        isDataReady.add(uri);
                    }
                } else {
                    log.debug("data point already seen");
                }
            } else {
                // For types other than series we simply check for the existence of the blob/doc/sheet etc.

                DateTime start = (specificDate != null)
                        ? DateTime.parse(specificDate, DateTimeFormat.forPattern(keyFormat))
                        : DateTime.now().withTimeAtStartOfDay();
                DateTime acceptableTime = getDateTime(start, requiredData.getDateWithin(),
                        requiredData.getTimeNoEarlierThan());

                DateTime lastDataPointTime = (specificDate == null) ? new DateTime() : acceptableTime;
                SimpleDateFormat sdf = new SimpleDateFormat(keyFormat);

                while (!lastDataPointTime.isBefore(acceptableTime)) {
                    String datedUri = uri;
                    if (uri.contains("<DATE>"))
                        datedUri = uri.replace("<DATE>", sdf.format(lastDataPointTime.toDate()));
                    if (isDataReady.contains(datedUri)) {
                        if (log.isDebugEnabled())
                            log.debug(datedUri.toString() + " already seen");
                        continue outer;
                    }

                    boolean uriExists = false;
                    // There is no generic exists method.
                    switch (ruri.getScheme()) {
                    case BLOB:
                        uriExists = Kernel.getBlob().blobExists(ctx, datedUri);
                        break;
                    case DOCUMENT:
                        uriExists = Kernel.getDoc().docExists(ctx, datedUri);
                        break;
                    default:
                        log.warn("Unexpected URI type : " + uri);
                        return false;
                    }
                    if (uriExists) {
                        if (log.isDebugEnabled())
                            log.debug(datedUri.toString() + " found");
                        isDataReady.add(uri);
                        continue outer;
                    }
                    // Go back one day
                    lastDataPointTime = lastDataPointTime.minusDays(1);
                }

                // Fail because we went outside acceptable time range
                // - we continue the outer for loop if successful
                log.debug("requirement not met");
                return false;
            }
        }
        // All found
        return true;
    }

    private String getCutoffAction(PrerequisiteConfig.CutoffAction cutoffAction) {
        if (cutoffAction == PrerequisiteConfig.CutoffAction.START)
            return NEXT;
        return QUIT;
    }

    /**
     * Converts time string to DateTime
     * 
     * @param timeStr
     *            hh:mm:ss timezone (eg 15:00:00 America/New_York)
     * @return DateTime
     */
    private DateTime getDateTime(String timeStr) {
        String[] parts = timeStr.split(" ");
        if (parts.length != 2) {
            throw RaptureExceptionFactory
                    .create("Time format should be hh:mm:ss timezone (eg. 15:30:00 America/New_York)");
        }
        DateTimeZone timeZone = DateTimeZone.forID(parts[1]);
        return LocalTime.parse(parts[0]).toDateTimeToday(timeZone);
    }

    private DateTime getDateTime(DateTime dateTime, String dateWithin, String timeNoEarlierThan) {
        if (!StringUtils.isEmpty(timeNoEarlierThan)) {
            DateTime hours = getDateTime(timeNoEarlierThan);
            if (dateTime == null)
                dateTime = hours;
            else {
                dateTime = dateTime.withMillisOfDay(hours.getMillisOfDay()).withZone(hours.getZone());
            }
        }
        if (!StringUtils.isEmpty(dateWithin)) {
            int number = Integer.valueOf(dateWithin.substring(0, dateWithin.length() - 1));
            char dateUnit = dateWithin.charAt(dateWithin.length() - 1);
            switch (dateUnit) {
            case 'M':
                dateTime = dateTime.minusMonths(number);
                break;
            case 'W':
                dateTime = dateTime.minusWeeks(number);
                break;
            case 'D':
                dateTime = dateTime.minusDays(number);
                break;
            case 'H':
                dateTime = dateTime.minusHours(number);
                break;
            case 'B':
                dateTime = USCalendar.minusBusinessDays(dateTime, number);
                break;
            default:
                throw RaptureExceptionFactory.create("Invalid date unit " + dateUnit);
            }
        }
        return dateTime;
    }

}