/* * 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"); } } } }