| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| * |
| */ |
| |
| package org.apache.directory.studio.ldapbrowser.core.jobs; |
| |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.naming.directory.SearchControls; |
| |
| import org.apache.directory.api.ldap.model.message.Control; |
| import org.apache.directory.api.ldap.model.name.Dn; |
| import org.apache.directory.studio.common.core.jobs.StudioProgressMonitor; |
| import org.apache.directory.studio.connection.core.Connection; |
| import org.apache.directory.studio.connection.core.Controls; |
| import org.apache.directory.studio.connection.core.Connection.AliasDereferencingMethod; |
| import org.apache.directory.studio.connection.core.Connection.ReferralHandlingMethod; |
| import org.apache.directory.studio.connection.core.StudioControl; |
| import org.apache.directory.studio.connection.core.io.StudioLdapException; |
| import org.apache.directory.studio.connection.core.io.api.StudioSearchResultEnumeration; |
| import org.apache.directory.studio.connection.core.jobs.StudioConnectionBulkRunnableWithProgress; |
| import org.apache.directory.studio.ldapbrowser.core.BrowserCoreMessages; |
| import org.apache.directory.studio.ldapbrowser.core.events.BulkModificationEvent; |
| import org.apache.directory.studio.ldapbrowser.core.events.EventRegistry; |
| import org.apache.directory.studio.ldapbrowser.core.events.SearchUpdateEvent; |
| import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection; |
| import org.apache.directory.studio.ldapbrowser.core.model.IEntry; |
| import org.apache.directory.studio.ldapbrowser.core.model.ISearch; |
| import org.apache.directory.studio.ldapbrowser.core.model.ISearchResult; |
| import org.apache.directory.studio.ldapbrowser.core.utils.JNDIUtils; |
| |
| |
| /** |
| * Runnable to delete entries. |
| * |
| * Deletes the entry recursively in a optimistic way: |
| * <ol> |
| * <li>Delete the entry |
| * <li>If that fails with error code 66 then perform a one-level search |
| * and start from 1. for each entry. |
| * </ol> |
| * |
| * TODO: delete subentries? |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class DeleteEntriesRunnable implements StudioConnectionBulkRunnableWithProgress |
| { |
| /** The entries to delete. */ |
| private Collection<IEntry> entriesToDelete; |
| |
| /** The deleted entries. */ |
| private Set<IEntry> deletedEntriesSet; |
| |
| /** The searches to update. */ |
| private Set<ISearch> searchesToUpdateSet; |
| |
| /** The use tree delete control flag. */ |
| private boolean useTreeDeleteControl; |
| |
| |
| /** |
| * Creates a new instance of DeleteEntriesRunnable. |
| * |
| * @param entriesToDelete the entries to delete |
| */ |
| public DeleteEntriesRunnable( final Collection<IEntry> entriesToDelete, boolean useTreeDeleteControl ) |
| { |
| this.entriesToDelete = entriesToDelete; |
| this.useTreeDeleteControl = useTreeDeleteControl; |
| |
| this.deletedEntriesSet = new HashSet<IEntry>(); |
| this.searchesToUpdateSet = new HashSet<ISearch>(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Connection[] getConnections() |
| { |
| Connection[] connections = new Connection[entriesToDelete.size()]; |
| int i = 0; |
| for ( IEntry entry : entriesToDelete ) |
| { |
| connections[i] = entry.getBrowserConnection().getConnection(); |
| i++; |
| } |
| return connections; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getName() |
| { |
| return entriesToDelete.size() == 1 ? BrowserCoreMessages.jobs__delete_entries_name_1 |
| : BrowserCoreMessages.jobs__delete_entries_name_n; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Object[] getLockedObjects() |
| { |
| List<IEntry> l = new ArrayList<IEntry>(); |
| l.addAll( entriesToDelete ); |
| return l.toArray(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getErrorMessage() |
| { |
| return entriesToDelete.size() == 1 ? BrowserCoreMessages.jobs__delete_entries_error_1 |
| : BrowserCoreMessages.jobs__delete_entries_error_n; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void run( StudioProgressMonitor monitor ) |
| { |
| monitor.beginTask( |
| entriesToDelete.size() == 1 ? BrowserCoreMessages.bind( BrowserCoreMessages.jobs__delete_entries_task_1, |
| new String[] |
| { entriesToDelete.iterator().next().getDn().getName() } ) : BrowserCoreMessages.bind( |
| BrowserCoreMessages.jobs__delete_entries_task_n, new String[] |
| { Integer.toString( entriesToDelete.size() ) } ), 2 + entriesToDelete.size() ); |
| monitor.reportProgress( " " ); //$NON-NLS-1$ |
| monitor.worked( 1 ); |
| |
| int num = 0; |
| StudioProgressMonitor dummyMonitor = new StudioProgressMonitor( monitor ); |
| for ( Iterator<IEntry> iterator = entriesToDelete.iterator(); !monitor.isCanceled() |
| && !monitor.errorsReported() && iterator.hasNext(); ) |
| { |
| IEntry entryToDelete = iterator.next(); |
| IBrowserConnection browserConnection = entryToDelete.getBrowserConnection(); |
| |
| // delete from directory |
| int errorStatusSize1 = monitor.getErrorStatus( "" ).getChildren().length; //$NON-NLS-1$ |
| num = optimisticDeleteEntryRecursive( browserConnection, entryToDelete.getDn(), entryToDelete.isReferral(), |
| useTreeDeleteControl, num, dummyMonitor, monitor ); |
| int errorStatusSize2 = monitor.getErrorStatus( "" ).getChildren().length; //$NON-NLS-1$ |
| |
| if ( !monitor.isCanceled() ) |
| { |
| if ( errorStatusSize1 == errorStatusSize2 ) |
| { |
| // delete |
| deletedEntriesSet.add( entryToDelete ); |
| //entryToDelete.setChildrenInitialized( false ); |
| |
| // delete from parent entry |
| entryToDelete.getParententry().setChildrenInitialized( false ); |
| entryToDelete.getParententry().deleteChild( entryToDelete ); |
| |
| // delete from searches |
| List<ISearch> searches = browserConnection.getSearchManager().getSearches(); |
| for ( ISearch search : searches ) |
| { |
| if ( search.getSearchResults() != null ) |
| { |
| ISearchResult[] searchResults = search.getSearchResults(); |
| List<ISearchResult> searchResultList = new ArrayList<ISearchResult>(); |
| searchResultList.addAll( Arrays.asList( searchResults ) ); |
| for ( Iterator<ISearchResult> it = searchResultList.iterator(); it.hasNext(); ) |
| { |
| ISearchResult result = it.next(); |
| if ( entryToDelete.equals( result.getEntry() ) ) |
| { |
| it.remove(); |
| searchesToUpdateSet.add( search ); |
| } |
| } |
| if ( searchesToUpdateSet.contains( search ) ) |
| { |
| search.setSearchResults( searchResultList.toArray( new ISearchResult[searchResultList |
| .size()] ) ); |
| } |
| } |
| } |
| |
| // delete from cache |
| browserConnection.uncacheEntryRecursive( entryToDelete ); |
| } |
| } |
| else |
| { |
| entryToDelete.setChildrenInitialized( false ); |
| } |
| |
| monitor.worked( 1 ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void runNotification( StudioProgressMonitor monitor ) |
| { |
| // don't fire an EntryDeletedEvent for each deleted entry |
| // that would cause massive UI updates |
| // instead we unset children information and fire a BulkModificationEvent |
| IBrowserConnection browserConnection = entriesToDelete.iterator().next().getBrowserConnection(); |
| EventRegistry.fireEntryUpdated( new BulkModificationEvent( browserConnection ), this ); |
| |
| for ( ISearch search : searchesToUpdateSet ) |
| { |
| EventRegistry.fireSearchUpdated( new SearchUpdateEvent( search, |
| SearchUpdateEvent.EventDetail.SEARCH_PERFORMED ), this ); |
| } |
| } |
| |
| |
| /** |
| * Deletes the entry recursively in a optimistic way: |
| * <ol> |
| * <li>Deletes the entry |
| * <li>If that fails then perform a one-level search and call the |
| * method for each found entry |
| * </ol> |
| * |
| * @param browserConnection the browser connection |
| * @param dn the Dn to delete |
| * @param useManageDsaItControl true to use the ManageDsaIT control |
| * @param useTreeDeleteControl true to use the tree delete control |
| * @param numberOfDeletedEntries the number of deleted entries |
| * @param dummyMonitor the dummy monitor |
| * @param monitor the progress monitor |
| * |
| * @return the cumulative number of deleted entries |
| */ |
| static int optimisticDeleteEntryRecursive( IBrowserConnection browserConnection, Dn dn, |
| boolean useManageDsaItControl, boolean useTreeDeleteControl, int numberOfDeletedEntries, |
| StudioProgressMonitor dummyMonitor, StudioProgressMonitor monitor ) |
| { |
| // try to delete entry |
| dummyMonitor.reset(); |
| deleteEntry( browserConnection, dn, useManageDsaItControl, useTreeDeleteControl, dummyMonitor ); |
| |
| if ( !dummyMonitor.errorsReported() ) |
| { |
| numberOfDeletedEntries++; |
| monitor.reportProgress( BrowserCoreMessages.bind( BrowserCoreMessages.model__deleted_n_entries, |
| new String[] |
| { "" + numberOfDeletedEntries } ) ); //$NON-NLS-1$ |
| } |
| else if ( StudioLdapException.isContextNotEmptyException( dummyMonitor.getException() ) ) |
| { |
| // do not follow referrals or dereference aliases when deleting entries |
| AliasDereferencingMethod aliasDereferencingMethod = AliasDereferencingMethod.NEVER; |
| ReferralHandlingMethod referralsHandlingMethod = ReferralHandlingMethod.IGNORE; |
| |
| // perform one-level search and delete recursively |
| int numberInBatch; |
| dummyMonitor.reset(); |
| do |
| { |
| numberInBatch = 0; |
| |
| SearchControls searchControls = new SearchControls(); |
| searchControls.setCountLimit( 1000 ); |
| searchControls.setReturningAttributes( new String[0] ); |
| searchControls.setSearchScope( SearchControls.ONELEVEL_SCOPE ); |
| StudioSearchResultEnumeration result = browserConnection |
| .getConnection() |
| .getConnectionWrapper() |
| .search( dn.getName(), ISearch.FILTER_TRUE, searchControls, aliasDereferencingMethod, |
| referralsHandlingMethod, null, dummyMonitor, null ); |
| |
| try |
| { |
| // delete all child entries |
| while ( !dummyMonitor.isCanceled() && !dummyMonitor.errorsReported() && result.hasMore() ) |
| { |
| Dn childDn = result.next().getDn(); |
| |
| numberOfDeletedEntries = optimisticDeleteEntryRecursive( browserConnection, childDn, false, |
| false, numberOfDeletedEntries, dummyMonitor, monitor ); |
| numberInBatch++; |
| } |
| } |
| catch ( Exception e ) |
| { |
| int ldapStatusCode = JNDIUtils.getLdapStatusCode( e ); |
| if ( ldapStatusCode == 3 || ldapStatusCode == 4 || ldapStatusCode == 11 ) |
| { |
| // continue with search |
| } |
| else |
| { |
| dummyMonitor.reportError( e ); |
| break; |
| } |
| } |
| } |
| while ( numberInBatch > 0 && !monitor.isCanceled() && !dummyMonitor.errorsReported() ); |
| |
| // try to delete the entry again |
| if ( !dummyMonitor.errorsReported() ) |
| { |
| deleteEntry( browserConnection, dn, false, false, dummyMonitor ); |
| } |
| if ( !dummyMonitor.errorsReported() ) |
| { |
| numberOfDeletedEntries++; |
| monitor.reportProgress( BrowserCoreMessages.bind( BrowserCoreMessages.model__deleted_n_entries, |
| new String[] |
| { "" + numberOfDeletedEntries } ) ); //$NON-NLS-1$ |
| } |
| } |
| else |
| { |
| Exception exception = dummyMonitor.getException(); |
| // we have another exception |
| // report it to the dummy monitor if we are in the recursion |
| dummyMonitor.reportError( exception ); |
| // also report it to the real monitor |
| monitor.reportError( exception ); |
| } |
| |
| return numberOfDeletedEntries; |
| } |
| |
| |
| static void deleteEntry( IBrowserConnection browserConnection, Dn dn, boolean useManageDsaItControl, |
| boolean useTreeDeleteControl, StudioProgressMonitor monitor ) |
| { |
| // controls |
| List<Control> controlList = new ArrayList<Control>(); |
| if ( useTreeDeleteControl |
| && browserConnection.getRootDSE().isControlSupported( StudioControl.TREEDELETE_CONTROL.getOid() ) ) |
| { |
| controlList.add( Controls.TREEDELETE_CONTROL ); |
| } |
| if ( useManageDsaItControl |
| && browserConnection.getRootDSE().isControlSupported( StudioControl.MANAGEDSAIT_CONTROL.getOid() ) ) |
| { |
| controlList.add( Controls.MANAGEDSAIT_CONTROL ); |
| } |
| Control[] controls = controlList.toArray( new Control[controlList.size()] ); |
| |
| // delete entry |
| if ( browserConnection.getConnection() != null ) |
| { |
| browserConnection.getConnection().getConnectionWrapper() |
| .deleteEntry( dn, controls, monitor, null ); |
| } |
| } |
| } |