/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Jun 23, 2008 */ package com.bigdata.rdf.sparql.ast.service.history; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import com.bigdata.btree.DefaultTupleSerializer; import com.bigdata.btree.IRangeQuery; import com.bigdata.btree.ITuple; import com.bigdata.btree.ITupleSerializer; import com.bigdata.btree.keys.ASCIIKeyBuilderFactory; import com.bigdata.btree.keys.IKeyBuilder; import com.bigdata.btree.keys.KeyBuilder; import com.bigdata.btree.raba.codec.IRabaCoder; import com.bigdata.rdf.changesets.ChangeAction; import com.bigdata.rdf.changesets.IChangeRecord; import com.bigdata.rdf.model.StatementEnum; import com.bigdata.rdf.spo.ISPO; import com.bigdata.rdf.spo.SPO; import com.bigdata.rdf.spo.SPOKeyOrder; import com.bigdata.util.Bytes; /** * (De-)serializes {@link IChangeRecord}s for the history index. * * @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/607"> History * Service</a> * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id: SPOTupleSerializer.java 5281 2011-10-03 14:00:39Z thompsonbry $ */ public class HistoryIndexTupleSerializer extends DefaultTupleSerializer<HistoryChangeRecord, HistoryChangeRecord> { private static final long serialVersionUID = -1L; /** * The natural order for the {@link ISPO} data in the index (this is not a * total key order since the key has a revision timestamp prefix). */ private SPOKeyOrder keyOrder; /** * If true, explicit SPOs decoded from index tuples will have a sid * attached. */ private boolean sids; /** * De-serialization constructor. */ public HistoryIndexTupleSerializer() { } /** * Create an {@link ITupleSerializer} for the indicated access path. * * @param keyOrder * The key order for the (s,p,o[,c]) component of the key. */ public HistoryIndexTupleSerializer(final SPOKeyOrder keyOrder, final boolean sids) { this(keyOrder, sids, getDefaultLeafKeysCoder(), getDefaultValuesCoder()); } /** * Create an {@link ITupleSerializer} for the indicated access path. * * @param keyOrder * The access path. * @param sids * If true, attach sids to decoded SPOs where appropriate. * @param leafKeySer * @param leafValSer */ public HistoryIndexTupleSerializer(final SPOKeyOrder keyOrder, final boolean sids, final IRabaCoder leafKeySer, final IRabaCoder leafValSer) { super(new ASCIIKeyBuilderFactory(), leafKeySer, leafValSer); this.keyOrder = keyOrder; this.sids = sids; } @Override public byte[] serializeKey(final Object obj) { if (obj == null) throw new IllegalArgumentException(); if (obj instanceof HistoryChangeRecord) return serializeKey((HistoryChangeRecord) obj); throw new UnsupportedOperationException(); } public byte[] serializeKey(final HistoryChangeRecord changeRecord) { final IKeyBuilder keyBuilder = getKeyBuilder().reset(); // append the revision time. keyBuilder.append(changeRecord.getRevisionTime()); // append the statement (encoded IVs). keyOrder.appendKey(keyBuilder, changeRecord.getStatement()); final byte[] key = keyBuilder.getKey(); return key; } @Override public byte[] serializeVal(final HistoryChangeRecord changeRecord) { if (changeRecord == null) throw new IllegalArgumentException(); final ISPO spo = changeRecord.getStatement(); final boolean override = spo.isOverride(); final boolean userFlag = spo.getUserFlag(); final StatementEnum type = spo.getStatementType(); // optionally set the override and user flag bits on the value. final byte lowNibble = (byte) // (type.code()// statement type | (override ? StatementEnum.MASK_OVERRIDE : 0x0) // | (userFlag ? StatementEnum.MASK_USER_FLAG : 0x0)// ); final byte highNibble = (byte) changeRecord.getChangeAction().ordinal(); final byte b = (byte) (0xff & (highNibble << 4 | lowNibble)); return new byte[] { b }; } public HistoryChangeRecord deserializeKey(final ITuple tuple) { // just de-serialize the whole tuple. return deserialize(tuple); } public HistoryChangeRecord deserialize(final ITuple tuple) { if (tuple == null) throw new IllegalArgumentException(); // copy of the key in a reused buffer. final byte[] key = tuple.getKeyBuffer().array(); // Decode the revision timestamp. final long revisionTimestamp = KeyBuilder.decodeLong(key, 0/* off */); // Decode statement, starting after the revision timestamp. final SPO spo = keyOrder.decodeKey(key, Bytes.SIZEOF_LONG/* off */); final ChangeAction changeAction; if ((tuple.flags() & IRangeQuery.VALS) != 0) { /* * Decode the statement type, bit flags, and optional sid based on * the tuple value. */ final byte b = tuple.getValueBuffer().array()[0]; // Just the low nibble. final byte lowNibble = (byte) (0xff & (b & 0x0f)); // Just the high nibble. final byte highNibble = (byte) (0xff & (b >> 4)); { final byte code = lowNibble; final StatementEnum type = StatementEnum.decode(code); spo.setStatementType(type); spo.setOverride(StatementEnum.isOverride(code)); spo.setUserFlag(StatementEnum.isUserFlag(code)); if (sids) { // SIDs only valid for triples. assert keyOrder.getKeyArity() == 3; if (spo.isExplicit()) { // spo.setStatementIdentifier(true); } } } changeAction = ChangeAction.values()[highNibble]; } else { // Not available unless VALS are read. changeAction = null; } final HistoryChangeRecord changeRecord = new HistoryChangeRecord(spo, changeAction, revisionTimestamp); return changeRecord; } /** * The initial version. */ private final static transient byte VERSION0 = 0; /** * The current version. */ private final static transient byte VERSION = VERSION0; public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); final byte version = in.readByte(); switch (version) { case VERSION0: keyOrder = SPOKeyOrder.valueOf(in.readByte()); sids = in.readByte() > 0; break; default: throw new UnsupportedOperationException("Unknown version: " + version); } } public void writeExternal(final ObjectOutput out) throws IOException { super.writeExternal(out); out.writeByte(VERSION); out.writeByte(keyOrder.index()); out.writeByte(sids ? 1 : 0); } }