All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.ldap.sdk.unboundidds.tools.ParallelUpdateOperationQueue Maven / Gradle / Ivy

/*
 * Copyright 2008-2020 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2008-2020 Ping Identity Corporation
 *
 * 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.
 */
/*
 * Copyright (C) 2008-2020 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * 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 .
 */
package com.unboundid.ldap.sdk.unboundidds.tools;



import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;

import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldif.LDIFChangeRecord;
import com.unboundid.ldif.LDIFDeleteChangeRecord;
import com.unboundid.ldif.LDIFModifyDNChangeRecord;
import com.unboundid.util.Debug;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;



/**
 * This class provides a data structure that will be used to hold the operations
 * to process by the parallel-update tool.  The thread reading change records
 * from the LDIF file will populate this queue, and modify threads will pull
 * entries out.  It will attempt to maintain a sane ordering for the operations
 * in order to resolve any dependencies that may exist between operations.
 * 
*
* NOTE: This class, and other classes within the * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only * supported for use against Ping Identity, UnboundID, and * Nokia/Alcatel-Lucent 8661 server products. These classes provide support * for proprietary functionality or for external specifications that are not * considered stable or mature enough to be guaranteed to work in an * interoperable way with other types of LDAP servers. *
*/ final class ParallelUpdateOperationQueue { // Indicates whether the end of the LDIF has been reached. private boolean endOfLDIF; // The map that will hold the DNs of the change records currently being // processed. @NotNull private final HashSet activeChanges; // The capacity for this queue. private final int capacity; // The current size of the queue. private int size; // The map that will hold the operations to be processed. @NotNull private final LinkedList opQueue; // The lock that will be used to provide thread safety for the queue. @NotNull private final Object queueLock; // The parallel update instance with which this queue is associated. @NotNull private final ParallelUpdate parallelUpdate; /** * Creates a new instance of this operation queue. * * @param parallelUpdate The parallel update instance with which this queue * is associated. * @param numThreads The number of threads expected to access this * queue. * @param capacity The maximum capacity to use for the queue. */ ParallelUpdateOperationQueue( @NotNull final ParallelUpdate parallelUpdate, final int numThreads, final int capacity) { this.parallelUpdate = parallelUpdate; this.capacity = capacity; endOfLDIF = false; activeChanges = new HashSet<>(numThreads); opQueue = new LinkedList<>(); queueLock = new Object(); size = 0; } /** * Adds the provided LDIF change record to this queue. This method will block * if the queue is currently at its capacity. * * @param changeRecord The change record to be added to this queue. * * @throws InterruptedException If the thread is interrupted while waiting * for available capacity in the queue. */ void addChangeRecord(@NotNull final LDIFChangeRecord changeRecord) throws InterruptedException { // First, make sure that we can parse the DN of the change record. If not, // then reject the change record. try { changeRecord.getParsedDN(); } catch (final LDAPException e) { Debug.debugException(e); parallelUpdate.reject(changeRecord, e); return; } synchronized (queueLock) { while (size >= capacity) { queueLock.wait(1000L); } opQueue.add(changeRecord); size++; queueLock.notifyAll(); } } /** * Retrieves the next LDIF change record to be processed. This method will * block if the queue is currently empty but the end of the LDIF file has not * yet been reached, or if all of the operations currently held in the queue * are dependent upon an operation in progress. * * @param completedDNs The DNs of the entries targeted by the last change. * It should be empty for the first request processed by * a thread, should have a single element if the last * change was an add, delete, or modify, and should have * two elements if the last change was a modify DN. * * @return The next LDIF change record to be processed, or {@code null} if * there are no more change records to process and the end of the * LDIF file has been reached. */ @Nullable() LDIFChangeRecord getChangeRecord(@NotNull final DN... completedDNs) { synchronized (queueLock) { for (final DN dn : completedDNs) { activeChanges.remove(dn); } while (! (endOfLDIF && opQueue.isEmpty())) { if (opQueue.isEmpty()) { try { queueLock.wait(1000L); } catch (final InterruptedException e) { Debug.debugException(e); } continue; } final Iterator iterator = opQueue.iterator(); iteratorLoop: while (iterator.hasNext()) { final LDIFChangeRecord r = iterator.next(); // Get the parsed target DN for the change record. final DN targetDN; try { targetDN = r.getParsedDN(); } catch (final LDAPException e) { // This should never happen, but if it does then reject the change. parallelUpdate.reject(r, e); iterator.remove(); size--; continue; } // Make sure that we are not currently processing any operation that // involves the entry or any of its ancestors. If it is a delete, // then make sure that we are not processing any operation that // involves any of its descendants. If it is a modify DN operation, // then we need to ensure that neither the current DN nor the new DN // conflict with any active operations. if (! activeChanges.isEmpty()) { for (final DN activeDN : activeChanges) { if (activeDN.isAncestorOf(targetDN, true)) { continue iteratorLoop; } } if (r instanceof LDIFDeleteChangeRecord) { for (final DN activeDN : activeChanges) { if (activeDN.isDescendantOf(targetDN, false)) { continue iteratorLoop; } } } else if (r instanceof LDIFModifyDNChangeRecord) { final LDIFModifyDNChangeRecord modDNRecord = (LDIFModifyDNChangeRecord) r; final DN newDN; try { newDN = modDNRecord.getNewDN(); } catch (final LDAPException e) { // We could not parse the new DN, so reject the change. parallelUpdate.reject(r, e); iterator.remove(); size--; continue iteratorLoop; } for (final DN activeDN : activeChanges) { if (activeDN.isDescendantOf(targetDN, false) || activeDN.isAncestorOf(newDN, true) || activeDN.isDescendantOf(newDN, false)) { continue iteratorLoop; } } // At this point, we know that the modify DN will be processed so // reserve the new DN as well. The target DN will be reserved // below. activeChanges.add(newDN); } } // At this point, the change will be processed so remove it from the // queue and add it to the list of active changes. Also, notify any // threads that might be waiting on the queue lock in case // addChangeRecord is waiting on available space. activeChanges.add(targetDN); iterator.remove(); size--; queueLock.notifyAll(); return r; } // If we've gotten here, then the queue isn't empty, but all of the // operations contained in it are dependent upon operations that are // actively being processed. In that case, wait for an operation to // complete before trying again. try { queueLock.wait(1000L); } catch (final InterruptedException e) { Debug.debugException(e); } } } // If we've gotten here, then there is no more data to be processed. return null; } /** * Blocks until the operation queue is idle (i.e., the operation queue is * empty and there are no active changes). */ public void waitUntilIdle() { synchronized (queueLock) { while (true) { if (opQueue.isEmpty() && activeChanges.isEmpty()) { return; } try { queueLock.wait(1000L); } catch (final InterruptedException e) { Debug.debugException(e); } } } } /** * Indicates that the end of the LDIF has been reached and that no more data * will be added to the queue. */ public void setEndOfLDIF() { synchronized (queueLock) { endOfLDIF = true; queueLock.notifyAll(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy