com.autodomum.core.event.EventComponent.java Source code

Java tutorial

Introduction

Here is the source code for com.autodomum.core.event.EventComponent.java

Source

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Kenny Colliander Nordin
 *
 * 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 com.autodomum.core.event;

import java.time.Clock;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.autodomum.core.daylight.Daylight;
import com.autodomum.core.holiday.Holiday;

/**
 * Handle all event execution via JavaScript and update
 * <code>EventContext</code>.
 * 
 * @author Kenny Colliander Nordin
 * @since 0.0.1
 */
@Component
@Scope
public class EventComponent implements Runnable {

    private static final Logger LOG = LoggerFactory.getLogger(EventComponent.class);

    private final Map<Class<?>, List<EventCallback>> eventCallbacks = new ConcurrentHashMap<>();

    protected final DelayQueue<DelayedEvent> delayedEvents = new DelayQueue<>();

    private Clock clock = Clock.systemUTC();

    private EventContext eventContext;

    private Daylight daylight;

    private Holiday holiday;

    @Override
    public void run() {
        try {
            LOG.info("Started!");
            try {
                this.updateEventContext();
                this.publish(new StartupEvent());
                while (true) {
                    this.updateEventContext();
                    this.handleDelayedEvent();
                }
            } catch (InterruptedException e) {
            }
            this.publish(new ShutdownEvent());
            LOG.info("Exit!");
        } catch (Exception e) {
            LOG.error("Unknown error ocurred in EventComponent", e);
        }
    }

    /**
     * Retrieve and handle events
     * 
     * @throws InterruptedException
     */
    void handleDelayedEvent() throws InterruptedException {
        final DelayedEvent delayedEvent = this.delayedEvents.poll(1, TimeUnit.SECONDS);

        if (delayedEvent != null) {
            this.publish(delayedEvent);
        }
    }

    /**
     * @return the current time
     */
    public Date getNow() {
        return new Date();
    }

    /**
     * @return an calendar instance
     */
    public Calendar getCalendar() {
        return Calendar.getInstance();
    }

    /**
     * Update the event context with correct parameters
     */
    public void updateEventContext() {
        final Date now = this.getNow();
        final Calendar tomorrowCalendar = this.getCalendar();
        tomorrowCalendar.setTime(now);

        this.eventContext.setHoliday(holiday.isHoliday(tomorrowCalendar));
        this.eventContext.setHour(tomorrowCalendar.get(Calendar.HOUR_OF_DAY));
        this.eventContext.setMinute(tomorrowCalendar.get(Calendar.MINUTE));

        tomorrowCalendar.add(Calendar.DATE, 1);
        final Date tomorrow = tomorrowCalendar.getTime();

        final Date sunrise = this.daylight.sunrise(eventContext.getCoordinate(), now);
        final Date sunset = this.daylight.sunset(eventContext.getCoordinate(), now);
        final Date sunriseTomorrow = this.daylight.sunrise(eventContext.getCoordinate(), tomorrow);
        final Date sunsetTomorrow = this.daylight.sunset(eventContext.getCoordinate(), tomorrow);

        boolean newDaylight = false;
        if (now.after(sunset)) {
            newDaylight = false;
            this.eventContext.setNextSunrise(sunriseTomorrow.getTime());
            this.eventContext.setNextSunset(sunsetTomorrow.getTime());

        } else if (now.after(sunrise)) {
            newDaylight = true;
            this.eventContext.setNextSunrise(sunriseTomorrow.getTime());
            this.eventContext.setNextSunset(sunset.getTime());
        } else {
            this.eventContext.setNextSunrise(sunrise.getTime());
            this.eventContext.setNextSunset(sunset.getTime());
        }

        if (newDaylight != this.eventContext.isDaylight()) {
            this.eventContext.setDaylight(newDaylight);

            if (newDaylight) {
                this.publish(new SunriseEvent());
            } else {
                this.publish(new SunsetEvent());
            }
        }
    }

    /**
     * Register callback to receive events for a specific type
     * 
     * @param eventType
     *            the event type that the callback should be assigned to
     * @param eventCallback
     *            the callback
     */
    public synchronized void register(final Class<?> eventType, final EventCallback eventCallback) {
        checkEventType(eventType);
        checkEventCallback(eventCallback);

        List<EventCallback> callbacks = this.eventCallbacks.get(eventType);

        if (callbacks == null) {
            callbacks = new CopyOnWriteArrayList<>();
            this.eventCallbacks.put(eventType, callbacks);
        }

        LOG.debug("Registered callback {} for event type {}", eventCallback, eventType);
        callbacks.add(eventCallback);
    }

    /**
     * Publish an event; will call all callbacks that has been registered with
     * the supplied event
     * 
     * @param event
     *            the event
     */
    public void publish(final Event event) {
        if (event == null) {
            throw new NullPointerException("Event may not be null");
        }

        final List<EventCallback> callbacks = this.eventCallbacks.getOrDefault(event.getClass(),
                Collections.emptyList());

        LOG.debug("Fire {} for {} callbacks", event);
        for (final EventCallback callback : callbacks) {
            callback.work(this.eventContext, event);
        }
    }

    /**
     * Unregister callback for a specific event
     * 
     * @param eventType
     *            the event type
     * @param eventCallback
     *            the worker that should be removed
     */
    public synchronized void unregister(final Class<?> eventType, final EventCallback eventCallback) {
        checkEventType(eventType);
        checkEventCallback(eventCallback);

        final List<EventCallback> callbacks = this.eventCallbacks.get(eventType);

        if (callbacks != null) {
            callbacks.remove(eventCallback);

            LOG.debug("Unregistered callback {} for event type {}", eventCallback, eventType);
        }
    }

    /**
     * Register an <code>FireOnceEvent</code> for tomorrow
     * 
     * @param hour
     *            the hour tomorrow
     * @param minute
     *            the minute tomorrow
     * @param name
     *            the name of the event
     */
    public void registerEventOnceTomorrow(final int hour, final int minute, final String name) {
        if (hour < 0 || hour > 23) {
            throw new IllegalArgumentException("Hour is a 24h clock, acceptable values are 0-23: " + hour);
        }

        if (minute < 0 || minute > 59) {
            throw new IllegalArgumentException("Minute, acceptable values are 0-59: " + minute);
        }

        checkName(name);

        final Calendar calendar = this.getCalendar();
        calendar.add(Calendar.DATE, 1);
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);

        this.addDelayedEvent(new FireOnceEvent(this.clock, calendar.getTimeInMillis(), name));
        LOG.debug("Added event {} to run {}", name, calendar.getTime());
    }

    /**
     * Register a <code>FireOnceEvent</code> in the future
     * 
     * @param milliseconds
     *            the amount of milliseconds in the future
     * @param name
     *            the name of the event
     */
    public void registerEventOnce(final long milliseconds, final String name) {
        if (milliseconds < 0) {
            throw new IllegalArgumentException("Event must be in the future");
        }

        checkName(name);
        final long time = this.clock.millis() + milliseconds;
        this.addDelayedEvent(new FireOnceEvent(this.clock, time, name));
        LOG.debug("Added event {} to run {}", name, time);
    }

    /**
     * Add a delayed event
     * 
     * @param delayed
     *            the delayed event
     */
    public void addDelayedEvent(final DelayedEvent delayed) {
        if (delayed == null) {
            throw new NullPointerException("Delayed event may not be null");
        }
        this.delayedEvents.put(delayed);
    }

    @Autowired
    public void setEventContext(EventContext eventContext) {
        this.eventContext = eventContext;
    }

    @Autowired
    public void setDaylight(Daylight daylight) {
        this.daylight = daylight;
    }

    @Autowired
    public void setHoliday(Holiday holiday) {
        this.holiday = holiday;
    }

    public void setClock(Clock clock) {
        this.clock = clock;
    }

    /**
     * Verify that a name is set and not null
     * 
     * @param name
     *            the name
     */
    private void checkName(final String name) {
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("Name must be set: " + name);
        }
    }

    /**
     * Verify that the callback not is null
     * 
     * @param eventCallback
     *            the callback
     */
    private void checkEventCallback(final EventCallback eventCallback) {
        if (eventCallback == null) {
            throw new NullPointerException("EventCallback may not be null");
        }
    }

    /**
     * Check that the event type not is null
     * 
     * @param eventType
     *            the event type
     */
    private void checkEventType(final Class<?> eventType) {
        if (eventType == null) {
            throw new NullPointerException("EventType may not be null");
        }
    }
}