/* * 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-2010 Sun Microsystems, Inc. * Portions Copyright 2013 ForgeRock AS */ package org.opends.guitools.controlpanel.task; import static org.opends.messages.AdminToolMessages.*; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.BasicControl; import javax.naming.ldap.Control; import javax.naming.ldap.InitialLdapContext; import javax.swing.SwingUtilities; import javax.swing.tree.TreePath; import org.opends.admin.ads.util.ConnectionUtils; import org.opends.guitools.controlpanel.browser.BrowserController; import org.opends.guitools.controlpanel.datamodel.BackendDescriptor; import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor; import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo; import org.opends.guitools.controlpanel.datamodel.CustomSearchResult; import org.opends.guitools.controlpanel.ui.ColorAndFontConstants; import org.opends.guitools.controlpanel.ui.ProgressDialog; import org.opends.guitools.controlpanel.ui.nodes.BasicNode; import org.opends.guitools.controlpanel.ui.nodes.BrowserNodeInfo; import org.opends.guitools.controlpanel.util.Utilities; import org.opends.messages.Message; import org.opends.server.schema.SchemaConstants; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.util.ServerConstants; /** * The task that is launched when an entry must be deleted. */ public class DeleteEntryTask extends Task { private Set<String> backendSet; private DN lastDn; private int nDeleted = 0; private int nToDelete = -1; private BrowserController controller; private TreePath[] paths; private long lastProgressTime; private boolean equivalentCommandWithControlPrinted = false; private boolean equivalentCommandWithoutControlPrinted = false; private boolean useAdminCtx; /** * Constructor of the task. * @param info the control panel information. * @param dlg the progress dialog where the task progress will be displayed. * @param paths the tree paths of the entries that must be deleted. * @param controller the Browser Controller. */ public DeleteEntryTask(ControlPanelInfo info, ProgressDialog dlg, TreePath[] paths, BrowserController controller) { super(info, dlg); backendSet = new HashSet<String>(); this.controller = controller; this.paths = paths; SortedSet<DN> entries = new TreeSet<DN>(); boolean canPrecalculateNumberOfEntries = true; nToDelete = paths.length; for (TreePath path : paths) { BasicNode node = (BasicNode)path.getLastPathComponent(); try { DN dn = DN.decode(node.getDN()); entries.add(dn); } catch (DirectoryException de) { throw new RuntimeException("Unexpected error parsing dn: "+ node.getDN(), de); } } for (BackendDescriptor backend : info.getServerDescriptor().getBackends()) { for (BaseDNDescriptor baseDN : backend.getBaseDns()) { for (DN dn : entries) { if (dn.isDescendantOf(baseDN.getDn())) { backendSet.add(backend.getBackendID()); break; } } } } if (!canPrecalculateNumberOfEntries) { nToDelete = -1; } } /** * {@inheritDoc} */ public Type getType() { return Type.DELETE_ENTRY; } /** * {@inheritDoc} */ public Set<String> getBackends() { return backendSet; } /** * {@inheritDoc} */ public Message getTaskDescription() { return INFO_CTRL_PANEL_DELETE_ENTRY_TASK_DESCRIPTION.get(); } /** * {@inheritDoc} */ protected String getCommandLinePath() { return null; } /** * {@inheritDoc} */ protected ArrayList<String> getCommandLineArguments() { return new ArrayList<String>(); } /** * {@inheritDoc} */ public boolean canLaunch(Task taskToBeLaunched, Collection<Message> incompatibilityReasons) { boolean canLaunch = true; if (!isServerRunning()) { if (state == State.RUNNING && runningOnSameServer(taskToBeLaunched)) { // All the operations are incompatible if they apply to this // backend for safety. Set<String> backends = new TreeSet<String>(taskToBeLaunched.getBackends()); backends.retainAll(getBackends()); if (backends.size() > 0) { incompatibilityReasons.add(getIncompatibilityMessage(this, taskToBeLaunched)); canLaunch = false; } } } return canLaunch; } /** * {@inheritDoc} */ public boolean regenerateDescriptor() { return false; } /** * {@inheritDoc} */ public void runTask() { state = State.RUNNING; lastException = null; ArrayList<DN> alreadyDeleted = new ArrayList<DN>(); ArrayList<BrowserNodeInfo> toNotify = new ArrayList<BrowserNodeInfo>(); try { for (TreePath path : paths) { BasicNode node = (BasicNode)path.getLastPathComponent(); try { DN dn = DN.decode(node.getDN()); boolean isDnDeleted = false; for (DN deletedDn : alreadyDeleted) { if (dn.isDescendantOf(deletedDn)) { isDnDeleted = true; break; } } if (!isDnDeleted) { InitialLdapContext ctx = controller.findConnectionForDisplayedEntry(node); useAdminCtx = controller.isConfigurationNode(node); if (node.hasSubOrdinates()) { deleteSubtreeWithControl(ctx, dn, path, toNotify); } else { deleteSubtreeRecursively(ctx, dn, path, toNotify); } alreadyDeleted.add(dn); } } catch (DirectoryException de) { throw new RuntimeException("Unexpected error parsing dn: "+ node.getDN(), de); } } if (toNotify.size() > 0) { final List<BrowserNodeInfo> fToNotify = new ArrayList<BrowserNodeInfo>(toNotify); toNotify.clear(); SwingUtilities.invokeLater(new Runnable() { public void run() { notifyEntriesDeleted(fToNotify); } }); } state = State.FINISHED_SUCCESSFULLY; } catch (Throwable t) { lastException = t; state = State.FINISHED_WITH_ERROR; } if (nDeleted > 1) { getProgressDialog().appendProgressHtml(Utilities.applyFont( "<br>"+INFO_CTRL_PANEL_ENTRIES_DELETED.get(nDeleted), ColorAndFontConstants.progressFont)); } } /** * Notifies that some entries have been deleted. This will basically update * the browser controller so that the tree reflects the changes that have * been made. * @param deletedNodes the nodes that have been deleted. */ private void notifyEntriesDeleted(Collection<BrowserNodeInfo> deletedNodes) { TreePath pathToSelect = null; for (BrowserNodeInfo nodeInfo : deletedNodes) { TreePath parentPath = controller.notifyEntryDeleted(nodeInfo); if (pathToSelect != null) { if (parentPath.getPathCount() < pathToSelect.getPathCount()) { pathToSelect = parentPath; } } else { pathToSelect = parentPath; } } if (pathToSelect != null) { TreePath selectedPath = controller.getTree().getSelectionPath(); if (selectedPath == null) { controller.getTree().setSelectionPath(pathToSelect); } else if (!selectedPath.equals(pathToSelect) && (pathToSelect.getPathCount() < selectedPath.getPathCount())) { controller.getTree().setSelectionPath(pathToSelect); } } } private void deleteSubtreeRecursively(InitialLdapContext ctx, DN dnToRemove, TreePath path, ArrayList<BrowserNodeInfo> toNotify) throws NamingException, DirectoryException { lastDn = dnToRemove; long t = System.currentTimeMillis(); boolean displayProgress = (((nDeleted % 20) == 0) || ((t - lastProgressTime) > 5000)) && (nToDelete > 0) && (nToDelete > nDeleted); if (displayProgress) { // Only display the first entry equivalent command-line. SwingUtilities.invokeLater(new Runnable() { public void run() { if (!equivalentCommandWithoutControlPrinted) { printEquivalentCommandToDelete(lastDn, false); equivalentCommandWithoutControlPrinted = true; } getProgressDialog().setSummary( Message.raw( Utilities.applyFont( INFO_CTRL_PANEL_DELETING_ENTRY_SUMMARY.get( lastDn.toString()).toString(), ColorAndFontConstants.defaultFont))); } }); } try { SearchControls ctls = new SearchControls(); ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE); String filter = "(|(objectClass=*)(objectclass=ldapsubentry))"; ctls.setReturningAttributes( new String[] { SchemaConstants.NO_ATTRIBUTES }); NamingEnumeration<SearchResult> entryDNs = ctx.search(Utilities.getJNDIName(dnToRemove.toString()), filter, ctls); DN entryDNFound = dnToRemove; try { while (entryDNs.hasMore()) { SearchResult sr = entryDNs.next(); if (!sr.getName().equals("")) { CustomSearchResult res = new CustomSearchResult(sr, dnToRemove.toString()); entryDNFound = DN.decode(res.getDN()); deleteSubtreeRecursively(ctx, entryDNFound, null, toNotify); } } } finally { entryDNs.close(); } } catch (NameNotFoundException nnfe) { // The entry is not there: it has been removed } try { ctx.destroySubcontext(Utilities.getJNDIName(dnToRemove.toString())); if (path != null) { toNotify.add(controller.getNodeInfoFromPath(path)); } nDeleted ++; if (displayProgress) { lastProgressTime = t; final Collection<BrowserNodeInfo> fToNotify; if (toNotify.size() > 0) { fToNotify = new ArrayList<BrowserNodeInfo>(toNotify); toNotify.clear(); } else { fToNotify = null; } SwingUtilities.invokeLater(new Runnable() { public void run() { getProgressDialog().getProgressBar().setIndeterminate(false); getProgressDialog().getProgressBar().setValue( (100 * nDeleted) / nToDelete); if (fToNotify != null) { notifyEntriesDeleted(fToNotify); } } }); } } catch (NameNotFoundException nnfe) { // The entry is not there: it has been removed } } private void deleteSubtreeWithControl(InitialLdapContext ctx, DN dn, TreePath path, ArrayList<BrowserNodeInfo> toNotify) throws NamingException { lastDn = dn; long t = System.currentTimeMillis(); // Only display the first entry equivalent command-line. SwingUtilities.invokeLater(new Runnable() { public void run() { if (!equivalentCommandWithControlPrinted) { printEquivalentCommandToDelete(lastDn, true); equivalentCommandWithControlPrinted = true; } getProgressDialog().setSummary( Message.raw( Utilities.applyFont( INFO_CTRL_PANEL_DELETING_ENTRY_SUMMARY.get( lastDn.toString()).toString(), ColorAndFontConstants.defaultFont))); } }); // Use a copy of the dir context since we are using an specific // control to delete the subtree and this can cause // synchronization problems when the tree is refreshed. InitialLdapContext ctx1 = null; try { ctx1 = ConnectionUtils.cloneInitialLdapContext(ctx, getInfo().getConnectTimeout(), getInfo().getTrustManager(), null); Control[] ctls = { new BasicControl(ServerConstants.OID_SUBTREE_DELETE_CONTROL)}; ctx1.setRequestControls(ctls); ctx1.destroySubcontext(Utilities.getJNDIName(dn.toString())); } finally { try { ctx1.close(); } catch (Throwable th) { } } nDeleted ++; lastProgressTime = t; if (path != null) { toNotify.add(controller.getNodeInfoFromPath(path)); } final Collection<BrowserNodeInfo> fToNotify; if (toNotify.size() > 0) { fToNotify = new ArrayList<BrowserNodeInfo>(toNotify); toNotify.clear(); } else { fToNotify = null; } SwingUtilities.invokeLater(new Runnable() { public void run() { getProgressDialog().getProgressBar().setIndeterminate(false); getProgressDialog().getProgressBar().setValue( (100 * nDeleted) / nToDelete); if (fToNotify != null) { notifyEntriesDeleted(fToNotify); } } }); } /** * Prints in the progress dialog the equivalent command-line to delete a * subtree. * @param dn the DN of the subtree to be deleted. * @param usingControl whether we must include the control or not. */ private void printEquivalentCommandToDelete(DN dn, boolean usingControl) { ArrayList<String> args = new ArrayList<String>(); args.addAll(getObfuscatedCommandLineArguments( getConnectionCommandLineArguments(useAdminCtx, true))); args.add(getNoPropertiesFileArgument()); if (usingControl) { args.add("-J"); args.add(ServerConstants.OID_SUBTREE_DELETE_CONTROL); } args.add(dn.toString()); printEquivalentCommandLine(getCommandLinePath("ldapdelete"), args, INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_DELETE_ENTRY.get(dn.toString())); } }