/*
* 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.util;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import junit.framework.Assert;
import org.apache.log4j.Logger;
import org.fosstrak.ale.exception.CCSpecValidationException;
import org.fosstrak.ale.exception.ImplementationException;
import org.fosstrak.ale.server.Pattern;
import org.fosstrak.ale.server.PatternUsage;
import org.fosstrak.ale.server.readers.LogicalReaderManager;
import org.fosstrak.ale.xsd.ale.epcglobal.CCBoundarySpec;
import org.fosstrak.ale.xsd.ale.epcglobal.CCBoundarySpec.StartTriggerList;
import org.fosstrak.ale.xsd.ale.epcglobal.CCBoundarySpec.StopTriggerList;
import org.fosstrak.ale.xsd.ale.epcglobal.CCCmdSpec;
import org.fosstrak.ale.xsd.ale.epcglobal.CCCmdSpec.OpSpecs;
import org.fosstrak.ale.xsd.ale.epcglobal.CCFilterSpec;
import org.fosstrak.ale.xsd.ale.epcglobal.CCOpSpec;
import org.fosstrak.ale.xsd.ale.epcglobal.ECBoundarySpec;
import org.fosstrak.ale.xsd.ale.epcglobal.ECFilterSpec;
import org.fosstrak.ale.xsd.ale.epcglobal.ECGroupSpec;
import org.fosstrak.ale.xsd.ale.epcglobal.ECReportOutputSpec;
import org.fosstrak.ale.xsd.ale.epcglobal.ECReportSpec;
import org.fosstrak.ale.xsd.ale.epcglobal.CCSpec;
import org.fosstrak.ale.xsd.ale.epcglobal.CCSpec.LogicalReaders;
import org.fosstrak.ale.xsd.ale.epcglobal.CCSpec.CmdSpecs;
import org.fosstrak.ale.xsd.ale.epcglobal.ECSpec.ReportSpecs;
import org.fosstrak.ale.xsd.ale.epcglobal.ECTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* helper utility validating an CCSpec.
* @author sbw
*
*/
@Service("ccSpecValidator")
public class CCSpecValidator {
/**
* private handle onto the logical reader manager.
*/
private LogicalReaderManager logicalReaderManager;
/** logger */
private static final Logger LOG = Logger.getLogger(CCSpecValidator.class);
/**
* constructor for class.
*/
public CCSpecValidator() {
}
/**
* inject the autowired bean logical reader manager into the static utility class.
* @param lrm
*/
@Autowired
public void setLogicalReaderManager(LogicalReaderManager lrm) {
LOG.debug("setting logical reader manager" + lrm.getClass().getCanonicalName());
logicalReaderManager = lrm;
}
/**
* This method validates an ec specification under criterias of chapter
* 8.2.11 of ALE specification version 1.0.
* @param spec to validate
* @throws CCSpecValidationException if the specification is invalid
* @throws ImplementationException if an implementation exception occurs
*/
public void validateSpec(CCSpec spec) throws CCSpecValidationException, ImplementationException {
if (logicalReaderManager == null) {
throw new IllegalStateException("Logical Reader Manager is null - aborting");
}
// check if the logical readers are known to the implementation
checkReadersAvailable(spec.getLogicalReaders(), logicalReaderManager);
checkBoundarySpec(spec.getBoundarySpec());
checkCmdSpecs(spec.getCmdSpecs());
}
/**
* verifies that the spec contains some readers and that those readers are available in the ALE.
* @param logicalReaders the logical reader definition from the CCSpec.
* @param readerManager handle to the logical reader manager.
* @return true if OK, throws exception otherwise.
* @throws CCSpecValidationException if no reader specified or the specified readers do not exist in the ALE.
*/
public boolean checkReadersAvailable(LogicalReaders logicalReaders, LogicalReaderManager readerManager) throws CCSpecValidationException {
if ((null == logicalReaders) || (logicalReaders.getLogicalReader().size() == 0)) {
throw logAndCreateCCSpecValidationException("CCSpec does not specify at least one reader");
}
for (String logicalReaderName : logicalReaders.getLogicalReader()) {
if (!readerManager.contains(logicalReaderName)) {
throw logAndCreateCCSpecValidationException("LogicalReader '" + logicalReaderName + "' is unknown.");
}
}
return true;
}
/**
* verifies the boundary spec of an CCSpec.
* @param boundarySpec the boundary spec to verify.
* @throws CCSpecValidationException if the specification does not meet the specification.
* @return true if OK, throws exception otherwise.
*/
public boolean checkBoundarySpec(CCBoundarySpec boundarySpec) throws CCSpecValidationException {
// boundaries parameter of CCSpec is null or omitted
if (boundarySpec == null) {
throw logAndCreateCCSpecValidationException("The boundaries parameter of CCSpec is null.");
}
// start and stop tiggers
checkStartTrigger(boundarySpec.getStartTriggerList());
checkStopTrigger(boundarySpec.getStopTriggerList());
// check if duration, stableSetInterval or repeatPeriod is negative
checkTimeNotNegative(boundarySpec.getDuration(), "The duration field of CCBoundarySpec is negative.");
checkTimeNotNegative(boundarySpec.getNoNewTagsInterval(), "The NoNewTagsInterval field of CCBoundarySpec is negative.");
checkTimeNotNegative(boundarySpec.getRepeatPeriod(), "The repeatPeriod field of CCBoundarySpec is negative.");
if (boundarySpec.getTagsProcessedCount() != null)
checkCountNotNegative(boundarySpec.getTagsProcessedCount(), "The tagsProcessedCount field of CCBoundarySpec is negative.");
// check if start trigger is non-empty and repeatPeriod is non-zero
checkStartTriggerConstraintsOnRepeatPeriod(boundarySpec);
// check if a stopping condition is specified
checkBoundarySpecStoppingCondition(boundarySpec);
return true;
}
/**
* check that the provided time value is not negative.
* @param duration the time value to check.
* @param string a message string to show in the exception.
* @return true if OK, throws Exception otherwise.
* @throws CCSpecValidationException if the time value is negative.
*/
public boolean checkTimeNotNegative(ECTime duration, String string) throws CCSpecValidationException {
if (duration != null) {
if (duration.getValue() < 0) {
throw logAndCreateCCSpecValidationException("The duration field of CCBoundarySpec is negative.");
}
}
return true;
}
/**
* check that the provided count value is not negative.
* @param cout value to check.
* @param string a message string to show in the exception.
* @return true if OK, throws Exception otherwise.
* @throws CCSpecValidationException if the time value is negative.
*/
public boolean checkCountNotNegative(int count, String string) throws CCSpecValidationException {
if (count < 0) {
throw logAndCreateCCSpecValidationException("The tagsProcessedCount field of CCBoundarySpec is negative.");
}
return true;
}
/**
* if a start trigger is specified, then the repeat period must be 0.
* @param boundarySpec the boundary spec to test.
* @return true if OK, throws exception otherwise.
* @throws CCSpecValidationException if a start trigger is specified, then the repeat period must be 0. if not, throw an exception.
*/
public boolean checkStartTriggerConstraintsOnRepeatPeriod(CCBoundarySpec boundarySpec) throws CCSpecValidationException {
if ((boundarySpec.getStartTriggerList() != null) && (boundarySpec.getRepeatPeriod().getValue() != 0)) {
throw logAndCreateCCSpecValidationException("The startTrigger field of CCBoundarySpec is non-empty and the repeatPeriod field of CCBoundarySpec is non-zero.");
}
return true;
}
/**
* check the stopping condition of the CC boundary spec:<br/>
* if there is no stop trigger or no duration value or no stableSetInterval, throw an exception.
* @param boundarySpec the boundary spec to test.
* @return true if OK, throws exception otherwise.
* @throws CCSpecValidationException if there is no stop trigger or no duration value or no stableSetInterval, throw an exception.
*/
public boolean checkBoundarySpecStoppingCondition(CCBoundarySpec boundarySpec) throws CCSpecValidationException {
if ((boundarySpec.getStopTriggerList() == null) && (boundarySpec.getDuration() == null) && (boundarySpec.getNoNewTagsInterval() == null)) {
throw logAndCreateCCSpecValidationException("No stopping condition is specified in CCBoundarySpec.");
}
return true;
}
/**
* checks the cmd specs.
* @param reportSpecs the cmd specs to verify.
* @throws CCSpecValidationException when the specifications do not meet the requirements.
* @return true if the specification is OK, throws exception otherwise.
*/
public boolean checkCmdSpecs(CmdSpecs cmdSpecs) throws CCSpecValidationException {
// check if there is a CCReportSpec instance
if ((cmdSpecs == null) || (cmdSpecs.getCmdSpec().isEmpty())) {
throw logAndCreateCCSpecValidationException("List of CCCmdSpec is empty or null.");
}
final List<CCCmdSpec> cmdSpecList = cmdSpecs.getCmdSpec();
// check that no two CCReportSpec instances have identical names
checkCmdSpecNoDuplicateReportSpecNames(cmdSpecList);
// check filters
for (CCCmdSpec cmdSpec : cmdSpecList) {
checkFilterSpec(cmdSpec.getFilterSpec());
}
// check grouping patterns
for (CCCmdSpec cmdSpec : cmdSpecList) {
checkOpSpec(cmdSpec.getOpSpecs());
}
return true;
}
/**
* verify that no two cmd specs have the same name.
* @param reportSpecList the list of cmd specs to check.
* @return a list containing all the names of the different cmd specs.
* @throws CCSpecValidationException when there are two cmd specs with the same name.
*/
public Set<String> checkCmdSpecNoDuplicateReportSpecNames(List<CCCmdSpec> cmdSpecList) throws CCSpecValidationException {
Set<String> cmdSpecNames = new HashSet<String>();
for (CCCmdSpec cmdSpec : cmdSpecList) {
LOG.debug("Verify report spec name not specified twice: " + cmdSpec.getName());
if (cmdSpecNames.contains(cmdSpec.getName())) {
throw logAndCreateCCSpecValidationException("Two CmdSpecs instances have identical names '" + cmdSpec.getName() + "'.");
} else {
cmdSpecNames.add(cmdSpec.getName());
}
}
return cmdSpecNames;
}
/**
* verify the report output specification.
* @param outputSpec the output specification.
* @return true if the specification is OK, otherwise throws exception.
* @throws CCSpecValidationException violates the specification.
*/
public boolean checkReportOutputSpec(String reportName, ECReportOutputSpec outputSpec) throws CCSpecValidationException {
if (null == outputSpec) {
throw logAndCreateCCSpecValidationException("there is no output spec for report spec: " + reportName);
}
if (!outputSpec.isIncludeEPC() && !outputSpec.isIncludeTag() && !outputSpec.isIncludeRawHex() && !outputSpec.isIncludeRawDecimal() && !outputSpec.isIncludeCount()) {
throw logAndCreateCCSpecValidationException("The ECReportOutputSpec of ReportSpec '" + reportName + "' has no output type specified.");
}
return true;
}
/**
* check the op spec patterns do not have intersecting ops -> all op patterns have to be disjoint:<br/>
* <ul>
* <li>the same pattern is not allowed to occur twice</li>
* <li>two different pattern with intersecting selectors are not allowed</li>
* <li>no pattern at all is allowed</li>
* </ul>
* @param opSpec the op spec to tested.
* @throws CCSpecValidationException upon violation.
* @return true if filter op spec is valid. exception otherwise.
*/
public boolean checkOpSpec(OpSpecs opSpec) throws CCSpecValidationException {
if (opSpec != null) {
if (opSpec.getOpSpec() != null) {
if (!opSpec.getOpSpec().isEmpty()) {
List<CCOpSpec> opspecs = opSpec.getOpSpec();
for (CCOpSpec opspec : opspecs)
{
String temp = opspec.getOpType();
if (temp.equalsIgnoreCase("READ")||temp.equalsIgnoreCase("CHECK")||temp.equalsIgnoreCase("INITIALIZE")||temp.equalsIgnoreCase("ADD")||temp.equalsIgnoreCase("WRITE")||temp.equalsIgnoreCase("DELETE")||temp.equalsIgnoreCase("PASSWORD")||temp.equalsIgnoreCase("KILL")||temp.equalsIgnoreCase("LOCK"))
{
if (temp.equalsIgnoreCase("READ")||temp.equalsIgnoreCase("CHECK")||temp.equalsIgnoreCase("INITIALIZE")||temp.equalsIgnoreCase("ADD")||temp.equalsIgnoreCase("WRITE")||temp.equalsIgnoreCase("DELETE")||temp.equalsIgnoreCase("LOCK"))
{
if (opspec.getFieldspec() != null) {
}
else
throw logAndCreateCCSpecValidationException("No FieldSpec.");
}
if (temp.equalsIgnoreCase("PASSWORD")||temp.equalsIgnoreCase("KILL")) {
if (opspec.getFieldspec() != null) {
throw logAndCreateCCSpecValidationException("Exist FieldSpec.");
}
}
if (temp.equalsIgnoreCase("CHECK")||temp.equalsIgnoreCase("INITIALIZE")||temp.equalsIgnoreCase("ADD")||temp.equalsIgnoreCase("WRITE")||temp.equalsIgnoreCase("PASSWORD")||temp.equalsIgnoreCase("KILL")||temp.equalsIgnoreCase("LOCK"))
{
if (opspec.getDataSpec() != null) {
}
else
throw logAndCreateCCSpecValidationException("No DataSpec.");
}
if (temp.equalsIgnoreCase("READ")||temp.equalsIgnoreCase("DELETE")) {
if (opspec.getDataSpec() != null) {
throw logAndCreateCCSpecValidationException("Exist DataSpec.");
}
}
}
else
throw logAndCreateCCSpecValidationException("Incorrect OpType.");
}
}
}
}
return true;
}
/**
* check the filter spec. if the filter spec is null, it is ignored.
* @param filterSpec the filter spec to verify.
* @throws CCSpecValidationException upon violation of the filter pattern.
* @return true if filter spec is valid. exception otherwise.
*/
public boolean checkFilterSpec(CCFilterSpec filterSpec) throws CCSpecValidationException {
if (filterSpec != null) {
// check include patterns
if (filterSpec.getFilterList() != null)
{
if(filterSpec.getFilterList().getFilter() != null)
{
if(filterSpec.getFilterList().getFilter().get(0) != null)
{
if(filterSpec.getFilterList().getFilter().get(0).getPatList() != null)
{
if(!filterSpec.getFilterList().getFilter().get(0).getPatList().getPat().isEmpty())
{
}
else
{
throw logAndCreateCCSpecValidationException("No Pat.");
}
}
else
throw logAndCreateCCSpecValidationException("No PatList.");
}
}
}
}
return true;
}
/**
* test if two pattern are disjoint.
* @param pattern1 the first and reference pattern.
* @param pattern2 the second pattern.
* @return true if pattern not disjoint, false otherwise.
*/
/*public boolean patternDisjoint(String pattern1, String pattern2) throws CCSpecValidationException {
Pattern pattern = new Pattern(pattern1, PatternUsage.GROUP);
return pattern.isDisjoint(pattern2);
}*/
/**
* log the given string and then create from the string an CCSpecValidationException.
* @param string the log and exception string.
* @return the CCSpecValidationException created from the input string.
*/
private CCSpecValidationException logAndCreateCCSpecValidationException(String string) {
LOG.debug(string);
return new CCSpecValidationException(string);
}
/**
* This method checks if the trigger is valid or not.
*
* @param stopTriggerList to check
* @throws CCSpecValidationException if the trigger is invalid.
*/
private void checkStopTrigger(StopTriggerList stopTriggerList) throws CCSpecValidationException {
// TODO: implement checkTrigger
LOG.debug("CHECK TRIGGER not implemented");
}
/**
* This method checks if the trigger is valid or not.
*
* @param startTriggerList to check
* @throws CCSpecValidationException if the trigger is invalid.
*/
private void checkStartTrigger(StartTriggerList startTriggerList) throws CCSpecValidationException {
// TODO: implement checkTrigger
LOG.debug("CHECK TRIGGER not implemented");
}
}