com.appeligo.alerts.KeywordAlertChecker.java Source code

Java tutorial

Introduction

Here is the source code for com.appeligo.alerts.KeywordAlertChecker.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.appeligo.alerts;

import java.sql.Time;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.document.DateTools;
import org.apache.lucene.document.Document;
import org.apache.lucene.search.Query;

import com.appeligo.epg.DefaultEpg;
import com.appeligo.search.entity.Message;
import com.appeligo.search.entity.MessageContextException;
import com.appeligo.search.entity.User;
import com.knowbout.epg.service.ProgramType;
import com.knowbout.epg.service.ScheduledProgram;
import com.knowbout.hibernate.HibernateUtil;

class KeywordAlertChecker {

    private static final Log log = LogFactory.getLog(KeywordAlertChecker.class);

    private String liveLineup;
    private String url;

    public KeywordAlertChecker(Configuration config) {
        liveLineup = config.getString("liveLineup");
        url = config.getString("url");
    }

    /**
     * Returns true only if we are not supposed to send any more keyword alerts
     * for this keyword today.
     * @param keywordAlert
     * @return false if ok to send the alert
     */
    public boolean maxAlertsExceeded(KeywordAlert keywordAlert) {
        int maxAlertsPerDay = keywordAlert.getMaxAlertsPerDay();
        int todaysAlertCount = keywordAlert.getTodaysAlertCount();
        if (todaysAlertCount < maxAlertsPerDay) {
            return false;
        }
        Date lastAlertDay = keywordAlert.getLastAlertDay();
        if (lastAlertDay == null) {
            return false;
        }
        User user = keywordAlert.getUser();
        Date today = calculateDay(user.getTimeZone(), user.getEarliestSmsTime());
        // Since calculateDay() sets the Date timestamp to midnight on a day, the
        // Date represents the Date part only, and the time part for all Date objects
        // created by calculateDay() is equal.  So simply comparing by greater than
        // is guaranteed to return true if the days are different.  No need for
        // a more sophisticated check.
        if (compare(today, lastAlertDay, user.getTimeZone()) > 0) {
            return false;
        }
        return true;
    }

    /**
     * You should call this within a transaction as it will change the
     * number of alerts today (todaysAlertCount).
     * Call this when you have decided to send an alert.  You should check that
     * maxAlertsExceeded() returns false first.  This method does not do
     * the check again because it would probably be redundant. Many times
     * you want to do something in between checking maxAlertsExceeded() and
     * actually incrementing the count.
     * @param keywordAlert
     */
    public void incrementTodaysAlertCount(KeywordAlert keywordAlert) {
        int todaysAlertCount = keywordAlert.getTodaysAlertCount();
        Date lastAlertDay = keywordAlert.getLastAlertDay();
        User user = keywordAlert.getUser();
        Date today = calculateDay(user.getTimeZone(), user.getEarliestSmsTime());
        // Since calculateDay() sets the Date timestamp to midnight on a day, the
        // Date represents the Date part only, and the time part for all Date objects
        // created by calculateDay() is equal.  So simply comparing by greater than
        // is guaranteed to return true if the days are different.  No need for
        // a more sophisticated check.
        if ((lastAlertDay == null) || (compare(today, lastAlertDay, user.getTimeZone()) > 0)) {
            todaysAlertCount = 0;
        }
        todaysAlertCount++;

        keywordAlert.setLastAlertDay(today);
        keywordAlert.setTodaysAlertCount(todaysAlertCount);
        keywordAlert.save();
    }

    public boolean isNewMatch(KeywordAlert keywordAlert, Document doc) {

        String programId = doc.get("programID");
        if (KeywordMatch.getKeywordMatch(keywordAlert.getId(), programId) != null) {
            if (log.isDebugEnabled())
                log.debug("This is not a new match, so keyword alert has already been sent.");
            return false;
        }

        Date endTime;
        try {
            String endTimeString = doc.get("lineup-" + liveLineup + "-endTime");
            if (endTimeString == null) {
                log.error("Software bug that 'endTime' for lineup " + liveLineup + " was not found for program id "
                        + programId);
                return false;
            }
            endTime = DateTools.stringToDate(endTimeString);
        } catch (ParseException e) {
            log.error(
                    "Software bug resulted in exception with document 'endTime' format in lucene document for program id "
                            + programId,
                    e);
            return false;
        }

        new KeywordMatch(keywordAlert, programId, endTime).insert();

        return true;
    }

    public void sendMessages(KeywordAlert keywordAlert, String fragments, Document doc, String messagePrefix) {

        User user = keywordAlert.getUser();
        if (user == null) {
            return;
        }
        String programId = doc.get("programID");
        String programTitle = doc.get("programTitle");

        if (log.isDebugEnabled())
            log.debug("keywordAlert: " + keywordAlert.getUserQuery() + ", sending message to "
                    + (user == null ? null : user.getUsername()));

        try {
            // Use the user's lineup to determine the start time of this program which might air at different times for diff timezones
            String startTimeString = doc.get("lineup-" + user.getLineupId() + "-startTime");
            if (startTimeString == null) {
                // This user doesn't have the channel or program that our local feed has
                if (log.isDebugEnabled()) {
                    String station = doc.get("lineup-" + liveLineup + "-stationName");
                    log.debug("No startTime for station " + station + ", program " + programTitle + ", lineup="
                            + user.getLineupId() + ", start time from live lineup="
                            + doc.get("lineup-" + liveLineup + "-startTime"));
                }
                return;
            }
            Date startTime = DateTools.stringToDate(startTimeString);
            Date endTime = DateTools.stringToDate(doc.get("lineup-" + user.getLineupId() + "-endTime"));
            long durationMinutes = (endTime.getTime() - startTime.getTime()) / (60 * 1000);

            Date now = new Date();
            boolean future = endTime.after(now);
            boolean onAirNow = startTime.before(now) && future;
            boolean past = !(future || onAirNow);

            ProgramType programType = ProgramType.fromProgramID(programId);

            boolean uniqueProgram = false;
            if (programType == ProgramType.EPISODE || programType == ProgramType.SPORTS
                    || programType == ProgramType.MOVIE) {
                uniqueProgram = true;
            }

            Map<String, String> context = new HashMap<String, String>();

            boolean includeDate;
            DateFormat format;
            if (Math.abs(startTime.getTime() - System.currentTimeMillis()) < 12 * 60 * 60 * 1000) {
                format = DateFormat.getTimeInstance(DateFormat.SHORT);
                includeDate = false;
            } else {
                format = new SimpleDateFormat("EEEE, MMMM d 'at' h:mm a");
                includeDate = true;
            }
            format.setTimeZone(user.getTimeZone());
            context.put("startTime", format.format(startTime));
            if (includeDate) {
                format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
                format.setTimeZone(user.getTimeZone());
            }
            context.put("shortStartTime", format.format(startTime));

            context.put("durationMinutes", Long.toString(durationMinutes));
            // Use the SDTW-C lineup because this is how we know the right channel (station callsign) where we caught the
            // keyword.
            String stationName = doc.get("lineup-" + liveLineup + "-stationName");
            context.put("stationName", stationName);
            boolean sameStation = false;
            if (stationName.equals(doc.get("lineup-" + user.getLineupId() + "-stationName"))) {
                sameStation = true;
            }
            context.put("stationCallSign", doc.get("lineup-" + liveLineup + "-stationCallSign"));

            if (sameStation) {
                if (onAirNow) {
                    context.put("timeChannelIntro", "We have been monitoring <b>" + stationName
                            + "</b>, and your topic was recently mentioned on the following program:");
                } else if (future) {
                    if (uniqueProgram) {
                        context.put("timeChannelIntro", "We are monitoring <b>" + stationName
                                + "</b>, and your topic will be mentioned on the following program:");
                    } else {
                        context.put("timeChannelIntro",
                                "We are monitoring <b>" + stationName + "</b>, and your topic was mentioned on <b>"
                                        + programTitle + "</b>. It may be mentioned when this program airs again:");
                    }
                } else {
                    context.put("timeChannelIntro", "We have been monitoring <b>" + stationName
                            + "</b>, and your topic was mentioned on a program that aired in your area in the past. "
                            + "You may have an opportunity to see this program in the future:");
                }
            } else {
                if (onAirNow) {
                    context.put("timeChannelIntro", "We have been monitoring <b>" + programTitle
                            + "</b>, and your topic was recently mentioned:");
                } else if (future) {
                    if (uniqueProgram) {
                        context.put("timeChannelIntro", "We have been monitoring <b>" + programTitle
                                + "</b>, and your topic was mentioned.  You may have an opportunity to catch this program when it airs again according to the following schedule:");
                    } else {
                        context.put("timeChannelIntro", "We have been monitoring <b>" + programTitle
                                + "</b>, and your topic was mentioned.  This program will air again as follows, but the topics may or may not be the same:");
                    }
                } else {
                    context.put("timeChannelIntro", "We have been monitoring <b>" + programTitle
                            + "</b>, and your topic was mentioned.  However, this program aired in your area in the past. "
                            + "You may have an opportunity to see this program in the future:");
                }
            }
            if (onAirNow) {
                context.put("startsAt", "Started at");
            } else if (future) {
                if (includeDate) {
                    context.put("startsAt", "Starts on");
                } else {
                    context.put("startsAt", "Starts at");
                }
            } else {
                if (includeDate) {
                    context.put("startsAt", "Last aired on");
                } else {
                    context.put("startsAt", "Previously aired at");
                }
            }
            context.put("lcStartsAt", context.get("startsAt").toLowerCase());

            String webPath = doc.get("webPath");
            if (webPath == null) {
                webPath = DefaultEpg.getInstance().getProgram(programId).getWebPath();
            }
            if (webPath.charAt(0) == '/') {
                webPath = webPath.substring(1);
            }
            String reducedTitle40 = doc.get("reducedTitle40");
            if (reducedTitle40 == null) {
                reducedTitle40 = DefaultEpg.getInstance().getProgram(programId).getReducedTitle40();
            }
            String programLabel = doc.get("programLabel");
            if (programLabel == null) {
                programLabel = DefaultEpg.getInstance().getProgram(programId).getLabel();
            }
            context.put("programId", programId);
            context.put("webPath", webPath);
            context.put("programLabel", programLabel);
            context.put("reducedTitle40", reducedTitle40);
            if (doc.get("description").trim().length() > 0) {
                context.put("description", "Description: " + doc.get("description") + "<br/>");
            } else {
                context.put("description", "");
            }
            if (fragments == null || fragments.trim().length() == 0) {
                context.put("fragments", "");
            } else {
                context.put("fragments", "Relevant Dialogue: <i>" + fragments + "</i><br/>");
            }
            context.put("query", keywordAlert.getUserQuery());
            context.put("keywordAlertId", Long.toString(keywordAlert.getId()));
            String greeting = user.getUsername();
            context.put("username", greeting);
            String firstName = user.getFirstName();
            if (firstName != null && firstName.trim().length() > 0) {
                greeting = firstName;
            }
            context.put("greeting", greeting);

            format = DateFormat.getTimeInstance(DateFormat.SHORT);
            format.setTimeZone(user.getTimeZone());
            context.put("now", format.format(new Date()));

            ScheduledProgram futureProgram = DefaultEpg.getInstance().getNextShowing(user.getLineupId(), programId,
                    false, false);
            if (uniqueProgram) {
                String typeString = null;
                if (programType == ProgramType.EPISODE) {
                    typeString = "episode";
                } else if (programType == ProgramType.SPORTS) {
                    typeString = "game";
                } else {
                    typeString = "movie";
                }
                if (futureProgram != null) {
                    String timePreposition = null;
                    if ((futureProgram.getStartTime().getTime() - System.currentTimeMillis()) < 12 * 60 * 60
                            * 1000) {
                        timePreposition = "at ";
                        format = DateFormat.getTimeInstance(DateFormat.SHORT);
                    } else {
                        timePreposition = "on ";
                        format = new SimpleDateFormat("EEEE, MMMM d 'at' h:mm a");
                    }
                    format.setTimeZone(user.getTimeZone());
                    context.put("rerunInfo", "You can still catch this " + typeString
                            + " in its entirety!  It's scheduled to replay " + timePreposition
                            + format.format(futureProgram.getStartTime()) + " on "
                            + futureProgram.getNetwork().getStationName() + ". Do you want to <a href=\"" + url
                            + webPath + "#addreminder\">set a reminder</a> to be notified the next time this "
                            + typeString + " airs?");
                } else {
                    if (programType == ProgramType.SPORTS) {
                        context.put("rerunInfo", "");
                    } else {
                        if (onAirNow) {
                            context.put("rerunInfo",
                                    "If it's too late to flip on the program now, you can <a href=\"" + url
                                            + webPath
                                            + "#addreminder\">set a reminder</a> to be notified the next time this "
                                            + typeString + " airs.");
                        } else {
                            context.put("rerunInfo",
                                    "You can <a href=\"" + url + webPath
                                            + "#addreminder\">set a reminder</a> to be notified the next time this "
                                            + typeString + " airs.");
                        }
                    }
                }
            } else {
                if ((futureProgram != null) && futureProgram.isNewEpisode()) {
                    context.put("rerunInfo",
                            "The next airing of this show will be new content, and is <i>not a rerun</i>,"
                                    + " so these same topics may or may not be discussed."
                                    + "  You may still be interested in catching future airings, and you can"
                                    + " <a href=\"" + url + webPath
                                    + "#addreminder\">set a Flip.TV reminder for this show</a>.");
                } else {
                    context.put("rerunInfo",
                            "The broadcaster did not provide enough information to know which future airings,"
                                    + " if any, are identical reruns with the same topics mentioned."
                                    + "  You may still be interested in catching future airings, and you can"
                                    + " <a href=\"" + url + webPath
                                    + "#addreminder\">set a Flip.TV reminder for this show</a>.");
                }
            }

            if (keywordAlert.getTodaysAlertCount() == keywordAlert.getMaxAlertsPerDay()) {
                context.put("maxAlertsExceededSentence",
                        "You asked to stop receiving alerts for this topic after receiving "
                                + keywordAlert.getMaxAlertsPerDay()
                                + " alerts in a single day. That limit has been reached. You can change this setting"
                                + " at any time.  Otherwise, we will resume sending alerts"
                                + " for this topic tomorrow.");
            } else {
                context.put("maxAlertsExceededSentence", "");
            }

            if (keywordAlert.isUsingPrimaryEmailRealtime()) {
                Message message = new Message(messagePrefix + "_email", context);
                message.setUser(user);
                message.setTo(user.getPrimaryEmail());
                if (log.isDebugEnabled())
                    log.debug("Sending email message to: " + user.getPrimaryEmail());
                message.insert();
            }
            if (keywordAlert.isUsingSMSRealtime() && user.getSmsEmail().trim().length() > 0) {
                Message message = new Message(messagePrefix + "_sms", context);
                message.setTo(user.getSmsEmail());
                message.setUser(user);
                message.setSms(true);
                if (log.isDebugEnabled())
                    log.debug("Sending sms message to: " + user.getSmsEmail());
                message.insert();
            }
        } catch (NumberFormatException e) {
            log.error("Couldn't process lucene document for program " + programId, e);
        } catch (MessageContextException e) {
            log.error("Software bug resulted in exception with email message context or configuration", e);
        } catch (ParseException e) {
            log.error(
                    "Software bug resulted in exception with document 'startTime' or 'endTime' format in lucene document for program id "
                            + programId,
                    e);
        }
    }

    /**
     * @param timeZone the user's timezone
     * @param startOfDay We're using the value of earliestSmsTime (user account setting) as a starting point,
     * so anything before this time is credited to the previous day)
     * @return 12am (midnight) using system time (not user time) because "Date" objects stored in SQL
     * come back in system time.
     */
    private Date calculateDay(TimeZone timeZone, Time startOfDay) {
        Calendar cal = Calendar.getInstance(timeZone);
        Calendar start = Calendar.getInstance();
        start.setTimeInMillis(startOfDay.getTime());
        cal.add(Calendar.HOUR_OF_DAY, 0 - start.get(Calendar.HOUR_OF_DAY));
        cal.add(Calendar.MINUTE, 0 - start.get(Calendar.MINUTE));
        cal.add(Calendar.SECOND, 0 - start.get(Calendar.SECOND));
        cal.clear(Calendar.MILLISECOND);
        cal.clear(Calendar.SECOND);
        cal.clear(Calendar.MINUTE);
        cal.clear(Calendar.HOUR_OF_DAY);
        cal.clear(Calendar.HOUR);
        cal.clear(Calendar.AM_PM);
        TimeZone systemTimeZone = TimeZone.getDefault();
        long now = System.currentTimeMillis();
        long difference = systemTimeZone.getOffset(now) - timeZone.getOffset(now);
        return new Date(cal.getTimeInMillis() - difference);
    }

    private int compare(Date left, Date right, TimeZone timeZone) {
        Calendar cal = Calendar.getInstance(timeZone);
        cal.setTime(left);
        int leftYear = cal.get(Calendar.YEAR);
        int leftDay = cal.get(Calendar.DAY_OF_YEAR);
        cal.setTime(right);
        int rightYear = cal.get(Calendar.YEAR);
        int rightDay = cal.get(Calendar.DAY_OF_YEAR);
        if (leftYear < rightYear) {
            return -1;
        }
        if (leftYear > rightYear) {
            return 1;
        }
        if (leftDay < rightDay) {
            return -1;
        }
        if (leftDay > rightDay) {
            return 1;
        }
        return 0;
    }
}