From b866af74208f6458de5e9090acb3145ea74083ed Mon Sep 17 00:00:00 2001 From: Noriko Hosoi Date: Thu, 16 Oct 2014 14:07:27 -0700 Subject: [PATCH] Ticket #47380 - RFE: Winsync loses connection with AD objects when they move from the console. Description: Previously, "move" operation was implemented as the combination of "cut" and "paste", which was internally, "delete" and "add". In the windows sync context, delete and add on one side does not fully recover the other side. This patch replaces "cut" and "pasted" with "move" and "movepaste". By "move", it puts selected DN(s) into DN clipboard. If the DN clipboard is not empty, "movepaste" is enabled, moving the cursor, selecting the new superior, then choosing movepaste, the entries in the DN clipboard are moved to the new superior by modrdn. The movepaste is restlicted to one time operation, that is, once the move was done, the DN clipboard is cleaned up and movepaste in the menu is grayed out. In terms of windows sync, since the modrdn operation is sent and replayed on the AD, the results on DS and AD are in sync. Note: At the "move" phase, you could choose multiple entries and move them into the same destination. --- .../admin/dirserv/ContentMenuController.java | 66 +++-- src/com/netscape/admin/dirserv/DSBaseModel.java | 2 + src/com/netscape/admin/dirserv/DSContentPage.java | 306 +++++++++++++++++++-- .../netscape/admin/dirserv/DSResourceModel.java | 6 +- src/com/netscape/admin/dirserv/dirserv.properties | 4 + 5 files changed, 331 insertions(+), 53 deletions(-) diff --git a/src/com/netscape/admin/dirserv/ContentMenuController.java b/src/com/netscape/admin/dirserv/ContentMenuController.java index e89841f..9a9121b 100644 --- a/src/com/netscape/admin/dirserv/ContentMenuController.java +++ b/src/com/netscape/admin/dirserv/ContentMenuController.java @@ -91,12 +91,13 @@ public class ContentMenuController { * Enables/disables the menu items, according to which entry is selected. * It assumes that an object is selected in the tree. */ - public void updateMenuState() { + public void updateMenuState() { boolean isRootSelected = _contentPage.isRootSelected(); boolean isSelectedNodeRemote = _contentPage.isSelectedNodeRemote(); boolean isSuffixSelected = _contentPage.isSelectedNodeSuffix(); - boolean isClipboardEmpty = _contentPage.isClipboardEmpty(); - Integer selectionActivationState = new Integer(AccountInactivation.CANNOT_BE_ACTIVATED_INACTIVATED); + boolean isClipboardEmpty = _contentPage.isClipboardEmpty(); + boolean isDnInClipboardEmpty = _contentPage.isDnInClipboardEmpty(); // used by move + Integer selectionActivationState = new Integer(AccountInactivation.CANNOT_BE_ACTIVATED_INACTIVATED); Integer selectionVlvState = new Integer(CreateVLVIndex.CAN_NOT_HAVE_INDEX); Integer selectionPWPState = new Integer(PasswordPolicyPanel.NO_PWP); @@ -110,9 +111,10 @@ public class ContentMenuController { updateAciMenuItems(isRootSelected); updateRoleMenuItems(isRootSelected); updateReferralMenuItems(isRootSelected); - updateCutCopyPasteMenuItems(isRootSelected, + updateMoveCopyPasteMenuItems(isRootSelected, isSuffixSelected, - isClipboardEmpty); + isClipboardEmpty, + isDnInClipboardEmpty); updateSearchMenuItems(isRootSelected); updateActivateInactivateMenuItems(selectionActivationState, isSelectedNodeRemote); updateVLVMenuItems(selectionVlvState, isSelectedNodeRemote); @@ -232,10 +234,10 @@ public class ContentMenuController { _resource.getString("menu", "inactivate-description"), _listener), new MenuItemSeparator(), - new MenuItemText( CUT, - _resource.getString("menu", "EditCut"), + new MenuItemText( MOVE, + _resource.getString("menu", "EditMove"), _resource.getString("menu", - "EditCut-description"), + "EditMove"), _listener), new MenuItemText( COPY, _resource.getString("menu", "EditCopy"), @@ -247,6 +249,11 @@ public class ContentMenuController { _resource.getString("menu", "EditPaste-description"), _listener), + new MenuItemText( MOVEPASTE, + _resource.getString("menu", "EditMovePaste"), + _resource.getString("menu", + "EditMovePaste-description"), + _listener), new MenuItemText( DELETE, _resource.getString("menu", "EditDelete"), _resource.getString("menu", @@ -257,8 +264,8 @@ public class ContentMenuController { _resource.getString("menu", "refresh"), _resource.getString("menu", "refresh-description"), - _listener), - }; + _listener), + }; addShortCuts(_contextMenuItems); setActionCommand(_contextMenuItems); } @@ -453,10 +460,10 @@ public class ContentMenuController { private void createEditMenuItems() { _editMenuItems = new IMenuItem[] { new MenuItemSeparator(), - new MenuItemText( CUT, - _resource.getString("menu", "EditCut"), + new MenuItemText( MOVE, + _resource.getString("menu", "EditMove"), _resource.getString("menu", - "EditCut-description"), + "EditMove-description"), _listener), new MenuItemText( COPY, _resource.getString("menu", "EditCopy"), @@ -468,6 +475,11 @@ public class ContentMenuController { _resource.getString("menu", "EditPaste-description"), _listener), + new MenuItemText( + _resource.getString("menu", "EditMovePaste"), + _resource.getString("menu", + "EditMovePaste-description"), + _listener), new MenuItemText( DELETE, _resource.getString("menu", "EditDelete"), _resource.getString("menu", @@ -1000,24 +1012,28 @@ public class ContentMenuController { setEnabledMenuItem(_contextMenuItems, SET_REFERRALS, !isRootSelected); } - private void updateCutCopyPasteMenuItems(boolean isRootSelected, - boolean isSuffixSelected, - boolean isClipboardEmpty) { + private void updateMoveCopyPasteMenuItems(boolean isRootSelected, + boolean isSuffixSelected, + boolean isClipboardEmpty, + boolean isDnInClipboardEmpty) { setEnabledMenuItem(_editMenuItems, COPY, (!isRootSelected && !isSuffixSelected)); setEnabledMenuItem(_contextMenuItems, COPY, (!isRootSelected && !isSuffixSelected)); - setEnabledMenuItem(_editMenuItems, CUT, (!isRootSelected && !isSuffixSelected)); - setEnabledMenuItem(_contextMenuItems, CUT, (!isRootSelected && !isSuffixSelected)); + setEnabledMenuItem(_editMenuItems, MOVE, (!isRootSelected && !isSuffixSelected)); + setEnabledMenuItem(_contextMenuItems, MOVE, (!isRootSelected && !isSuffixSelected)); setEnabledMenuItem(_editMenuItems, DELETE, (!isRootSelected && !isSuffixSelected)); setEnabledMenuItem(_contextMenuItems, DELETE, (!isRootSelected && !isSuffixSelected)); - setEnabledMenuItem(_editMenuItems, COPYDN, !isRootSelected); - setEnabledMenuItem(_editMenuItems, COPYLDAPURL, !isRootSelected); - + setEnabledMenuItem(_editMenuItems, COPYDN, !isRootSelected); + setEnabledMenuItem(_editMenuItems, COPYLDAPURL, !isRootSelected); + setEnabledMenuItem(_editMenuItems, PASTE, !isRootSelected && !isClipboardEmpty); - setEnabledMenuItem(_contextMenuItems, PASTE, !isRootSelected && !isClipboardEmpty); + setEnabledMenuItem(_contextMenuItems, PASTE, !isRootSelected && !isClipboardEmpty); + + setEnabledMenuItem(_editMenuItems, MOVEPASTE, !isRootSelected && !isDnInClipboardEmpty); + setEnabledMenuItem(_contextMenuItems, MOVEPASTE, !isRootSelected && !isDnInClipboardEmpty); } private void updatePWPMenuItems(Integer selectionPWPState, @@ -1136,8 +1152,9 @@ public class ContentMenuController { //_shortCutTable.put(REFRESHTREE, KeyStroke.getKeyStroke(KeyEvent.VK_R, Event.CTRL_MASK)); _shortCutTable.put(REFRESHNODE, KeyStroke.getKeyStroke(KeyEvent.VK_R, Event.CTRL_MASK)); _shortCutTable.put(COPY, KeyStroke.getKeyStroke(KeyEvent.VK_C, Event.CTRL_MASK)); - _shortCutTable.put(CUT, KeyStroke.getKeyStroke(KeyEvent.VK_X, Event.CTRL_MASK)); + _shortCutTable.put(MOVE, KeyStroke.getKeyStroke(KeyEvent.VK_M, Event.CTRL_MASK)); _shortCutTable.put(PASTE, KeyStroke.getKeyStroke(KeyEvent.VK_V, Event.CTRL_MASK)); + _shortCutTable.put(MOVEPASTE, KeyStroke.getKeyStroke(KeyEvent.VK_W, Event.CTRL_MASK)); _shortCutTable.put(DELETE, KeyStroke.getKeyStroke(KeyEvent.VK_D, Event.CTRL_MASK)); } @@ -1190,9 +1207,10 @@ public class ContentMenuController { static final String ROLES = "roles"; static final String SET_REFERRALS = "referral"; static final String AUTHENTICATE = "authenticate"; - static final String CUT = "cut"; + static final String MOVE = "move"; static final String COPY = "copy"; static final String PASTE = "paste"; + static final String MOVEPASTE = "movepaste"; static final String UNDO = "undo"; static final String DELETE = "delete"; static final String NEW_USER = "newuser"; diff --git a/src/com/netscape/admin/dirserv/DSBaseModel.java b/src/com/netscape/admin/dirserv/DSBaseModel.java index 1e719d9..bff044c 100644 --- a/src/com/netscape/admin/dirserv/DSBaseModel.java +++ b/src/com/netscape/admin/dirserv/DSBaseModel.java @@ -455,8 +455,10 @@ abstract public class DSBaseModel extends ResourceModel static final String ROLES = "roles"; static final String AUTHENTICATE = "authenticate"; static final String CUT = "cut"; + static final String MOVE = "move"; static final String COPY = "copy"; static final String PASTE = "paste"; + static final String MOVEPASTE = "movepaste"; static final String UNDO = "undo"; static final String DELETE = "delete"; static final String NEW_USER = "newuser"; diff --git a/src/com/netscape/admin/dirserv/DSContentPage.java b/src/com/netscape/admin/dirserv/DSContentPage.java index bb6de7b..a28df74 100644 --- a/src/com/netscape/admin/dirserv/DSContentPage.java +++ b/src/com/netscape/admin/dirserv/DSContentPage.java @@ -71,7 +71,6 @@ implements IPage, } - /** * IPage implementation * ==================== @@ -438,18 +437,23 @@ implements IPage, } return isSelectedNodeSuffix; } - /** - * Implements IContentPageInfo - */ + + /** + * Implements IContentPageInfo + */ public boolean isClipboardEmpty() { return (_clipboard.isEmpty()); } - /** - * Implements IContentPageInfo - * - * Should not be called if the node is remote - */ + public boolean isDnInClipboardEmpty() { + return (_clipboard.isDnEmpty()); + } + + /** + * Implements IContentPageInfo + * + * Should not be called if the node is remote + */ public Integer getSelectionVlvState() { IBrowserNodeInfo node = getSelectedNodeInfo(); return getNodeVlvState(node); @@ -803,19 +807,22 @@ implements IPage, actionAuthenticate(); } else if( cmd.equals( ContentMenuController.COPY ) ) { - actionCopy( ); + actionCopy(); - } else if( cmd.equals( ContentMenuController.PASTE ) ) { - actionPaste( ); + } else if( cmd.equals( ContentMenuController.PASTE ) ) { + actionPaste(); - } else if( cmd.equals( ContentMenuController.CUT ) ) { - actionCut(); + } else if( cmd.equals( ContentMenuController.MOVEPASTE ) ) { + actionMovePaste(); + + } else if( cmd.equals( ContentMenuController.MOVE ) ) { + actionMove(); } else if( cmd.equals( ContentMenuController.DELETE ) ) { actionDelete(); } else if( cmd.equals( ContentMenuController.COPYDN ) ) { - actionCopyDN( ); + actionCopyDN(false); } else if( cmd.equals( ContentMenuController.COPYLDAPURL ) ) { actionCopyLDAPURL( ); @@ -1636,7 +1643,7 @@ implements IPage, } } - private void actionCopyDN() { + private void actionCopyDN(boolean move) { IBrowserNodeInfo node = getSelectedNodeInfo(); if (node != null) { if (continueOperation(node)) { @@ -1645,6 +1652,9 @@ implements IPage, Toolkit.getDefaultToolkit().getSystemClipboard().setContents( ss, ss ); + if (move) { + _clipboard.putDn(dn); + } } } } @@ -1659,6 +1669,11 @@ implements IPage, } } + private void actionMove() { + actionCopyDN(true); + _menuController.updateMenuState(); + } + private void actionPaste() { IBrowserNodeInfo node = getSelectedNodeInfo(); if (node != null) { @@ -1709,6 +1724,57 @@ implements IPage, } } + private void actionMovePaste() { + Debug.println("DSContentPage.actionMovePaste()"); + IBrowserNodeInfo node = getSelectedNodeInfo(); + if (node != null) { + if (continueOperation(node)) { + LDAPConnection ldc = null; + try { + ldc = getConnectionForNode(node); + } catch (LDAPException e) { + displayConnectionError(e, node); + } + if (ldc != null) { + String dn = Helper.getNodeInfoDN(node); + /* Do the paste */ + LDAPConnection ldcNr = prepareReferralConnection(ldc); + MovePaster movePaster = new MovePaster(ldcNr, dn, _framework, _clipboard); + movePaster.execute(); + + /* Update the tree */ + Vector addedRootEntries = movePaster.getMovedRootEntries(); + if (addedRootEntries != null) { + if (!_layout.equals(ContentMenuController.NODE_LEAF_LAYOUT) || + (_lastFocusComponent == _tree)) { + Enumeration e = addedRootEntries.elements(); + while (e.hasMoreElements()) { + String pastedDn = (String)e.nextElement(); + _controller.notifyEntryAdded(node, pastedDn); + if (_layout.equals(ContentMenuController.NODE_LEAF_LAYOUT)) { + _childrenController.notifyEntryAdded(pastedDn); + } + } + } else if (_lastFocusComponent == _list) { + _childrenController.notifyEntryChanged(node); + TreePath path = getLastSelectedPath(); + if (path != null) { + IBrowserNodeInfo treeNode = _controller.getNodeInfoFromPath(path); + if (treeNode != null) { + _controller.notifyChildEntryChanged(treeNode, dn); + } + } + } + } + try { + ldcNr.disconnect(); // prepareReferralConnection clones connection + } catch (Exception ignore) {} + _connectionPool.releaseConnection(ldc); + } + } + } + } + private void actionCut() { IBrowserNodeInfo node = getSelectedNodeInfo(); if (node != null) { @@ -1915,14 +1981,14 @@ implements IPage, String ugDn; if (ugLdc.getHost().equals(aciLdc.getHost()) && (ugLdc.getPort() == aciLdc.getPort())) { - Debug.println("ContentModel.actionACL: ACI and users are on the same directory"); + Debug.println("ContentPage.actionACL: ACI and users are on the same directory"); ugDn = Console.getConsoleInfo().getUserBaseDN(); } else { - Debug.println("ContentModel.actionACL: ACI and users are on different directories"); + Debug.println("ContentPage.actionACL: ACI and users are on different directories"); ugDn = _databaseConfig.getRootSuffixForEntry(dn); } - Debug.println("ContentModel.actionACL: users will be search from " + ugDn); + Debug.println("ContentPage.actionACL: users will be search from " + ugDn); ACIManager acm = new ACIManager(_framework, dn, aciLdc, dn, aciLdc, ugDn); acm.show(); try { @@ -2746,7 +2812,7 @@ implements IPage, _controller.setLDAPConnection(_framework.getServerObject().getServerInfo().getLDAPConnection()); actionRefreshTree(); } else { - Debug.println("ContentModel.actionAuthenticate(): could not reauthenticate"); + Debug.println("ContentPage.actionAuthenticate(): could not reauthenticate"); } } @@ -4473,11 +4539,178 @@ class Paster implements Runnable, ActionListener { } } +class MovePaster implements Runnable, ActionListener { + boolean _isCancelled = false; + + JFrame _frame; + GenericProgressDialog _progressDialog; + + Clipboard _clipboard; + + Vector _pastedRootEntries; + + int _numberMovedObjects = 0; + + String _startDn; + + LDAPConnection _ldc; + + public MovePaster(LDAPConnection ldc, String startDn, JFrame frame, Clipboard clipboard) { + _frame = frame; + _ldc = ldc; + _startDn = startDn; + _clipboard = clipboard; + } + + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals(GenericProgressDialog.CANCEL)) { + _isCancelled = true; + _progressDialog.disableCancelButton(); + } else if (e.getActionCommand().equals(GenericProgressDialog.CLOSE)) { + _progressDialog.closeCallBack(); + } + } + + public void execute() { + if (_startDn.equals("") && + (_clipboard.getSize() > 0)) { + /* Can't paste in the root */ + DSUtil.showErrorDialog( _frame, + "paste-in-root-error-title", + "paste-in-root-error-msg", + (String[]) null, + "browser" ); + } else { + createPasteProgressDialog(); + Thread thread = new Thread(MovePaster.this); + thread.start(); + _progressDialog.packAndShow(); + } + } + + public void run() { + _pastedRootEntries = new Vector(); + boolean pasteOk = movePasteObjs(_ldc, _startDn, _pastedRootEntries); + updateProgressDialogForEnd(pasteOk); + } + + public Vector getMovedRootEntries() { + return _pastedRootEntries; + } + + /* We make the assumption that the clipboard is a LIFO list */ + /* If pastedRootEntries has elements, the moved entreis are refreshed on the console page. */ + private boolean movePasteObjs(LDAPConnection ldc, + String baseDN, + Vector pastedRootEntries) { + Vector triedToPasteRootEntries = new Vector(); + boolean pasteOk = true; + for (int i = 0 ; (i < _clipboard.getDnSize()) && !_isCancelled; i++) { + String currentDn = _clipboard.getDnAt(i); + String movedDn = getDnToMove(currentDn, baseDN, triedToPasteRootEntries); + if ((null == movedDn) || movedDn.equals("")) { + continue; + } + try { + // rdn remains the same, just move to the new parent. + String[] rdns = LDAPDN.explodeDN(currentDn, false); + Debug.println("MovePaster.rename " + currentDn + " newrdn: " + rdns[0] + " to " + baseDN); + ldc.rename(currentDn, rdns[0], baseDN, false); + + if ((_numberMovedObjects % 5) == 0) { + String[] arg = {DSUtil.abreviateString(currentDn, 45)}; + String msg = DSUtil._resource.getString("browser", + "pasting-object-label", + arg); + _progressDialog.setTextInLabel(msg); + } + + _numberMovedObjects++; + DN lastTriedDN = (DN)triedToPasteRootEntries.elementAt(triedToPasteRootEntries.size() - 1); + DN movedDN = new DN(movedDn); + if (lastTriedDN.equals(movedDN)) { + pastedRootEntries.addElement(movedDn); + } + } catch (LDAPException lde) { + pasteOk = false; + Debug.println("MovePaster.movePasteObjs: " + "error pasting entry=" + movedDn + + ":" + lde); + _progressDialog.appendTextToTextArea(movedDn+": "+lde+"\n"); + } + } + _clipboard.cleanDn(); + + return pasteOk; + } + + private String getDnToMove(String dn, String baseDn, Vector triedToPasteRootEntries) { + /* We calculate the new dn of the entry, and we take into account that we may be adding + a tree, so we check if we have added the parent entry for this entry to calculate the + new dn. This dn is composed of the rdn of the entry relative to the old parent and + the dn of the new parent (baseDn). + This method updates the list of subtree roots that have been added. The last added + root is put at the end of the list. + */ + DN entryDN = new DN(dn); + String[] rdns = LDAPDN.explodeDN( dn, false ); + String newDn = ""; + boolean pastedRootFound = false; + for (int i=0; (i 0) ) { try { ldc.modify( dn, mods, (LDAPConstraints)cons ); - if ( DSUtil.requiresRestart ( dn, mods )) { - DSUtil.showInformationDialog( _frame, - "requires-restart", (String)null ); - } + if ( DSUtil.requiresRestart ( dn, mods )) { + DSUtil.showInformationDialog( _frame, "requires-restart", (String)null ); + } } catch ( LDAPException e ) { Debug.println( "DSContentPage$EntryEditor.saveChanges Modifying " + dn + ", " + e ); /* Allow reauthenticating on insufficient privileges */ @@ -5315,6 +5568,7 @@ interface IContentPageInfo { public boolean isSelectedNodeRemote(); public boolean isSelectedNodeSuffix(); public boolean isClipboardEmpty(); + public boolean isDnInClipboardEmpty(); public boolean isSorted(); public Integer getSelectionActivationState(); public Integer getSelectionVlvState(); diff --git a/src/com/netscape/admin/dirserv/DSResourceModel.java b/src/com/netscape/admin/dirserv/DSResourceModel.java index da5358d..1b46355 100644 --- a/src/com/netscape/admin/dirserv/DSResourceModel.java +++ b/src/com/netscape/admin/dirserv/DSResourceModel.java @@ -130,10 +130,10 @@ public class DSResourceModel extends DSBaseModel if (_menuEdit == null) { _menuEdit = new IMenuItem[] { /* - new MenuItemText( CUT, - _resource.getString("menu", "EditCut"), + new MenuItemText( MOVE, + _resource.getString("menu", "EditMove"), _resource.getString("menu", - "EditCut-description")), + "EditMove-description")), new MenuItemText( COPY, _resource.getString("menu", "EditCopy"), _resource.getString("menu", diff --git a/src/com/netscape/admin/dirserv/dirserv.properties b/src/com/netscape/admin/dirserv/dirserv.properties index 2cfcdf1..65b6f17 100644 --- a/src/com/netscape/admin/dirserv/dirserv.properties +++ b/src/com/netscape/admin/dirserv/dirserv.properties @@ -47,6 +47,8 @@ menu-EditUndo=&Undo menu-EditUndo-description=Undo the last change menu-EditCut=C&ut menu-EditCut-description=Remove object and move it to the clipboard +menu-EditMove=M&ove +menu-EditMove-description=Move it to the clipboard menu-EditCopy=Cop&y menu-EditCopy-description=Copy object to the clipboard menu-EditCopyDN=Copy D&N @@ -55,6 +57,8 @@ menu-EditCopyLDAPURL=Copy &LDAP URL menu-EditCopyLDAPURL-description=Copy LDAP URL of object to the clipboard menu-EditPaste=Pas&te menu-EditPaste-description=Paste object from clipboard +menu-EditMovePaste=MovePas&te +menu-EditMovePaste-description=Move object from clipboard menu-EditDelete=&Delete menu-EditDelete-description=Delete object menu-EditNew=&New -- 1.9.3