package net.jxta.impl.xindice.core.filer;
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999 The Apache Software Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xindice" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999-2001, The dbXML
* Group, L.L.C., http://www.dbxmlgroup.com. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
import net.jxta.impl.xindice.core.DBException;
import net.jxta.impl.xindice.core.FaultCodes;
import net.jxta.impl.xindice.core.data.Key;
import net.jxta.impl.xindice.core.data.Record;
import net.jxta.impl.xindice.core.data.RecordSet;
import net.jxta.impl.xindice.core.data.Value;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* BTreeFiler is a Filer implementation based on the BTree class.
*/
public final class BTreeFiler extends BTree implements Filer {
protected static final byte RECORD = 20;
private static final short PAGESIZE = 512;
// TODO: MAXKEYSIZE might need tuning
private static final short MAXKEYSIZE = 256;
private BTreeFilerHeader fileHeader;
private static final int DBE_CANNOT_READ = (int) (572l);
public BTreeFiler() {
super();
fileHeader = (BTreeFilerHeader) getFileHeader();
}
public void setLocation(String dir, String file) {
setFile(new File(dir, file + ".tbl"));
}
public String getName() {
return getFile().getName();
}
@Override
public boolean open() throws DBException {
if (super.open()) {
// These are the only properties that can be changed after creation
fileHeader.setMaxKeySize(MAXKEYSIZE);
return true;
} else {
return false;
}
}
@Override
public boolean create() throws DBException {
fileHeader.setPageSize(PAGESIZE);
fileHeader.setMaxKeySize(MAXKEYSIZE);
return super.create();
}
public Record readRecord(Key key) throws DBException {
if (key == null || key.getLength() == 0) {
return null;
}
checkOpened();
try {
long pos = findValue(key);
Record record = readRecord(pos);
record.setKey(key);
return record;
} catch (BTreeNotFoundException b) {// do nothing
} catch (IOException e) {
throw new FilerException(DBE_CANNOT_READ, "Can't read record '" + key + "': " + e.getMessage(), e);
}
return null;
}
public Record readRecord(long pos) throws DBException {
checkOpened();
try {
Page startPage = getPage(pos);
Value v = readValue(startPage);
BTreeFilerPageHeader sph = (BTreeFilerPageHeader) startPage.getPageHeader();
HashMap<String, Long> meta = new HashMap<String, Long>(4);
meta.put(Record.CREATED, sph.getCreated());
meta.put(Record.MODIFIED, sph.getModified());
meta.put(Record.LIFETIME, sph.getLifetime());
meta.put(Record.EXPIRATION, sph.getExpiration());
return new Record(null, v, meta);
} catch (IOException e) {
throw new FilerException(DBE_CANNOT_READ, "Can't read record : " + e.getMessage(), e);
}
}
public long writeRecord(Key key, Value value) throws DBException {
return writeRecord(key, value, 0, 0);
}
public long writeRecord(Key key, Value value, long lifetime, long expiration) throws DBException {
if (key == null || key.getLength() == 0) {
throw new FilerException(FaultCodes.DBE_CANNOT_CREATE, "Invalid key: '" + key + "'");
}
if (value == null) {
throw new FilerException(FaultCodes.DBE_CANNOT_CREATE, "Invalid null value");
}
checkOpened();
try {
Page p;
long pos;
try {
pos = findValue(key);
p = getPage(pos);
} catch (BTreeNotFoundException b) {
p = getFreePage();
pos = p.getPageNum();
addValue(key, p.getPageNum());
fileHeader.incRecordCount();
}
BTreeFilerPageHeader ph = (BTreeFilerPageHeader) p.getPageHeader();
long t = System.currentTimeMillis();
if (ph.getStatus() == UNUSED) {
ph.setCreated(t);
}
ph.setModified(t);
ph.setLifetime(lifetime);
ph.setExpiration(expiration);
ph.setStatus(RECORD);
writeValue(p, value);
flush();
return pos;
} catch (IOException e) {
throw new FilerException(FaultCodes.DBE_CANNOT_CREATE, "Can't write record '" + key + "': " + e.getMessage(), e);
}
}
public long writeRecord(long pos, Value value) throws DBException {
if (value == null) {
throw new FilerException(FaultCodes.DBE_CANNOT_CREATE, "Invalid null value");
}
checkOpened();
try {
writeValue(pos, value);
flush();
return pos;
} catch (IOException e) {
throw new FilerException(FaultCodes.DBE_CANNOT_CREATE, "Can't write record '" + value + "': " + e.getMessage(), e);
}
}
public boolean deleteRecord(Key key) throws DBException {
if (key == null || key.getLength() == 0) {
return false;
}
checkOpened();
try {
long pos = findValue(key);
Page p = getPage(pos);
removeValue(key);
unlinkPages(p.getPageNum());
fileHeader.decRecordCount();
flush();
return true;
} catch (BTreeNotFoundException b) {// not found move on
} catch (IOException e) {
throw new FilerException(FaultCodes.DBE_CANNOT_DROP, "Can't delete record '" + key + "': " + e.getMessage(), e);
}
return false;
}
public long getRecordCount() throws DBException {
checkOpened();
return fileHeader.getRecordCount();
}
public RecordSet getRecordSet() throws DBException {
checkOpened();
return new BTreeFilerRecordSet();
}
/**
* BTreeFilerRecordSet
*/
private class BTreeFilerRecordSet implements RecordSet, BTreeCallback {
private List<Key> keys = new ArrayList<Key>();
private Iterator<Key> it;
public BTreeFilerRecordSet() throws DBException {
try {
query(null, this);
it = keys.iterator();
} catch (IOException e) {
throw new FilerException(FaultCodes.GEN_CRITICAL_ERROR, "Error generating RecordSet", e);
}
}
public synchronized boolean indexInfo(Value value, long pointer) {
keys.add(new Key(value));
return true;
}
public synchronized Key getNextKey() {
return it.next();
}
public synchronized Record getNextRecord() throws DBException {
return readRecord(it.next());
}
public synchronized Value getNextValue() throws DBException {
return getNextRecord().getValue();
}
public synchronized boolean hasMoreRecords() {
return it.hasNext();
}
}
// //////////////////////////////////////////////////////////////////
@Override
public FileHeader createFileHeader() {
return new BTreeFilerHeader();
}
@Override
public FileHeader createFileHeader(boolean read) throws IOException {
return new BTreeFilerHeader(read);
}
@Override
public FileHeader createFileHeader(long pageCount) {
return new BTreeFilerHeader(pageCount);
}
@Override
public FileHeader createFileHeader(long pageCount, int pageSize) {
return new BTreeFilerHeader(pageCount, pageSize);
}
@Override
public PageHeader createPageHeader() {
return new BTreeFilerPageHeader();
}
/**
* BTreeFilerHeader
*/
private final class BTreeFilerHeader extends BTreeFileHeader {
private long totalBytes = 0;
public BTreeFilerHeader() {}
public BTreeFilerHeader(long pageCount) {
super(pageCount);
}
public BTreeFilerHeader(long pageCount, int pageSize) {
super(pageCount, pageSize);
}
public BTreeFilerHeader(boolean read) throws IOException {
super(read);
}
@Override
public synchronized void read(RandomAccessFile raf) throws IOException {
super.read(raf);
totalBytes = raf.readLong();
}
@Override
public synchronized void write(RandomAccessFile raf) throws IOException {
super.write(raf);
raf.writeLong(totalBytes);
}
/**
* The total number of bytes in use by the file
* @param totalBytes the new total number of bytes
*/
public synchronized void setTotalBytes(long totalBytes) {
this.totalBytes = totalBytes;
setDirty();
}
/**
* The total number of bytes in use by the file
* @return the total number of bytes
*/
public synchronized long getTotalBytes() {
return totalBytes;
}
}
/**
* BTreeFilerPageHeader
*/
private final class BTreeFilerPageHeader extends BTreePageHeader {
private long created = 0;
private long modified = 0;
private long lifetime = 0;
private long expiration = 0;
public BTreeFilerPageHeader() {}
public BTreeFilerPageHeader(DataInputStream dis) throws IOException {
super(dis);
}
@Override
public synchronized void read(DataInputStream dis) throws IOException {
super.read(dis);
if (getStatus() == UNUSED) {
return;
}
created = dis.readLong();
modified = dis.readLong();
lifetime = dis.readLong();
expiration = dis.readLong();
}
@Override
public synchronized void write(DataOutputStream dos) throws IOException {
super.write(dos);
dos.writeLong(created);
dos.writeLong(modified);
dos.writeLong(lifetime);
dos.writeLong(expiration);
}
@Override
public synchronized void setRecordLen(int recordLen) {
fileHeader.setTotalBytes((fileHeader.totalBytes - getRecordLen()) + recordLen);
super.setRecordLen(recordLen);
}
/**
* UNIX-time when this record was created
* @param created creation time
*/
public synchronized void setCreated(long created) {
this.created = created;
setDirty();
}
/**
* UNIX-time when this record was created
* @return creation time
*/
public synchronized long getCreated() {
return created;
}
/**
* UNIX-time when this record was last modified
* @param modified modified time
*/
public synchronized void setModified(long modified) {
this.modified = modified;
setDirty();
}
/**
* UNIX-time when this record was last modified
* @return modified time
*/
public synchronized long getModified() {
return modified;
}
/**
* JXTA-lifetime this record's lifetime
* @param lifetime the new record lifetime
*/
public synchronized void setLifetime(long lifetime) {
this.lifetime = lifetime;
setDirty();
}
/**
* JXTA-lifetime this record's lifetime
* @return the record lifetime
*/
public synchronized long getLifetime() {
return lifetime;
}
/**
* JXTA-expiration this record's expiration
* @param expiration the record expiration time
*/
public synchronized void setExpiration(long expiration) {
this.expiration = expiration;
setDirty();
}
/**
* JXTA-expiration this record's expiration
* @return the record expiration time
*/
public synchronized long getExpiration() {
return expiration;
}
}
}