/* DBObjectDeltaRec.java This class is used to represent a record of changes that need to be made to an individual DBObject in the DBStore. This class will be used in to handle writing and reading records of changes made to objects in the Ganymede journal file. Created: 11 June 1998 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 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; either version 2 of the License, or (at your option) any later version. 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, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.server; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Vector; import arlut.csd.ganymede.common.FieldType; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.IPAddress; /*------------------------------------------------------------------------------ class DBObjectDeltaRec ------------------------------------------------------------------------------*/ /** * <p>This class is used to represent a record of changes that need to * be made to an individual DBObject in the DBStore.</p> * * <p>This class will be used in to handle writing and reading records * of changes made to objects in the Ganymede journal file.</p> */ public final class DBObjectDeltaRec implements FieldType, Iterable<fieldDeltaRec> { private Invid invid = null; private Vector<fieldDeltaRec> fieldRecs = new Vector<fieldDeltaRec>(); /* -- */ /** * <p>This DBObjectDeltaRec constructor is used to generate a delta * record that records the difference between two objects for the * Ganymede journal</p> */ public DBObjectDeltaRec(DBObject oldObj, DBObject newObj) { if (oldObj == null || newObj == null) { throw new IllegalArgumentException("Got a null object parameter" + ((oldObj == null) ? " old " : "") + ((newObj == null) ? " new " : "")); } if (!oldObj.getInvid().equals(newObj.getInvid())) { throw new IllegalArgumentException("Old and New object id's don't match"); } this.invid = oldObj.getInvid(); DBObjectBase objectBase = oldObj.getBase(); // algorithm: iterate over base.getFieldsInFieldOrder() to find // all fields possibly contained in the object.. for each field, // check to see if the value has changed. if so, create a // fieldDeltaRec for it. // note that we're counting on objectBase.sortedFields not being // changed while we're iterating here.. this is an ok assumption, // since only the schema editor will trigger changes in // sortedFields. for (DBObjectBaseField fieldDef: objectBase.getFieldsInFieldOrder()) { DBField origField = oldObj.getField(fieldDef.getID()); DBField currentField = newObj.getField(fieldDef.getID()); if ((origField == null || !origField.isDefined()) && (currentField == null || !currentField.isDefined())) { // no change.. was null/undefined, still is. continue; } if (currentField == null || !currentField.isDefined()) { // lost this field this.fieldRecs.add(new fieldDeltaRec(fieldDef.getID(), null)); continue; } if (origField == null || !origField.isDefined()) { // we gained this field this.fieldRecs.add(new fieldDeltaRec(fieldDef.getID(), currentField)); continue; } if (currentField.equals(origField)) { // no changes, we don't need to write this one out. continue; } // at this point, we know we need to write out a change // record.. the only question now is whether it is for a // scalar or a vector. if (!fieldDef.isArray()) { // got a scalar.. save this field entire to write out // when we emit to the journal this.fieldRecs.add(new fieldDeltaRec(fieldDef.getID(), currentField)); continue; } // it's a vector.. use the DBField.getVectorDiff() method // to generate a vector diff. this.fieldRecs.add(currentField.getVectorDiff(origField)); } } /** * This DBObjectDeltaRec constructor is used to load a delta record * from a Journal stream. */ public DBObjectDeltaRec(DataInput in) throws IOException { try { this.invid = Invid.createInvid(in); DBObjectBase baseDef = Ganymede.db.getObjectBase(this.invid.getType()); if (baseDef == null) { throw new RuntimeException("Unrecognized object type in journal: " + this.invid.getType()); } DBObject obj = Ganymede.db.viewDBObject(this.invid); int fieldcount = in.readInt(); for (int i = 0; i < fieldcount; i++) { short fieldcode = in.readShort(); DBObjectBaseField fieldDef = baseDef.getField(fieldcode); if (fieldDef == null) { throw new RuntimeException("Unrecognized field identifier in journal " + fieldcode + " for object type " + baseDef.getName()); } short typecode = fieldDef.getType(); String fieldName = fieldDef.getName(); if (in.readBoolean()) { // we're deleting this field this.fieldRecs.add(new fieldDeltaRec(fieldcode, null)); continue; } if (in.readShort() != typecode) { throw new RuntimeException("Error, field type mismatch in journal file"); } boolean scalar = in.readBoolean(); if (scalar) { this.fieldRecs.add(new fieldDeltaRec(fieldcode, DBField.readField(obj, in, fieldDef))); } else { // read in additions and deletions for a vector field fieldDeltaRec fieldRec = new fieldDeltaRec(fieldcode); Object value = null; int size = in.readInt(); // additions for (int j = 0; j < size; j++) { // we only support vectors of strings, invids, and // ip addresses switch (typecode) { case STRING: value = in.readUTF(); break; case INVID: value = Invid.createInvid(in); break; case IP: value = IPAddress.readIPAddr(in); } fieldRec.addValue(value); } size = in.readInt(); // deletions for (int j = 0; j < size; j++) { switch (typecode) { case STRING: value = in.readUTF(); break; case INVID: value = Invid.createInvid(in); break; case IP: value = IPAddress.readIPAddr(in); } fieldRec.delValue(value); } this.fieldRecs.add(fieldRec); } } } catch (IOException ex) { Ganymede.logError(ex); throw ex; } } /** * This method emits this delta rec to a file */ public void emit(DataOutput out) throws IOException { DBObjectBase baseDef = Ganymede.db.getObjectBase(this.invid.getType()); this.invid.emit(out); out.writeInt(this.fieldRecs.size()); for (fieldDeltaRec fdRec: this.fieldRecs) { // write out our field code.. this will be used by the loader // code to determine what kind of field this is, and what // kind of data types need to be loaded. out.writeShort(fdRec.fieldcode); // are we deleting? if (!fdRec.vector && fdRec.scalarValue == null) { out.writeBoolean(true); continue; } // no, we're redefining this field out.writeBoolean(false); // write out our field type code.. this will be used by the loader // to verify that the schema hasn't undergone an incompatible // change since the journal was written. DBObjectBaseField fieldDef = baseDef.getField(fdRec.fieldcode); out.writeShort(fieldDef.getType()); if (fdRec.scalarValue != null) { out.writeBoolean(true); // scalar redefinition fdRec.scalarValue.emit(out); continue; } out.writeBoolean(false); // vector mod // write out what is being added to this vector if (fdRec.addValues == null) { out.writeInt(0); } else { out.writeInt(fdRec.addValues.size()); for (Object value: fdRec.addValues) { if (value instanceof String) { out.writeUTF((String) value); } else if (value instanceof Invid) { ((Invid) value).emit(out); } else if (value instanceof IPAddress) { ((IPAddress) value).emit(out); } else { Ganymede.debug("DBObjectDeltaRec.emit(): Error! Unrecognized element in vector!"); } } } // write out what is being removed from this vector if (fdRec.delValues == null) { out.writeInt(0); } else { out.writeInt(fdRec.delValues.size()); for (Object value: fdRec.delValues) { if (value instanceof String) { out.writeUTF((String) value); } else if (value instanceof Invid) { ((Invid) value).emit(out); } else if (value instanceof IPAddress) { ((IPAddress) value).emit(out); } } } } } /** * <p>Returns an Iterator over the fieldDeltaRec objects contained * in this DBObjectDeltaRec.</p> */ public synchronized Iterator<fieldDeltaRec> iterator() { return this.fieldRecs.iterator(); } public Invid getInvid() { return this.invid; } public String toString() { StringBuilder buf = new StringBuilder(); buf.append("DBObjectDeltaRec: invid "); buf.append(this.invid); buf.append("\n"); for (fieldDeltaRec fr: this.fieldRecs) { buf.append(fr.toString()); buf.append("\n"); } return buf.toString(); } }