summaryrefslogtreecommitdiff
path: root/src/main/src/data/io/SafeDataReader.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/src/data/io/SafeDataReader.java')
-rw-r--r--src/main/src/data/io/SafeDataReader.java155
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;
+ }
+}