/** * Copyright 2013 Benjamin Lerer * * 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 io.horizondb.db.series; import io.horizondb.io.Buffer; import io.horizondb.io.ByteReader; import io.horizondb.io.ByteWriter; import io.horizondb.io.ReadableBuffer; import io.horizondb.io.buffers.Buffers; import io.horizondb.io.checksum.ChecksumByteReader; import io.horizondb.io.checksum.ChecksumByteWriter; import io.horizondb.io.checksum.ChecksumMismatchException; import io.horizondb.io.encoding.VarInts; import io.horizondb.io.files.FileUtils; import io.horizondb.io.serialization.Parser; import io.horizondb.io.serialization.Serializable; import io.horizondb.model.core.Field; import java.io.IOException; import java.util.Arrays; import javax.annotation.concurrent.Immutable; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import com.google.common.collect.Range; import static io.horizondb.model.core.util.SerializationUtils.parseRangeFrom; import static io.horizondb.model.core.util.SerializationUtils.writeRange; /** * The meta data of a time series file. * * @author Benjamin * */ @Immutable public final class FileMetaData implements Serializable { /** * The parser instance. */ private static final Parser<FileMetaData> PARSER = new Parser<FileMetaData>() { /** * {@inheritDoc} */ @Override public FileMetaData parseFrom(ByteReader reader) throws IOException { ChecksumByteReader checksumByteReader = ChecksumByteReader.wrap(reader); ReadableBuffer buffer = checksumByteReader.slice(METADATA_LENGTH - CHECKSUM_LENGTH); if (!checksumByteReader.readChecksum()) { throw new ChecksumMismatchException("The meta data CRC does not match the expected one."); } byte[] magicNumber = new byte[MAGIC_NUMBER.length]; buffer.readBytes(magicNumber); if (!Arrays.equals(MAGIC_NUMBER, magicNumber)) { throw new IOException("The file is not a time series file."); } byte version = buffer.readByte(); String database = VarInts.readString(buffer); String timeSeries = VarInts.readString(buffer); Range<Field> range = parseRangeFrom(buffer); return new FileMetaData(version, database, timeSeries, range); } }; /** * The length in byte of the meta data. */ public static final int METADATA_LENGTH = FileUtils.ONE_KB; /** * The magic number starting the meta data header. */ public static final byte[] MAGIC_NUMBER = "delta".getBytes(); /** * The CRC length in bytes. */ private static final int CHECKSUM_LENGTH = 8; /** * The default file format version. */ public static final byte DEFAULT_VERSION = 1; /** * The version of the file format used for this file. */ private final byte version; /** * The name of the database to which belongs this file. */ private final String database; /** * The name of the time series to which belongs this file. */ private final String timeSeries; /** * The partition range. */ private final Range<Field> range; /** * Creates the file meta data. * * @param database the name of the database to which belongs this file * @param timeSeries the name of the time series to which belongs this file * @param range the partition range */ public FileMetaData(String database, String timeSeries, Range<Field> range) { this(DEFAULT_VERSION, database, timeSeries, range); } /** * Creates the file meta data. * * @param version the file format version * @param database the name of the database to which belongs this file * @param timeSeries the name of the time series to which belongs this file * @param range the partition range */ private FileMetaData(byte version, String database, String timeSeries, Range<Field> range) { this.version = version; this.database = database; this.timeSeries = timeSeries; this.range = range; } /** * Returns the version of the file format used for this file. * * @return the version of the file format used for this file. */ public byte getVersion() { return this.version; } /** * Returns the name of the database to which belongs this file. * * @return the name of the database to which belongs this file. */ public String getDatabase() { return this.database; } /** * Returns the name of the time series to which belongs this file. * * @return the name of the time series to which belongs this file. */ public String getTimeSeries() { return this.timeSeries; } /** * Returns the partition range. * * @return the partition range. */ public Range<Field> getRange() { return this.range; } /** * Creates a new <code>FileMetaData</code> by reading the data from the specified reader. * * @param reader the reader to read from. * @throws IOException if an I/O problem occurs */ public static FileMetaData parseFrom(ByteReader reader) throws IOException { return getParser().parseFrom(reader); } /** * Returns the parser that can be used to deserialize <code>FileMetaData</code> instances. * * @return the parser that can be used to deserialize <code>FileMetaData</code> instances. */ public static Parser<FileMetaData> getParser() { return PARSER; } /** * {@inheritDoc} */ @Override public int computeSerializedSize() { return METADATA_LENGTH; } /** * {@inheritDoc} */ @Override public void writeTo(ByteWriter writer) throws IOException { Buffer buffer = Buffers.allocate(METADATA_LENGTH); ChecksumByteWriter checksumByteWriter = ChecksumByteWriter.wrap(buffer); checksumByteWriter.writeBytes(MAGIC_NUMBER).writeByte(this.version); VarInts.writeString(checksumByteWriter, this.database); VarInts.writeString(checksumByteWriter, this.timeSeries); writeRange(checksumByteWriter, this.range); checksumByteWriter.writeZeroBytes(buffer.writeableBytes() - CHECKSUM_LENGTH); checksumByteWriter.writeChecksum(); writer.transfer(buffer); } /** * {@inheritDoc} */ @Override public boolean equals(Object object) { if (object == this) { return true; } if (!(object instanceof FileMetaData)) { return false; } FileMetaData rhs = (FileMetaData) object; return new EqualsBuilder().append(this.range, rhs.range) .append(this.timeSeries, rhs.timeSeries) .append(this.database, rhs.database) .append(this.version, rhs.version) .isEquals(); } /** * {@inheritDoc} */ @Override public int hashCode() { return new HashCodeBuilder(-500494123, 1424401799).append(this.range) .append(this.timeSeries) .append(this.database) .append(this.version) .toHashCode(); } /** * {@inheritDoc} */ @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("range", this.range) .append("timeSeries", this.timeSeries) .append("database", this.database) .append("version", this.version) .toString(); } }