/* * Copyright (C) 2014 KAIST * @author Wondeuk Yoon <wdyoon@resl.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.cc.impl; import java.util.ArrayList; import java.util.Collections; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Random; import java.util.Set; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import org.apache.log4j.Logger; import org.fosstrak.ale.exception.CCSpecValidationException; import org.fosstrak.ale.exception.ImplementationException; import org.fosstrak.ale.server.ALEApplicationContext; import org.fosstrak.ale.server.Tag; import org.fosstrak.ale.server.cc.CommandCycle; import org.fosstrak.ale.server.cc.Report; import org.fosstrak.ale.server.cc.ReportsGenerator; import org.fosstrak.ale.server.readers.LogicalReader; import org.fosstrak.ale.server.readers.LogicalReaderManager; import org.fosstrak.ale.util.ECTimeUnit; import org.fosstrak.ale.xsd.ale.epcglobal.CCFilterSpec; import org.fosstrak.ale.xsd.ale.epcglobal.CCReports; import org.fosstrak.ale.xsd.ale.epcglobal.CCCmdReport; import org.fosstrak.ale.xsd.ale.epcglobal.CCOpReport; import org.fosstrak.ale.xsd.ale.epcglobal.CCReports.CmdReports; import org.fosstrak.ale.xsd.ale.epcglobal.CCTagReport; import org.fosstrak.ale.xsd.ale.epcglobal.CCCmdSpec; import org.fosstrak.ale.xsd.ale.epcglobal.CCOpSpec; import org.fosstrak.ale.xsd.ale.epcglobal.CCSpec; import org.fosstrak.ale.xsd.ale.epcglobal.ECFilterListMember; import org.fosstrak.ale.xsd.ale.epcglobal.ECTime; import org.fosstrak.alecc.util.CCTerminationCondition; import kr.ac.kaist.resl.ltk.generated.parameters.AccessSpecID; /** * default implementation of the command cycle. * * @author regli * @author swieland * @author benoit.plomion@orange.com * @author nkef@ait.edu.gr * @author Wondeuk Yoon */ public final class CommandCycleImpl implements CommandCycle, Runnable { /** logger. */ private static final Logger LOG = Logger.getLogger(CommandCycleImpl.class); /** random numbers generator. */ private static final Random rand = new Random(System.currentTimeMillis()); /** ale id. */ private static final String ALEID = "ETHZ-ALE" + rand.nextInt(); /** number of this command cycle. */ private static int number = 0; /** name of this command cycle. */ private final String name; /** report generator which contains this command cycle. */ private final ReportsGenerator generator; /** thread. */ private final Thread thread; /** command cycle specification for this command cycle. */ private final CCSpec ccspec; /** set of logical readers which deliver tags for this command cycle. */ private final Set<LogicalReader> logicalReaders = new HashSet<LogicalReader>(); /** set of reports for this command cycle. */ private final Set<Report> reports = new HashSet<Report>(); /** a hash map with all the reports generated in the last round. */ private final Map<String, CCReports> lastCCReports = new HashMap<String, CCReports> (); /** contains all the cc cmd specs hashed by their report name. */ private final Map<String, CCCmdSpec> CmdSpecByName = new HashMap<String, CCCmdSpec> (); /** set of tags for this command cycle. */ private Set<Tag> tags = Collections.synchronizedSet(new HashSet<Tag>()); /** this set stores the tags from the previous CommandCycle run. */ private Set<Tag> lastCommandCycleTags = null; /** this set stores the tags between two command cycle in the case of rejectTagsBetweenCycle is false */ private Set<Tag> betweenCommandsCycleTags = Collections.synchronizedSet(new HashSet<Tag>()); /** flags to know if the command cycle haven t to reject tags in the case than duration and repeatPeriod is same */ private boolean rejectTagsBetweenCycle = true; /** indicates if this command cycle is terminated or not .*/ private boolean isTerminated = false; /** * lock for thread synchronization between reports generator and this. * swieland 2012-09-29: do not use primitive type as int or Integer as autoboxing can result in new thread object for the lock -> non-threadsafe... */ private final CommandCycleLock lock = new CommandCycleLock(); /** flag whether the command cycle has passed through or not. */ private boolean roundOver = false; /** the duration of collecting tags for this command cycle in milliseconds. */ private long durationValue; /** the total time this command cycle runs in milliseconds. */ private long totalTime; /** the termination condition of this command cycle. */ private String terminationCondition = null; /** flags the commandCycle whether it shall run several times or not. */ private boolean running = false; /** flags whether the CommandCycle is currently not accepting tags. */ private boolean acceptTags = false; /** tells how many times this CommandCycle has been scheduled. */ private int rounds = 0; /** number of this command cycle. */ private int accessSpecID = 0; // TODO: check if we can use this instead of the dummy class. private final class CommandCycleLock { } /** * Constructor sets parameter and starts thread. * * @param generator to which this command cycle belongs to * @throws ImplementationException if an implementation exception occurs */ public CommandCycleImpl(ReportsGenerator generator) throws ImplementationException { this(generator, ALEApplicationContext.getBean(LogicalReaderManager.class)); } /** * Constructor sets parameter and starts thread. * * @param generator to which this command cycle belongs to * @throws ImplementationException if an implementation exception occurs */ public CommandCycleImpl(ReportsGenerator generator, LogicalReaderManager logicalReaderManager) throws ImplementationException { // set accessSpecID accessSpecID = number+11; // set name name = generator.getName() + "_" + number++; // set ReportGenerator this.generator = generator; // set spec ccspec = generator.getCCSpec(); // get cmd specs and create a report for each spec for (CCCmdSpec cccmdSpec : ccspec.getCmdSpecs().getCmdSpec()) { // add report spec and report to reports try { reports.add(new Report(cccmdSpec, this, this.generator)); } catch (CCSpecValidationException e) { // TODO Auto-generated catch block e.printStackTrace(); } // hash into the report spec structure CmdSpecByName.put(cccmdSpec.getName(), cccmdSpec); } // init BoundarySpec values durationValue = getDurationValue(); long repeatPeriod = getRepeatPeriodValue(); if (durationValue == repeatPeriod) { setRejectTagsBetweenCycle(false); } LOG.debug(String.format("durationValue: %s\n", durationValue)); setAcceptTags(false); LOG.debug("adding logicalReaders to CommandCycle"); // get LogicalReaderStubs if (ccspec.getLogicalReaders() != null) { List<String> logicalReaderNames = ccspec.getLogicalReaders().getLogicalReader(); for (String logicalReaderName : logicalReaderNames) { LOG.debug("retrieving logicalReader " + logicalReaderName); LogicalReader logicalReader = logicalReaderManager.getLogicalReader(logicalReaderName); if (logicalReader != null) { LOG.debug("adding logicalReader " + logicalReader.getName() + " to CommandCycle " + name); logicalReaders.add(logicalReader); } } } else { LOG.error("CCSpec contains no readers"); } for (LogicalReader logicalReader : logicalReaders) { // subscribe this command cycle to the logical readers LOG.debug("registering CommandCycle " + name + " on reader " + logicalReader.getName()); logicalReader.addObserver(this); } rounds = 0; // create and start Thread thread = new Thread(this, "CommandCycle" + name); thread.setDaemon(true); thread.start(); LOG.debug("New CommandCycle '" + name + "' created."); } /** * This method returns the cc reports. * * @return cc reports * @throws CCSpecValidationException if the tags of the report are not valid * @throws ImplementationException if an implementation exception occurs. */ private CCReports getCCReports() throws CCSpecValidationException, ImplementationException { // create CCReports CCReports reports = new CCReports(); // set spec name reports.setSpecName(generator.getName()); // set date try { reports.setDate(DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar())); } catch (DatatypeConfigurationException e) { LOG.error("Could not create date: " + e.getMessage()); } // set ale id reports.setALEID(ALEID); // set total time in milliseconds reports.setTotalMilliseconds(totalTime); // set termination condition reports.setTerminationCondition(terminationCondition); //TODO: This code for preemption. /* for(Tag t : tags) { AccessSpecID AccessSpecID = t.getAccessSpecID(); }*/ // set spec if (ccspec.isIncludeSpecInReports()) { reports.setCCSpec(ccspec); } //TODO: �대뼡 Spec���곕씪��AccessID瑜�李얘퀬 �l쓬 // set reports reports.setCmdReports(new CmdReports()); reports.getCmdReports().getCmdReport().addAll(getCmdReportList()); return reports; } @Override public void addTag(Tag tag) { if (!isAcceptingTags()) { return; } // add command only if CommandCycle is still running if (isCommandCycleActive()) { logTagOnDebugEnabled(tag); // add tag to tags addTagAndLogOnNotAdded(tags, tag); } } /** * This method adds a tag between 2 command cycle. * * @param tag to add * @throws ImplementationException if an implementation exception occurs * @throws CCSpecValidationException if the tag is not valid */ private void addTagBetweenCommandsCycle(Tag tag) { if (isRejectTagsBetweenCycle()) { return; } // add command only if CommandCycle is still running if (isCommandCycleActive()) { logTagOnDebugEnabled(tag); // add tag to tags addTagAndLogOnNotAdded(betweenCommandsCycleTags, tag); } } /** * determine if this command cycle is active (running) or not. * @return true if the command cycle is active, false if not. */ private boolean isCommandCycleActive() { return thread.isAlive(); } /** * log a tag to the logger if debug is enabled. the command cycles name and the tag as pure URI is written to the log on one line. * @param tagToLog the tag to be logged. */ private void logTagOnDebugEnabled(Tag tagToLog) { if (LOG.isDebugEnabled()) { LOG.debug("CommandCycle '" + name + "' add Tag '" + tagToLog.getTagIDAsPureURI() + "'."); } } /** * little helper method adding a tag to a given set. if the tag is not added (as already contained) log it. * @param whereToAddTheTag the set where to add the tag to. * @param theTagToAdd the tag which is meant to be added. */ private void addTagAndLogOnNotAdded(Set<Tag> whereToAddTheTag, Tag theTagToAdd) { if (!whereToAddTheTag.add(theTagToAdd) && LOG.isDebugEnabled()) { LOG.debug("tag already contained, therefore not adding."); } } @Override public void update(Observable o, Object arg) { LOG.debug("CommandCycle "+ getName() + ": Update notification received. "); List<Tag> tags = new LinkedList<Tag> (); // process the new tag. if (arg instanceof Tag) { LOG.debug("processing one tag"); // process one tag tags.add((Tag) arg); } else if (arg instanceof List) { LOG.debug("processing a list of tags"); for (Object entry : (List<?>) arg) { if (entry instanceof Tag) { tags.add((Tag) entry); } } } if (tags.size() > 0) { handleTags(tags); } else { LOG.debug("CommandCycle "+ getName() + ": Update notification received - but not with any tags - ignoring. "); } } private void handleTags(List<Tag> tags) { if (!isAcceptingTags()) { handleTagsWhileNotAccepting(tags); } else { handleTagsWhileAccepting(tags); } } /** * deal with new tags. * @param tags */ private void handleTagsWhileAccepting(List<Tag> tags) { // process all the tags we did not process between two commandcycles (or while we did not accept any tags). if (!isRejectTagsBetweenCycle()) { for (Tag tag : betweenCommandsCycleTags) { addTag(tag); } betweenCommandsCycleTags.clear(); } LOG.debug("CommandCycle "+ getName() + ": Received list of tags :"); for (Tag tag : tags) { addTag(tag); } } /** * deal with tags while the command cycle is not accepting tags. (eg. between two command cycles). * @param arg the update we received. */ private void handleTagsWhileNotAccepting(List<Tag> tags) { if (!isRejectTagsBetweenCycle()) { for (Tag tag : tags) { LOG.debug("received tag between commandcycles: " + tag.getTagIDAsPureURI()); addTagBetweenCommandsCycle(tag); } } } @Override public void stop() { // unsubscribe this command cycle from logical readers for (LogicalReader logicalReader : logicalReaders) { logicalReader.deleteObserver(this); } running = false; thread.interrupt(); LOG.debug("CommandCycle '" + name + "' stopped."); isTerminated = true; synchronized (this) { this.notifyAll(); } } @Override public String getName() { return name; } @Override public boolean isTerminated() { return isTerminated; } /** * This method is the main loop of the command cycle in which the tags will be collected. * At the end the reports will be generated and the subscribers will be notified. */ @Override public void run() { lastCommandCycleTags = new HashSet<Tag>(); // wait for the start // running will be set by the ReportsGenerator when the CommandCycle // has a subscriber if (!running) { synchronized (this) { try { this.wait(); } catch (InterruptedException e) { LOG.info("commandcycle got interrupted"); return; } } } while (running) { rounds ++; synchronized (lock) { roundOver = false; } LOG.info("CommandCycle "+ getName() + ": Starting (Round " + rounds + ")."); // set start time long startTime = System.currentTimeMillis(); // accept tags setAcceptTags(true); //------------------------------ run for the specified time try { if (durationValue > 0) { // if durationValue is specified and larger than zero, // wait for notify or durationValue elapsed. synchronized (this) { long dt = (System.currentTimeMillis() - startTime); this.wait(Math.max(1, durationValue - dt)); terminationCondition = CCTerminationCondition.DURATION; } } else { // if durationValue is not specified or smaller than zero, // wait for notify. synchronized (this) { this.wait(); } } } catch (InterruptedException e) { // if Thread is stopped with method stop(), // then return without notify subscribers. LOG.info("commandcycle got interrupted"); return; } // don't accept tags anymore setAcceptTags(false); //------------------------ generate the reports // get reports try { // compute total time totalTime = System.currentTimeMillis() - startTime; LOG.info("CommandCycle "+ getName() + ": Number of Tags read in the current CommandCyle.java: " + tags.size()); CCReports ccReports = getCCReports(); for (LogicalReader logicalReader : logicalReaders) { logicalReader.DELETEACCESSSPEC(); logicalReader.recoveryACCESSSPEC3(); } // notifySubscribers generator.notifySubscribers(ccReports, this); // store the current tags into the old tags // explicitly clear the tags if (lastCommandCycleTags != null) { lastCommandCycleTags.clear(); } if (null != tags) { lastCommandCycleTags.addAll(tags); } tags = Collections.synchronizedSet(new HashSet<Tag>()); } catch (Exception e) { if (e instanceof InterruptedException) { LOG.info("commandcycle got interrupted"); return; } LOG.error("CommandCycle "+ getName() + ": Could not create CCReports", e); } LOG.info("CommandCycle "+ getName() + ": CommandCycle finished (Round " + rounds + ")."); try { // inform possibly waiting workers about the finish synchronized (lock) { roundOver = true; lock.notifyAll(); } // wait until reschedule. synchronized (this) { this.wait(); } LOG.debug("commandcycle continues"); } catch (InterruptedException e) { LOG.info("commandcycle got interrupted"); return; } } // stop CommandCycle stop(); } @Override public void launch() { for (LogicalReader logicalReader : logicalReaders) { // subscribe this command cycle to the logical readers logicalReader.ADDACCESSSPECfromCCSpec(ccspec, this.generator.getOpSpecTable()); LOG.debug("registering CommandCycle " + name + " on reader " + logicalReader.getName()); } this.running = true; LOG.debug("launching commandCycle" + getName()); synchronized (this) { this.notifyAll(); } } /** * This method returns all reports of this command cycle as command cycle * reports. * @return array of cmd report * @throws CCSpecValidationException if a tag of this report is not valid * @throws ImplementationException if an implementation exception occurs. */ private List<CCCmdReport> getCmdReportList() throws CCSpecValidationException, ImplementationException { ArrayList<CCCmdReport> ccCmdReports = new ArrayList<CCCmdReport>(); for (Report report : reports) { CCCmdReport r = report.getCCCmdReport(); if (null != r) ccCmdReports.add(r); } return ccCmdReports; } /** * This method returns the duration value extracted from the command cycle * specification. * @return duration value in milliseconds * @throws ImplementationException if an implementation exception occurs */ private long getDurationValue() throws ImplementationException { if (ccspec.getBoundarySpec() != null) { ECTime duration = ccspec.getBoundarySpec().getDuration(); if (duration != null) { if (duration.getUnit().compareToIgnoreCase(ECTimeUnit.MS) == 0) { return duration.getValue(); } else { throw new ImplementationException( "The only ECTimeUnit allowed is milliseconds (MS)."); } } } return -1; } /** * This method returns the repeat period value on the basis of the command * cycle specification. * @return repeat period value * @throws ImplementationException if the time unit in use is unknown */ private long getRepeatPeriodValue() throws ImplementationException { if (ccspec.getBoundarySpec() != null) { ECTime repeatPeriod = ccspec.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 -1; } @Override public Set<Tag> getLastCommandCycleTags() { return copyContentToNewDatastructure(lastCommandCycleTags); } @Override public Set<Tag> getTags() { return copyContentToNewDatastructure(tags); } /** * create a copy of the content of the given data structure -> we use synchronized sets -> make sure not to leak them.<br/> * this method synchronizes the original data structure during to copy process. * <br/> * <strong>Notice that the content is NOT cloned, simply referenced!</strong> * * @param contentToCopy the data structure to copy. * @return a copy of the data structure with the content of the input. */ private Set<Tag> copyContentToNewDatastructure(Set<Tag> contentToCopy) { Set<Tag> copy = new HashSet<Tag> (); synchronized (contentToCopy) { for (Tag tag : contentToCopy) { copy.add(tag); } } return copy; } private boolean isRejectTagsBetweenCycle() { return rejectTagsBetweenCycle; } private void setRejectTagsBetweenCycle(boolean rejectTagsBetweenCycle) { this.rejectTagsBetweenCycle = rejectTagsBetweenCycle; } /** * tells whether the cc accepts tags. * @return boolean telling whether the cc accepts tags */ private boolean isAcceptingTags() { return acceptTags; } /** * sets the flag acceptTags to the passed boolean value. * @param acceptTags sets the flag acceptTags to the passed boolean value. */ private void setAcceptTags(boolean acceptTags) { this.acceptTags = acceptTags; } @Override public int getRounds() { return rounds; } @Override public void join() throws InterruptedException { synchronized (lock) { while (!isRoundOver()) { lock.wait(); } } } /** * whether the command cycle round is over. * <strong>notice that this method is not exported via interface</strong>. * @return true if over, false otherwise */ public boolean isRoundOver() { return roundOver; } // FIXME: Implementation is currently leaking... need to do something. @Override public CCCmdSpec getCmdReportSpecByName(String name) { return CmdSpecByName.get(name); } /** * @return the lastReports */ /** * get a handle onto the map holding all the report specs. * @return the map. */ protected Map<String, CCCmdSpec> getCmdReportSpecByName() { return CmdSpecByName; } // FIXME: Implementation is currently leaking... need to do something. @Override public Map<String, CCReports> getLastCCReports() { return lastCCReports; } }