/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008-2009 Sun Microsystems, Inc. */ package org.opends.server.backends.ndb; import com.mysql.cluster.ndbj.Ndb; import com.mysql.cluster.ndbj.NdbApiException; import com.mysql.cluster.ndbj.NdbBlob; import com.mysql.cluster.ndbj.NdbIndexScanOperation; import com.mysql.cluster.ndbj.NdbOperation; import com.mysql.cluster.ndbj.NdbOperation.AbortOption; import com.mysql.cluster.ndbj.NdbResultSet; import com.mysql.cluster.ndbj.NdbTransaction; import com.mysql.cluster.ndbj.NdbTransaction.ExecType; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.opends.server.core.DirectoryServer; import org.opends.server.types.DN; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeBuilder; import org.opends.server.types.AttributeType; import org.opends.server.types.AttributeValue; import org.opends.server.types.AttributeValues; import org.opends.server.types.ByteString; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; import org.opends.server.types.ObjectClass; import org.opends.server.types.ObjectClassType; import static org.opends.server.util.ServerConstants.ATTR_REFERRAL_URL; import static org.opends.server.loggers.debug.DebugLogger.*; /** * This class represents the DN database, which has one record for each entry. */ public class OperationContainer extends DatabaseContainer { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** * The default name of the ordered index for the primary key. */ private static final String PRIMARY_INDEX_NAME = "PRIMARY"; /** * The name of extensible object objectclass. */ private static final String OC_EXTENSIBLEOBJECT = "extensibleObject"; /** * Lowest multivalued attribute id. */ private static final int MIN_MID = 1; /** * Create a OperationContainer instance for the database in a * given entryContainer. * * @param name The name of the database. * @param entryContainer The entryContainer of the database. * @throws NdbApiException If an error occurs. */ OperationContainer(String name, EntryContainer entryContainer) throws NdbApiException { super(name, entryContainer); } /** * Insert a new entry into the database. * @param txn Abstract transaction to be used for the database operation. * @param dn The entry DN. * @param id The entry ID. * @param entry The entry. * @return true if the entry was inserted, false if a entry already exists. * @throws NdbApiException If an error occurred while attempting to insert * the new entry. */ public boolean insert(AbstractTransaction txn, DN dn, long id, Entry entry) throws NdbApiException { return writeNDBEntry(txn, dn, id, entry, false); } /** * Put an entry to the database. If an entry already exists, the entry * will be replaced, otherwise a new entry will be inserted. * @param txn Abstract transaction to be used for the database operation. * @param dn The entry DN. * @param id The entry ID. * @param entry The new entry. * @param originalEntry The old entry. * @return true if the entry was written, false if it was not written. * @throws NdbApiException If an error occurred while attempting to write * the entry. */ public boolean put(AbstractTransaction txn, DN dn, long id, Entry entry, Entry originalEntry) throws NdbApiException { // Delete first. deleteNDBEntry(txn, originalEntry, id); return writeNDBEntry(txn, dn, id, entry, true); } /** * Write an entry to the database. * @param txn Abstract transaction to be used for the database operation. * @param dn The entry DN. * @param id The entry ID. * @param entry The entry. * @param overwrite Whether or not the entry should be overwritten. * @return true if the entry was written, false if it was not written. * @throws com.mysql.cluster.ndbj.NdbApiException If an error occurred * while attempting to write the entry. */ private boolean writeNDBEntry(AbstractTransaction txn, DN dn, long id, Entry entry, boolean overwrite) throws NdbApiException { int nClasses = 0; NdbOperation op = null; NdbOperation tagOp = null; StringBuilder ocBuffer = new StringBuilder(); StringBuilder xocBuffer = new StringBuilder(); Map ocMap = entry.getObjectClasses(); Map> userAttrMap = entry.getUserAttributes(); ArrayList userAttributes = new ArrayList(); boolean extensibleObject = false; // Update ocs tables. NdbTransaction ndbDATxn = null; for (Map.Entry ocEntry : ocMap.entrySet()) { ObjectClass oc = ocEntry.getKey(); String ocName = oc.getNameOrOID(); Map mvOpMap = new HashMap(); if (nClasses > 0) { ocBuffer.append(" "); } ocBuffer.append(ocName); nClasses++; if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) { continue; } if (ocName.equalsIgnoreCase(OC_EXTENSIBLEOBJECT)) { extensibleObject = true; } if (ndbDATxn == null) { ndbDATxn = txn.getNdbDATransaction(ocName, id); } if (overwrite) { op = ndbDATxn.getWriteOperation(ocName); } else { op = ndbDATxn.getInsertOperation(ocName); } op.equalLong(BackendImpl.EID, id); op.equalInt(BackendImpl.MID, MIN_MID); mvOpMap.put(MIN_MID, op); for (AttributeType reqAttr : oc.getRequiredAttributes()) { if (userAttributes.contains(reqAttr)) { continue; } if (reqAttr.isOperational()) { userAttrMap.put(reqAttr, entry.getOperationalAttribute(reqAttr)); } String attrName = reqAttr.getNameOrOID(); if (entry.hasAttribute(reqAttr)) { boolean indexed = BackendImpl.indexes.contains(attrName); List attrList = userAttrMap.get(reqAttr); int mid = MIN_MID; for (Attribute attr : attrList) { if (attr.isVirtual() || attr.isEmpty()) { continue; } // Attribute options. Set attrOptionsSet = attr.getOptions(); if (!attrOptionsSet.isEmpty()) { if (overwrite) { tagOp = ndbDATxn.getWriteOperation(BackendImpl.TAGS_TABLE); } else { tagOp = ndbDATxn.getInsertOperation(BackendImpl.TAGS_TABLE); } tagOp.equalLong(BackendImpl.EID, id); tagOp.equalString(BackendImpl.TAG_ATTR, attrName); tagOp.equalInt(BackendImpl.MID, mid); StringBuilder buffer = new StringBuilder(); for (String option : attrOptionsSet) { buffer.append(';'); buffer.append(option); } tagOp.setString(BackendImpl.TAG_TAGS, buffer.toString()); } for (AttributeValue attrVal : attr) { String attrStringVal = attrVal.toString(); NdbOperation attrOp = mvOpMap.get(mid); if (attrOp == null) { if (overwrite) { attrOp = ndbDATxn.getWriteOperation(ocName); } else { attrOp = ndbDATxn.getInsertOperation(ocName); } attrOp.equalLong(BackendImpl.EID, id); attrOp.equalInt(BackendImpl.MID, mid); mvOpMap.put(mid, attrOp); } if (BackendImpl.blobAttributes.contains(attrName)) { NdbBlob blob = attrOp.getBlobHandle(attrName); blob.setValue(attrVal.getValue().toByteArray()); } else { attrOp.setString(attrName, attrStringVal); } // Update Indexes. if (indexed) { NdbOperation idxOp = null; if (overwrite) { idxOp = ndbDATxn.getWriteOperation( BackendImpl.IDX_TABLE_PREFIX + attrName); } else { idxOp = ndbDATxn.getInsertOperation( BackendImpl.IDX_TABLE_PREFIX + attrName); } idxOp.equalLong(BackendImpl.EID, id); idxOp.equalInt(BackendImpl.MID, mid); idxOp.setString(BackendImpl.IDX_VAL, attrStringVal); } mid++; } } userAttributes.add(reqAttr); } } for (AttributeType optAttr : oc.getOptionalAttributes()) { if (userAttributes.contains(optAttr)) { continue; } if (optAttr.isOperational()) { userAttrMap.put(optAttr, entry.getOperationalAttribute(optAttr)); } String attrName = optAttr.getNameOrOID(); if (entry.hasAttribute(optAttr)) { boolean indexed = BackendImpl.indexes.contains(attrName); List attrList = userAttrMap.get(optAttr); int mid = MIN_MID; for (Attribute attr : attrList) { if (attr.isVirtual() || attr.isEmpty()) { continue; } // Attribute options. Set attrOptionsSet = attr.getOptions(); if (!attrOptionsSet.isEmpty()) { if (overwrite) { tagOp = ndbDATxn.getWriteOperation(BackendImpl.TAGS_TABLE); } else { tagOp = ndbDATxn.getInsertOperation(BackendImpl.TAGS_TABLE); } tagOp.equalLong(BackendImpl.EID, id); tagOp.equalString(BackendImpl.TAG_ATTR, attrName); tagOp.equalInt(BackendImpl.MID, mid); StringBuilder buffer = new StringBuilder(); for (String option : attrOptionsSet) { buffer.append(';'); buffer.append(option); } tagOp.setString(BackendImpl.TAG_TAGS, buffer.toString()); } for (AttributeValue attrVal : attr) { String attrStringVal = attrVal.toString(); NdbOperation attrOp = mvOpMap.get(mid); if (attrOp == null) { if (overwrite) { attrOp = ndbDATxn.getWriteOperation(ocName); } else { attrOp = ndbDATxn.getInsertOperation(ocName); } attrOp.equalLong(BackendImpl.EID, id); attrOp.equalInt(BackendImpl.MID, mid); mvOpMap.put(mid, attrOp); } if (BackendImpl.blobAttributes.contains(attrName)) { NdbBlob blob = attrOp.getBlobHandle(attrName); blob.setValue(attrVal.getValue().toByteArray()); } else { attrOp.setString(attrName, attrStringVal); } // Update Indexes. if (indexed) { NdbOperation idxOp = null; if (overwrite) { idxOp = ndbDATxn.getWriteOperation( BackendImpl.IDX_TABLE_PREFIX + attrName); } else { idxOp = ndbDATxn.getInsertOperation( BackendImpl.IDX_TABLE_PREFIX + attrName); } idxOp.equalLong(BackendImpl.EID, id); idxOp.equalInt(BackendImpl.MID, mid); idxOp.setString(BackendImpl.IDX_VAL, attrStringVal); } mid++; } } userAttributes.add(optAttr); } } } // Extensible object. if (extensibleObject) { int xnClasses = 0; for (Map.Entry> attrEntry : userAttrMap.entrySet()) { AttributeType attrType = attrEntry.getKey(); if (!userAttributes.contains(attrType)) { String attrName = attrType.getNameOrOID(); String ocName = BackendImpl.attr2Oc.get(attrName); Map mvOpMap = new HashMap(); boolean indexed = BackendImpl.indexes.contains(attrName); if (ndbDATxn == null) { ndbDATxn = txn.getNdbDATransaction(ocName, id); } if (overwrite) { op = ndbDATxn.getWriteOperation(ocName); } else { op = ndbDATxn.getInsertOperation(ocName); } op.equalLong(BackendImpl.EID, id); op.equalInt(BackendImpl.MID, MIN_MID); mvOpMap.put(MIN_MID, op); List attrList = userAttrMap.get(attrType); int mid = MIN_MID; for (Attribute attr : attrList) { if (attr.isVirtual() || attr.isEmpty()) { continue; } // Attribute options. Set attrOptionsSet = attr.getOptions(); if (!attrOptionsSet.isEmpty()) { if (overwrite) { tagOp = ndbDATxn.getWriteOperation(BackendImpl.TAGS_TABLE); } else { tagOp = ndbDATxn.getInsertOperation(BackendImpl.TAGS_TABLE); } tagOp.equalLong(BackendImpl.EID, id); tagOp.equalString(BackendImpl.TAG_ATTR, attrName); tagOp.equalInt(BackendImpl.MID, mid); StringBuilder buffer = new StringBuilder(); for (String option : attrOptionsSet) { buffer.append(';'); buffer.append(option); } tagOp.setString(BackendImpl.TAG_TAGS, buffer.toString()); } for (AttributeValue attrVal : attr) { String attrStringVal = attrVal.toString(); NdbOperation attrOp = mvOpMap.get(mid); if (attrOp == null) { if (overwrite) { attrOp = ndbDATxn.getWriteOperation(ocName); } else { attrOp = ndbDATxn.getInsertOperation(ocName); } attrOp.equalLong(BackendImpl.EID, id); attrOp.equalInt(BackendImpl.MID, mid); mvOpMap.put(mid, attrOp); } if (BackendImpl.blobAttributes.contains(attrName)) { NdbBlob blob = attrOp.getBlobHandle(attrName); blob.setValue(attrVal.getValue().toByteArray()); } else { attrOp.setString(attrName, attrStringVal); } // Update Indexes. if (indexed) { NdbOperation idxOp = null; if (overwrite) { idxOp = ndbDATxn.getWriteOperation( BackendImpl.IDX_TABLE_PREFIX + attrName); } else { idxOp = ndbDATxn.getInsertOperation( BackendImpl.IDX_TABLE_PREFIX + attrName); } idxOp.equalLong(BackendImpl.EID, id); idxOp.equalInt(BackendImpl.MID, mid); idxOp.setString(BackendImpl.IDX_VAL, attrStringVal); } mid++; } } userAttributes.add(attrType); if (xnClasses > 0) { xocBuffer.append(" "); } xocBuffer.append(ocName); xnClasses++; } } } // Update operational attributes table. if (overwrite) { op = ndbDATxn.getWriteOperation(BackendImpl.OPATTRS_TABLE); } else { op = ndbDATxn.getInsertOperation(BackendImpl.OPATTRS_TABLE); } op.equalLong(BackendImpl.EID, id); for (List attrList : entry.getOperationalAttributes().values()) { for (Attribute attr : attrList) { if (attr.isVirtual() || attr.isEmpty()) { continue; } if (userAttrMap.containsKey(attr.getAttributeType())) { continue; } String attrName = attr.getAttributeType().getNameOrOID(); for (AttributeValue attrVal : attr) { op.setString(attrName, attrVal.toString()); } } } // Update dn2id table. NdbTransaction ndbTxn = txn.getNdbTransaction(); if (overwrite) { op = ndbTxn.getWriteOperation(name); } else { op = ndbTxn.getInsertOperation(name); } int componentIndex = dn.getNumComponents() - 1; for (int i=0; i < BackendImpl.DN2ID_DN_NC; i++) { while (componentIndex >= 0) { op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i), dn.getRDN(componentIndex).toNormalizedString()); componentIndex--; i++; } op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i), ""); } op.setLong(BackendImpl.EID, id); op.setString(BackendImpl.DN2ID_OC, ocBuffer.toString()); op.setString(BackendImpl.DN2ID_XOC, xocBuffer.toString()); return true; } /** * Delete an entry from the database. * @param txn Abstract transaction to be used for the database operation. * @param originalEntry The original entry. * @param id The entry ID. * @throws com.mysql.cluster.ndbj.NdbApiException If an error occurred * while attempting to write the entry. */ private void deleteNDBEntry(AbstractTransaction txn, Entry originalEntry, long id) throws NdbApiException { NdbOperation op = null; NdbOperation tagOp = null; NdbTransaction ndbDATxn = null; boolean extensibleObject = false; // Delete attributes. Map originalOcMap = originalEntry.getObjectClasses(); ArrayList originalUserAttributes = new ArrayList(); Map> originalUserAttrMap = originalEntry.getUserAttributes(); for (Map.Entry ocEntry : originalOcMap.entrySet()) { ObjectClass oc = ocEntry.getKey(); String ocName = oc.getNameOrOID(); Map mvOpMap = new HashMap(); if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) { continue; } if (ocName.equalsIgnoreCase(OC_EXTENSIBLEOBJECT)) { extensibleObject = true; } if (ndbDATxn == null) { ndbDATxn = txn.getNdbDATransaction(ocName, id); } op = ndbDATxn.getDeleteOperation(ocName); op.equalLong(BackendImpl.EID, id); op.equalInt(BackendImpl.MID, MIN_MID); mvOpMap.put(MIN_MID, op); for (AttributeType reqAttr : oc.getRequiredAttributes()) { String attrName = reqAttr.getNameOrOID(); if (originalUserAttributes.contains(reqAttr)) { continue; } if (originalEntry.hasUserAttribute(reqAttr)) { boolean indexed = BackendImpl.indexes.contains(attrName); List attrList = originalUserAttrMap.get(reqAttr); int mid = MIN_MID; for (Attribute attr : attrList) { if (attr.isVirtual() || attr.isEmpty()) { continue; } // Attribute options. Set attrOptionsSet = attr.getOptions(); if (!attrOptionsSet.isEmpty()) { tagOp = ndbDATxn.getDeleteOperation(BackendImpl.TAGS_TABLE); tagOp.equalLong(BackendImpl.EID, id); tagOp.equalString(BackendImpl.TAG_ATTR, attrName); tagOp.equalInt(BackendImpl.MID, mid); } for (AttributeValue attrVal : attr) { NdbOperation attrOp = mvOpMap.get(mid); if (attrOp == null) { attrOp = ndbDATxn.getDeleteOperation(ocName); attrOp.equalLong(BackendImpl.EID, id); attrOp.equalInt(BackendImpl.MID, mid); mvOpMap.put(mid, attrOp); } // Update Indexes. if (indexed) { NdbOperation idxOp = ndbDATxn.getDeleteOperation( BackendImpl.IDX_TABLE_PREFIX + attrName); idxOp.equalLong(BackendImpl.EID, id); idxOp.equalInt(BackendImpl.MID, mid); } mid++; } } originalUserAttributes.add(reqAttr); } } for (AttributeType optAttr : oc.getOptionalAttributes()) { String attrName = optAttr.getNameOrOID(); if (originalUserAttributes.contains(optAttr)) { continue; } if (originalEntry.hasUserAttribute(optAttr)) { boolean indexed = BackendImpl.indexes.contains(attrName); List attrList = originalUserAttrMap.get(optAttr); int mid = MIN_MID; for (Attribute attr : attrList) { if (attr.isVirtual() || attr.isEmpty()) { continue; } // Attribute options. Set attrOptionsSet = attr.getOptions(); if (!attrOptionsSet.isEmpty()) { tagOp = ndbDATxn.getDeleteOperation(BackendImpl.TAGS_TABLE); tagOp.equalLong(BackendImpl.EID, id); tagOp.equalString(BackendImpl.TAG_ATTR, attrName); tagOp.equalInt(BackendImpl.MID, mid); } for (AttributeValue attrVal : attr) { NdbOperation attrOp = mvOpMap.get(mid); if (attrOp == null) { attrOp = ndbDATxn.getDeleteOperation(ocName); attrOp.equalLong(BackendImpl.EID, id); attrOp.equalInt(BackendImpl.MID, mid); mvOpMap.put(mid, attrOp); } // Update Indexes. if (indexed) { NdbOperation idxOp = ndbDATxn.getDeleteOperation( BackendImpl.IDX_TABLE_PREFIX + attrName); idxOp.equalLong(BackendImpl.EID, id); idxOp.equalInt(BackendImpl.MID, mid); } mid++; } } originalUserAttributes.add(optAttr); } } } // Extensible object. if (extensibleObject) { for (Map.Entry> attrEntry : originalUserAttrMap.entrySet()) { AttributeType attrType = attrEntry.getKey(); if (!originalUserAttributes.contains(attrType)) { String attrName = attrType.getNameOrOID(); String ocName = BackendImpl.attr2Oc.get(attrName); Map mvOpMap = new HashMap(); boolean indexed = BackendImpl.indexes.contains(attrName); if (ndbDATxn == null) { ndbDATxn = txn.getNdbDATransaction(ocName, id); } op = ndbDATxn.getDeleteOperation(ocName); op.equalLong(BackendImpl.EID, id); op.equalInt(BackendImpl.MID, MIN_MID); mvOpMap.put(MIN_MID, op); List attrList = originalUserAttrMap.get(attrType); int mid = MIN_MID; for (Attribute attr : attrList) { if (attr.isVirtual() || attr.isEmpty()) { continue; } // Attribute options. Set attrOptionsSet = attr.getOptions(); if (!attrOptionsSet.isEmpty()) { tagOp = ndbDATxn.getDeleteOperation(BackendImpl.TAGS_TABLE); tagOp.equalLong(BackendImpl.EID, id); tagOp.equalString(BackendImpl.TAG_ATTR, attrName); tagOp.equalInt(BackendImpl.MID, mid); } for (AttributeValue attrVal : attr) { NdbOperation attrOp = mvOpMap.get(mid); if (attrOp == null) { attrOp = ndbDATxn.getDeleteOperation(ocName); attrOp.equalLong(BackendImpl.EID, id); attrOp.equalInt(BackendImpl.MID, mid); mvOpMap.put(mid, attrOp); } // Update Indexes. if (indexed) { NdbOperation idxOp = ndbDATxn.getDeleteOperation( BackendImpl.IDX_TABLE_PREFIX + attrName); idxOp.equalLong(BackendImpl.EID, id); idxOp.equalInt(BackendImpl.MID, mid); } mid++; } } originalUserAttributes.add(attrType); } } } } /** * Remove an entry from the database. * @param txn Abstract transaction to be used for the database operation. * @param entry The entry. * @return true if the entry was removed, false if it was not removed. * @throws NdbApiException If an error occurred while attempting to remove * the entry. */ public boolean remove(AbstractTransaction txn, Entry entry) throws NdbApiException { DN dn = entry.getDN(); NdbResultSet rs = null; NdbTransaction ndbTxn = txn.getNdbTransaction(); NdbOperation op = ndbTxn.getSelectOperation(name, NdbOperation.LockMode.LM_CommittedRead); boolean extensibleObject = false; int componentIndex = dn.getNumComponents() - 1; for (int i=0; i < BackendImpl.DN2ID_DN_NC; i++) { while (componentIndex >= 0) { op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i), dn.getRDN(componentIndex).toNormalizedString()); componentIndex--; i++; } op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i), ""); } op.getValue(BackendImpl.EID); op.getValue(BackendImpl.DN2ID_OC); op.getValue(BackendImpl.DN2ID_XOC); rs = op.resultData(); ndbTxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true); long eid = 0; NdbTransaction ndbDATxn = null; String[] ocsStringArray = null; String[] xocsStringArray = null; List ocRsList = new ArrayList(); NdbIndexScanOperation indexScanOp = null; if (rs.next()) { eid = rs.getLong(BackendImpl.EID); String ocsString = rs.getString(BackendImpl.DN2ID_OC); ocsStringArray = ocsString.split(" "); String xocsString = rs.getString(BackendImpl.DN2ID_XOC); xocsStringArray = xocsString.split(" "); if (xocsString.length() > 0) { extensibleObject = true; } for (String ocName : ocsStringArray) { ObjectClass oc = DirectoryServer.getObjectClass(ocName, true); if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) { continue; } if (ndbDATxn == null) { ndbDATxn = txn.getNdbDATransaction(ocName, eid); } indexScanOp = ndbDATxn.getSelectIndexScanOperation(PRIMARY_INDEX_NAME, ocName); indexScanOp.setBoundLong(BackendImpl.EID, NdbIndexScanOperation.BoundType.BoundEQ, eid); indexScanOp.getValue(BackendImpl.MID); ocRsList.add(indexScanOp.resultData()); } // Extensible object. if (extensibleObject) { for (String xocName : xocsStringArray) { ObjectClass xoc = DirectoryServer.getObjectClass(xocName, true); if (xoc.getObjectClassType() == ObjectClassType.ABSTRACT) { continue; } if (ndbDATxn == null) { ndbDATxn = txn.getNdbDATransaction(xocName, eid); } indexScanOp = ndbDATxn.getSelectIndexScanOperation(PRIMARY_INDEX_NAME, xocName); indexScanOp.setBoundLong(BackendImpl.EID, NdbIndexScanOperation.BoundType.BoundEQ, eid); indexScanOp.getValue(BackendImpl.MID); ocRsList.add(indexScanOp.resultData()); } } } // Attribute options. if (ndbDATxn == null) { ndbDATxn = txn.getNdbDATransaction(BackendImpl.TAGS_TABLE, eid); } indexScanOp = ndbDATxn.getSelectIndexScanOperation(PRIMARY_INDEX_NAME, BackendImpl.TAGS_TABLE); indexScanOp.setBoundLong(BackendImpl.EID, NdbIndexScanOperation.BoundType.BoundEQ, eid); indexScanOp.getValue(BackendImpl.TAG_ATTR); indexScanOp.getValue(BackendImpl.MID); NdbResultSet tagRs = indexScanOp.resultData(); ndbDATxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true); Iterator rsIterator = ocRsList.iterator(); for (String ocName : ocsStringArray) { ObjectClass oc = DirectoryServer.getObjectClass(ocName, true); if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) { continue; } NdbResultSet ocRs = rsIterator.next(); while (ocRs.next()) { int mid = ocRs.getInt(BackendImpl.MID); op = ndbDATxn.getDeleteOperation(ocName); op.equalLong(BackendImpl.EID, eid); op.equalInt(BackendImpl.MID, mid); } } // Extensible object. if (extensibleObject) { for (String xocName : xocsStringArray) { ObjectClass xoc = DirectoryServer.getObjectClass(xocName, true); if (xoc.getObjectClassType() == ObjectClassType.ABSTRACT) { continue; } NdbResultSet ocRs = rsIterator.next(); while (ocRs.next()) { int mid = ocRs.getInt(BackendImpl.MID); op = ndbDATxn.getDeleteOperation(xocName); op.equalLong(BackendImpl.EID, eid); op.equalInt(BackendImpl.MID, mid); } } } // Operational attributes. op = ndbDATxn.getDeleteOperation(BackendImpl.OPATTRS_TABLE); op.equalLong(BackendImpl.EID, eid); // Attribute options. while (tagRs.next()) { String attrName = tagRs.getString(BackendImpl.TAG_ATTR); int mid = tagRs.getInt(BackendImpl.MID); op = ndbDATxn.getDeleteOperation(BackendImpl.TAGS_TABLE); op.equalLong(BackendImpl.EID, eid); op.equalString(BackendImpl.TAG_ATTR, attrName); op.equalInt(BackendImpl.MID, mid); } // Indexes. for (String attrName : BackendImpl.indexes) { AttributeType attributeType = DirectoryServer.getAttributeType( attrName.toLowerCase(), true); if (entry.hasAttribute(attributeType)) { List attrList = entry.getAttribute(attributeType); int mid = MIN_MID; for (Attribute attr : attrList) { for (AttributeValue attrVal : attr) { NdbOperation idxOp = ndbDATxn.getDeleteOperation( BackendImpl.IDX_TABLE_PREFIX + attrName); idxOp.equalLong(BackendImpl.EID, eid); idxOp.equalInt(BackendImpl.MID, mid); mid++; } } } } // dn2id. op = ndbTxn.getDeleteOperation(name); componentIndex = dn.getNumComponents() - 1; for (int i=0; i < BackendImpl.DN2ID_DN_NC; i++) { while (componentIndex >= 0) { op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i), dn.getRDN(componentIndex).toNormalizedString()); componentIndex--; i++; } op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i), ""); } return true; } /** * Fetch the entry for a given ID. * @param txn Abstract transaction to be used for the database read. * @param eid The ID for which the entry is desired. * @param lockMode NDB locking mode for this operation. * @return The entry, or null if the given ID is not in the database. * @throws NdbApiException If an error occurs in the database. * @throws DirectoryException If a problem occurs while trying to * retrieve the entry. */ public Entry get(AbstractTransaction txn, long eid, NdbOperation.LockMode lockMode) throws NdbApiException, DirectoryException { NdbIndexScanOperation indexScanOp = null; NdbResultSet rs = null; DN dn = null; NdbTransaction ndbTxn = txn.getNdbTransaction(); indexScanOp = ndbTxn.getSelectIndexScanOperation( BackendImpl.EID, name, lockMode); indexScanOp.setBoundLong(BackendImpl.EID, NdbIndexScanOperation.BoundType.BoundEQ, eid); for (int i = 0; i < BackendImpl.DN2ID_DN_NC; i++) { indexScanOp.getValue(BackendImpl.DN2ID_DN + Integer.toString(i)); } indexScanOp.getValue(BackendImpl.DN2ID_OC); indexScanOp.getValue(BackendImpl.DN2ID_XOC); rs = indexScanOp.resultData(); ndbTxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true); if (rs.next()) { StringBuilder dnBuffer = new StringBuilder(); int dnColumnIndex = BackendImpl.DN2ID_DN_NC - 1; while (dnColumnIndex >= 0) { String rdnString = rs.getString(BackendImpl.DN2ID_DN + Integer.toString(dnColumnIndex)); if (rdnString.length() > 0) { dnBuffer.append(rdnString); if (dnColumnIndex > 0) { dnBuffer.append(","); } } dnColumnIndex--; } String dnString = dnBuffer.toString(); if (dnString.length() == 0) { return null; } dn = DN.decode(dnString); return getNDBEntry(txn, rs, dn, eid); } else { return null; } } /** * Fetch the entry for a given DN. * @param txn Abstract transaction to be used for the database read. * @param dn The DN for which the entry is desired. * @param lockMode NDB locking mode for this operation. * @return The entry, or null if the given DN is not in the database. * @throws NdbApiException If an error occurs in the database. */ public Entry get(AbstractTransaction txn, DN dn, NdbOperation.LockMode lockMode) throws NdbApiException { NdbOperation op = null; NdbResultSet rs = null; NdbTransaction ndbTxn = txn.getNdbTransaction(); op = ndbTxn.getSelectOperation(name, lockMode); int componentIndex = dn.getNumComponents() - 1; for (int i=0; i < BackendImpl.DN2ID_DN_NC; i++) { while (componentIndex >= 0) { op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i), dn.getRDN(componentIndex).toNormalizedString()); componentIndex--; i++; } op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i), ""); } op.getValue(BackendImpl.EID); op.getValue(BackendImpl.DN2ID_OC); op.getValue(BackendImpl.DN2ID_XOC); rs = op.resultData(); ndbTxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true); if (rs.next()) { long eid = rs.getLong(BackendImpl.EID); if (eid == 0) { return null; } Entry entry = getNDBEntry(txn, rs, dn, eid); if (entry != null) { entry.setAttachment(eid); } return entry; } else { return null; } } /** * Get the entry from the database. * @param txn Abstract transaction to be used for the database read. * @param rs NDB results set from the initial get entry operation. * @param dn The entry DN. * @param id The entry ID. * @return The entry. * @throws NdbApiException If an error occurs in the database. */ private Entry getNDBEntry( AbstractTransaction txn, NdbResultSet rs, DN dn, long eid) throws NdbApiException { NdbOperation op = null; NdbIndexScanOperation indexScanOp = null; boolean extensibleObject = false; String ocsString = rs.getString(BackendImpl.DN2ID_OC); String[] ocsStringArray = ocsString.split(" "); String xocsString = rs.getString(BackendImpl.DN2ID_XOC); String[] xocsStringArray = xocsString.split(" "); if (xocsString.length() > 0) { extensibleObject = true; } LinkedHashMap xObjectClasses = new LinkedHashMap(); List ocRsList = new ArrayList(); Map blobMap = new HashMap(); LinkedHashMap objectClasses = new LinkedHashMap(ocsStringArray.length); NdbTransaction ndbDATxn = null; NdbIndexScanOperation tagScanOp = null; for (String ocName : ocsStringArray) { ObjectClass oc = DirectoryServer.getObjectClass(ocName, true); objectClasses.put(oc, ocName); if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) { continue; } if (ndbDATxn == null) { ndbDATxn = txn.getNdbDATransaction(ocName, eid); } indexScanOp = ndbDATxn.getSelectIndexScanOperation( PRIMARY_INDEX_NAME, ocName, NdbOperation.LockMode.LM_CommittedRead); indexScanOp.setBoundLong(BackendImpl.EID, NdbIndexScanOperation.BoundType.BoundEQ, eid); indexScanOp.getValue(BackendImpl.MID); for (AttributeType reqAttr : oc.getRequiredAttributes()) { String attrName = reqAttr.getNameOrOID(); if (BackendImpl.blobAttributes.contains(attrName)) { NdbBlob blob = indexScanOp.getBlobHandle(attrName); blobMap.put(attrName, blob); } else { indexScanOp.getValue(attrName); } } for (AttributeType optAttr : oc.getOptionalAttributes()) { String attrName = optAttr.getNameOrOID(); if (BackendImpl.blobAttributes.contains(attrName)) { NdbBlob blob = indexScanOp.getBlobHandle(attrName); blobMap.put(attrName, blob); } else { indexScanOp.getValue(attrName); } } ocRsList.add(indexScanOp.resultData()); } // Extensible object. if (extensibleObject) { for (String xocName : xocsStringArray) { ObjectClass xoc = DirectoryServer.getObjectClass(xocName, true); objectClasses.put(xoc, xocName); xObjectClasses.put(xoc, xocName); if (xoc.getObjectClassType() == ObjectClassType.ABSTRACT) { continue; } if (ndbDATxn == null) { ndbDATxn = txn.getNdbDATransaction(xocName, eid); } indexScanOp = ndbDATxn.getSelectIndexScanOperation( PRIMARY_INDEX_NAME, xocName, NdbOperation.LockMode.LM_CommittedRead); indexScanOp.setBoundLong(BackendImpl.EID, NdbIndexScanOperation.BoundType.BoundEQ, eid); indexScanOp.getValue(BackendImpl.MID); for (AttributeType reqAttr : xoc.getRequiredAttributes()) { String attrName = reqAttr.getNameOrOID(); if (BackendImpl.blobAttributes.contains(attrName)) { NdbBlob blob = indexScanOp.getBlobHandle(attrName); blobMap.put(attrName, blob); } else { indexScanOp.getValue(attrName); } } for (AttributeType optAttr : xoc.getOptionalAttributes()) { String attrName = optAttr.getNameOrOID(); if (BackendImpl.blobAttributes.contains(attrName)) { NdbBlob blob = indexScanOp.getBlobHandle(attrName); blobMap.put(attrName, blob); } else { indexScanOp.getValue(attrName); } } ocRsList.add(indexScanOp.resultData()); } } // Operational attributes. op = ndbDATxn.getSelectOperation(BackendImpl.OPATTRS_TABLE, NdbOperation.LockMode.LM_CommittedRead); op.equalLong(BackendImpl.EID, eid); for (String attrName : BackendImpl.operationalAttributes) { op.getValue(attrName); } ocRsList.add(op.resultData()); // Attribute options. tagScanOp = ndbDATxn.getSelectIndexScanOperation( PRIMARY_INDEX_NAME, BackendImpl.TAGS_TABLE, NdbOperation.LockMode.LM_CommittedRead); tagScanOp.setBoundLong(BackendImpl.EID, NdbIndexScanOperation.BoundType.BoundEQ, eid); tagScanOp.getValue(BackendImpl.TAG_ATTR); tagScanOp.getValue(BackendImpl.MID); tagScanOp.getValue(BackendImpl.TAG_TAGS); NdbResultSet tagRs = tagScanOp.resultData(); ndbDATxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true); return decodeNDBEntry(dn, ocRsList, tagRs, objectClasses, xObjectClasses, blobMap, extensibleObject); } /** * Decode the entry from NDB results. * @param dn The entry DN. * @param ocRsList ObjectClass result sets list. * @param tagRs Attribute tags result set. * @param objectClasses ObjectClasses map. * @param xObjectClasses Extensible ObjectClasses map. * @param blobMap Blob attributes map. * @param extensibleObject true if the entry is Extensible Object, * false otherwise. * @return The entry. * @throws com.mysql.cluster.ndbj.NdbApiException */ private Entry decodeNDBEntry( DN dn, List ocRsList, NdbResultSet tagRs, Map objectClasses, Map xObjectClasses, Map blobMap, boolean extensibleObject) throws NdbApiException { LinkedHashMap> userAttributes = new LinkedHashMap>(); LinkedHashMap> opAttributes = new LinkedHashMap>(); // Attribute options. Map>> attr2tagMap = new HashMap>>(); while (tagRs.next()) { String attrName = tagRs.getString(BackendImpl.TAG_ATTR); int mid = tagRs.getInt(BackendImpl.MID); String attrOptions = tagRs.getString(BackendImpl.TAG_TAGS); if (!tagRs.wasNull()) { int currentIndex = attrOptions.indexOf(';'); int nextIndex = attrOptions.indexOf(';', currentIndex + 1); String option = null; Map> mid2tagMap = attr2tagMap.get(attrName); if (mid2tagMap == null) { mid2tagMap = new HashMap>(); } LinkedHashSet options = new LinkedHashSet(); while (nextIndex > 0) { option = attrOptions.substring(currentIndex + 1, nextIndex); if (option.length() > 0) { options.add(option); } currentIndex = nextIndex; nextIndex = attrOptions.indexOf(';', currentIndex + 1); } option = attrOptions.substring(currentIndex + 1); if (option.length() > 0) { options.add(option); } mid2tagMap.put(mid, options); attr2tagMap.put(attrName, mid2tagMap); } } // Object classes and user atributes. Iterator ocRsIterator = ocRsList.iterator(); NdbResultSet ocRs = ocRsIterator.next(); AttributeBuilder attrBuilder = new AttributeBuilder(); for (ObjectClass oc : objectClasses.keySet()) { if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) { continue; } while (ocRs.next()) { int mid = ocRs.getInt(BackendImpl.MID); for (AttributeType reqAttr : oc.getRequiredAttributes()) { String attrName = reqAttr.getNameOrOID(); byte[] attrValBytes = null; NdbBlob blob = null; if (BackendImpl.blobAttributes.contains(attrName)) { blob = blobMap.get(attrName); } else { attrValBytes = ocRs.getStringBytes(attrName); if (ocRs.wasNull()) { continue; } } AttributeType attributeType = DirectoryServer.getAttributeType( BackendImpl.attrName2LC.get(attrName), true); List attrList = userAttributes.get(attributeType); if (attrList == null) { attrList = new ArrayList(); } Attribute attr = null; LinkedHashSet options = null; Map> mid2tagMap = attr2tagMap.get(attrName); if (mid2tagMap != null) { options = mid2tagMap.get(mid); } if ((options == null) && !attrList.isEmpty()) { attr = attrList.get(attrList.size() - 1); } if (attr == null) { attrBuilder.setAttributeType(attributeType, attrName); } else { attrBuilder = new AttributeBuilder(attr); } if (blob != null) { if (blob.getNull()) { continue; } int len = blob.getLength().intValue(); byte[] buf = new byte[len]; blob.readData(buf, len); attrBuilder.add(AttributeValues.create(attributeType, ByteString.wrap(buf))); } else { attrBuilder.add(AttributeValues.create(attributeType, ByteString.wrap(attrValBytes))); } // Create or update an attribute. if (options != null) { attrBuilder.setOptions(options); } attr = attrBuilder.toAttribute(); if (attrList.isEmpty()) { attrList.add(attr); } else { attrList.set(attrList.size() - 1, attr); } userAttributes.put(attributeType, attrList); } for (AttributeType optAttr : oc.getOptionalAttributes()) { String attrName = optAttr.getNameOrOID(); byte[] attrValBytes = null; NdbBlob blob = null; if (BackendImpl.blobAttributes.contains(attrName)) { blob = blobMap.get(attrName); } else { attrValBytes = ocRs.getStringBytes(attrName); if (ocRs.wasNull()) { continue; } } AttributeType attributeType = DirectoryServer.getAttributeType( BackendImpl.attrName2LC.get(attrName), true); List attrList = userAttributes.get(attributeType); if (attrList == null) { attrList = new ArrayList(); } Attribute attr = null; LinkedHashSet options = null; Map> mid2tagMap = attr2tagMap.get(attrName); if (mid2tagMap != null) { options = mid2tagMap.get(mid); } if ((options == null) && !attrList.isEmpty()) { attr = attrList.get(attrList.size() - 1); } if (attr == null) { attrBuilder.setAttributeType(attributeType, attrName); } else { attrBuilder = new AttributeBuilder(attr); } if (blob != null) { if (blob.getNull()) { continue; } int len = blob.getLength().intValue(); byte[] buf = new byte[len]; blob.readData(buf, len); attrBuilder.add(AttributeValues.create(attributeType, ByteString.wrap(buf))); } else { attrBuilder.add(AttributeValues.create(attributeType, ByteString.wrap(attrValBytes))); } // Create or update an attribute. if (options != null) { attrBuilder.setOptions(options); } attr = attrBuilder.toAttribute(); if (attrList.isEmpty()) { attrList.add(attr); } else { attrList.set(attrList.size() - 1, attr); } userAttributes.put(attributeType, attrList); } } if (ocRsIterator.hasNext()) { ocRs = ocRsIterator.next(); } } // Operational attributes. if (ocRs.next()) { for (String attrName : BackendImpl.operationalAttributes) { byte[] attrValBytes = ocRs.getStringBytes(attrName); if (ocRs.wasNull()) { continue; } AttributeType attributeType = DirectoryServer.getAttributeType( BackendImpl.attrName2LC.get(attrName), true); attrBuilder.setAttributeType(attributeType, attrName); attrBuilder.add(AttributeValues.create(attributeType, ByteString.wrap(attrValBytes))); Attribute attr = attrBuilder.toAttribute(); List attrList = opAttributes.get(attributeType); if (attrList == null) { attrList = new ArrayList(); attrList.add(attr); opAttributes.put(attributeType, attrList); } else { attrList.add(attr); } } } // Extensible object. if (extensibleObject) { for (ObjectClass oc : xObjectClasses.keySet()) { objectClasses.remove(oc); } } Entry entry = new Entry(dn, objectClasses, userAttributes, opAttributes); if (entry != null) { entry.processVirtualAttributes(); } return entry; } /** * Fetch the entry ID for a given DN. * @param txn Abstract transaction to be used for the database read. * @param dn The DN for which the entry ID is desired. * @param lockMode The lock mode for this operation. * @return The entry ID, or zero if the given DN is not in the database. * @throws NdbApiException If an error occurs in the database. */ public long getID(AbstractTransaction txn, DN dn, NdbOperation.LockMode lockMode) throws NdbApiException { NdbOperation op = null; NdbResultSet rs = null; long eid = 0; NdbTransaction ndbTxn = txn.getNdbTransaction(); op = ndbTxn.getSelectOperation(name, lockMode); int componentIndex = dn.getNumComponents() - 1; for (int i=0; i < BackendImpl.DN2ID_DN_NC; i++) { while (componentIndex >= 0) { op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i), dn.getRDN(componentIndex).toNormalizedString()); componentIndex--; i++; } op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i), ""); } op.getValue(BackendImpl.EID); rs = op.resultData(); ndbTxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true); if (rs.next()) { eid = rs.getLong(BackendImpl.EID); } return eid; } /** * Get referrals for a given entry ID. * @param txn Abstract transaction to be used for the operation. * @param id The ID for which the referral is desired. * @return The referral set, or empty set if the entry has no referrals. * @throws NdbApiException If an error occurs in the database. */ public Set getReferrals(AbstractTransaction txn, long id) throws NdbApiException { NdbIndexScanOperation op = null; NdbResultSet rs = null; Set referrals = new HashSet(); NdbTransaction ndbDATxn = txn.getNdbDATransaction(BackendImpl.REFERRALS_TABLE, id); op = ndbDATxn.getSelectIndexScanOperation( PRIMARY_INDEX_NAME, BackendImpl.REFERRALS_TABLE, NdbOperation.LockMode.LM_CommittedRead); op.setBoundLong(BackendImpl.EID, NdbIndexScanOperation.BoundType.BoundEQ, id); op.getValue(ATTR_REFERRAL_URL); rs = op.resultData(); ndbDATxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true); while (rs.next()) { String referral = rs.getString(ATTR_REFERRAL_URL); if (rs.wasNull() || (referral.length() == 0)) { break; } referrals.add(referral); } return referrals; } /** * Get the count of rows in the database. * @return The number of rows in the database. * @throws NdbApiException If an error occurs in the database. */ public long getRecordCount() throws NdbApiException { Ndb ndb = entryContainer.getRootContainer().getNDB(); try { return ndb.selectCount(name); } finally { if (ndb != null) { entryContainer.getRootContainer().releaseNDB(ndb); } } } /** * Determine if the entry has subordinate entries. * @param txn Abstract transaction to be used for the operation. * @param dn The entry DN. * @return true if the entry has subordinates, false otherwise. * @throws com.mysql.cluster.ndbj.NdbApiException If an error * occurs in the database. */ public boolean hasSubordinates(AbstractTransaction txn, DN dn) throws NdbApiException { // NdbInterpretedOperation op; NdbIndexScanOperation op; NdbResultSet rs; NdbTransaction ndbTxn = txn.getNdbTransaction(); op = ndbTxn.getSelectIndexScanOperation( PRIMARY_INDEX_NAME, name, NdbOperation.LockMode.LM_Read); int numComponents = dn.getNumComponents(); int componentIndex = numComponents - 1; for (int i=0; i < numComponents; i++) { op.setBoundString(BackendImpl.DN2ID_DN + Integer.toString(i), NdbIndexScanOperation.BoundType.BoundEQ, dn.getRDN(componentIndex).toNormalizedString()); componentIndex--; } if (dn.getNumComponents() < BackendImpl.DN2ID_DN_NC) { String nextRDNColumn = BackendImpl.DN2ID_DN + Integer.toString(numComponents); op.setBoundString(nextRDNColumn, NdbIndexScanOperation.BoundType.BoundLT, ""); } // FIXME: This is extremely inefficient, need NDB/J API // like interpretExitLastRow to count result rows node- // side without returning them here for check/count. // op.interpretExitLastRow(); op.getValue(BackendImpl.EID); rs = op.resultData(); ndbTxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true); if (rs.next()) { return true; } return false; } /** * Get a new instance of the Search Cursor object. * @param txn Abstract Transaction to be used for the operation. * @param baseDN Search Cursor base DN. * @return New instance of the Search Cursor object. */ public DN2IDSearchCursor getSearchCursor( AbstractTransaction txn, DN baseDN) { return new DN2IDSearchCursor(txn, baseDN); } /** * This inner class represents the Search Cursor which can be * used to cursor entries in the database starting from some * arbitrary base DN. */ protected class DN2IDSearchCursor { private NdbIndexScanOperation op; private NdbResultSet rs; private NdbTransaction ndbTxn; private AbstractTransaction txn; private DN baseDN; /** * Object constructor. * @param txn Abstract Transaction to be used for the operation. * @param baseDN Search Cursor base DN. */ public DN2IDSearchCursor( AbstractTransaction txn, DN baseDN) { this.txn = txn; this.baseDN = baseDN; } /** * Open the cursor. * @throws com.mysql.cluster.ndbj.NdbApiException If an error * occurs in the database. */ public void open() throws NdbApiException { ndbTxn = txn.getNdbTransaction(); op = ndbTxn.getSelectIndexScanOperation( PRIMARY_INDEX_NAME, name, NdbOperation.LockMode.LM_CommittedRead); int numComponents = baseDN.getNumComponents(); int componentIndex = numComponents - 1; for (int i = 0; i < numComponents; i++) { op.setBoundString(BackendImpl.DN2ID_DN + Integer.toString(i), NdbIndexScanOperation.BoundType.BoundEQ, baseDN.getRDN(componentIndex).toNormalizedString()); componentIndex--; } if (baseDN.getNumComponents() < BackendImpl.DN2ID_DN_NC) { String nextRDNColumn = BackendImpl.DN2ID_DN + Integer.toString(numComponents); op.setBoundString(nextRDNColumn, NdbIndexScanOperation.BoundType.BoundLT, ""); } op.getValue(BackendImpl.EID); for (int i = 0; i < BackendImpl.DN2ID_DN_NC; i++) { op.getValue(BackendImpl.DN2ID_DN + Integer.toString(i)); } rs = op.resultData(); ndbTxn.execute(ExecType.NoCommit, AbortOption.AbortOnError, true); } /** * Advance one position and return the result. * @return An instance of Search Cursor Result. * @throws com.mysql.cluster.ndbj.NdbApiException If an error * occurs in the database. */ public SearchCursorResult getNext() throws NdbApiException { if (rs.next()) { long eid = rs.getLong(BackendImpl.EID); StringBuilder dnBuffer = new StringBuilder(); int dnColumnIndex = BackendImpl.DN2ID_DN_NC - 1; while (dnColumnIndex >= 0) { String rdnString = rs.getString(BackendImpl.DN2ID_DN + Integer.toString(dnColumnIndex)); if (rdnString.length() > 0) { dnBuffer.append(rdnString); if (dnColumnIndex > 0) { dnBuffer.append(","); } } dnColumnIndex--; } String dnString = dnBuffer.toString(); if ((eid == 0) || (dnString.length() == 0)) { return null; } SearchCursorResult result = new SearchCursorResult(dnString, eid); return result; } return null; } /** * Close the cursor. */ public void close() { ndbTxn = null; txn = null; } } /** * This inner class represents a Search Cursor Result * as returned by the Search Cursor operations. */ protected class SearchCursorResult { /** * Entry DN. */ public String dn; /** * Entry ID. */ public long id; /** * Object constructor. * @param dn The entry DN. * @param id The entry ID. */ public SearchCursorResult(String dn, long id) { this.dn = dn; this.id = id; } } }