/* * 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 org.apache.log4j.Logger; import org.fosstrak.ale.exception.ECSpecValidationException; import org.fosstrak.ale.exception.ImplementationException; import org.fosstrak.ale.server.ALEApplicationContext; import org.fosstrak.ale.server.Pattern; import org.fosstrak.ale.server.PatternUsage; import org.fosstrak.ale.server.readers.LogicalReaderManager; import org.fosstrak.ale.server.tm.ALETM; import org.fosstrak.ale.server.tm.SymbolicFieldRepo; import org.fosstrak.ale.xsd.ale.epcglobal.ECBoundarySpec; import org.fosstrak.ale.xsd.ale.epcglobal.ECFilterListMember; import org.fosstrak.ale.xsd.ale.epcglobal.ECFilterSpec; import org.fosstrak.ale.xsd.ale.epcglobal.ECGroupSpec; import org.fosstrak.ale.xsd.ale.epcglobal.ECReportOutputFieldSpec; import org.fosstrak.ale.xsd.ale.epcglobal.ECReportOutputSpec; import org.fosstrak.ale.xsd.ale.epcglobal.ECReportSpec; import org.fosstrak.ale.xsd.ale.epcglobal.ECSpec; import org.fosstrak.ale.xsd.ale.epcglobal.ECSpec.LogicalReaders; 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 ECSpec. * @author sbw * */ @Service("ecSpecValidator") public class ECSpecValidator { /** * private handle onto the logical reader manager. */ private LogicalReaderManager logicalReaderManager; /** logger */ private static final Logger LOG = Logger.getLogger(ECSpecValidator.class); /** * constructor for class. */ public ECSpecValidator() { } /** * 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 ECSpecValidationException if the specification is invalid * @throws ImplementationException if an implementation exception occurs */ public void validateSpec(ECSpec spec) throws ECSpecValidationException, 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()); checkReportSpecs(spec.getReportSpecs()); } /** * verifies that the spec contains some readers and that those readers are available in the ALE. * @param logicalReaders the logical reader definition from the ECSpec. * @param readerManager handle to the logical reader manager. * @return true if OK, throws exception otherwise. * @throws ECSpecValidationException if no reader specified or the specified readers do not exist in the ALE. */ public boolean checkReadersAvailable(LogicalReaders logicalReaders, LogicalReaderManager readerManager) throws ECSpecValidationException { if ((null == logicalReaders) || (logicalReaders.getLogicalReader().size() == 0)) { throw logAndCreateECSpecValidationException("ECSpec does not specify at least one reader"); } for (String logicalReaderName : logicalReaders.getLogicalReader()) { if (!readerManager.contains(logicalReaderName)) { throw logAndCreateECSpecValidationException("LogicalReader '" + logicalReaderName + "' is unknown."); } } return true; } /** * verifies the boundary spec of an ECSpec. * @param boundarySpec the boundary spec to verify. * @throws ECSpecValidationException if the specification does not meet the specification. * @return true if OK, throws exception otherwise. */ public boolean checkBoundarySpec(ECBoundarySpec boundarySpec) throws ECSpecValidationException { // boundaries parameter of ECSpec is null or omitted if (boundarySpec == null) { throw logAndCreateECSpecValidationException("The boundaries parameter of ECSpec is null."); } // start and stop tiggers checkTrigger(boundarySpec.getStartTrigger()); checkTrigger(boundarySpec.getStopTrigger()); // check if duration, stableSetInterval or repeatPeriod is negative checkTimeNotNegative(boundarySpec.getDuration(), "The duration field of ECBoundarySpec is negative."); checkTimeNotNegative(boundarySpec.getStableSetInterval(), "The stableSetInterval field of ECBoundarySpec is negative."); checkTimeNotNegative(boundarySpec.getRepeatPeriod(), "The repeatPeriod field of ECBoundarySpec 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 ECSpecValidationException if the time value is negative. */ public boolean checkTimeNotNegative(ECTime duration, String string) throws ECSpecValidationException { if (duration != null) { if (duration.getValue() < 0) { throw logAndCreateECSpecValidationException("The duration field of ECBoundarySpec 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 ECSpecValidationException if a start trigger is specified, then the repeat period must be 0. if not, throw an exception. */ public boolean checkStartTriggerConstraintsOnRepeatPeriod(ECBoundarySpec boundarySpec) throws ECSpecValidationException { if ((boundarySpec.getStartTrigger() != null) && (boundarySpec.getRepeatPeriod() != null) && boundarySpec.getRepeatPeriod().getValue() != 0) { throw logAndCreateECSpecValidationException("The startTrigger field of ECBoundarySpec is non-empty and the repeatPeriod field of ECBoundarySpec is non-zero."); } return true; } /** * check the stopping condition of the EC 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 ECSpecValidationException if there is no stop trigger or no duration value or no stableSetInterval, throw an exception. */ public boolean checkBoundarySpecStoppingCondition(ECBoundarySpec boundarySpec) throws ECSpecValidationException { if ((boundarySpec.getStopTrigger() == null) && (boundarySpec.getDuration() == null) && (boundarySpec.getStableSetInterval() == null)) { throw logAndCreateECSpecValidationException("No stopping condition is specified in ECBoundarySpec."); } return true; } /** * checks the report specs. * @param reportSpecs the report specs to verify. * @throws ECSpecValidationException when the specifications do not meet the requirements. * @return true if the specification is OK, throws exception otherwise. */ public boolean checkReportSpecs(ReportSpecs reportSpecs) throws ECSpecValidationException { // check if there is a ECReportSpec instance if ((reportSpecs == null) || (reportSpecs.getReportSpec().size() == 0)) { throw logAndCreateECSpecValidationException("List of ECReportSpec is empty or null."); } final List<ECReportSpec> reportSpecList = reportSpecs.getReportSpec(); // check report set for (ECReportSpec reportSpec : reportSpecList) { String reportSet = reportSpec.getReportSet().getSet(); if(!reportSet.equalsIgnoreCase("CURRENT") && !reportSet.equalsIgnoreCase("ADDITIONS") && !reportSet.equalsIgnoreCase("DELETIONS")) { throw new ECSpecValidationException("report set spec should be either CURRENT, ADDITIONS, or DELETIONS"); } } // check that no two ECReportSpec instances have identical names checkReportSpecNoDuplicateReportSpecNames(reportSpecList); // check filters for (ECReportSpec reportSpec : reportSpecList) { checkFilterSpec(reportSpec.getFilterSpec()); } // check grouping patterns for (ECReportSpec reportSpec : reportSpecList) { checkGroupSpec(reportSpec.getGroupSpec()); } // check if there is a output type specified for each ECReportSpec for (ECReportSpec reportSpec : reportSpecList) { checkReportOutputSpec(reportSpec.getReportName(), reportSpec.getOutput()); } // check if there is a output type specified for each ECReportSpec for (ECReportSpec reportSpec : reportSpecList) { if (reportSpec.getExtension() != null) { if (reportSpec.getExtension().getStatProfileNames() != null) { if (reportSpec.getExtension().getStatProfileNames().getStatProfileName() != null) { for (String name : reportSpec.getExtension().getStatProfileNames().getStatProfileName()) { if (name.equalsIgnoreCase("default") || name.equalsIgnoreCase("TagTimestamps")) { } else throw new ECSpecValidationException("StatProfileName is not valid type."); } } } } } return true; } /** * verify that no two report specs have the same name. * @param reportSpecList the list of report specs to check. * @return a list containing all the names of the different report specs. * @throws ECSpecValidationException when there are two report specs with the same name. */ public Set<String> checkReportSpecNoDuplicateReportSpecNames(List<ECReportSpec> reportSpecList) throws ECSpecValidationException { Set<String> reportSpecNames = new HashSet<String>(); for (ECReportSpec reportSpec : reportSpecList) { LOG.debug("Verify report spec name not specified twice: " + reportSpec.getReportName()); if (reportSpecNames.contains(reportSpec.getReportName())) { throw logAndCreateECSpecValidationException("Two ReportSpecs instances have identical names '" + reportSpec.getReportName() + "'."); } else { reportSpecNames.add(reportSpec.getReportName()); } } return reportSpecNames; } /** * verify the report output specification. * @param outputSpec the output specification. * @return true if the specification is OK, otherwise throws exception. * @throws ECSpecValidationException violates the specification. */ public boolean checkReportOutputSpec(String reportName, ECReportOutputSpec outputSpec) throws ECSpecValidationException { if (null == outputSpec) { throw logAndCreateECSpecValidationException("there is no output spec for report spec: " + reportName); } if (!outputSpec.isIncludeEPC() && !outputSpec.isIncludeTag() && !outputSpec.isIncludeRawHex() && !outputSpec.isIncludeRawDecimal() && !outputSpec.isIncludeCount()) { throw logAndCreateECSpecValidationException("The ECReportOutputSpec of ReportSpec '" + reportName + "' has no output type specified."); } if(outputSpec.getExtension() != null && outputSpec.getExtension().getFieldList() != null) { for(ECReportOutputFieldSpec outputFieldSpec : outputSpec.getExtension().getFieldList().getField()) { String fieldname = null; if(outputFieldSpec.getName() != null) { fieldname = outputFieldSpec.getName(); } else { if(outputFieldSpec.getFieldspec() != null) { fieldname = outputFieldSpec.getFieldspec().getFieldname(); } } if(fieldname == null) { throw new ECSpecValidationException("output fieldname is not specified"); } if(!fieldname.equalsIgnoreCase("epc") && !fieldname.equalsIgnoreCase("killPwd") && !fieldname.equalsIgnoreCase("accessPwd") && !fieldname.equalsIgnoreCase("epcBank") && !fieldname.equalsIgnoreCase("tidBank") && !fieldname.equalsIgnoreCase("userBank") && !fieldname.equalsIgnoreCase("afi") && !fieldname.equalsIgnoreCase("nsi")) { // check generic fieldname if(fieldname.startsWith("@")) { try { String[] part = fieldname.substring(1).split("\\."); int bank = Integer.parseInt(part[0]); if( bank < 0 || bank > 4 ) throw new ECSpecValidationException("fieldname "+fieldname+" whose bank is not valid"); int length = Integer.parseInt(part[1]); if(length < 0) throw new ECSpecValidationException("fieldname "+fieldname+" whose length is not valid"); int offset = Integer.parseInt(part[2]); if(offset < 0) throw new ECSpecValidationException("fieldname "+fieldname+" whose offset is not valid"); } catch(NumberFormatException e) { throw new ECSpecValidationException("fieldname "+fieldname+" is not valid"); } } else { // check symbolic fieldname if(SymbolicFieldRepo.getInstance().getSymbolicField(fieldname) == null) { throw new ECSpecValidationException("symbolic fieldname "+fieldname+" is not defined using TM API"); } } } } } return true; } /** * check the group spec patterns do not have intersecting groups -> all group 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 groupSpec the group spec to tested. * @throws ECSpecValidationException upon violation. * @return true if filter group spec is valid. exception otherwise. */ public boolean checkGroupSpec(ECGroupSpec groupSpec) throws ECSpecValidationException { if (groupSpec != null) { String[] patterns = groupSpec.getPattern().toArray(new String[] {}); for (int i=0; i<patterns.length-1; i++) { final String pattern1 = patterns[i]; for (int j=i+1; j<patterns.length; j++) { final String pattern2 = patterns[j]; if (!patternDisjoint(pattern1, pattern2)) { throw logAndCreateECSpecValidationException("The two grouping patterns '" + pattern1 + "' and '" + pattern2 + "' are not disjoint."); } } } } if (groupSpec != null) { if (groupSpec.getPattern() != null) { for (String temp : groupSpec.getPattern()) { new Pattern(temp, PatternUsage.GROUP); } } } return true; } /** * check the filter spec. if the filter spec is null, it is ignored. * @param filterSpec the filter spec to verify. * @throws ECSpecValidationException upon violation of the filter pattern. * @return true if filter spec is valid. exception otherwise. */ public boolean checkFilterSpec(ECFilterSpec filterSpec) throws ECSpecValidationException { if (filterSpec != null) { // check include patterns if (filterSpec.getIncludePatterns() != null) { for (String pattern : filterSpec.getIncludePatterns().getIncludePattern()) { new Pattern(pattern, PatternUsage.FILTER); } } // check exclude patterns if (filterSpec.getExcludePatterns() != null) { for (String pattern : filterSpec.getExcludePatterns().getExcludePattern()) { new Pattern(pattern, PatternUsage.FILTER); } } if (filterSpec.getExtension() != null) { if (filterSpec.getExtension().getFilterList() != null) { if (filterSpec.getExtension().getFilterList().getFilter() != null) { for (ECFilterListMember pattern : filterSpec.getExtension().getFilterList().getFilter()) { if(pattern.getFieldspec() != null && pattern.getFieldspec().getFieldname().equalsIgnoreCase("epc")) { if (pattern.getPatList() != null) { if (!pattern.getPatList().getPat().isEmpty()) { for (String temp : pattern.getPatList().getPat()) new Pattern(temp, PatternUsage.FILTER); } else { throw new ECSpecValidationException("PatList is empty."); } } } } } } } } 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 ECSpecValidationException { Pattern pattern = new Pattern(pattern1, PatternUsage.GROUP); return pattern.isDisjoint(pattern2); } /** * log the given string and then create from the string an ECSpecValidationException. * @param string the log and exception string. * @return the ECSpecValidationException created from the input string. */ private ECSpecValidationException logAndCreateECSpecValidationException(String string) { LOG.debug(string); return new ECSpecValidationException(string); } /** * This method checks if the trigger is valid or not. * * @param trigger to check * @throws ECSpecValidationException if the trigger is invalid. */ private void checkTrigger(String trigger) throws ECSpecValidationException { // TODO: implement checkTrigger LOG.debug("CHECK TRIGGER not implemented"); } }