/*
* Copyright (C) 2014 KAIST
* @author Janggwan Im <limg00n@kaist.ac.kr>
*
* Copyright (C) 2007 ETH Zurich
*
* This file is part of Fosstrak (www.fosstrak.org).
*
* Fosstrak is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1, as published by the Free Software Foundation.
*
* Fosstrak is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Fosstrak; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package org.fosstrak.ale.server.impl;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Logger;
import org.fosstrak.ale.exception.DuplicateSubscriptionException;
import org.fosstrak.ale.exception.ECSpecValidationException;
import org.fosstrak.ale.exception.ImplementationException;
import org.fosstrak.ale.exception.InvalidURIException;
import org.fosstrak.ale.exception.NoSuchSubscriberException;
import org.fosstrak.ale.server.ALEApplicationContext;
import org.fosstrak.ale.server.EventCycle;
import org.fosstrak.ale.server.ReportsGenerator;
import org.fosstrak.ale.server.ReportsGeneratorState;
import org.fosstrak.ale.server.Subscriber;
import org.fosstrak.ale.server.readers.llrp.PhysicalReaderAcceptor;
import org.fosstrak.ale.server.util.ECReportsHelper;
import org.fosstrak.ale.server.util.ECSpecValidator;
import org.fosstrak.ale.util.ECTimeUnit;
import org.fosstrak.ale.xsd.ale.epcglobal.ECBoundarySpecExtension;
import org.fosstrak.ale.xsd.ale.epcglobal.ECReport;
import org.fosstrak.ale.xsd.ale.epcglobal.ECReportGroup;
import org.fosstrak.ale.xsd.ale.epcglobal.ECReportSpec;
import org.fosstrak.ale.xsd.ale.epcglobal.ECReports;
import org.fosstrak.ale.xsd.ale.epcglobal.ECReports.Reports;
import org.fosstrak.ale.xsd.ale.epcglobal.ECSpec;
import org.fosstrak.ale.xsd.ale.epcglobal.ECTime;
import com.rits.cloning.Cloner;
/**
* default implementation of the reports generator.
* @author swieland
*
*/
public class ReportsGeneratorImpl implements ReportsGenerator, Runnable {
/**
* a negative interval means that no such interval is set.
*/
private static final long INTERVAL_NOT_SET = -1L;
/**
* period for how long to wait for calls in between waiting times.
*/
private static final long WAKEUP_PERIOD = 50L;
/** logger */
private static final Logger LOG = Logger.getLogger(ReportsGenerator.class);
/** name of the report generator */
private final String name;
/** ec specification which defines how the report should be generated */
private final ECSpec spec;
/** map of subscribers of this report generator */
private final Map<String, Subscriber> subscribers = new ConcurrentHashMap<String, Subscriber>();
// boundary spec values
/** start trigger */
private final String startTriggerValue;
/** stop trigger */
private final String stopTriggerValue;
/** time between one and the following event cycle in milliseconds */
private final long repeatPeriodValue;
/**
* The stable set interval in milliseconds. If there are no new tags
* detected for this time, the reports generation should stop.
*/
private final long stableSetInterval;
/**
* If true, specifies that an event cycle may be stopped when any Tag is read that
* matches the filter conditions of at least one ECReportSpec within this ECSpec.
*/
private boolean whenDataAvailable = false;
/** thread to run the main loop */
private Thread thread;
/** state of this report generator */
private ReportsGeneratorState state = ReportsGeneratorState.UNREQUESTED;
/** indicates if this report generator is running or not */
private boolean reportsGeneratorRunning = false;
/** indicates if somebody is polling this input generator at the moment. */
private boolean polling = false;
/** ec report for the poller */
private ECReports pollReport = null;
private EventCycle eventCycle = null;
private ECReportsHelper reportsHelper;
private List<StartTrigger> listStartTrigger;
private List<StopTrigger> listStopTrigger;
/**
* Constructor validates the ec specification and sets some parameters.
*
* @param name of this reports generator
* @param spec which defines how the reports of this generator should be build
* @throws ECSpecValidationException if the ec specification is invalid
* @throws ImplementationException if an implementation exception occurs
*/
public ReportsGeneratorImpl(String name, ECSpec spec) throws ECSpecValidationException, ImplementationException {
this(name, spec, ALEApplicationContext.getBean(ECSpecValidator.class), ALEApplicationContext.getBean(ECReportsHelper.class));
}
/**
* Constructor validates the ec specification and sets some parameters.
*
* @param name of this reports generator
* @param spec which defines how the reports of this generator should be build
* @param validator the ECSpec validator to use for the validation of the ECSpec.
* @throws ECSpecValidationException if the ec specification is invalid
* @throws ImplementationException if an implementation exception occurs
*/
public ReportsGeneratorImpl(String name, ECSpec spec, ECSpecValidator validator, ECReportsHelper reportsHelper) throws ECSpecValidationException, ImplementationException {
LOG.debug("Try to create new ReportGenerator '" + name + "'.");
// set name
this.name = name;
this.reportsHelper = reportsHelper;
// set spec
try {
validator.validateSpec(spec);
} catch (ECSpecValidationException e) {
LOG.error(e.getClass().getSimpleName() + ": " + e.getMessage(), e);
throw e;
} catch (ImplementationException e) {
LOG.error(e.getClass().getSimpleName() + ": " + e.getMessage(), e);
throw e;
}
this.spec = spec;
// init boundary spec values
startTriggerValue = getStartTriggerValue();
stopTriggerValue = getStopTriggerValue();
repeatPeriodValue = getRepeatPeriodValue();
stableSetInterval = getStableSetInterval();
whenDataAvailable = getWhenDataAvailable();
if(stopTriggerValue != null) {
StopTrigger trigger = new StopTrigger(this, stopTriggerValue);
getListStopTrigger().add(trigger);
}
if(spec.getBoundarySpec().getExtension() != null) {
if(spec.getBoundarySpec().getExtension().getStopTriggerList() != null) {
List<String> stopTriggerStrs = spec.getBoundarySpec().getExtension().getStopTriggerList().getStopTrigger();
for(String stopTriggerStr : stopTriggerStrs) {
StopTrigger trigger = new StopTrigger(this, stopTriggerStr);
getListStopTrigger().add(trigger);
}
}
}
if(startTriggerValue != null) {
StartTrigger trigger = new StartTrigger(this, startTriggerValue);
getListStartTrigger().add(trigger);
}
if(spec.getBoundarySpec().getExtension() != null) {
if(spec.getBoundarySpec().getExtension().getStartTriggerList() != null) {
List<String> startTriggerStrs = spec.getBoundarySpec().getExtension().getStartTriggerList().getStartTrigger();
for(String startTriggerStr : startTriggerStrs) {
StartTrigger trigger = new StartTrigger(this, startTriggerStr);
getListStartTrigger().add(trigger);
}
}
}
LOG.debug(String.format("[startTriggerValue: %s, stopTriggerValue: %s, repeatPeriodValue: %s, stableSetInterval: %s]",
startTriggerValue, stopTriggerValue, repeatPeriodValue, stableSetInterval));
LOG.debug("ReportGenerator '" + name + "' successfully created.");
}
/**
* This method returns the ec specification of this generator.
*
* @return ec specification
*/
@Override
public ECSpec getSpec() {
return spec;
}
/**
* This method sets the state of this report generator.
* If the state changes from UNREQUESTED to REQUESTED, the report generators
* main loop will be started.
* If the state changes from REQUESTED to UNREQUESTED, the report generators
* main loop will be stopped.
* <strong>please notice that this method is not available through the ReportsGeneratorInterface.</strong>
*
* @param state to set
*/
public synchronized void setState(ReportsGeneratorState state) {
ReportsGeneratorState oldState = this.state;
this.state = state;
LOG.debug("ReportGenerator '" + name + "' change state from '" + oldState + "' to '" + state + "'");
if (isStateRequested() && !isRunning()) {
start();
} else if (isStateUnRequested() && isRunning()) {
stop();
} else if(isStateRequested() && isRunning()) {
synchronized(eventCycle) {
eventCycle.notifyAll();
}
}
}
/**
* This method returns the state of this report generator.
* <strong>please notice that this method is not available through the ReportsGeneratorInterface.</strong>
*
* @return state the state of the generator.
*/
public synchronized ReportsGeneratorState getState() {
return state;
}
/**
* This method subscribes a notification uri of a subscriber to this
* report generator.
* @param notificationURI to subscribe
* @throws DuplicateSubscriptionException if the specified notification uri
* is already subscribed
* @throws InvalidURIException if the notification uri is invalid
*/
@Override
public void subscribe(String notificationURI) throws DuplicateSubscriptionException, InvalidURIException {
Subscriber uri = new Subscriber(notificationURI);
if (subscribers.containsKey(notificationURI)) {
throw new DuplicateSubscriptionException(String.format("the URI is already subscribed on this specification %s, %s", name, uri));
} else {
subscribers.put(notificationURI, uri);
LOG.debug("NotificationURI '" + notificationURI + "' subscribed to spec '" + name + "'.");
if (isStateUnRequested()) {
setState(ReportsGeneratorState.REQUESTED);
}
}
}
/**
* This method unsubscribes a notification uri of a subscriber from this
* report generator.
* @param notificationURI to unsubscribe
* @throws NoSuchSubscriberException if the specified notification uri is
* not yet subscribed
* @throws InvalidURIException if the notification uri is invalid
*/
@Override
public void unsubscribe(String notificationURI) throws NoSuchSubscriberException, InvalidURIException {
// validate the URI:
new Subscriber(notificationURI);
if (subscribers.containsKey(notificationURI)) {
subscribers.remove(notificationURI);
LOG.debug("NotificationURI '" + notificationURI + "' unsubscribed from spec '" + name + "'.");
if (subscribers.isEmpty() && !isPolling()) {
setState(ReportsGeneratorState.UNREQUESTED);
}
} else {
throw new NoSuchSubscriberException("there is no subscriber on the given notification URI: " + notificationURI);
}
}
/**
* This method return the notification uris of all the subscribers of this
* report generator.
* @return list of notification uris
*/
@Override
public List<String> getSubscribers() {
return new ArrayList<String>(subscribers.keySet());
}
/**
* This method notifies all subscribers of this report generator about the
* specified ec reports.
* @param reports to notify the subscribers about
*/
@Override
public void notifySubscribers(ECReports reports, EventCycle ec) {
// according the ALE 1.1 standard:
// When the processing of reportIfEmpty and reportOnlyOnChange
// results in all ECReport instances being omitted from an
// ECReports for an event cycle, then the delivery of results
// to subscribers SHALL be suppressed altogether. [...] poll
// and immediate SHALL always be returned [...] even if that
// ECReports instance contains zero ECReport instances.
// An ECReports instance SHALL include an ECReport instance corresponding to each
// ECReportSpec in the governing ECSpec, in the same order specified in the ECSpec,
// except that an ECReport instance SHALL be omitted under the following circumstances:
// - If an ECReportSpec has reportIfEmpty set to false, then the corresponding
// ECReport instance SHALL be omitted from the ECReports for this event cycle if
// the final, filtered set of Tags is empty (i.e., if the final Tag list would be empty, or if
// the final count would be zero).
// - If an ECReportSpec has reportOnlyOnChange set to true, then the
// corresponding ECReport instance SHALL be omitted from the ECReports for
// this event cycle if the filtered set of Tags is identical to the filtered prior set of Tags,
// where equality is tested by considering the primaryKeyFields as specified in the
// ECSpec (see Section 8.2), and where the phrase 'the prior set of Tags' is as defined
// in Section 8.2.6. This comparison takes place before the filtered set has been modified
// based on reportSet or output parameters. The comparison also disregards
// whether the previous ECReports was actually sent due to the effect of this
// parameter, or the reportIfEmpty parameter.
// When the processing of reportIfEmpty and reportOnlyOnChange results in all
// ECReport instances being omitted from an ECReports for an event cycle, then the
// delivery of results to subscribers SHALL be suppressed altogether. That is, a result
// consisting of an ECReports having zero contained ECReport instances SHALL NOT
// be sent to a subscriber. (Because an ECSpec must contain at least one
// ECReportSpec, this can only arise as a result of reportIfEmpty or
// reportOnlyOnChange processing.) This rule only applies to subscribers (event cycle
// requestors that were registered by use of the subscribe method); an ECReports
// instance SHALL always be returned to the caller of immediate or poll at the end of
// an event cycle, even if that ECReports instance contains zero ECReport instances.
Cloner cloner = new Cloner();
// deep clone the original input in order to keep it as the
// next event cycles last cycle reports.
ECReports originalInput = cloner.deepClone(reports);
// we deep clone (clone not sufficient) for the pollers
// in order to deliver them the correct set of reports.
if (isPolling()) {
// deep clone for the pollers (poll and immediate)
pollReport = cloner.deepClone(reports);
}
// we remove the reports that are equal to the ones in the
// last event cycle. then we send the subscribers.
List<ECReport> equalReps = new LinkedList<ECReport> ();
List<ECReport> reportsToNotify = new LinkedList<ECReport> ();
try {
for (ECReport r : reports.getReports().getReport()) {
final ECReportSpec reportSpec = ec.getReportSpecByName(r.getReportName());
boolean tagsInReport = hasTags(r);
// case no tags in report but report if empty
if (!tagsInReport && reportSpec.isReportIfEmpty()) {
LOG.debug("requesting empty for report: " + r.getReportName());
reportsToNotify.add(r);
} else if (tagsInReport) {
reportsToNotify.add(r);
}
// check for equal reports since last notification.
if (reportSpec.isReportOnlyOnChange()) {
// report from the previous EventCycle run.
ECReport oldR = ec.getLastReports().get(r.getReportName());
// compare the new report with the old one.
if (reportsHelper.areReportsEqual(reportSpec, r, oldR)) {
equalReps.add(r);
}
}
}
} catch (Exception e) {
LOG.error("caught exception while processing reports: ", e);
}
// check if the intersection of all reports to notify (including empty ones) and the equal ones is empty
// -> if so, do not notify at all.
reportsToNotify.removeAll(equalReps);
// remove the equal reports
Reports re = reports.getReports();
if (null != re) re.getReport().removeAll(equalReps);
LOG.debug("reports size: " + reports.getReports().getReport().size());
LOG.debug("check size of reportsToNotify: "+reportsToNotify.size());
// next step is to check, if the total report is empty (even if requestIfEmpty but when all reports are equal, do not deliver)
//if (reportsToNotify.size() > 0) {
// notify the ECReports
notifySubscribersWithFilteredReports(reports);
//}
// store the new reports as old reports
ec.getLastReports().clear();
if (null != originalInput.getReports()) {
for (ECReport r : originalInput.getReports().getReport()) {
ec.getLastReports().put(r.getReportName(), r);
}
}
// notify pollers
// pollers always receive reports (even when empty).
if (isPolling()) {
polling = false;
if (subscribers.isEmpty()) {
setState(ReportsGeneratorState.UNREQUESTED);
}
synchronized (this) {
this.notifyAll();
}
}
}
/**
* check if a given ECReport contains at least one tag in its data structures.
* @param r the report to check.
* @return true if tags contained, false otherwise.
*/
private boolean hasTags(ECReport r) {
try {
for (ECReportGroup g : r.getGroup()) {
if (g.getGroupList().getMember().size() > 0) {
return true;
}
}
} catch (Exception ex) {
LOG.debug("could not check for tag occurence - report considered not to containing tags", ex);
}
return false;
}
/**
* once all the filtering is done eventually notify the subscribers with the reports.
* @param reports the filtered reports.
*/
protected void notifySubscribersWithFilteredReports(ECReports reports) {
Thread threadNotify = new Thread(new NotificationThread(reports, subscribers));
threadNotify.start();
/*
// notify subscribers
for (Subscriber listener : subscribers.values()) {
try {
listener.notify(reports);
} catch (Exception e) {
LOG.error("Could not notify subscriber '" + listener.toString(), e);
}
}
*/
}
/**
* This method is invoked if somebody polls this report generator.
* The result of the polling can be picked up by the method getPollReports.
*/
@Override
public void poll() {
LOG.debug("Spec '" + name + "' polled.");
pollReport = null;
polling = true;
if (isStateUnRequested()) {
setState(ReportsGeneratorState.REQUESTED);
}
}
/**
* This method delivers the ec reports which have been generated because
* of a poll.
* @return ec reports
*/
@Override
public ECReports getPollReports() {
return pollReport;
}
/**
* This method starts the main loop of the report generator.
*/
protected void start() {
thread = new Thread(this, name);
thread.setDaemon(true);
setRunning(true);
thread.start();
LOG.debug("Thread of spec '" + name + "' started.");
}
/**
* This method stops the main loop of the report generator.
*/
public void stop() {
eventCycle.stop();
// stop Thread
setRunning(false);
thread.interrupt();
if(listStartTrigger != null) {
for(StartTrigger trigger : getListStartTrigger()) {
trigger.cancel();
}
}
if(listStopTrigger != null) {
for(StopTrigger trigger : getListStopTrigger()) {
trigger.cancel();
}
}
LOG.debug("Thread of spec '" + name + "' stopped.");
}
/**
* This method returns the name of this reports generator.
*
* @return name of reports generator
*/
@Override
public String getName() {
return name;
}
/**
* create a new EventCycle that can be used by this reports generator.
* @return the newly created event cycle.
* @throws ImplementationException when the event cycle cannot be created.
*/
protected EventCycle createEventCycle() throws ImplementationException {
LOG.debug("creating new event cycle.");
return new EventCycleImpl(this);
}
/**
* This method contains the main loop of the reports generator.
* Here the event cycles will be generated and started.
*/
@Override
public void run() {
try {
eventCycle = createEventCycle();
} catch (ImplementationException e) {
LOG.error("could not create a new EventCycle - aborting.", e);
return;
}
if (listStartTrigger != null && listStartTrigger.size() > 0) {
LOG.debug("start trigger defined - not invoking the event cycle start..");
if (!isRepeatPeriodSet() || repeatPeriodValue == 0) {
// startTrigger is specified and repeatPeriod is not specified
// eventCycle is started when:
// state is REQUESTED and startTrigger is received
while (isRunning()) {
// wait until state is REQUESTED
synchronized (state) {
while (!isStateRequested()) {
try {
// wakeup the reports generator every once in a while
state.wait(WAKEUP_PERIOD);
} catch (InterruptedException e) {
LOG.debug("caught interrupted exception - leaving reports generator.");
return;
}
}
}
for(StartTrigger trigger : getListStartTrigger()) {
trigger.schedule();
}
for(StopTrigger trigger : getListStopTrigger()) {
trigger.schedule();
}
// wait until start trigger is received
synchronized (state) {
while (!state.equals(ReportsGeneratorState.ACTIVE)) {
try {
// wakeup the reports generator every once in a while
state.wait(WAKEUP_PERIOD);
} catch (InterruptedException e) {
LOG.debug("caught interrupted exception - leaving reports generator.");
return;
}
}
}
// start a new EventCycle
if (eventCycle == null) {
LOG.error("eventCycle is null");
} else {
eventCycle.launch();
}
try {
// wait for the event cycle to finish...
eventCycle.join();
} catch (InterruptedException e) {
LOG.debug("caught interrupted exception - leaving reports generator.");
return;
}
synchronized (state) {
setState(ReportsGeneratorState.REQUESTED);
}
}
}
} else {
if (isRepeatPeriodSet()) {
// startTrigger is not specified and repeatPeriod is specified
// eventCycle is started when:
// state transitions from UNREQUESTED to REQUESTED or
// repeatPeriod has elapsed from start of the last eventCycle and
// in that interval the state was never UNREQUESTED
while (isRunning()) {
// wait until state is REQUESTED
synchronized (state) {
while (!isStateRequested()) {
try {
// wakeup the reports generator every once in a while
state.wait(WAKEUP_PERIOD);
} catch (InterruptedException e) {
LOG.debug("caught interrupted exception - leaving reports generator.");
return;
}
}
}
// while state is REQUESTED start every repeatPeriod a
// new EventCycle
while (isStateRequested()) {
if (eventCycle == null) {
LOG.error("eventCycle is null");
} else {
eventCycle.launch();
}
try {
synchronized (state) {
state.wait(repeatPeriodValue);
}
// wait for the event cycle to finish...
eventCycle.join();
} catch (InterruptedException e) {
LOG.debug("caught interrupted exception - leaving reports generator.");
return;
}
}
LOG.debug("Stopping ReportsGenerator " + getName());
}
} else {
// neither startTrigger nor repeatPeriod are specified
// eventCycle is started when:
// state transitions from UNREQUESTED to REQUESTED or
// immediately after the previous event cycle, if the state
// is still REQUESTED
while (isRunning()) {
// wait until state is REQUESTED
while (!isStateRequested()) {
try {
synchronized (state) {
state.wait();
}
} catch (InterruptedException e) {
LOG.debug("caught interrupted exception - leaving reports generator.");
return;
}
}
// while state is REQUESTED start one EventCycle
// after the other
while (isStateRequested()) {
eventCycle.launch();
while (!eventCycle.isTerminated()) {
try {
synchronized (eventCycle) {
eventCycle.wait(WAKEUP_PERIOD);
}
} catch (InterruptedException e) {
LOG.debug("caught interrupted exception - leaving reports generator.");
return;
}
}
}
}
LOG.debug("Stopping ReportsGenerator " + getName());
}
}
}
@Override
public void setStateRequested() {
setState(ReportsGeneratorState.REQUESTED);
}
@Override
public void setStateUnRequested() {
setState(ReportsGeneratorState.UNREQUESTED);
}
@Override
public boolean isStateRequested() {
return state == ReportsGeneratorState.REQUESTED;
}
@Override
public boolean isStateUnRequested() {
return state == ReportsGeneratorState.UNREQUESTED;
}
/**
* This method returns the repeat period value on the basis of the event
* cycle specification.
* @return repeat period value or NO_REPEAT_PERIOD if none set.
* @throws ImplementationException if the time unit in use is unknown
*/
private long getRepeatPeriodValue() throws ImplementationException {
ECTime repeatPeriod = spec.getBoundarySpec().getRepeatPeriod();
if (repeatPeriod != null) {
if (repeatPeriod.getUnit().compareToIgnoreCase(ECTimeUnit.MS) != 0) {
throw new ImplementationException("The only ECTimeUnit allowed is milliseconds (MS).");
} else {
return repeatPeriod.getValue();
}
}
return INTERVAL_NOT_SET;
}
private boolean isRepeatPeriodSet() {
return repeatPeriodValue != INTERVAL_NOT_SET;
}
/**
* This method returns the start trigger value on the basis of the event
* cycle specification.
* @return start trigger value
*/
private String getStartTriggerValue() {
String startTrigger = spec.getBoundarySpec().getStartTrigger();
if (startTrigger != null) {
return startTrigger;
}
return null;
}
/**
* This method returns the stop trigger value on the basis of the event
* cycle specification.
* @return stop trigger value
*/
private String getStopTriggerValue() {
String stopTrigger = spec.getBoundarySpec().getStopTrigger();
if (stopTrigger != null) {
return stopTrigger;
}
return null;
}
/**
* This method returns the stabel set interval on the basis of the event
* cycle specification.
* @return stable set interval
*/
private long getStableSetInterval() {
ECTime stableSetInterval = spec.getBoundarySpec().getStableSetInterval();
if (stableSetInterval != null) {
return stableSetInterval.getValue();
}
return INTERVAL_NOT_SET;
}
/**
* This method extracts the value 'whenDataAvailable' from the ECSpec
* @return boolean value of whenDataAvailable
*/
private boolean getWhenDataAvailable() {
ECBoundarySpecExtension extension;
if( (extension = spec.getBoundarySpec().getExtension()) != null) {
if(extension.isWhenDataAvailable() != null) {
return extension.isWhenDataAvailable();
}
}
return false;
}
/**
* flags whether this reports generator is running or stopped.
* @return true if running, false otherwise.
*/
protected boolean isRunning() {
return reportsGeneratorRunning;
}
/**
* activate/deactivate a reports generator.
* @param runningState the new state of the reports generator.
*/
protected void setRunning(boolean runningState) {
reportsGeneratorRunning = runningState;
}
/**
* is the reports generator in polling state???
* @return true if polling, false otherwise.
*/
protected boolean isPolling() {
return polling;
}
/**
* the poll reports - <strong>Attention></strong> not null safe.
* @return the poll reports - <strong>Attention></strong> not null safe.
*/
protected ECReports getPollReport() {
return pollReport;
}
public boolean isWhenDataAvailable() {
return whenDataAvailable;
}
public List<StartTrigger> getListStartTrigger() {
if(listStartTrigger == null) {
synchronized (this) {
listStartTrigger = new ArrayList<StartTrigger>();
}
}
return listStartTrigger;
}
public void setListStartTrigger(List<StartTrigger> listStartTrigger) {
this.listStartTrigger = listStartTrigger;
}
public List<StopTrigger> getListStopTrigger() {
if(listStopTrigger == null) {
synchronized (this) {
listStopTrigger = new ArrayList<StopTrigger>();
}
}
return listStopTrigger;
}
public void setListStopTrigger(List<StopTrigger> listStopTrigger) {
this.listStopTrigger = listStopTrigger;
}
public class NotificationThread implements Runnable {
private ECReports reports;
private Map<String, Subscriber> subscribers;
public NotificationThread(ECReports reports, Map<String, Subscriber> subscribers) {
this.reports = reports;
this.subscribers = subscribers;
}
@Override
public void run() {
LOG.info("notify subscribers at time "+System.currentTimeMillis());
// notify subscribers
for (Subscriber listener : subscribers.values()) {
try {
listener.notify(reports);
} catch (Exception e) {
LOG.error("Could not notify subscriber '" + listener.toString(), e);
}
}
}
}
public class StartTrigger {
Timer timer = null;
ReportsGeneratorImpl rgImpl = null;
String startTriggerValue;
long period;
long offset;
public StartTrigger(ReportsGeneratorImpl rg, String startTriggerValue) throws ECSpecValidationException {
rgImpl = rg;
if(startTriggerValue.startsWith("urn:epcglobal:ale:trigger:rtc:")) {
String rtcString = startTriggerValue.substring(30);
String[] rtcStrings = rtcString.split("\\.");
if(rtcStrings.length == 2) {
int period = Integer.parseInt(rtcStrings[0]);
int offset = Integer.parseInt(rtcStrings[1]);
this.offset = offset;
this.period = period;
this.startTriggerValue = startTriggerValue;
}
} else {
throw new ECSpecValidationException("start trigger string is not valid : "+startTriggerValue);
}
}
public void cancel() {
timer.cancel();
}
public void schedule() {
GregorianCalendar gregorianCalendar = new GregorianCalendar();
gregorianCalendar.setTimeZone(TimeZone.getTimeZone("Asia/Seoul"));
gregorianCalendar.setTime(new Date());//.set(2013, 10, 10, 17, 34, 00);
int year = gregorianCalendar.get(Calendar.YEAR);
int month = gregorianCalendar.get(Calendar.MONTH);
int dayOfMonth = gregorianCalendar.get(Calendar.DAY_OF_MONTH);
gregorianCalendar.set(year, month, dayOfMonth+1, 0, 0, 0); // set the timer to tomorrow midnight
//gregorianCalendar.set(year, month, dayOfMonth, 18, 10, 0); // for the test purpose
long tomorrowMidnightInMillis = gregorianCalendar.getTimeInMillis();
long tomorrowFirstTime = tomorrowMidnightInMillis + offset;
long todayNextTime = tomorrowFirstTime;
while(todayNextTime > System.currentTimeMillis()) {
todayNextTime = todayNextTime - period;
}
todayNextTime += period;
timer = new Timer();
timer.schedule(new StartTriggerTask(rgImpl), new Date(todayNextTime), period);
}
}
public class StartTriggerTask extends TimerTask {
ReportsGeneratorImpl rgImpl = null;
public StartTriggerTask(ReportsGeneratorImpl rgImpl) {
this.rgImpl = rgImpl;
}
@Override
public void run() {
if(rgImpl.isStateRequested()) {
System.out.println("StartTrigger received when state was REQUESTED, so set the state to ACTIVE");
rgImpl.setState(ReportsGeneratorState.ACTIVE);
System.out.println("now the state is ACTIVE");
} else {
System.out.println("StartTrigger received, but state was not REQUESTED");
}
}
}
public class StopTrigger {
Timer timer = null;
ReportsGeneratorImpl rgImpl = null;
String stopTriggerValue;
long period;
long offset;
public StopTrigger(ReportsGeneratorImpl rg, String stopTriggerValue) throws ECSpecValidationException {
rgImpl = rg;
if(stopTriggerValue.startsWith("urn:epcglobal:ale:trigger:rtc:")) {
String rtcString = stopTriggerValue.substring(30);
String[] rtcStrings = rtcString.split("\\.");
if(rtcStrings.length == 2) {
int period = Integer.parseInt(rtcStrings[0]);
int offset = Integer.parseInt(rtcStrings[1]);
this.period = period;
this.offset = offset;
this.stopTriggerValue = stopTriggerValue;
}
} else {
throw new ECSpecValidationException("stop trigger string is not valid : "+stopTriggerValue);
}
}
public void cancel() {
timer.cancel();
}
public void schedule() {
GregorianCalendar gregorianCalendar = new GregorianCalendar();
gregorianCalendar.setTimeZone(TimeZone.getTimeZone("Asia/Seoul"));
gregorianCalendar.setTime(new Date());//.set(2013, 10, 10, 17, 34, 00);
int year = gregorianCalendar.get(Calendar.YEAR);
int month = gregorianCalendar.get(Calendar.MONTH);
int dayOfMonth = gregorianCalendar.get(Calendar.DAY_OF_MONTH);
gregorianCalendar.set(year, month, dayOfMonth+1, 0, 0, 0); // set the timer to tomorrow midnight
//gregorianCalendar.set(year, month, dayOfMonth, 18, 10, 0); // for the test purpose
long tomorrowMidnightInMillis = gregorianCalendar.getTimeInMillis();
long tomorrowFirstTime = tomorrowMidnightInMillis + offset;
long todayNextTime = tomorrowFirstTime;
while(todayNextTime > System.currentTimeMillis()) {
todayNextTime = todayNextTime - period;
}
todayNextTime += period;
timer = new Timer();
timer.schedule(new StopTriggerTask(rgImpl), new Date(todayNextTime), period);
}
}
public class StopTriggerTask extends TimerTask {
ReportsGeneratorImpl rgImpl = null;
public StopTriggerTask(ReportsGeneratorImpl rgImpl) {
this.rgImpl = rgImpl;
}
@Override
public void run() {
if(rgImpl.getState().equals(ReportsGeneratorState.ACTIVE)) {
if(rgImpl.getListStartTrigger().size() > 0) {
System.out.println("StopTrigger received when state was ACTIVE and startTrigger is not null, so set the state to REQUESTED");
rgImpl.setState(ReportsGeneratorState.REQUESTED);
} else {
System.out.println("StopTrigger received when state was ACTIVE and startTrigger is null, so set the state to UNREQUESTED");
rgImpl.setState(ReportsGeneratorState.UNREQUESTED);
}
} else {
System.out.println("StopTrigger received, but state was not ACTIVE");
}
}
}
}