/* * Copyright 2011 Future Systems, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.krakenapps.confdb.file; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import org.krakenapps.api.PrimitiveParseCallback; import org.krakenapps.codec.EncodingRule; import org.krakenapps.confdb.Config; import org.krakenapps.confdb.ConfigCache; import org.krakenapps.confdb.ConfigCollection; import org.krakenapps.confdb.ConfigDatabase; import org.krakenapps.confdb.ConfigIterator; import org.krakenapps.confdb.ConfigParser; import org.krakenapps.confdb.Manifest; import org.krakenapps.confdb.ObjectBuilder; import org.krakenapps.confdb.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Build collection snapshot based on collection log. Snapshot contains only doc * pointers, and read actual doc data when you call next() * * @author xeraph * */ class FileConfigIterator implements ConfigIterator { private final Logger logger = LoggerFactory.getLogger(FileConfigIterator.class.getName()); private ConfigDatabase db; private ConfigCache cache; private int manifestId; private ConfigCollection col; private String colName; private RevLogReader reader; private Iterator<RevLog> it; private Predicate pred; private Config prefetch; private boolean loaded; private boolean closed; private ConfigParser parser; public FileConfigIterator(ConfigDatabase db, Manifest manifest, ConfigCollection col, RevLogReader reader, List<RevLog> snapshot, Predicate pred) { this.db = db; this.manifestId = manifest.getId(); this.cache = db.getCache(); this.col = col; this.colName = col.getName(); this.reader = reader; this.it = snapshot.iterator(); this.pred = pred; if (logger.isDebugEnabled()) logger.debug("kraken confdb: db [{}], col [{}], snapshot size [{}], iterator hash code [{}]", new Object[] { db.getName(), col.getName(), snapshot.size(), hashCode() }); } @Override public boolean hasNext() { if (closed) return false; if (loaded) return prefetch != null; try { prefetch = getNextMatch(); } catch (IOException e) { close(); return false; } return prefetch != null; } @Override public Config next() { if (!it.hasNext() && !loaded) throw new NoSuchElementException("no more config item in collection"); if (it.hasNext() && !loaded) { if (!hasNext()) { close(); throw new NoSuchElementException("no more config item in collection"); } } loaded = false; Config r = prefetch; prefetch = null; return r; } private Config getNextMatch() throws IOException { if (loaded) return prefetch; Config matched = null; while (it.hasNext()) { Config c = getNextConfig(); if (logger.isDebugEnabled()) logger.debug("kraken confdb: db [{}], col [{}], config [{}]", new Object[] { db.getName(), col.getName(), c.getDocument() }); if (pred == null || pred.eval(c)) { matched = c.duplicate(); break; } } loaded = (matched != null); if (loaded == false) close(); return matched; } private Config getNextConfig() throws IOException { RevLog log = it.next(); // check cache Config cachedConfig = cache.findEntry(colName, manifestId, log.getDocId(), log.getRev()); if (cachedConfig != null) { Object doc = cachedConfig.getDocument(); return new FileConfig(db, col, log.getDocId(), log.getRev(), log.getPrevRev(), doc, parser); } // fetch doc binary and decode it byte[] b = reader.readDoc(log.getDocOffset(), log.getDocLength()); Object doc = EncodingRule.decode(ByteBuffer.wrap(b)); FileConfig config = new FileConfig(db, col, log.getDocId(), log.getRev(), log.getPrevRev(), doc, parser); cache.putEntry(colName, manifestId, config); return config; } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public void setParser(ConfigParser parser) { this.parser = parser; } @Override public List<Config> getConfigs(int offset, int limit) { try { int p = 0; int count = 0; ArrayList<Config> configs = new ArrayList<Config>(); while (hasNext()) { if (count >= limit) break; Config next = next(); if (p++ < offset) continue; configs.add(next); count++; } return configs; } finally { close(); } } @Override public Collection<Object> getDocuments() { try { Collection<Object> docs = new ArrayList<Object>(); while (hasNext()) docs.add(next().getDocument()); return docs; } finally { close(); } } @Override public <T> Collection<T> getDocuments(Class<T> cls) { return getDocuments(cls, null); } @Override public <T> Collection<T> getDocuments(Class<T> cls, PrimitiveParseCallback callback) { return getDocuments(cls, callback, 0, Integer.MAX_VALUE); } @Override public <T> Collection<T> getDocuments(Class<T> cls, PrimitiveParseCallback callback, int offset, int limit) { try { int p = 0; int count = 0; Collection<T> docs = new ArrayList<T>(); while (hasNext()) { if (count >= limit) break; Config next = next(); if (p++ < offset) continue; docs.add(next.getDocument(cls, callback)); count++; } return docs; } finally { close(); } } @Override public <T> Collection<T> getObjects(ObjectBuilder<T> builder, int offset, int limit) { try { int p = 0; int count = 0; Collection<T> objects = new ArrayList<T>(); while (hasNext()) { if (count >= limit) break; Config next = next(); if (p++ < offset) continue; T obj = builder.build(next); if (obj == null) continue; objects.add(obj); count++; } return objects; } finally { close(); } } @Override public int count() { int total = 0; try { while (it.hasNext()) { Config c = getNextConfig(); if (pred == null || pred.eval(c)) total++; } } catch (IOException e) { throw new IllegalStateException("cannot count collection " + col.getName() + " of database " + db.getName(), e); } finally { close(); } return total; } @Override public void close() { if (!closed) { reader.close(); closed = true; } } }