/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2010 Sun Microsystems, Inc.
* Portions copyright 2012-2013 ForgeRock AS.
*/
package org.opends.server.backends.jeb.importLDIF;
import static org.opends.messages.JebMessages.ERR_JEB_IMPORT_BUFFER_IO_ERROR;
import static org.opends.server.loggers.ErrorLogger.logError;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.opends.messages.Message;
import org.opends.server.backends.jeb.importLDIF.Importer.IndexManager;
import com.sleepycat.util.PackedInteger;
/**
* The buffer class is used to process a buffer from the temporary index files
* during phase 2 processing.
*/
public final class IndexInputBuffer implements Comparable<IndexInputBuffer>
{
private final IndexManager indexMgr;
private final FileChannel channel;
private final long begin;
private final long end;
private final int id;
private long offset;
private final ByteBuffer cache;
private Integer indexID = null;
private ByteBuffer keyBuf = ByteBuffer.allocate(128);
/**
* Possible states while reading a record.
*/
private enum RecordState
{
START, NEED_INSERT_ID_SET, NEED_DELETE_ID_SET
}
private RecordState recordState = RecordState.START;
/**
* Creates a new index input buffer.
*
* @param indexMgr
* The index manager.
* @param channel
* The index file channel.
* @param begin
* The position of the start of the buffer in the scratch file.
* @param end
* The position of the end of the buffer in the scratch file.
* @param id
* The index ID.
* @param cacheSize
* The cache size.
* @throws IOException
* If an IO error occurred when priming the cache.
*/
public IndexInputBuffer(IndexManager indexMgr, FileChannel channel,
long begin, long end, int id, int cacheSize) throws IOException
{
this.indexMgr = indexMgr;
this.channel = channel;
this.begin = begin;
this.end = end;
this.offset = 0;
this.id = id;
this.cache = ByteBuffer.allocate(Math.max(cacheSize - 384, 256));
loadCache();
cache.flip();
keyBuf.flip();
}
private void loadCache() throws IOException
{
channel.position(begin + offset);
long leftToRead = end - (begin + offset);
long bytesToRead;
if (leftToRead < cache.remaining())
{
cache.limit((int) (cache.position() + leftToRead));
bytesToRead = (int) leftToRead;
}
else
{
bytesToRead = Math.min((end - offset), cache.remaining());
}
int bytesRead = 0;
while (bytesRead < bytesToRead)
{
bytesRead += channel.read(cache);
}
offset += bytesRead;
indexMgr.addBytesRead(bytesRead);
}
/**
* Returns {@code true} if this buffer has more data.
*
* @return {@code true} if this buffer has more data.
* @throws IOException
* If an IO error occurred.
*/
public boolean hasMoreData() throws IOException
{
boolean ret = ((begin + offset) >= end);
return !(cache.remaining() == 0 && ret);
}
/**
* Returns the length of the next key.
*
* @return The length of the next key.
*/
public int getKeyLen()
{
return keyBuf.limit();
}
/**
* Returns the next key.
*
* @param b
* A buffer into which the key should be added.
*/
public void getKey(ByteBuffer b)
{
keyBuf.get(b.array(), 0, keyBuf.limit());
b.limit(keyBuf.limit());
}
/**
* Returns the index ID of the next record.
*
* @return The index ID of the next record.
*/
public Integer getIndexID()
{
if (indexID == null)
{
try
{
getNextRecord();
}
catch (IOException ex)
{
Message message = ERR_JEB_IMPORT_BUFFER_IO_ERROR.get(indexMgr
.getBufferFileName());
logError(message);
throw new RuntimeException(ex);
}
}
return indexID;
}
/**
* Reads the next record from the buffer, skipping any remaining data in the
* current record.
*
* @throws IOException
* If an IO error occurred.
*/
public void getNextRecord() throws IOException
{
switch (recordState)
{
case START:
// Nothing to skip.
break;
case NEED_INSERT_ID_SET:
// The previous record's ID sets were not read, so skip them both.
mergeIDSet(null);
mergeIDSet(null);
break;
case NEED_DELETE_ID_SET:
// The previous record's delete ID set was not read, so skip it.
mergeIDSet(null);
break;
}
indexID = getInt();
ensureData(20);
byte[] ba = cache.array();
int p = cache.position();
int len = PackedInteger.getReadIntLength(ba, p);
int keyLen = PackedInteger.readInt(ba, p);
cache.position(p + len);
if (keyLen > keyBuf.capacity())
{
keyBuf = ByteBuffer.allocate(keyLen);
}
ensureData(keyLen);
keyBuf.clear();
cache.get(keyBuf.array(), 0, keyLen);
keyBuf.limit(keyLen);
recordState = RecordState.NEED_INSERT_ID_SET;
}
private int getInt() throws IOException
{
ensureData(4);
return cache.getInt();
}
/**
* Reads the next ID set from the record and merges it with the provided ID
* set.
*
* @param idSet
* The ID set to be merged.
* @throws IOException
* If an IO error occurred.
*/
public void mergeIDSet(ImportIDSet idSet) throws IOException
{
if (recordState == RecordState.START)
{
throw new IllegalStateException();
}
ensureData(20);
int p = cache.position();
byte[] ba = cache.array();
int len = PackedInteger.getReadIntLength(ba, p);
int keyCount = PackedInteger.readInt(ba, p);
p += len;
cache.position(p);
for (int k = 0; k < keyCount; k++)
{
if (ensureData(9))
{
p = cache.position();
}
len = PackedInteger.getReadLongLength(ba, p);
long l = PackedInteger.readLong(ba, p);
p += len;
cache.position(p);
// idSet will be null if skipping.
if (idSet != null)
{
idSet.addEntryID(l);
}
}
switch (recordState)
{
case START:
throw new IllegalStateException();
case NEED_INSERT_ID_SET:
recordState = RecordState.NEED_DELETE_ID_SET;
break;
case NEED_DELETE_ID_SET:
recordState = RecordState.START;
break;
}
}
private boolean ensureData(int len) throws IOException
{
boolean ret = false;
if (cache.remaining() == 0)
{
cache.clear();
loadCache();
cache.flip();
ret = true;
}
else if (cache.remaining() < len)
{
cache.compact();
loadCache();
cache.flip();
ret = true;
}
return ret;
}
/**
* Compares this buffer with the provided key and index ID.
*
* @param cKey
* The key.
* @param cIndexID
* The index ID.
* @return A negative number if this buffer is less than the provided key and
* index ID, a positive number if this buffer is greater, or zero if
* it is the same.
*/
int compare(ByteBuffer cKey, Integer cIndexID)
{
int returnCode, rc;
if (keyBuf.limit() == 0)
{
getIndexID();
}
rc = Importer.indexComparator.compare(keyBuf.array(), 0, keyBuf.limit(),
cKey.array(), cKey.limit());
if (rc != 0)
{
returnCode = 1;
}
else
{
returnCode = (indexID.intValue() == cIndexID.intValue()) ? 0 : 1;
}
return returnCode;
}
/**
* {@inheritDoc}
*/
public int compareTo(IndexInputBuffer o)
{
// used in remove.
if (this == o)
{
return 0;
}
if (keyBuf.limit() == 0)
{
getIndexID();
}
if (o.keyBuf.limit() == 0)
{
o.getIndexID();
}
byte[] oKey = o.keyBuf.array();
int oLen = o.keyBuf.limit();
int returnCode = Importer.indexComparator.compare(keyBuf.array(), 0,
keyBuf.limit(), oKey, oLen);
if (returnCode == 0)
{
returnCode = indexID.intValue() - o.getIndexID().intValue();
if (returnCode == 0)
{
returnCode = id - o.id;
}
}
return returnCode;
}
}