/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.gss; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; import org.geotools.data.FeatureDiff; import org.geotools.data.FeatureDiffReader; import org.geotools.data.postgis.FeatureDiffImpl; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; /** * Merges the diffs of the various delegates into one. Delegates will be examined in order to build * a global diff. All changes to feaures under conflict will be ignored. This class is used to get a * history of all the local changes that still haven't been communicated to Central, skipping * Central own changes. * * @author Andrea Aime - OpenGeo * */ class MergingFeatureDiffReader implements FeatureDiffReader { /** * The readers that we need to merge. They are supposed to be sorted */ FeatureDiffReader[] delegates; /** * The lowest fromVersion */ String fromVersion; /** * The highest toVersion */ String toVersion; /** * The schema shared by all readers */ SimpleFeatureType schema; /** * The set of fids that we have "live" FeatureDiff for. At even next() we just want to grab the * lowest and proces it. */ TreeSet<String> sortedFids = new TreeSet<String>(); /** * A map from the ids to the set of FeatureDiff gathered from the readers. Usually we'll just * have one of them, but we might have one per readers. The various diffs are placed in the same * order as the readers, there might be holes. */ Map<String, FeatureDiff[]> diffHistory = new HashMap<String, FeatureDiff[]>(); /** * Flag marking if we have an outstanding FeatureDiff to be processed for the corresponding * reader. Since the readers return diffs in feature id order we just need to keep one * FeatureDiff per reader at any time. */ boolean[] featureRead; /** * The next difference we're going to return */ FeatureDiff nextDifference; /** * Builds a new merging reader. Mind, the order of the delegates is important, as changes are * built up in order * * @param delegates */ public MergingFeatureDiffReader(FeatureDiffReader... delegates) throws IOException { this.delegates = new FeatureDiffReader[delegates.length]; System.arraycopy(delegates, 0, this.delegates, 0, delegates.length); this.featureRead = new boolean[delegates.length]; this.fromVersion = delegates[0].getFromVersion(); this.toVersion = delegates[delegates.length - 1].getToVersion(); this.schema = delegates[0].getSchema(); } public String getFromVersion() { return fromVersion; } public SimpleFeatureType getSchema() { return schema; } public String getToVersion() { return toVersion; } public boolean hasNext() throws IOException { if (nextDifference != null) { return true; } advance(); while (sortedFids.size() > 0) { nextDifference = buildNextDiff(); if (nextDifference != null && (nextDifference.getState() != FeatureDiff.UPDATED || nextDifference.getChangedAttributes().size() > 0)) { return true; } else { // we grabbed the result of a revert over modifications -> two modification changes // that amount to no global change. Let's move to the next set of changes. nextDifference = null; advance(); } } // if we got here it means the set of fids to process dried up return false; } public FeatureDiff next() throws IOException, NoSuchElementException { if (!hasNext()) { throw new NoSuchElementException("No more feature diffs in this reader"); } FeatureDiff result = nextDifference; nextDifference = null; return result; } /** * Builds the next difference object using the stored fids and diffs (assumes you called * advance() before calling it) * * @return */ FeatureDiff buildNextDiff() { String fid = sortedFids.first(); sortedFids.remove(fid); FeatureDiff[] history = diffHistory.remove(fid); // cases we need to handle // - we have only updates -> build a cumulative update // - we have insert and then updates -> build a new insert with the final values // - we have a remove at the end -> forget the rest, it is a remove // - we have some diff, then a remove and then an insert -> the remove has been rolled back // but we don't really know if the roll back included also the roll backs of // updates -> the set of changes has to be rebuilt SimpleFeature from = null; SimpleFeature to = null; boolean removed = false; for (int i = 0; i < history.length; i++) { FeatureDiff diff = history[i]; if (diff != null) { if (diff.getState() == FeatureDiff.INSERTED) { // is this the rollback of a removal? if(removed == true) { from = null; to = null; } else { from = null; to = diff.getFeature(); } removed = false; } else if (diff.getState() == FeatureDiff.DELETED) { if (from == null) { from = diff.getOldFeature(); } to = null; removed = true; } else { // might we have a removed flag before this update? Maybe... if Central // for some reason reinserted the feature (remember we're skipping // changes coming from Central. But in that case we start over fresh if (removed) { removed = false; from = diff.getOldFeature(); } // is this the first diff or we have a history? if (from == null) { from = diff.getOldFeature(); } to = diff.getFeature(); } } } if(from == null && to == null) { return null; } return new FeatureDiffImpl(from, to); } /** * Reads a new FeatureDiff from all readers that still don't have a pending one and that are * still open */ void advance() throws IOException { for (int i = 0; i < delegates.length; i++) { if (delegates[i] != null && !featureRead[i]) { // read the next diff that is not linked to a conflicting feature while (delegates[i].hasNext()) { // grab a new feature diff FeatureDiff fd = delegates[i].next(); String fid = fd.getID(); // is it about a feature id we already know about? FeatureDiff[] history; if (sortedFids.contains(fid)) { history = diffHistory.get(fid); } else { history = new FeatureDiff[delegates.length]; diffHistory.put(fid, history); sortedFids.add(fid); } history[i] = fd; // ok, we're done break; } // did we exit the loop because the reader ended? if(!delegates[i].hasNext()) { // close the delegate and get rid of it delegates[i].close(); delegates[i] = null; } } } } public void close() throws IOException { for (int i = 0; i < delegates.length; i++) { if (delegates[i] != null) { delegates[i].close(); delegates[i] = null; } } } }