/* * 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. */ 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.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.BasicAttribute; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import javax.naming.ldap.InitialLdapContext; import javax.swing.SwingUtilities; import javax.swing.tree.TreePath; 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.CannotRenameException; 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.StatusGenericPanel; import org.opends.guitools.controlpanel.ui.ViewEntryPanel; import org.opends.guitools.controlpanel.ui.nodes.BasicNode; import org.opends.guitools.controlpanel.util.Utilities; import org.opends.messages.AdminToolMessages; import org.opends.messages.Message; import org.opends.server.config.ConfigConstants; import org.opends.server.core.DirectoryServer; import org.opends.server.types.*; /** * The task that is called when we must modify an entry. * */ public class ModifyEntryTask extends Task { private Set<String> backendSet; private boolean mustRename; private boolean hasModifications; private CustomSearchResult oldEntry; private DN oldDn; private ArrayList<ModificationItem> modifications; private ModificationItem passwordModification; private Entry newEntry; private BrowserController controller; private TreePath treePath; private boolean useAdminCtx = false; /** * Constructor of the task. * @param info the control panel information. * @param dlg the progress dialog where the task progress will be displayed. * @param newEntry the entry containing the new values. * @param oldEntry the old entry as we retrieved using JNDI. * @param controller the BrowserController. * @param path the TreePath corresponding to the node in the tree that we * want to modify. */ public ModifyEntryTask(ControlPanelInfo info, ProgressDialog dlg, Entry newEntry, CustomSearchResult oldEntry, BrowserController controller, TreePath path) { super(info, dlg); backendSet = new HashSet<String>(); this.oldEntry = oldEntry; this.newEntry = newEntry; this.controller = controller; this.treePath = path; DN newDn = newEntry.getDN(); try { oldDn = DN.decode(oldEntry.getDN()); for (BackendDescriptor backend : info.getServerDescriptor().getBackends()) { for (BaseDNDescriptor baseDN : backend.getBaseDns()) { if (newDn.isDescendantOf(baseDN.getDn()) || oldDn.isDescendantOf(baseDN.getDn())) { backendSet.add(backend.getBackendID()); } } } mustRename = !newDn.equals(oldDn); } catch (OpenDsException ode) { throw new RuntimeException("Could not parse DN: "+oldEntry.getDN(), ode); } modifications = getModifications(newEntry, oldEntry, getInfo()); // Find password modifications for (ModificationItem mod : modifications) { if (mod.getAttribute().getID().equalsIgnoreCase("userPassword")) { passwordModification = mod; break; } } if (passwordModification != null) { modifications.remove(passwordModification); } hasModifications = modifications.size() > 0 || !oldDn.equals(newEntry.getDN()) || (passwordModification != null); } /** * Tells whether there actually modifications on the entry. * @return <CODE>true</CODE> if there are modifications and <CODE>false</CODE> * otherwise. */ public boolean hasModifications() { return hasModifications; } /** * {@inheritDoc} */ public Type getType() { return Type.MODIFY_ENTRY; } /** * {@inheritDoc} */ public Set<String> getBackends() { return backendSet; } /** * {@inheritDoc} */ public Message getTaskDescription() { return INFO_CTRL_PANEL_MODIFY_ENTRY_TASK_DESCRIPTION.get(oldEntry.getDN()); } /** * {@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. This is a short operation so the limitation // has not a lot of impact. 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; try { BasicNode node = (BasicNode)treePath.getLastPathComponent(); InitialLdapContext ctx = controller.findConnectionForDisplayedEntry(node); useAdminCtx = controller.isConfigurationNode(node); if (!mustRename) { if (modifications.size() > 0) { ModificationItem[] mods = new ModificationItem[modifications.size()]; modifications.toArray(mods); SwingUtilities.invokeLater(new Runnable() { public void run() { printEquivalentCommandToModify(newEntry.getDN(), modifications, useAdminCtx); getProgressDialog().appendProgressHtml( Utilities.getProgressWithPoints( INFO_CTRL_PANEL_MODIFYING_ENTRY.get(oldEntry.getDN()), ColorAndFontConstants.progressFont)); } }); ctx.modifyAttributes(Utilities.getJNDIName(oldEntry.getDN()), mods); SwingUtilities.invokeLater(new Runnable() { public void run() { getProgressDialog().appendProgressHtml( Utilities.getProgressDone( ColorAndFontConstants.progressFont)); controller.notifyEntryChanged( controller.getNodeInfoFromPath(treePath)); controller.getTree().removeSelectionPath(treePath); controller.getTree().setSelectionPath(treePath); } }); } } else { modifyAndRename(ctx, oldDn, oldEntry, newEntry, modifications); } state = State.FINISHED_SUCCESSFULLY; } catch (Throwable t) { lastException = t; state = State.FINISHED_WITH_ERROR; } } /** * {@inheritDoc} */ public void postOperation() { if ((lastException == null) && (state == State.FINISHED_SUCCESSFULLY) && (passwordModification != null)) { try { Object o = passwordModification.getAttribute().get(); String sPwd; if (o instanceof byte[]) { try { sPwd = new String((byte[])o, "UTF-8"); } catch (Throwable t) { throw new RuntimeException("Unexpected error: "+t, t); } } else { sPwd = String.valueOf(o); } ResetUserPasswordTask newTask = new ResetUserPasswordTask(getInfo(), getProgressDialog(), (BasicNode)treePath.getLastPathComponent(), controller, sPwd.toCharArray()); if ((modifications.size() > 0) || mustRename) { getProgressDialog().appendProgressHtml("<br><br>"); } StatusGenericPanel.launchOperation(newTask, INFO_CTRL_PANEL_RESETTING_USER_PASSWORD_SUMMARY.get(), INFO_CTRL_PANEL_RESETTING_USER_PASSWORD_SUCCESSFUL_SUMMARY.get(), INFO_CTRL_PANEL_RESETTING_USER_PASSWORD_SUCCESSFUL_DETAILS.get(), ERR_CTRL_PANEL_RESETTING_USER_PASSWORD_ERROR_SUMMARY.get(), ERR_CTRL_PANEL_RESETTING_USER_PASSWORD_ERROR_DETAILS.get(), null, getProgressDialog(), false, getInfo()); getProgressDialog().setVisible(true); } catch (NamingException ne) { // This should not happen throw new RuntimeException("Unexpected exception: "+ne, ne); } } } /** * Modifies and renames the entry. * @param ctx the connection to the server. * @param oldDN the oldDN of the entry. * @param originalEntry the original entry. * @param newEntry the new entry. * @param originalMods the original modifications (these are required since * we might want to update them). * @throws CannotRenameException if we cannot perform the modification. * @throws NamingException if an error performing the modification occurs. */ private void modifyAndRename(DirContext ctx, final DN oldDN, CustomSearchResult originalEntry, final Entry newEntry, final ArrayList<ModificationItem> originalMods) throws CannotRenameException, NamingException { RDN oldRDN = oldDN.getRDN(); RDN newRDN = newEntry.getDN().getRDN(); boolean rdnTypeChanged = newRDN.getNumValues() != oldRDN.getNumValues(); for (int i=0; (i<newRDN.getNumValues()) && !rdnTypeChanged; i++) { boolean found = false; for (int j=0; (j<oldRDN.getNumValues()) && !found; j++) { found = newRDN.getAttributeName(i).equalsIgnoreCase( oldRDN.getAttributeName(j)); } rdnTypeChanged = !found; } if (rdnTypeChanged) { /* Check if user changed the objectclass...*/ boolean changedOc = false; for (ModificationItem mod : originalMods) { Attribute attr = mod.getAttribute(); changedOc = attr.getID().equalsIgnoreCase( ConfigConstants.ATTR_OBJECTCLASS); if (changedOc) { break; } } if (changedOc) { /* See if the original entry contains the new naming attribute(s) if it does we will be able to perform the renaming and then the modifications without problem */ boolean entryContainsRdnTypes = true; for (int i=0; (i<newRDN.getNumValues()) && entryContainsRdnTypes; i++) { List<Object> values = originalEntry.getAttributeValues( newRDN.getAttributeName(i)); entryContainsRdnTypes = !values.isEmpty(); } if (!entryContainsRdnTypes) { throw new CannotRenameException( AdminToolMessages.ERR_CANNOT_MODIFY_OBJECTCLASS_AND_RENAME.get()); } } } SwingUtilities.invokeLater(new Runnable() { public void run() { printEquivalentRenameCommand(oldDN, newEntry.getDN(), useAdminCtx); getProgressDialog().appendProgressHtml( Utilities.getProgressWithPoints( INFO_CTRL_PANEL_RENAMING_ENTRY.get(oldDN.toString(), newEntry.getDN().toString()), ColorAndFontConstants.progressFont)); } }); ctx.rename(Utilities.getJNDIName(oldDn.toString()), Utilities.getJNDIName(newEntry.getDN().toString())); final TreePath[] newPath = {null}; SwingUtilities.invokeLater(new Runnable() { public void run() { getProgressDialog().appendProgressHtml( Utilities.getProgressDone(ColorAndFontConstants.progressFont)); getProgressDialog().appendProgressHtml("<br>"); TreePath parentPath = controller.notifyEntryDeleted( controller.getNodeInfoFromPath(treePath)); newPath[0] = controller.notifyEntryAdded( controller.getNodeInfoFromPath(parentPath), newEntry.getDN().toString()); } }); ModificationItem[] mods = new ModificationItem[originalMods.size()]; originalMods.toArray(mods); if (mods.length > 0) { SwingUtilities.invokeLater(new Runnable() { public void run() { DN dn = newEntry.getDN(); printEquivalentCommandToModify(dn, originalMods, useAdminCtx); getProgressDialog().appendProgressHtml( Utilities.getProgressWithPoints( INFO_CTRL_PANEL_MODIFYING_ENTRY.get(dn.toString()), ColorAndFontConstants.progressFont)); } }); ctx.modifyAttributes(Utilities.getJNDIName(newEntry.getDN().toString()), mods); SwingUtilities.invokeLater(new Runnable() { public void run() { getProgressDialog().appendProgressHtml( Utilities.getProgressDone(ColorAndFontConstants.progressFont)); if (newPath[0] != null) { controller.getTree().setSelectionPath(newPath[0]); } } }); } } /** * Gets the modifications to apply between two entries. * @param newEntry the new entry. * @param oldEntry the old entry. * @param info the ControlPanelInfo, used to retrieve the schema for instance. * @return the modifications to apply between two entries. */ public static ArrayList<ModificationItem> getModifications(Entry newEntry, CustomSearchResult oldEntry, ControlPanelInfo info) { ArrayList<ModificationItem> modifications = new ArrayList<ModificationItem>(); Schema schema = info.getServerDescriptor().getSchema(); List<org.opends.server.types.Attribute> newAttrs = newEntry.getAttributes(); newAttrs.add(newEntry.getObjectClassAttribute()); for (org.opends.server.types.Attribute attr : newAttrs) { String attrName = attr.getNameWithOptions(); if (!ViewEntryPanel.isEditable(attrName, schema)) { continue; } AttributeType attrType = schema.getAttributeType( attr.getName().toLowerCase()); if (attrType == null) { attrType = DirectoryServer.getDefaultAttributeType( attr.getName().toLowerCase()); } List<AttributeValue> newValues = new ArrayList<AttributeValue>(); Iterator<AttributeValue> it = attr.iterator(); while (it.hasNext()) { newValues.add(it.next()); } List<Object> oldValues = oldEntry.getAttributeValues(attrName); boolean isAttributeInNewRdn = false; AttributeValue rdnValue = null; RDN rdn = newEntry.getDN().getRDN(); for (int i=0; i<rdn.getNumValues() && !isAttributeInNewRdn; i++) { isAttributeInNewRdn = rdn.getAttributeName(i).equalsIgnoreCase(attrName); if (isAttributeInNewRdn) { rdnValue = rdn.getAttributeValue(i); } } /* Check the attributes of the old DN. If we are renaming them they * will be deleted. Check that they are on the new entry but not in * the new RDN. If it is the case we must add them after the renaming. */ AttributeValue oldRdnValueToAdd = null; /* Check the value in the RDN that will be deleted. If the value was * on the previous RDN but not in the new entry it will be deleted. So * we must avoid to include it as a delete modification in the * modifications. */ AttributeValue oldRdnValueDeleted = null; RDN oldRDN = null; try { oldRDN = DN.decode(oldEntry.getDN()).getRDN(); } catch (DirectoryException de) { throw new RuntimeException("Unexpected error parsing DN: "+ oldEntry.getDN(), de); } for (int i=0; i<oldRDN.getNumValues(); i++) { if (oldRDN.getAttributeName(i).equalsIgnoreCase(attrName)) { AttributeValue value = oldRDN.getAttributeValue(i); boolean containsValue = false; it = attr.iterator(); while (it.hasNext()) { if (value.equals(it.next())) { containsValue = true; break; } } if (containsValue) { if ((rdnValue == null) || !rdnValue.equals(value)) { oldRdnValueToAdd = value; } } else { oldRdnValueDeleted = value; } break; } } if (oldValues == null) { Set<AttributeValue> vs = new HashSet<AttributeValue>(); vs.addAll(newValues); if (rdnValue != null) { vs.remove(rdnValue); } if (vs.size() > 0) { modifications.add(new ModificationItem( DirContext.ADD_ATTRIBUTE, createAttribute(attrName, newValues))); } } else { List<AttributeValue> toDelete = getValuesToDelete(oldValues, newValues, attrType); if (oldRdnValueDeleted != null) { toDelete.remove(oldRdnValueDeleted); } List<AttributeValue> toAdd = getValuesToAdd(oldValues, newValues, attrType); if (oldRdnValueToAdd != null) { toAdd.add(oldRdnValueToAdd); } if ((toDelete.size() + toAdd.size() >= newValues.size()) && !isAttributeInNewRdn) { modifications.add(new ModificationItem( DirContext.REPLACE_ATTRIBUTE, createAttribute(attrName, newValues))); } else { if (toDelete.size() > 0) { modifications.add(new ModificationItem( DirContext.REMOVE_ATTRIBUTE, createAttribute(attrName, toDelete))); } if (toAdd.size() > 0) { List<AttributeValue> vs = new ArrayList<AttributeValue>(); vs.addAll(toAdd); if (rdnValue != null) { vs.remove(rdnValue); } if (vs.size() > 0) { modifications.add(new ModificationItem( DirContext.ADD_ATTRIBUTE, createAttribute(attrName, vs))); } } } } } /* Check if there are attributes to delete */ for (String attrName : oldEntry.getAttributeNames()) { if (!ViewEntryPanel.isEditable(attrName, schema)) { continue; } List<Object> oldValues = oldEntry.getAttributeValues(attrName); String attrNoOptions = Utilities.getAttributeNameWithoutOptions(attrName).toLowerCase(); List<org.opends.server.types.Attribute> attrs = newEntry.getAttribute(attrNoOptions); boolean found = false; if (attrs != null) { for (org.opends.server.types.Attribute attr : attrs) { if (attr.getNameWithOptions().equalsIgnoreCase(attrName)) { found = true; break; } } } if (!found && (oldValues.size() > 0)) { modifications.add(new ModificationItem( DirContext.REMOVE_ATTRIBUTE, new BasicAttribute(attrName))); } } return modifications; } /** * Creates a JNDI attribute using an attribute name and a set of values. * @param attrName the attribute name. * @param values the values. * @return a JNDI attribute using an attribute name and a set of values. */ private static Attribute createAttribute(String attrName, List<AttributeValue> values) { Attribute attribute = new BasicAttribute(attrName); for (AttributeValue value : values) { attribute.add(value.getValue().toByteArray()); } return attribute; } /** * Creates an AttributeValue for an attribute and a value (the one we got * using JNDI). * @param attrType the attribute type. * @param value the value found using JNDI. * @return an AttributeValue object. */ private static AttributeValue createAttributeValue(AttributeType attrType, Object value) { ByteString v; if (value instanceof String) { v = ByteString.valueOf((String)value); } else if (value instanceof byte[]) { v = ByteString.wrap((byte[])value); } else { v = ByteString.valueOf(String.valueOf(value)); } return AttributeValues.create(attrType, v); } /** * Returns the set of AttributeValue that must be deleted. * @param oldValues the old values of the entry. * @param newValues the new values of the entry. * @param attrType the attribute type. * @return the set of AttributeValue that must be deleted. */ private static List<AttributeValue> getValuesToDelete(List<Object> oldValues, List<AttributeValue> newValues, AttributeType attrType) { List<AttributeValue> valuesToDelete = new ArrayList<AttributeValue>(); for (Object o : oldValues) { AttributeValue oldValue = createAttributeValue(attrType, o); if (!newValues.contains(oldValue)) { valuesToDelete.add(oldValue); } } return valuesToDelete; } /** * Returns the set of AttributeValue that must be added. * @param oldValues the old values of the entry. * @param newValues the new values of the entry. * @param attrType the attribute type. * @return the set of AttributeValue that must be added. */ private static List<AttributeValue> getValuesToAdd(List<Object> oldValues, List<AttributeValue> newValues, AttributeType attrType) { List<AttributeValue> valuesToAdd = new ArrayList<AttributeValue>(); for (AttributeValue newValue : newValues) { boolean found = false; for (Object o : oldValues) { found = newValue.equals(createAttributeValue(attrType, o)); if (found) { break; } } if (!found) { valuesToAdd.add(newValue); } } return valuesToAdd; } }