/* * Copyright 2014 Alexey Plotnik * * 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.stem.db; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; import org.stem.domain.BlobDescriptor; import org.stem.domain.ExtendedBlobDescriptor; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.zip.CRC32; public class Blob // TODO: integrate all descriptors (mountpoint, fatfile, etc) { Header header; byte[] data; BlobDescriptor descriptor; public Header getHeader() { return header; } public BlobDescriptor getDescriptor() { return descriptor; } public static Blob deserialize(FatFile ff, long blobHeaderOffset) throws IOException { FileChannel channel = ff.getReader().getChannel(); Header header = Header.deserialize(channel, blobHeaderOffset); //System.out.println("deserialized: 0x" + Hex.encodeHexString(header.key) + ", offset=" + (int) blobHeaderOffset + Header.SIZE + ", valid=" + header.valid()); // boolean valid = header.valid(); // if (!valid) { // valid = header.valid(); // if (valid) { // int a = 1; // } // } if (header.corrupted()) { return null; } //throw new IOException("Blob header is corrupted"); int bodyOffset = (int) blobHeaderOffset + Header.SIZE; byte[] data = ff.readBlob(bodyOffset, header.length); // TODO: long to int conversion. Why? return new Blob(header, new BlobDescriptor(ff.id, (int) blobHeaderOffset, bodyOffset), data); } public static ExtendedBlobDescriptor deserializeDescriptor(FatFile ff, long blobHeaderOffset) throws IOException { FileChannel channel = ff.getReader().getChannel(); Header header = Header.deserialize(channel, blobHeaderOffset); if (!header.valid()) { return null; } return new ExtendedBlobDescriptor(header.key, header.length, ff.id, (int) blobHeaderOffset, (int) blobHeaderOffset + Header.SIZE); } public Blob(Header header, byte[] data) { this.header = header; this.data = data; } public Blob(Header header, BlobDescriptor descriptor, byte[] data) { this(header, data); this.descriptor = descriptor; } public int size() { return header.length; } public byte[] key() { return header.key; } public byte[] data() { return data; } public boolean deleted() { return header.isDeleted(); } public static class Header { private static final int KEY_SIZE = 16; private static final int LENGTH_SIZE = 4; private static final int CRC32_SIZE = 4; private static final int DELETE_FLAG_SIZE = 1; public static final int SIZE = KEY_SIZE + LENGTH_SIZE + CRC32_SIZE + DELETE_FLAG_SIZE; public byte[] key; public Integer length; public Integer crc32; public byte deleteFlag; private CRC32 crc = new CRC32(); public static Header create(byte[] keyBytes, int payloadLength, int crc32, byte deleteFlag) { return new Header(keyBytes, payloadLength, crc32, deleteFlag); } public static Header create(byte[] keyBytes, int payloadLength, byte deleteFlag) { return new Header(keyBytes, payloadLength, deleteFlag); } public static Header create(String key, int payloadLength, byte deleteFlag) throws IOException { byte[] keyBytes; try { keyBytes = Hex.decodeHex(key.toCharArray()); } catch (DecoderException e) { throw new IOException("Can not decode the key " + key, e); } assert keyBytes.length == 16; return new Header(keyBytes, payloadLength, deleteFlag); } public Header(byte[] key, int length, int crc32, byte deleteFlag) { this.key = key; this.length = length; this.crc32 = crc32; this.deleteFlag = deleteFlag; } public Header(byte[] key, int length, byte deleteFlag) { this.key = key; this.length = length; this.deleteFlag = deleteFlag; } /** * 1. Write key (16 bytes) * 2. Write blob length (4 bytes) * 3. Write CRC32 (4 bytes) of key and blob length * 4. Write delete flag (1 byte) * * @return */ public ByteBuffer serialize() { ByteBuffer buf = ByteBuffer.allocate(SIZE); buf.put(key); buf.putInt(length); if (null == crc32) crc32 = calculateChecksum(); ByteBuffer crc32Buf = ByteBuffer.allocate(4).putInt(crc32); crc32Buf.position(0); buf.put(crc32Buf); buf.put(deleteFlag); return buf; } public static Header deserialize(FileChannel channel, long blobHeaderOffset) throws IOException { FileLock lock = channel.lock(); try { channel.position(blobHeaderOffset); ByteBuffer buf = ByteBuffer.allocate(SIZE); channel.read(buf); buf.position(0); byte[] key = new byte[KEY_SIZE]; buf.get(key); int length = buf.getInt(); int crc32 = buf.getInt(); byte deleteFlag = buf.get(); return Header.create(key, length, crc32, deleteFlag); } finally { lock.release(); } } private Integer calculateChecksum() { ByteBuffer blobLengthBuf = ByteBuffer.allocate(LENGTH_SIZE).putInt(length); blobLengthBuf.position(0); ByteBuffer keyAndLengthBuf = ByteBuffer.allocate(KEY_SIZE + LENGTH_SIZE).put(key).put(blobLengthBuf); CRC32 crc = new CRC32(); crc.update(keyAndLengthBuf.array()); return (int) crc.getValue(); } public void nextOffset() { } public boolean valid() { return calculateChecksum().equals(crc32); } public boolean corrupted() { return !valid(); } public FatFileIndex.Entry toIndexEntry(int offset) { return new FatFileIndex.Entry(key, offset, length, deleteFlag); } public boolean isLive() { return FatFileIndex.Entry.FLAG_LIVE == this.deleteFlag; } public boolean isDeleted() { return FatFileIndex.Entry.FLAG_DELETED == this.deleteFlag; } } }