diff options
Diffstat (limited to 'src/main/src/data/io/SafeDataReader.java')
-rw-r--r-- | src/main/src/data/io/SafeDataReader.java | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/src/main/src/data/io/SafeDataReader.java b/src/main/src/data/io/SafeDataReader.java new file mode 100644 index 0000000..2c5dda9 --- /dev/null +++ b/src/main/src/data/io/SafeDataReader.java @@ -0,0 +1,155 @@ +/* + * SSSync, a Simple and Stupid Synchronizer for data with multi-valued attributes + * Copyright (C) 2014 Ludovic Pouzenc <ludovic@pouzenc.fr> + * + * This file is part of SSSync. + * + * SSSync is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SSSync 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SSSync. If not, see <http://www.gnu.org/licenses/> + */ + +package data.io; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apache.log4j.Logger; + +import data.MVDataEntry; + +/** + * Multi-valued "safe" stream reader proxy. + * Adds logging and skipReadError mode feature. Check if items are well ordered. + * Ensures consistency of hasNext() / next() even if source stream is faulty. + * Never returns null items but throw NoSuchElementException if no other choices. + * + * @author lpouzenc + */ +public class SafeDataReader extends AbstractMVDataReader { + + private static final Logger logger = Logger.getLogger(SafeDataReader.class.getName()); + + private final MVDataReader src; + /** + * If true, continue even in case of read errors + */ + private final boolean skipReadErrors; + + private transient Iterator<MVDataEntry> srcIt; + private transient boolean abort; + private transient MVDataEntry previousData; + + + public SafeDataReader(MVDataReader src, boolean skipReadErrors) { + this.src = src; + this.dataSourceName = src.getDataSourceName(); + this.skipReadErrors = skipReadErrors; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator<MVDataEntry> iterator() { + // Reset everything + srcIt = src.iterator(); + abort = false; + previousData = null; + + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasNext() { + return (!abort && srcIt.hasNext()); + } + + /** + * {@inheritDoc} + */ + @Override + public MVDataEntry next() { + boolean alreadyWarned=false; + boolean done=false; + MVDataEntry entry = null; + + // Prepare an hint for read exception (knowledge of last successfully read entry could help) + String hint = ( previousData != null )?previousData.getKey():"(nothing)"; + + // Seek for the next valid entry + while (!this.abort && !done && srcIt.hasNext()) { + + // Try to read next entry + try { + entry=src.next(); + if ( entry == null ) throw new NoSuchElementException("Null item returned"); + } catch (Exception e) { + logger.warn(src.getDataSourceName() + " : exception when seeking next valid entry after " + hint, e); + entry = null; // Make sure don't re-use a previous entry + } + + // Sanity checks + boolean valid = ( entry != null && entry.isValid() ); + //XXX Regex should be a parameter + if ( valid && !entry.getKey().matches("^\\p{Print}+$") ) { + logger.warn(src.getDataSourceName() + " : Invalid key found : '" + entry.getKey().replaceAll("[^\\p{Print}]", "?") + "' after " + hint); + valid = false; + } + + + // Two branches : If valid, check ordering then skip or done. If invalid : skip or abort. + if ( valid ) { + // Ensure that data.key is greater than previousData.key or abort + if ( previousData != null && entry.getKey().compareTo(previousData.getKey()) <= 0 ) { + //TODO : this is almost useless in case of reverse-sortered query because everything will be deleted by the Syncer before asking the second item + logger.error(src.getDataSourceName() + " : Input data is not well ordered but the sync task require it : '" + + entry.getKey() + "' is not lexicographically greater than '" + previousData.getKey() + "'"); + // Escape the while loop + abort=true; continue; + } + + // We have found a valid entry, so escape gracefully the loop + done=true; + } else { + // Log read problems and choose between skip or abort + if ( ! this.skipReadErrors ) { + logger.error(src.getDataSourceName() + " has returned an invalid entry after " + hint); + // Escape the while loop + abort=true; continue; + } + if ( !alreadyWarned ) { + alreadyWarned=true; + logger.info("Invalid entry read but skipReadErrors is enabled, will try to read next entry (warned only once)"); + } + + // We don't have a valid entry, give a chance to the next iteration + done=false; + } /* if ( valid )*/ + + } /* while */ + + // If we don't have found anything valid, throw exception (better semantics than returning null) + if (!done) { + throw new NoSuchElementException(); + } + + // Keep track of previous read record + // -> for hinting in log messages when bad things happens + // -> to check if entries are well ordered + previousData=entry; + return entry; + } +} |