/* * Copyright 2009-2016 Tilmann Zaeschke. All rights reserved. * * This file is part of ZooDB. * * ZooDB 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 3 of the License, or * (at your option) any later version. * * ZooDB 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 ZooDB. If not, see <http://www.gnu.org/licenses/>. * * See the README and COPYING files for further information. */ package org.zoodb.internal.model1p; import java.lang.reflect.Field; import java.util.Arrays; import org.zoodb.api.impl.ZooPC; import org.zoodb.internal.DataDeleteSink; import org.zoodb.internal.GenericObject; import org.zoodb.internal.SerializerTools; import org.zoodb.internal.ZooClassDef; import org.zoodb.internal.ZooFieldDef; import org.zoodb.internal.server.index.BitTools; import org.zoodb.internal.server.index.LongLongIndex; import org.zoodb.internal.server.index.PagedOidIndex; import org.zoodb.internal.server.index.PagedPosIndex; import org.zoodb.internal.server.index.SchemaIndex.SchemaIndexEntry; import org.zoodb.internal.util.DBLogger; import org.zoodb.internal.util.Util; /** * A data sink deletes objects of a given class. It can be backed either by a file- or * in-memory-storage, or in future by a network channel through which data is sent to a server. * * Each sink handles objects of one class only. Therefore sinks can be associated with * ZooClassDefs and PCContext instances. * * @author ztilmann */ public class DataDeleteSink1P implements DataDeleteSink { private static final int BUFFER_SIZE = 1000; private final Node1P node; private final ZooClassDef cls; private final PagedOidIndex oidIndex; private SchemaIndexEntry sie; private final ZooPC[] buffer = new ZooPC[BUFFER_SIZE]; private int bufferCnt = 0; private final GenericObject[] bufferGO = new GenericObject[BUFFER_SIZE]; private int bufferGOCnt = 0; private boolean isStarted = false; public DataDeleteSink1P(Node1P node, ZooClassDef cls, PagedOidIndex oidIndex) { this.node = node; this.cls = cls; this.oidIndex = oidIndex; this.sie = node.getSchemaIE(cls); } /* (non-Javadoc) * @see org.zoodb.internal.model1p.DataSink#write(org.zoodb.api.impl.ZooPC) */ @Override public void delete(ZooPC obj) { if (obj.getClass() == GenericObject.class) { throw new IllegalArgumentException(); // deleteGeneric((GenericObject) obj); // return; } if (!isStarted) { this.sie = node.getSchemaIE(cls); isStarted = true; } //updated index //This is buffered to reduce look-ups to find field indices. buffer[bufferCnt++] = obj; if (bufferCnt == BUFFER_SIZE) { flushBuffer(); } } @Override public void deleteGeneric(GenericObject obj) { if (obj.checkPcDeleted()) { return; } if (!isStarted) { this.sie = node.getSchemaIE(cls); isStarted = true; } //updated index //This is buffered to reduce look-ups to find field indices. bufferGO[bufferGOCnt++] = obj; if (bufferGOCnt == BUFFER_SIZE) { flushBuffer(); } } @Override public void reset() { if (isStarted) { Arrays.fill(buffer, null); bufferCnt = 0; if (bufferGOCnt > 0) { Arrays.fill(bufferGO, null); bufferGOCnt = 0; } isStarted = false; } } /* (non-Javadoc) * @see org.zoodb.internal.model1p.DataSink#flush() */ @Override public void flush() { if (isStarted) { flushBuffer(); //To avoid memory leaks... Arrays.fill(buffer, null); isStarted = false; } } private void flushBuffer() { updateFieldIndices(); bufferCnt = 0; if (bufferGOCnt > 0) { updateFieldIndicesGO(); bufferGOCnt = 0; } } private void updateFieldIndices() { final ZooPC[] buffer = this.buffer; final int bufferCnt = this.bufferCnt; //remove field index entries int iInd = -1; for (ZooFieldDef field: cls.getAllFields()) { if (!field.isIndexed()) { continue; } iInd++; //For now we define that an index is shared by all classes and sub-classes that have //a matching field. So there is only one index which is defined in the top-most class SchemaIndexEntry schemaTop = node.getSchemaIE(field.getDeclaringType()); LongLongIndex fieldInd = (LongLongIndex) schemaTop.getIndex(field); try { Field jField = field.getJavaField(); for (int i = 0; i < bufferCnt; i++) { ZooPC co = buffer[i]; //This can be null for objects that have not been modified. //These are still dirty, because of the deletion if (co.jdoZooGetBackup() != null) { long l = co.jdoZooGetBackup().getA()[iInd]; fieldInd.removeLong(l, co.jdoZooGetOid()); continue; } long l; if (field.isString()) { //No need to check for hollow objects here. //If a hollow object gets deleted, it is automatically refreshed, zee ZooPC String str = (String)jField.get(co); l = BitTools.toSortableLong(str); } else if (field.isPersistentType()) { ZooPC pc = (ZooPC)jField.get(co); l = BitTools.toSortableLong(pc); } else { l = SerializerTools.primitiveFieldToLong(co, jField, field.getPrimitiveType()); } fieldInd.removeLong(l, co.jdoZooGetOid()); } } catch (SecurityException e) { throw DBLogger.newFatal("Error accessing field: " + field.getName(), e); } catch (IllegalArgumentException e) { throw DBLogger.newFatal("Error accessing field: " + field.getName(), e); } catch (IllegalAccessException e) { throw DBLogger.newFatal("Error accessing field: " + field.getName(), e); } } //now delete the object PagedPosIndex ois = sie.getObjectIndexLatestSchemaVersion(); for (int i = 0; i < bufferCnt; i++) { long oid = buffer[i].jdoZooGetOid(); delete(oid, ois); } } private void updateFieldIndicesGO() { final GenericObject[] buffer = this.bufferGO; final int bufferCnt = this.bufferGOCnt; //remove field index entries int iInd = -1; for (ZooFieldDef field: cls.getAllFields()) { if (!field.isIndexed()) { continue; } iInd++; //For now we define that an index is shared by all classes and sub-classes that have //a matching field. So there is only one index which is defined in the top-most class SchemaIndexEntry schemaTop = node.getSchemaIE(field.getDeclaringType()); LongLongIndex fieldInd = (LongLongIndex) schemaTop.getIndex(field); try { for (int i = 0; i < bufferCnt; i++) { GenericObject co = buffer[i]; //This can be null for objects that have not been modified. //These are still dirty, because of the deletion if (co.jdoZooGetBackup() != null) { long l = co.jdoZooGetBackup().getA()[iInd]; fieldInd.removeLong(l, co.getOid()); continue; } long l; if (field.isString()) { if (co.jdoZooIsStateHollow()) { //We need to activate it to get the values! //But only for String, the primitives should be fine. throw new UnsupportedOperationException(); //TODO do we really need this? //co.getContext().getNode().refreshObject(co); } l = (Long)co.getFieldRaw(field.getFieldPos()); } else if (field.isPersistentType()) { throw new UnsupportedOperationException(); //TODO the following needs a test! //Object oid = co.getFieldRaw(field.getFieldPos()); //l = oid == null ? BitTools.NULL : (long)oid; } else { Object primO = co.getFieldRaw(field.getFieldPos()); l = SerializerTools.primitiveToLong(primO, field.getPrimitiveType()); } fieldInd.removeLong(l, co.getOid()); } } catch (IllegalArgumentException e) { throw DBLogger.newFatal( "Error accessing field: " + field.getName(), e); } } //now delete the object PagedPosIndex ois = sie.getObjectIndexLatestSchemaVersion(); for (int i = 0; i < bufferCnt; i++) { long oid = buffer[i].getOid(); delete(oid, ois); } } private void delete(long oid, PagedPosIndex ois) { long pos = oidIndex.removeOidNoFail(oid, -1); //value=long with 32=page + 32=offs if (pos == -1) { throw DBLogger.newObjectNotFoundException("Object not found: " + Util.oidToString(oid)); } //update class index and //tell the FSM about the free page (if we have one) //prevPos.getValue() returns > 0, so the loop is performed at least once. do { //remove and report to FSM if applicable long nextPos = ois.removePosLongAndCheck(pos); //use mark for secondary pages nextPos = nextPos | PagedPosIndex.MARK_SECONDARY; pos = nextPos; } while (pos != PagedPosIndex.MARK_SECONDARY); } }