directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From seelm...@apache.org
Subject [directory-studio] branch DIRSTUDIO-648-password-modify-extended-op updated: DIRSTUDIO-648: UI implementation and tests for password modify extended operation
Date Sun, 29 Mar 2020 17:44:33 GMT
This is an automated email from the ASF dual-hosted git repository.

seelmann pushed a commit to branch DIRSTUDIO-648-password-modify-extended-op
in repository https://gitbox.apache.org/repos/asf/directory-studio.git


The following commit(s) were added to refs/heads/DIRSTUDIO-648-password-modify-extended-op by this push:
     new 6d68330  DIRSTUDIO-648: UI implementation and tests for password modify extended operation
6d68330 is described below

commit 6d683307e090a3886412b45cfe5331b799512f86
Author: Stefan Seelmann <mail@stefan-seelmann.de>
AuthorDate: Sun Mar 29 19:08:11 2020 +0200

    DIRSTUDIO-648: UI implementation and tests for password modify extended operation
---
 ...directory.studio.eclipse-trgt-platform.template |   2 +-
 .../ui}/dialogs/MessageDialogWithTextarea.java     |   2 +-
 .../core/io/api/DirectoryApiConnectionWrapper.java |   3 +-
 .../ldapbrowser/core/BrowserCoreMessages.java      |   6 +
 .../core/browsercoremessages.properties            |   4 +
 .../core/jobs/ExtendedOperationRunnable.java       | 105 +++
 .../studio/ldapbrowser/core/model/IRootDSE.java    |  10 +
 .../ldapbrowser/core/model/impl/RootDSE.java       |  10 +
 plugins/ldapbrowser.ui/pom-first.xml               |   2 +
 .../PasswordModifyExtendedOperationAction.java     | 130 ++++
 .../ldapbrowser/ui/actions/messages.properties     |   1 +
 .../PasswordModifyExtendedOperationDialog.java     | 390 ++++++++++
 .../ldapbrowser/ui/dialogs/messages.properties     |  10 +
 .../ui/views/browser/BrowserViewActionGroup.java   |  10 +
 .../ui/views/browser/messages.properties           |   1 +
 .../ui/views/browser/messages_de.properties        |   1 +
 .../ui/views/browser/messages_fr.properties        |   1 +
 .../connection/ConnectionViewActionGroup.java      |  11 +
 .../ui/views/connection/messages.properties        |   1 +
 .../ui/views/connection/messages_de.properties     |   1 +
 .../ui/views/connection/messages_fr.properties     |   1 +
 .../view/wizards/MergeSchemasWizard.java           |   2 +-
 .../core/ConnectionWrapperTestBase.java            | 787 -------------------
 .../core/DirectoryApiConnectionWrapperTest.java    | 842 ++++++++++++++++++++-
 .../integration/ui/ExtendedOperationsTest.java     | 228 ++++++
 .../test/integration/ui/NewEntryWizardTest.java    |   2 -
 .../test/integration/ui/bots/BrowserViewBot.java   |   7 +
 .../studio/test/integration/ui/bots/DialogBot.java |  14 +
 .../PasswordModifyExtendedOperationDialogBot.java  | 122 +++
 29 files changed, 1910 insertions(+), 796 deletions(-)

diff --git a/eclipse-trgt-platform/template/org.apache.directory.studio.eclipse-trgt-platform.template b/eclipse-trgt-platform/template/org.apache.directory.studio.eclipse-trgt-platform.template
index ab02274..27748b0 100644
--- a/eclipse-trgt-platform/template/org.apache.directory.studio.eclipse-trgt-platform.template
+++ b/eclipse-trgt-platform/template/org.apache.directory.studio.eclipse-trgt-platform.template
@@ -19,7 +19,7 @@
   @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 -->
 <?pde version="3.8"?>
-<target name="Apache Directory Studio Platform" sequenceNumber="443">
+<target name="Apache Directory Studio Platform" sequenceNumber="445">
   <locations>
 
     <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
diff --git a/plugins/schemaeditor/src/main/java/org/apache/directory/studio/schemaeditor/view/dialogs/MessageDialogWithTextarea.java b/plugins/common.ui/src/main/java/org/apache/directory/studio/common/ui/dialogs/MessageDialogWithTextarea.java
similarity index 97%
rename from plugins/schemaeditor/src/main/java/org/apache/directory/studio/schemaeditor/view/dialogs/MessageDialogWithTextarea.java
rename to plugins/common.ui/src/main/java/org/apache/directory/studio/common/ui/dialogs/MessageDialogWithTextarea.java
index ee89f07..be5c32b 100644
--- a/plugins/schemaeditor/src/main/java/org/apache/directory/studio/schemaeditor/view/dialogs/MessageDialogWithTextarea.java
+++ b/plugins/common.ui/src/main/java/org/apache/directory/studio/common/ui/dialogs/MessageDialogWithTextarea.java
@@ -17,7 +17,7 @@
  *  under the License. 
  *  
  */
-package org.apache.directory.studio.schemaeditor.view.dialogs;
+package org.apache.directory.studio.common.ui.dialogs;
 
 
 import org.eclipse.jface.dialogs.IDialogConstants;
diff --git a/plugins/connection.core/src/main/java/org/apache/directory/studio/connection/core/io/api/DirectoryApiConnectionWrapper.java b/plugins/connection.core/src/main/java/org/apache/directory/studio/connection/core/io/api/DirectoryApiConnectionWrapper.java
index 3a04d24..b7ebdb3 100644
--- a/plugins/connection.core/src/main/java/org/apache/directory/studio/connection/core/io/api/DirectoryApiConnectionWrapper.java
+++ b/plugins/connection.core/src/main/java/org/apache/directory/studio/connection/core/io/api/DirectoryApiConnectionWrapper.java
@@ -1052,12 +1052,11 @@ public class DirectoryApiConnectionWrapper implements ConnectionWrapper
                     exception = e;
                 }
 
+                // TODO: logging?
                 LdapException le = toLdapException( exception );
 
                 for ( ILdapLogger logger : getLdapLoggers() )
                 {
-                    // TODO: logging
-                    // logger.log
                 }
             }
         };
diff --git a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/BrowserCoreMessages.java b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/BrowserCoreMessages.java
index 09f5527..ab0bbde 100644
--- a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/BrowserCoreMessages.java
+++ b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/BrowserCoreMessages.java
@@ -419,6 +419,12 @@ public class BrowserCoreMessages extends NLS
 
     public static String jobs__search_error_n;
 
+    public static String jobs__extended_operation_name;
+
+    public static String jobs__extended_operation_error;
+
+    public static String jobs__extended_operation_task;
+
     public static String model__empty_string_value;
 
     public static String model__empty_binary_value;
diff --git a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/browsercoremessages.properties b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/browsercoremessages.properties
index 77d043f..f764191 100644
--- a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/browsercoremessages.properties
+++ b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/browsercoremessages.properties
@@ -176,6 +176,10 @@ jobs__open_connections_task=Opening connection {0}
 jobs__open_connections_error_1=Error while opening connection
 jobs__open_connections_error_n=Error while opening connections
 
+jobs__extended_operation_name=Extended Operation
+jobs__extended_operation_task=Executing extended operation {0}
+jobs__extended_operation_error=Error while executing extended operation
+
 jobs__export_ldif_name=LDIF Export
 jobs__export_ldif_task=Exporting LDIF
 jobs__export_ldif_error=Error while exporting LDIF
diff --git a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExtendedOperationRunnable.java b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExtendedOperationRunnable.java
new file mode 100644
index 0000000..2110be6
--- /dev/null
+++ b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExtendedOperationRunnable.java
@@ -0,0 +1,105 @@
+/*
+ *  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 org.apache.directory.api.ldap.model.message.ExtendedRequest;
+import org.apache.directory.api.ldap.model.message.ExtendedResponse;
+import org.apache.directory.studio.common.core.jobs.StudioProgressMonitor;
+import org.apache.directory.studio.connection.core.Connection;
+import org.apache.directory.studio.connection.core.Utils;
+import org.apache.directory.studio.connection.core.jobs.StudioConnectionRunnableWithProgress;
+import org.apache.directory.studio.ldapbrowser.core.BrowserCoreMessages;
+import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection;
+
+
+/**
+ * Runnable to execute extended operations.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class ExtendedOperationRunnable implements StudioConnectionRunnableWithProgress
+{
+    private IBrowserConnection connection;
+
+    private ExtendedRequest request;
+
+    private ExtendedResponse response;
+
+
+    public ExtendedOperationRunnable( final IBrowserConnection connection, ExtendedRequest request )
+    {
+        this.connection = connection;
+        this.request = request;
+    }
+
+
+    public Connection[] getConnections()
+    {
+        return new Connection[]
+            { connection.getConnection() };
+    }
+
+
+    public String getName()
+    {
+        return BrowserCoreMessages.jobs__extended_operation_name;
+    }
+
+
+    public Object[] getLockedObjects()
+    {
+        return new Object[]
+            { connection };
+    }
+
+
+    public String getErrorMessage()
+    {
+        return BrowserCoreMessages.jobs__extended_operation_error;
+    }
+
+
+    public void run( StudioProgressMonitor monitor )
+    {
+
+        monitor.beginTask( BrowserCoreMessages.bind( BrowserCoreMessages.jobs__extended_operation_task,
+            new String[]
+            { Utils.getOidDescription( request.getRequestName() ) } ), 2 );
+        monitor.reportProgress( " " ); //$NON-NLS-1$
+        monitor.worked( 1 );
+
+        try
+        {
+            response = connection.getConnection().getConnectionWrapper().extended( request, monitor );
+        }
+        catch ( Exception e )
+        {
+            monitor.reportError( e );
+        }
+    }
+
+
+    public ExtendedResponse getResponse()
+    {
+        return response;
+    }
+}
diff --git a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/model/IRootDSE.java b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/model/IRootDSE.java
index ba48e53..77fe33a 100644
--- a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/model/IRootDSE.java
+++ b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/model/IRootDSE.java
@@ -53,6 +53,16 @@ public interface IRootDSE extends IEntry
 
 
     /**
+     * Checks if extension is supported.
+     * 
+     * @param oid the OID
+     * 
+     * @return true, if extension is supported
+     */
+    boolean isExtensionSupported( String oid );
+
+
+    /**
      * Checks if control is supported.
      * 
      * @param oid the OID
diff --git a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/model/impl/RootDSE.java b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/model/impl/RootDSE.java
index 2425bdb..d67819c 100644
--- a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/model/impl/RootDSE.java
+++ b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/model/impl/RootDSE.java
@@ -127,6 +127,16 @@ public final class RootDSE extends BaseDNEntry implements IRootDSE
 
 
     /**
+     * @see org.apache.directory.studio.ldapbrowser.core.model.IRootDSE#isExtensionSupported(java.lang.String)
+     */
+    public boolean isExtensionSupported( String oid )
+    {
+        String[] supportedExtensions = getSupportedExtensions();
+        return Arrays.asList( supportedExtensions ).contains( oid );
+    }
+
+
+    /**
      * @see org.apache.directory.studio.ldapbrowser.core.model.IRootDSE#isControlSupported(java.lang.String)
      */
     public boolean isControlSupported( String oid )
diff --git a/plugins/ldapbrowser.ui/pom-first.xml b/plugins/ldapbrowser.ui/pom-first.xml
index 46b81b3..4b46973 100644
--- a/plugins/ldapbrowser.ui/pom-first.xml
+++ b/plugins/ldapbrowser.ui/pom-first.xml
@@ -70,6 +70,8 @@
             </Import-Package>
             
             <Require-Bundle>
+ org.apache.directory.api.ldap.codec.core;bundle-version="${org.apache.directory.api.bundleversion}",
+ org.apache.directory.api.ldap.extras.codec.api;bundle-version="${org.apache.directory.api.bundleversion}",
  org.apache.directory.api.ldap.model;bundle-version="${org.apache.directory.api.bundleversion}",
  org.apache.directory.api.util;bundle-version="${org.apache.directory.api.bundleversion}",
  org.apache.directory.studio.common.core,
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/actions/PasswordModifyExtendedOperationAction.java b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/actions/PasswordModifyExtendedOperationAction.java
new file mode 100644
index 0000000..4a9bc0b
--- /dev/null
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/actions/PasswordModifyExtendedOperationAction.java
@@ -0,0 +1,130 @@
+/*
+ *  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.ui.actions;
+
+
+import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyRequest;
+import org.apache.directory.studio.connection.core.Connection;
+import org.apache.directory.studio.ldapbrowser.common.actions.BrowserAction;
+import org.apache.directory.studio.ldapbrowser.core.BrowserCorePlugin;
+import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection;
+import org.apache.directory.studio.ldapbrowser.core.model.IEntry;
+import org.apache.directory.studio.ldapbrowser.ui.dialogs.PasswordModifyExtendedOperationDialog;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.ui.PlatformUI;
+
+
+/**
+ * This Action opens the password modify extended operation dialog.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class PasswordModifyExtendedOperationAction extends BrowserAction
+{
+    public PasswordModifyExtendedOperationAction()
+    {
+        super();
+    }
+
+
+    public void run()
+    {
+        ConnectionAndEntry connectionAndDn = getConnectionAndEntry();
+        PasswordModifyExtendedOperationDialog passwordDialog = new PasswordModifyExtendedOperationDialog(
+            PlatformUI.getWorkbench().getActiveWorkbenchWindow()
+                .getShell(),
+            connectionAndDn.connection, connectionAndDn.entry );
+        passwordDialog.open();
+    }
+
+
+    private ConnectionAndEntry getConnectionAndEntry()
+    {
+        if ( getSelectedEntries().length > 0 )
+        {
+            return new ConnectionAndEntry( getSelectedEntries()[0].getBrowserConnection(),
+                getSelectedEntries()[0] );
+        }
+        else if ( getSelectedSearchResults().length > 0 )
+        {
+            return new ConnectionAndEntry( getSelectedSearchResults()[0].getEntry().getBrowserConnection(),
+                getSelectedSearchResults()[0].getEntry() );
+        }
+        else if ( getSelectedBookmarks().length > 0 )
+        {
+            return new ConnectionAndEntry( getSelectedBookmarks()[0].getEntry().getBrowserConnection(),
+                getSelectedBookmarks()[0].getEntry() );
+        }
+        else if ( getSelectedConnections().length > 0 )
+        {
+            Connection connection = getSelectedConnections()[0];
+            IBrowserConnection browserConnection = BrowserCorePlugin.getDefault().getConnectionManager()
+                .getBrowserConnection( connection );
+            return new ConnectionAndEntry( browserConnection, null );
+        }
+        else if ( getInput() instanceof IBrowserConnection )
+        {
+            return new ConnectionAndEntry( ( IBrowserConnection ) getInput(), null );
+        }
+
+        return null;
+    }
+
+    protected class ConnectionAndEntry
+    {
+        private IBrowserConnection connection;
+        private IEntry entry;
+
+
+        protected ConnectionAndEntry( IBrowserConnection connection, IEntry entry )
+        {
+            this.connection = connection;
+            this.entry = entry;
+        }
+    }
+
+
+    public String getText()
+    {
+        return Messages.getString( "PasswordModifyExtendedOperationAction.Text" ); //$NON-NLS-1$
+    }
+
+
+    public ImageDescriptor getImageDescriptor()
+    {
+        return null;
+    }
+
+
+    public String getCommandId()
+    {
+        return null;
+    }
+
+
+    public boolean isEnabled()
+    {
+        return getConnectionAndEntry() != null
+            && getConnectionAndEntry().connection.getRootDSE()
+                .isExtensionSupported( PasswordModifyRequest.EXTENSION_OID );
+    }
+
+}
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/actions/messages.properties b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/actions/messages.properties
index e341bbc..d848aaa 100644
--- a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/actions/messages.properties
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/actions/messages.properties
@@ -97,3 +97,4 @@ BrowserPasteAction.PasteValues=Paste Values
 BrowserPasteAction.SelectCopyDepth=Select Copy Depth
 PropertiesAction.PropertiesForX=Properties for {0}
 ReloadSchemaAction.ReloadSchema=Reload Schema
+PasswordModifyExtendedOperationAction.Text=Password Modify...
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/dialogs/PasswordModifyExtendedOperationDialog.java b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/dialogs/PasswordModifyExtendedOperationDialog.java
new file mode 100644
index 0000000..76dd65a
--- /dev/null
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/dialogs/PasswordModifyExtendedOperationDialog.java
@@ -0,0 +1,390 @@
+/*
+ *  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.ui.dialogs;
+
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.api.ldap.codec.api.LdapApiService;
+import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
+import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyRequest;
+import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyResponse;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.directory.api.util.Strings;
+import org.apache.directory.studio.common.ui.dialogs.MessageDialogWithTextarea;
+import org.apache.directory.studio.common.ui.widgets.BaseWidgetUtils;
+import org.apache.directory.studio.connection.ui.RunnableContextRunner;
+import org.apache.directory.studio.ldapbrowser.common.widgets.search.EntryWidget;
+import org.apache.directory.studio.ldapbrowser.core.events.EntryModificationEvent;
+import org.apache.directory.studio.ldapbrowser.core.events.EventRegistry;
+import org.apache.directory.studio.ldapbrowser.core.jobs.ExtendedOperationRunnable;
+import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection;
+import org.apache.directory.studio.ldapbrowser.core.model.IEntry;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+
+/**
+` * The PasswordModifyExtendedOperationDialog is used to ask for input for the RFC 3062 LDAP Password Modify Extended Operation.
+ * <pre>
+ * .------------------------------------------------- -.
+ * |        Password Modify Extended Operation         |
+ * +---------------------------------------------------+
+ * | User identity: [                                ] |
+ * |                [ ] Use bind user identity         |
+ * | Old password:  [                                ] |
+ * |                [ ] Old password not available     |
+ * | New password:  [                                ] |
+ * |                [ ] Generate new password          |
+ * |                [ ] Show passwords                 |
+ * |                                                   |
+ * |                                 (Cancel) (  OK  ) |
+ * .___________________________________________________.
+ * </pre>
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class PasswordModifyExtendedOperationDialog extends Dialog
+{
+    private IBrowserConnection connection;
+    private IEntry entry;
+
+    private Dn userIdentity = null;
+    private String oldPassword = StringUtils.EMPTY;
+    private String newPassword = StringUtils.EMPTY;
+
+    private EntryWidget entryWidget;
+    private Button useBindUserIdentityCheckbox;
+    private Text oldPasswordText;
+    private Button noOldPasswordCheckbox;
+    private Text newPasswordText;
+    private Button generateNewPasswordCheckbox;
+    private Button showPasswordsCheckbox;
+
+
+    public PasswordModifyExtendedOperationDialog( Shell parentShell, IBrowserConnection connection, IEntry entry )
+    {
+        super( parentShell );
+        this.connection = connection;
+        this.entry = entry;
+        if ( entry != null )
+        {
+            this.userIdentity = entry.getDn();
+        }
+    }
+
+
+    @Override
+    protected void configureShell( Shell shell )
+    {
+        super.configureShell( shell );
+
+        shell.setText( Messages.getString( "PasswordModifyExtendedOperationDialog.Title" ) ); //$NON-NLS-1$ );
+    }
+
+
+    @Override
+    protected void buttonPressed( int buttonId )
+    {
+        if ( buttonId == IDialogConstants.OK_ID )
+        {
+            userIdentity = entryWidget.getDn();
+            oldPassword = oldPasswordText.getText();
+            newPassword = newPasswordText.getText();
+
+            // Build extended request
+            LdapApiService ldapApiService = LdapApiServiceFactory.getSingleton();
+            PasswordModifyRequest request = ( PasswordModifyRequest ) ldapApiService.getExtendedRequestFactories()
+                .get( PasswordModifyRequest.EXTENSION_OID ).newRequest();
+            if ( !useBindUserIdentityCheckbox.getSelection() )
+            {
+                request.setUserIdentity( Strings.getBytesUtf8( userIdentity.getName() ) );
+            }
+            if ( !noOldPasswordCheckbox.getSelection() )
+            {
+                request.setOldPassword( Strings.getBytesUtf8( oldPassword ) );
+            }
+            if ( !generateNewPasswordCheckbox.getSelection() )
+            {
+                request.setNewPassword( Strings.getBytesUtf8( newPassword ) );
+            }
+            ExtendedOperationRunnable runnable = new ExtendedOperationRunnable( connection, request );
+
+            // Execute extended operations
+            ProgressMonitorDialog dialog = new ProgressMonitorDialog( getShell() );
+            IStatus status = RunnableContextRunner.execute( runnable, dialog, true );
+
+            // Check for error status
+            if ( !status.isOK() )
+            {
+                // Error already handled, don't close dialog
+                return;
+            }
+
+            // Update entry
+            if ( entry != null )
+            {
+                EventRegistry.fireEntryUpdated( new EntryModificationEvent( entry.getBrowserConnection(), entry ),
+                    this );
+            }
+
+            // Show generated password
+            PasswordModifyResponse response = ( PasswordModifyResponse ) runnable.getResponse();
+            if ( response.getGenPassword() != null )
+            {
+                String generatedPassword = Strings.utf8ToString( response.getGenPassword() );
+                new MessageDialogWithTextarea( getShell(),
+                    Messages.getString( "PasswordModifyExtendedOperationDialog.GeneratedPasswordTitle" ),
+                    Messages.getString( "PasswordModifyExtendedOperationDialog.GeneratedPasswordMessage" ),
+                    generatedPassword ).open();
+            }
+
+            // Continue to close dialog
+        }
+        else
+        {
+            userIdentity = null;
+            oldPassword = null;
+            newPassword = null;
+        }
+
+        super.buttonPressed( buttonId );
+    }
+
+    /**
+     * The listener for the "use bind user identity" checkbox
+     */
+    private SelectionAdapter useBindUserIdentityCheckboxListener = new SelectionAdapter()
+    {
+        @Override
+        public void widgetSelected( SelectionEvent event )
+        {
+            if ( useBindUserIdentityCheckbox.getSelection() )
+            {
+                entryWidget.setInput( connection, null );
+                entryWidget.setEnabled( false );
+            }
+            else
+            {
+                entryWidget.setEnabled( true );
+            }
+            validate();
+        }
+    };
+
+    /**
+     * The listener for the "no old password" checkbox
+     */
+    private SelectionAdapter noOldPasswordCheckboxListener = new SelectionAdapter()
+    {
+        @Override
+        public void widgetSelected( SelectionEvent event )
+        {
+            if ( noOldPasswordCheckbox.getSelection() )
+            {
+                oldPasswordText.setText( StringUtils.EMPTY );
+                oldPasswordText.setEnabled( false );
+            }
+            else
+            {
+                oldPasswordText.setEnabled( true );
+            }
+            validate();
+        }
+    };
+    /**
+     * The listener for the "generate new password" checkbox
+     */
+    private SelectionAdapter generateNewPasswordCheckboxListener = new SelectionAdapter()
+    {
+        @Override
+        public void widgetSelected( SelectionEvent event )
+        {
+            if ( generateNewPasswordCheckbox.getSelection() )
+            {
+                newPasswordText.setText( StringUtils.EMPTY );
+                newPasswordText.setEnabled( false );
+            }
+            else
+            {
+                newPasswordText.setEnabled( true );
+            }
+            validate();
+        }
+    };
+
+    /**
+     * The listener for the "show passwords" checkbox
+     */
+    private SelectionAdapter showPasswordsCheckboxListener = new SelectionAdapter()
+    {
+        @Override
+        public void widgetSelected( SelectionEvent event )
+        {
+            if ( showPasswordsCheckbox.getSelection() )
+            {
+                oldPasswordText.setEchoChar( '\0' );
+                newPasswordText.setEchoChar( '\0' );
+            }
+            else
+            {
+                oldPasswordText.setEchoChar( '\u2022' );
+                newPasswordText.setEchoChar( '\u2022' );
+            }
+        }
+    };
+
+
+    @Override
+    protected Control createContents( Composite parent )
+    {
+        Control contents = super.createContents( parent );
+        validate();
+        return contents;
+    }
+
+
+    @Override
+    protected Control createDialogArea( Composite parent )
+    {
+        // Composite
+        Composite composite = new Composite( parent, SWT.NONE );
+        GridLayout layout = new GridLayout( 3, false );
+        layout.marginHeight = convertVerticalDLUsToPixels( IDialogConstants.VERTICAL_MARGIN );
+        layout.marginWidth = convertHorizontalDLUsToPixels( IDialogConstants.HORIZONTAL_MARGIN );
+        layout.verticalSpacing = convertVerticalDLUsToPixels( IDialogConstants.VERTICAL_SPACING );
+        layout.horizontalSpacing = convertHorizontalDLUsToPixels( IDialogConstants.HORIZONTAL_SPACING );
+        composite.setLayout( layout );
+        GridData compositeGridData = new GridData( SWT.FILL, SWT.FILL, true, true );
+        compositeGridData.widthHint = convertHorizontalDLUsToPixels(
+            IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH * 3 / 2 );
+        composite.setLayoutData( compositeGridData );
+
+        // User identity
+        BaseWidgetUtils.createLabel( composite,
+            Messages.getString( "PasswordModifyExtendedOperationDialog.UserIdentity" ), 1 ); //$NON-NLS-1$
+        entryWidget = new EntryWidget( connection, userIdentity );
+        entryWidget.addWidgetModifyListener( event -> validate() );
+        entryWidget.createWidget( composite );
+
+        // Use bind user identity checkbox
+        BaseWidgetUtils.createLabel( composite, StringUtils.EMPTY, 1 );
+        useBindUserIdentityCheckbox = BaseWidgetUtils.createCheckbox( composite,
+            Messages.getString( "PasswordModifyExtendedOperationDialog.UseBindUserIdentity" ), 2 ); //$NON-NLS-1$
+        useBindUserIdentityCheckbox.addSelectionListener( useBindUserIdentityCheckboxListener );
+
+        // Old password text
+        BaseWidgetUtils.createLabel( composite,
+            Messages.getString( "PasswordModifyExtendedOperationDialog.OldPassword" ), 1 ); //$NON-NLS-1$
+        oldPasswordText = BaseWidgetUtils.createText( composite, oldPassword, 2 );
+        oldPasswordText.setEchoChar( '\u2022' );
+        oldPasswordText.addModifyListener( event -> validate() );
+
+        // No old password checkbox
+        BaseWidgetUtils.createLabel( composite, StringUtils.EMPTY, 1 );
+        noOldPasswordCheckbox = BaseWidgetUtils.createCheckbox( composite,
+            Messages.getString( "PasswordModifyExtendedOperationDialog.NoOldPassword" ), 2 ); //$NON-NLS-1$
+        noOldPasswordCheckbox.addSelectionListener( noOldPasswordCheckboxListener );
+
+        // New password text
+        BaseWidgetUtils.createLabel( composite,
+            Messages.getString( "PasswordModifyExtendedOperationDialog.NewPassword" ), 1 ); //$NON-NLS-1$
+        newPasswordText = BaseWidgetUtils.createText( composite, newPassword, 2 );
+        newPasswordText.setEchoChar( '\u2022' );
+        newPasswordText.addModifyListener( event -> validate() );
+
+        // Generate new password checkbox
+        BaseWidgetUtils.createLabel( composite, StringUtils.EMPTY, 1 );
+        generateNewPasswordCheckbox = BaseWidgetUtils.createCheckbox( composite,
+            Messages.getString( "PasswordModifyExtendedOperationDialog.GenerateNewPassword" ), 2 ); //$NON-NLS-1$
+        generateNewPasswordCheckbox.addSelectionListener( generateNewPasswordCheckboxListener );
+
+        // Show password checkbox
+        BaseWidgetUtils.createLabel( composite, StringUtils.EMPTY, 1 );
+        showPasswordsCheckbox = BaseWidgetUtils.createCheckbox( composite,
+            Messages.getString( "PasswordModifyExtendedOperationDialog.ShowPasswords" ), 2 ); //$NON-NLS-1$
+        showPasswordsCheckbox.addSelectionListener( showPasswordsCheckboxListener );
+
+        applyDialogFont( composite );
+
+        return composite;
+    }
+
+
+    private void validate()
+    {
+        if ( getButton( IDialogConstants.OK_ID ) != null )
+        {
+            boolean userIdentityInputValid = useBindUserIdentityCheckbox.getSelection()
+                || ( entryWidget.getDn() != null && !entryWidget.getDn().isEmpty() );
+            boolean oldPasswordInputValid = noOldPasswordCheckbox.getSelection()
+                || !oldPasswordText.getText().isEmpty();
+            boolean newPasswordInputValid = generateNewPasswordCheckbox.getSelection()
+                || !newPasswordText.getText().isEmpty();
+            getButton( IDialogConstants.OK_ID )
+                .setEnabled( userIdentityInputValid && oldPasswordInputValid && newPasswordInputValid );
+        }
+    }
+
+
+    /**
+     * Returns the user identity.
+     * 
+     * @return the user identity, may be null if dialog was canceled
+     */
+    public Dn getUserIdentity()
+    {
+        return userIdentity;
+    }
+
+
+    /**
+     * Returns the old password.
+     * 
+     * @return the old password, may be empty, null if dialog was canceled
+     */
+    public String getOldPassword()
+    {
+        return oldPassword;
+    }
+
+
+    /**
+     * Returns the new password.
+     * 
+     * @return the new password, may be empty, null if dialog was canceled
+     */
+    public String getNewPassword()
+    {
+        return newPassword;
+    }
+}
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/dialogs/messages.properties b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/dialogs/messages.properties
index e303793..48f6941 100644
--- a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/dialogs/messages.properties
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/dialogs/messages.properties
@@ -21,3 +21,13 @@ EncoderDecoderDialog.ISOHex=ISO-8859-1 Hex:
 EncoderDecoderDialog.LDAPEncodeDecoder=LDAP Encoder/Decoder
 EncoderDecoderDialog.UTF=UTF-8:
 EncoderDecoderDialog.UTFHex=UTF-8 Hex:
+PasswordModifyExtendedOperationDialog.Title=Password Modify Extended Operation (RFC 3062)
+PasswordModifyExtendedOperationDialog.GeneratedPasswordTitle=Generated Password
+PasswordModifyExtendedOperationDialog.GeneratedPasswordMessage=Generated password:
+PasswordModifyExtendedOperationDialog.UserIdentity=User Identity:
+PasswordModifyExtendedOperationDialog.UseBindUserIdentity=Use bind user identity
+PasswordModifyExtendedOperationDialog.OldPassword=Old Password:
+PasswordModifyExtendedOperationDialog.NoOldPassword=Old password not available (admin only)
+PasswordModifyExtendedOperationDialog.NewPassword=New Password:
+PasswordModifyExtendedOperationDialog.GenerateNewPassword=Generate new password (if supported by server)
+PasswordModifyExtendedOperationDialog.ShowPasswords=Show passwords
\ No newline at end of file
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/BrowserViewActionGroup.java b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/BrowserViewActionGroup.java
index f16c848..96627bb 100644
--- a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/BrowserViewActionGroup.java
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/BrowserViewActionGroup.java
@@ -48,6 +48,7 @@ import org.apache.directory.studio.ldapbrowser.ui.actions.NewContextEntryAction;
 import org.apache.directory.studio.ldapbrowser.ui.actions.NewEntryAction;
 import org.apache.directory.studio.ldapbrowser.ui.actions.NewSearchAction;
 import org.apache.directory.studio.ldapbrowser.ui.actions.OpenEntryEditorAction;
+import org.apache.directory.studio.ldapbrowser.ui.actions.PasswordModifyExtendedOperationAction;
 import org.apache.directory.studio.utils.ActionUtils;
 import org.eclipse.jface.action.IAction;
 import org.eclipse.jface.action.IMenuManager;
@@ -190,6 +191,9 @@ public class BrowserViewActionGroup extends BrowserActionGroup
     /** The Constant openEntryEditorAction. */
     private static final String openEntryEditorAction = "openEntryEditor"; //$NON-NLS-1$
 
+    /** The Constant passwordModifyExtendedOperationAction. */
+    private static final String passwordModifyExtendedOperationAction = "passwordModifyExtendedOperation"; //$NON-NLS-1$
+
 
     /**
      * Creates a new instance of BrowserViewActionGroup and 
@@ -272,6 +276,8 @@ public class BrowserViewActionGroup extends BrowserActionGroup
         browserActionMap.put( fetchSubentriesAction, new BrowserViewActionProxy( viewer, new FetchSubentriesAction() ) );
 
         browserActionMap.put( openEntryEditorAction, new BrowserViewActionProxy( viewer, new OpenEntryEditorAction() ) );
+
+        browserActionMap.put( passwordModifyExtendedOperationAction, new BrowserViewActionProxy( viewer, new PasswordModifyExtendedOperationAction() ) );
     }
 
 
@@ -433,6 +439,10 @@ public class BrowserViewActionGroup extends BrowserActionGroup
 
         // additions
         menuManager.add( new Separator( IWorkbenchActionConstants.MB_ADDITIONS ) );
+        MenuManager extendedOperationsMenuManager = new MenuManager(
+            Messages.getString( "BrowserViewActionGroup.ExtendedOperations" ) ); //$NON-NLS-1$
+        extendedOperationsMenuManager.add( browserActionMap.get( passwordModifyExtendedOperationAction ) );
+        menuManager.add( extendedOperationsMenuManager );
         menuManager.add( new Separator() );
 
         // properties
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages.properties b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages.properties
index b3244dd..69bfeca 100644
--- a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages.properties
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages.properties
@@ -20,6 +20,7 @@ BrowserViewActionGroup.Advanced=Advanced
 BrowserViewActionGroup.Export=Export
 BrowserViewActionGroup.Import=Import
 BrowserViewActionGroup.Fetch=Fetch
+BrowserViewActionGroup.ExtendedOperations=Extended Operations
 LinkWithEditorAction.LinkWithEditor=Link with editor
 OpenBrowserPreferencePageAction.Preferences=Preferences...
 OpenBrowserPreferencePageAction.PreferencesToolTip=Preferences...
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages_de.properties b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages_de.properties
index 7d0e0f8..e69c8c7 100644
--- a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages_de.properties
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages_de.properties
@@ -20,6 +20,7 @@ BrowserViewActionGroup.Advanced=Erweitert
 BrowserViewActionGroup.Export=Exportieren
 BrowserViewActionGroup.Import=Importieren
 BrowserViewActionGroup.Fetch=Abrufen
+BrowserViewActionGroup.ExtendedOperations=Erweiterte Operationen
 LinkWithEditorAction.LinkWithEditor=Verkn\u00FCpfne mit Editor
 OpenBrowserPreferencePageAction.Preferences=Benutzervorgaben...
 OpenBrowserPreferencePageAction.PreferencesToolTip=Benutzervorgaben...
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages_fr.properties b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages_fr.properties
index 59afa9f..44b5695 100644
--- a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages_fr.properties
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/browser/messages_fr.properties
@@ -20,6 +20,7 @@ BrowserViewActionGroup.Advanced=Avanc\u00E9
 BrowserViewActionGroup.Export=Export
 BrowserViewActionGroup.Import=Import
 BrowserViewActionGroup.Fetch=R\u00E9cup\u00E9rer
+BrowserViewActionGroup.ExtendedOperations=TODO: Extended Operations
 LinkWithEditorAction.LinkWithEditor=Lien avec l''\u00E9diteur
 OpenBrowserPreferencePageAction.Preferences=Pr\u00E9f\u00E9rences...
 OpenBrowserPreferencePageAction.PreferencesToolTip=Pr\u00E9f\u00E9rences...
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/ConnectionViewActionGroup.java b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/ConnectionViewActionGroup.java
index 34a4968..417c7ba 100644
--- a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/ConnectionViewActionGroup.java
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/ConnectionViewActionGroup.java
@@ -27,6 +27,7 @@ import org.apache.directory.studio.ldapbrowser.ui.actions.ExportConnectionsActio
 import org.apache.directory.studio.ldapbrowser.ui.actions.ImportConnectionsAction;
 import org.apache.directory.studio.ldapbrowser.ui.actions.ImportExportAction;
 import org.apache.directory.studio.ldapbrowser.ui.actions.OpenSchemaBrowserAction;
+import org.apache.directory.studio.ldapbrowser.ui.actions.PasswordModifyExtendedOperationAction;
 import org.apache.directory.studio.ldapbrowser.ui.actions.ReloadSchemaAction;
 import org.eclipse.jface.action.IAction;
 import org.eclipse.jface.action.IMenuManager;
@@ -83,6 +84,9 @@ public class ConnectionViewActionGroup extends ConnectionActionGroup
     /** The Constant reloadSchemaAction. */
     private static final String reloadSchemaAction = "reloadSchemaAction"; //$NON-NLS-1$
 
+    /** The Constant passwordModifyExtendedOperationAction. */
+    private static final String passwordModifyExtendedOperationAction = "passwordModifyExtendedOperation"; //$NON-NLS-1$
+
 
     /**
      * Creates a new instance of ConnectionViewActionGroup and creates
@@ -120,6 +124,9 @@ public class ConnectionViewActionGroup extends ConnectionActionGroup
             new OpenSchemaBrowserAction() ) );
         connectionActionMap.put( reloadSchemaAction, new ConnectionViewActionProxy( viewer, this,
             new ReloadSchemaAction() ) );
+
+        connectionActionMap.put( passwordModifyExtendedOperationAction, new ConnectionViewActionProxy( viewer, this,
+            new PasswordModifyExtendedOperationAction() ) );
     }
 
 
@@ -194,6 +201,10 @@ public class ConnectionViewActionGroup extends ConnectionActionGroup
 
         // additions
         menuManager.add( new Separator( IWorkbenchActionConstants.MB_ADDITIONS ) );
+        MenuManager extendedOperationsMenuManager = new MenuManager(
+            Messages.getString( "ConnectionViewActionGroup.ExtendedOperations" ) ); //$NON-NLS-1$
+        extendedOperationsMenuManager.add( connectionActionMap.get( passwordModifyExtendedOperationAction ) );
+        menuManager.add( extendedOperationsMenuManager );
         menuManager.add( new Separator() );
 
         // properties
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages.properties b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages.properties
index 2740288..3053b9a 100644
--- a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages.properties
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages.properties
@@ -17,4 +17,5 @@
 
 ConnectionViewActionGroup.Export=Export
 ConnectionViewActionGroup.Import=Import
+ConnectionViewActionGroup.ExtendedOperations=Extended Operations
 LinkWithEditorAction.LinkWithEditor=Link with editor
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages_de.properties b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages_de.properties
index 3266697..08231b7 100644
--- a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages_de.properties
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages_de.properties
@@ -17,4 +17,5 @@
 
 ConnectionViewActionGroup.Export=Exportieren
 ConnectionViewActionGroup.Import=Importieren
+ConnectionViewActionGroup.ExtendedOperations=Erweiterte Operationen
 LinkWithEditorAction.LinkWithEditor=Verkn\u00FCpfen mit Editor
diff --git a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages_fr.properties b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages_fr.properties
index ea8fc98..400697c 100644
--- a/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages_fr.properties
+++ b/plugins/ldapbrowser.ui/src/main/java/org/apache/directory/studio/ldapbrowser/ui/views/connection/messages_fr.properties
@@ -17,4 +17,5 @@
 
 ConnectionViewActionGroup.Export=Export
 ConnectionViewActionGroup.Import=Import
+ConnectionViewActionGroup.ExtendedOperations=TODO: Extended Operations
 LinkWithEditorAction.LinkWithEditor=Lien avec l''\u00E9diteur
diff --git a/plugins/schemaeditor/src/main/java/org/apache/directory/studio/schemaeditor/view/wizards/MergeSchemasWizard.java b/plugins/schemaeditor/src/main/java/org/apache/directory/studio/schemaeditor/view/wizards/MergeSchemasWizard.java
index a63af1e..ebab522 100644
--- a/plugins/schemaeditor/src/main/java/org/apache/directory/studio/schemaeditor/view/wizards/MergeSchemasWizard.java
+++ b/plugins/schemaeditor/src/main/java/org/apache/directory/studio/schemaeditor/view/wizards/MergeSchemasWizard.java
@@ -30,10 +30,10 @@ import java.util.Set;
 import org.apache.directory.api.ldap.model.schema.AbstractSchemaObject;
 import org.apache.directory.api.ldap.model.schema.AttributeType;
 import org.apache.directory.api.ldap.model.schema.ObjectClass;
+import org.apache.directory.studio.common.ui.dialogs.MessageDialogWithTextarea;
 import org.apache.directory.studio.schemaeditor.Activator;
 import org.apache.directory.studio.schemaeditor.model.Project;
 import org.apache.directory.studio.schemaeditor.model.Schema;
-import org.apache.directory.studio.schemaeditor.view.dialogs.MessageDialogWithTextarea;
 import org.apache.directory.studio.schemaeditor.view.wizards.MergeSchemasSelectionWizardPage.AttributeTypeFolder;
 import org.apache.directory.studio.schemaeditor.view.wizards.MergeSchemasSelectionWizardPage.AttributeTypeWrapper;
 import org.apache.directory.studio.schemaeditor.view.wizards.MergeSchemasSelectionWizardPage.ObjectClassFolder;
diff --git a/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/ConnectionWrapperTestBase.java b/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/ConnectionWrapperTestBase.java
deleted file mode 100644
index 80ebbdc..0000000
--- a/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/ConnectionWrapperTestBase.java
+++ /dev/null
@@ -1,787 +0,0 @@
-/*
- *  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.test.integration.core;
-
-
-import static org.apache.directory.studio.test.integration.core.Constants.LOCALHOST;
-import static org.hamcrest.CoreMatchers.hasItems;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import java.net.ConnectException;
-import java.nio.channels.UnresolvedAddressException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Function;
-
-import javax.naming.directory.SearchControls;
-
-import org.apache.directory.api.ldap.codec.api.LdapApiService;
-import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
-import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyRequest;
-import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
-import org.apache.directory.api.ldap.model.entry.DefaultEntry;
-import org.apache.directory.api.ldap.model.entry.DefaultModification;
-import org.apache.directory.api.ldap.model.entry.Entry;
-import org.apache.directory.api.ldap.model.entry.Modification;
-import org.apache.directory.api.ldap.model.entry.ModificationOperation;
-import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
-import org.apache.directory.api.ldap.model.exception.LdapException;
-import org.apache.directory.api.ldap.model.exception.LdapLoopDetectedException;
-import org.apache.directory.api.ldap.model.message.ExtendedResponse;
-import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
-import org.apache.directory.api.ldap.model.name.Dn;
-import org.apache.directory.api.util.Strings;
-import org.apache.directory.ldap.client.api.exception.InvalidConnectionException;
-import org.apache.directory.server.annotations.CreateLdapServer;
-import org.apache.directory.server.annotations.CreateTransport;
-import org.apache.directory.server.core.annotations.ApplyLdifFiles;
-import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
-import org.apache.directory.server.core.integ.FrameworkRunner;
-import org.apache.directory.server.ldap.handlers.extended.PwdModifyHandler;
-import org.apache.directory.studio.common.core.jobs.StudioProgressMonitor;
-import org.apache.directory.studio.connection.core.Connection;
-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.ConnectionCorePlugin;
-import org.apache.directory.studio.connection.core.ConnectionParameter;
-import org.apache.directory.studio.connection.core.ConnectionParameter.AuthenticationMethod;
-import org.apache.directory.studio.connection.core.ConnectionParameter.EncryptionMethod;
-import org.apache.directory.studio.connection.core.IReferralHandler;
-import org.apache.directory.studio.connection.core.io.ConnectionWrapper;
-import org.apache.directory.studio.connection.core.io.api.StudioSearchResult;
-import org.apache.directory.studio.connection.core.io.api.StudioSearchResultEnumeration;
-import org.apache.mina.util.AvailablePortFinder;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-
-/**
- * Base class for {@link ConnectionWrapper} tests.
- *
- * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
- * @version $Rev$, $Date$
- */
-@RunWith(FrameworkRunner.class)
-@CreateLdapServer(transports =
-    { @CreateTransport(protocol = "LDAP"), @CreateTransport(protocol = "LDAPS") }, extendedOpHandlers =
-    { PwdModifyHandler.class })
-@ApplyLdifFiles(clazz = ConnectionWrapperTestBase.class, value = "org/apache/directory/studio/test/integration/core/TestData.ldif")
-public abstract class ConnectionWrapperTestBase extends AbstractLdapTestUnit
-{
-
-    protected ConnectionWrapper connectionWrapper;
-
-
-    @Before
-    public void setUp() throws Exception
-    {
-        // create referral entries
-        Entry referralsOu = new DefaultEntry( getService().getSchemaManager() );
-        referralsOu.setDn( new Dn( "ou=referrals,ou=system" ) );
-        referralsOu.add( "objectClass", "top", "organizationalUnit" );
-        referralsOu.add( "ou", "referrals" );
-        service.getAdminSession().add( referralsOu );
-
-        // direct referral
-        Entry r1 = new DefaultEntry( getService().getSchemaManager() );
-        r1.setDn( new Dn( "cn=referral1,ou=referrals,ou=system" ) );
-        r1.add( "objectClass", "top", "referral", "extensibleObject" );
-        r1.add( "cn", "referral1" );
-        r1.add( "ref", "ldap://" + LOCALHOST + ":" + ldapServer.getPort() + "/ou=users,ou=system" );
-        service.getAdminSession().add( r1 );
-
-        // referral via another immediate referral
-        Entry r2 = new DefaultEntry( getService().getSchemaManager() );
-        r2.setDn( new Dn( "cn=referral2,ou=referrals,ou=system" ) );
-        r2.add( "objectClass", "top", "referral", "extensibleObject" );
-        r2.add( "cn", "referral2" );
-        r2.add( "ref", "ldap://" + LOCALHOST + ":" + ldapServer.getPort() + "/cn=referral1,ou=referrals,ou=system" );
-        service.getAdminSession().add( r2 );
-
-        // referral to parent which contains this referral
-        Entry r3 = new DefaultEntry( getService().getSchemaManager() );
-        r3.setDn( new Dn( "cn=referral3,ou=referrals,ou=system" ) );
-        r3.add( "objectClass", "top", "referral", "extensibleObject" );
-        r3.add( "cn", "referral3" );
-        r3.add( "ref", "ldap://" + LOCALHOST + ":" + ldapServer.getPort() + "/ou=referrals,ou=system" );
-        service.getAdminSession().add( r3 );
-
-        // referrals pointing to each other (loop)
-        Entry r4a = new DefaultEntry( getService().getSchemaManager() );
-        r4a.setDn( new Dn( "cn=referral4a,ou=referrals,ou=system" ) );
-        r4a.add( "objectClass", "top", "referral", "extensibleObject" );
-        r4a.add( "cn", "referral4a" );
-        r4a.add( "ref", "ldap://" + LOCALHOST + ":" + ldapServer.getPort() + "/cn=referral4b,ou=referrals,ou=system" );
-        service.getAdminSession().add( r4a );
-        Entry r4b = new DefaultEntry( getService().getSchemaManager() );
-        r4b.setDn( new Dn( "cn=referral4b,ou=referrals,ou=system" ) );
-        r4b.add( "objectClass", "top", "referral", "extensibleObject" );
-        r4b.add( "cn", "referral4b" );
-        r4b.add( "ref", "ldap://" + LOCALHOST + ":" + ldapServer.getPort() + "/cn=referral4a,ou=referrals,ou=system" );
-        service.getAdminSession().add( r4b );
-    }
-
-
-    @After
-    public void tearDown() throws Exception
-    {
-        if ( connectionWrapper != null )
-        {
-            connectionWrapper.disconnect();
-        }
-    }
-
-
-    /**
-     * Tests connecting to the server.
-     */
-    @Test
-    public void testConnect()
-    {
-        StudioProgressMonitor monitor = getProgressMonitor();
-        ConnectionParameter connectionParameter = new ConnectionParameter( null, LOCALHOST, ldapServer.getPort(),
-            EncryptionMethod.NONE, AuthenticationMethod.NONE, null, null, null, true, null, 30000L );
-        Connection connection = new Connection( connectionParameter );
-        ConnectionWrapper connectionWrapper = connection.getConnectionWrapper();
-
-        assertFalse( connectionWrapper.isConnected() );
-
-        connectionWrapper.connect( monitor );
-        assertTrue( connectionWrapper.isConnected() );
-        assertNull( monitor.getException() );
-
-        connectionWrapper.disconnect();
-        assertFalse( connectionWrapper.isConnected() );
-
-        // TODO: SSL, StartTLS
-    }
-
-
-    /**
-     * Test failed connections to the server.
-     */
-    @Test
-    public void testConnectFailures()
-    {
-        StudioProgressMonitor monitor = null;
-        ConnectionParameter connectionParameter = null;
-        Connection connection = null;
-        ConnectionWrapper connectionWrapper = null;
-
-        // invalid port
-        monitor = getProgressMonitor();
-        connectionParameter = new ConnectionParameter( null, LOCALHOST, AvailablePortFinder.getNextAvailable(),
-            EncryptionMethod.NONE, AuthenticationMethod.NONE, null, null, null, true, null, 30000L );
-        connection = new Connection( connectionParameter );
-        connectionWrapper = connection.getConnectionWrapper();
-        connectionWrapper.connect( monitor );
-        assertFalse( connectionWrapper.isConnected() );
-        assertNotNull( monitor.getException() );
-        assertTrue( monitor.getException() instanceof InvalidConnectionException );
-        assertNotNull( monitor.getException().getCause() );
-        assertTrue( monitor.getException().getCause() instanceof ConnectException );
-
-        // unknown host
-        monitor = getProgressMonitor();
-        connectionParameter = new ConnectionParameter( null, "555.555.555.555", ldapServer.getPort(),
-            EncryptionMethod.NONE, AuthenticationMethod.NONE, null, null, null, true, null, 30000L );
-        connection = new Connection( connectionParameter );
-        connectionWrapper = connection.getConnectionWrapper();
-        connectionWrapper.connect( monitor );
-        assertFalse( connectionWrapper.isConnected() );
-        assertNotNull( monitor.getException() );
-        assertTrue( monitor.getException() instanceof InvalidConnectionException );
-        assertNotNull( monitor.getException().getCause() );
-        assertTrue( monitor.getException().getCause() instanceof UnresolvedAddressException );
-
-        // TODO: SSL, StartTLS
-    }
-
-
-    /**
-     * Test binding to the server.
-     */
-    @Test
-    public void testBind()
-    {
-        StudioProgressMonitor monitor = getProgressMonitor();
-        ConnectionParameter connectionParameter = new ConnectionParameter( null, LOCALHOST, ldapServer.getPort(),
-            EncryptionMethod.NONE, AuthenticationMethod.SIMPLE, "uid=admin,ou=system", "secret", null, true,
-            null, 30000L );
-        Connection connection = new Connection( connectionParameter );
-        ConnectionWrapper connectionWrapper = connection.getConnectionWrapper();
-
-        assertFalse( connectionWrapper.isConnected() );
-
-        connectionWrapper.connect( monitor );
-        connectionWrapper.bind( monitor );
-        assertTrue( connectionWrapper.isConnected() );
-        assertNull( monitor.getException() );
-
-        connectionWrapper.unbind();
-        connectionWrapper.disconnect();
-        assertFalse( connectionWrapper.isConnected() );
-    }
-
-
-    /**
-     * Test failed binds to the server.
-     */
-    @Test
-    public void testBindFailures()
-    {
-        StudioProgressMonitor monitor = null;
-        ConnectionParameter connectionParameter = null;
-        Connection connection = null;
-        ConnectionWrapper connectionWrapper = null;
-
-        // simple auth without principal and credential
-        monitor = getProgressMonitor();
-        connectionParameter = new ConnectionParameter( null, LOCALHOST, ldapServer.getPort(), EncryptionMethod.NONE,
-            AuthenticationMethod.SIMPLE, "uid=admin", "invalid", null, true, null, 30000L );
-        connection = new Connection( connectionParameter );
-        connectionWrapper = connection.getConnectionWrapper();
-        connectionWrapper.connect( monitor );
-        connectionWrapper.bind( monitor );
-        assertFalse( connectionWrapper.isConnected() );
-        assertNotNull( monitor.getException() );
-        assertTrue( monitor.getException() instanceof LdapAuthenticationException );
-        assertTrue( monitor.getException().getMessage().contains( "INVALID_CREDENTIALS" ) );
-
-        // simple auth with invalid principal and credential
-        monitor = getProgressMonitor();
-        connectionParameter = new ConnectionParameter( null, LOCALHOST, ldapServer.getPort(), EncryptionMethod.NONE,
-            AuthenticationMethod.SIMPLE, "uid=admin,ou=system", "bar", null, true, null, 30000L );
-        connection = new Connection( connectionParameter );
-        connectionWrapper = connection.getConnectionWrapper();
-        connectionWrapper.connect( monitor );
-        connectionWrapper.bind( monitor );
-        assertFalse( connectionWrapper.isConnected() );
-        assertNotNull( monitor.getException() );
-        assertTrue( monitor.getException() instanceof LdapAuthenticationException );
-        assertTrue( monitor.getException().getMessage().contains( "INVALID_CREDENTIALS" ) );
-    }
-
-
-    /**
-     * Test searching.
-     */
-    @Test
-    public void testSearch() throws Exception
-    {
-        StudioProgressMonitor monitor = getProgressMonitor();
-        SearchControls searchControls = new SearchControls();
-        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search( "ou=system", "(objectClass=*)",
-            searchControls, AliasDereferencingMethod.NEVER, ReferralHandlingMethod.IGNORE, null, monitor, null );
-
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertNotNull( result );
-        assertTrue( result.hasMore() );
-        assertNotNull( result.next() );
-    }
-
-
-    /**
-     * Test binary attributes.
-     */
-    @Test
-    public void testSearchBinaryAttributes() throws Exception
-    {
-        StudioProgressMonitor monitor = getProgressMonitor();
-        SearchControls searchControls = new SearchControls();
-        searchControls.setSearchScope( SearchControls.OBJECT_SCOPE );
-        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search( "uid=admin,ou=system",
-            "(objectClass=*)",
-            searchControls, AliasDereferencingMethod.NEVER, ReferralHandlingMethod.IGNORE, null, monitor, null );
-
-        assertNotNull( result );
-        assertTrue( result.hasMore() );
-        StudioSearchResult entry = result.next();
-        assertNotNull( entry );
-
-        Object userPasswordValue = entry.getEntry().get( "userPassword" ).getBytes();
-        assertEquals( byte[].class, userPasswordValue.getClass() );
-        assertEquals( "secret", new String( ( byte[] ) userPasswordValue, StandardCharsets.UTF_8 ) );
-    }
-
-
-    @Test
-    public void testSearchContinuation_Follow_DirectReferral() throws Exception
-    {
-        StudioProgressMonitor monitor = getProgressMonitor();
-        SearchControls searchControls = new SearchControls();
-        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search(
-            "cn=referral1,ou=referrals,ou=system", "(objectClass=*)", searchControls, AliasDereferencingMethod.NEVER,
-            ReferralHandlingMethod.FOLLOW, null, monitor, null );
-
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertNotNull( result );
-
-        List<String> dns = consume( result, sr -> sr.getDn().getName() );
-        assertEquals( 1, dns.size() );
-        assertEquals( "uid=user.1,ou=users,ou=system", dns.get( 0 ) );
-    }
-
-
-    @Test
-    public void testSearchContinuation_Follow_IntermediateReferral() throws Exception
-    {
-        StudioProgressMonitor monitor = getProgressMonitor();
-        SearchControls searchControls = new SearchControls();
-        searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
-        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search(
-            "cn=referral2,ou=referrals,ou=system", "(objectClass=*)", searchControls, AliasDereferencingMethod.NEVER,
-            ReferralHandlingMethod.FOLLOW, null, monitor, null );
-
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertNotNull( result );
-
-        List<String> dns = consume( result, sr -> sr.getDn().getName() );
-        assertEquals( 2, dns.size() );
-        assertThat( dns, hasItems( "ou=users,ou=system", "uid=user.1,ou=users,ou=system" ) );
-    }
-
-
-    @Test
-    public void testSearchContinuation_Follow_ReferralToParent() throws Exception
-    {
-        StudioProgressMonitor monitor = getProgressMonitor();
-        SearchControls searchControls = new SearchControls();
-        searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
-        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search(
-            "cn=referral3,ou=referrals,ou=system", "(objectClass=*)", searchControls, AliasDereferencingMethod.NEVER,
-            ReferralHandlingMethod.FOLLOW, null, monitor, null );
-
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertNotNull( result );
-
-        List<String> dns = consume( result, sr -> sr.getDn().getName() );
-        assertEquals( 3, dns.size() );
-        assertThat( dns, hasItems( "ou=referrals,ou=system", "ou=users,ou=system", "uid=user.1,ou=users,ou=system" ) );
-    }
-
-
-    @Test
-    public void testSearchContinuation_Follow_ReferralLoop() throws Exception
-    {
-        StudioProgressMonitor monitor = getProgressMonitor();
-        SearchControls searchControls = new SearchControls();
-        searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
-        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search(
-            "cn=referral4a,ou=referrals,ou=system", "(objectClass=*)", searchControls, AliasDereferencingMethod.NEVER,
-            ReferralHandlingMethod.FOLLOW, null, monitor, null );
-
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertNotNull( result );
-
-        List<String> dns = consume( result, sr -> sr.getDn().getName() );
-        assertEquals( 0, dns.size() );
-    }
-
-
-    @Test
-    public void testSearchContinuationFollowManually() throws Exception
-    {
-        StudioProgressMonitor monitor = getProgressMonitor();
-        SearchControls searchControls = new SearchControls();
-        searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
-        ConnectionWrapper connectionWrapper = getConnectionWrapper( monitor );
-        ConnectionCorePlugin.getDefault().setReferralHandler( null );
-        StudioSearchResultEnumeration result = connectionWrapper.search( "ou=referrals,ou=system", "(objectClass=*)",
-            searchControls, AliasDereferencingMethod.NEVER, ReferralHandlingMethod.FOLLOW_MANUALLY, null, monitor,
-            null );
-
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertNotNull( result );
-
-        List<String> dns = consume( result, sr -> sr.getDn().getName() );
-        assertEquals( 6, dns.size() );
-        assertThat( dns,
-            hasItems( "ou=referrals,ou=system", "ou=users,ou=system", "cn=referral1,ou=referrals,ou=system",
-                "cn=referral4a,ou=referrals,ou=system", "cn=referral4b,ou=referrals,ou=system" ) );
-    }
-
-
-    @Test
-    public void testSearchContinuationIgnore() throws Exception
-    {
-        StudioProgressMonitor monitor = getProgressMonitor();
-        SearchControls searchControls = new SearchControls();
-        searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
-        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search( "ou=referrals,ou=system",
-            "(objectClass=*)", searchControls, AliasDereferencingMethod.NEVER, ReferralHandlingMethod.IGNORE, null,
-            monitor, null );
-
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertNotNull( result );
-
-        List<String> dns = consume( result, sr -> sr.getDn().getName() );
-        assertEquals( 1, dns.size() );
-        assertThat( dns, hasItems( "ou=referrals,ou=system" ) );
-    }
-
-
-    protected <T> List<T> consume( StudioSearchResultEnumeration result, Function<StudioSearchResult, T> fn )
-        throws LdapException
-    {
-        List<T> list = new ArrayList<>();
-        while ( result.hasMore() )
-        {
-            StudioSearchResult sr = result.next();
-            list.add( fn.apply( sr ) );
-        }
-        return list;
-    }
-
-
-    @Test
-    public void testAdd() throws Exception
-    {
-        String dn = "uid=user.X,ou=users,ou=system";
-
-        StudioProgressMonitor monitor = getProgressMonitor();
-        Entry entry = new DefaultEntry( dn, "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" );
-        getConnectionWrapper( monitor ).createEntry( entry, null, monitor, null );
-
-        // should have created entry
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertTrue( service.getAdminSession().exists( dn ) );
-    }
-
-
-    @Test
-    public void testAddFollowsReferral_DirectReferral() throws Exception
-    {
-        String targetDn = "uid=user.X,ou=users,ou=system";
-        String referralDn = "uid=user.X,cn=referral1,ou=referrals,ou=system";
-
-        // create entry under referral
-        StudioProgressMonitor monitor = getProgressMonitor();
-        Entry entry = new DefaultEntry( referralDn, "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" );
-        getConnectionWrapper( monitor ).createEntry( entry, null, monitor, null );
-
-        // should have created target entry
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertTrue( service.getAdminSession().exists( targetDn ) );
-    }
-
-
-    @Test
-    public void testAddFollowsReferral_IntermediateReferral() throws Exception
-    {
-        String targetDn = "uid=user.X,ou=users,ou=system";
-        String referralDn = "uid=user.X,cn=referral2,ou=referrals,ou=system";
-
-        // create entry under referral
-        StudioProgressMonitor monitor = getProgressMonitor();
-        Entry entry = new DefaultEntry( referralDn, "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" );
-        getConnectionWrapper( monitor ).createEntry( entry, null, monitor, null );
-
-        // should have created target entry
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertTrue( service.getAdminSession().exists( targetDn ) );
-    }
-
-
-    @Test
-    public void testAddFollowsReferral_ReferralLoop() throws Exception
-    {
-        String referralDn = "uid=user.X,cn=referral4a,ou=referrals,ou=system";
-
-        // create entry under referral
-        StudioProgressMonitor monitor = getProgressMonitor();
-        Entry entry = new DefaultEntry( referralDn, "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" );
-        getConnectionWrapper( monitor ).createEntry( entry, null, monitor, null );
-
-        // should not have created target entry
-        assertFalse( monitor.isCanceled() );
-        assertTrue( monitor.errorsReported() );
-        assertNotNull( monitor.getException() );
-        assertTrue( monitor.getException() instanceof LdapLoopDetectedException );
-    }
-
-
-    @Test
-    public void testModify() throws Exception
-    {
-        String dn = "uid=user.X,ou=users,ou=system";
-
-        // create entry
-        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), dn,
-            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
-
-        // modify entry
-        StudioProgressMonitor monitor = getProgressMonitor();
-        List<Modification> modifications = Collections.singletonList(
-            new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
-                new DefaultAttribute( "sn", "modified" ) ) );
-        getConnectionWrapper( monitor ).modifyEntry( new Dn( dn ), modifications, null, monitor, null );
-
-        // should have modified the entry
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        Entry entry = service.getAdminSession().lookup( new Dn( dn ) );
-        assertEquals( "modified", entry.get( "sn" ).getString() );
-    }
-
-
-    @Test
-    public void testModifyFollowsReferral_DirectReferral() throws Exception
-    {
-        String targetDn = "uid=user.X,ou=users,ou=system";
-        String referralDn = "uid=user.X,cn=referral1,ou=referrals,ou=system";
-
-        // create target entry
-        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
-            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
-
-        // modify referral entry 
-        StudioProgressMonitor monitor = getProgressMonitor();
-        List<Modification> modifications = Collections.singletonList(
-            new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
-                new DefaultAttribute( "sn", "modified" ) ) );
-        getConnectionWrapper( monitor ).modifyEntry( new Dn( referralDn ), modifications, null, monitor, null );
-
-        // should have modified the target entry
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        Entry entry = service.getAdminSession().lookup( new Dn( targetDn ) );
-        assertEquals( "modified", entry.get( "sn" ).getString() );
-    }
-
-
-    @Test
-    public void testModifyFollowsReferral_IntermediateReferral() throws Exception
-    {
-        String targetDn = "uid=user.X,ou=users,ou=system";
-        String referralDn = "uid=user.X,cn=referral2,ou=referrals,ou=system";
-
-        // create target entry
-        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
-            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
-
-        // modify referral entry 
-        StudioProgressMonitor monitor = getProgressMonitor();
-        List<Modification> modifications = Collections.singletonList(
-            new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
-                new DefaultAttribute( "sn", "modified" ) ) );
-        getConnectionWrapper( monitor ).modifyEntry( new Dn( referralDn ), modifications, null, monitor, null );
-
-        // should have modified the target entry
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        Entry entry = service.getAdminSession().lookup( new Dn( targetDn ) );
-        assertEquals( "modified", entry.get( "sn" ).getString() );
-    }
-
-
-    @Test
-    public void testModifyFollowsReferral_ReferralLoop() throws Exception
-    {
-        String targetDn = "uid=user.X,ou=users,ou=system";
-        String referralDn = "uid=user.X,cn=referral4a,ou=referrals,ou=system";
-
-        // create target entry
-        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
-            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
-
-        // modify referral entry 
-        StudioProgressMonitor monitor = getProgressMonitor();
-        List<Modification> modifications = Collections.singletonList(
-            new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
-                new DefaultAttribute( "sn", "modified" ) ) );
-        getConnectionWrapper( monitor ).modifyEntry( new Dn( referralDn ), modifications, null, monitor, null );
-
-        // should not have modified the target entry
-        assertFalse( monitor.isCanceled() );
-        assertTrue( monitor.errorsReported() );
-        assertNotNull( monitor.getException() );
-        assertTrue( monitor.getException() instanceof LdapLoopDetectedException );
-    }
-
-
-    @Test
-    public void testDelete() throws Exception
-    {
-        String dn = "uid=user.X,ou=users,ou=system";
-
-        // create entry
-        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), dn,
-            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
-
-        // delete entry
-        StudioProgressMonitor monitor = getProgressMonitor();
-        getConnectionWrapper( monitor ).deleteEntry( new Dn( dn ), null, monitor, null );
-
-        // should have deleted the entry
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertFalse( service.getAdminSession().exists( dn ) );
-    }
-
-
-    @Test
-    public void testDeleteFollowsReferral_DirectReferral() throws Exception
-    {
-        String targetDn = "uid=user.X,ou=users,ou=system";
-        String referralDn = "uid=user.X,cn=referral1,ou=referrals,ou=system";
-
-        // create target entry
-        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
-            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
-
-        // delete referral entry 
-        StudioProgressMonitor monitor = getProgressMonitor();
-        getConnectionWrapper( monitor ).deleteEntry( new Dn( referralDn ), null, monitor, null );
-
-        // should have deleted the target entry
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertFalse( service.getAdminSession().exists( targetDn ) );
-    }
-
-
-    @Test
-    public void testDeleteFollowsReferral_IntermediateReferral() throws Exception
-    {
-        String targetDn = "uid=user.X,ou=users,ou=system";
-        String referralDn = "uid=user.X,cn=referral2,ou=referrals,ou=system";
-
-        // create target entry
-        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
-            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
-
-        // delete referral entry 
-        StudioProgressMonitor monitor = getProgressMonitor();
-        getConnectionWrapper( monitor ).deleteEntry( new Dn( referralDn ), null, monitor, null );
-
-        // should have deleted the target entry
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        assertFalse( service.getAdminSession().exists( targetDn ) );
-    }
-
-
-    @Test
-    public void testDeleteFollowsReferral_ReferralLoop() throws Exception
-    {
-        String targetDn = "uid=user.X,ou=users,ou=system";
-        String referralDn = "uid=user.X,cn=referral4a,ou=referrals,ou=system";
-
-        // create target entry
-        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
-            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
-
-        // delete referral entry 
-        StudioProgressMonitor monitor = getProgressMonitor();
-        getConnectionWrapper( monitor ).deleteEntry( new Dn( referralDn ), null, monitor, null );
-
-        // should not have deleted the target entry
-        assertFalse( monitor.isCanceled() );
-        assertTrue( monitor.errorsReported() );
-        assertNotNull( monitor.getException() );
-        assertTrue( monitor.getException() instanceof LdapLoopDetectedException );
-        assertTrue( service.getAdminSession().exists( targetDn ) );
-    }
-
-
-    protected StudioProgressMonitor getProgressMonitor()
-    {
-        StudioProgressMonitor monitor = new StudioProgressMonitor( new NullProgressMonitor() );
-        return monitor;
-    }
-
-
-    protected ConnectionWrapper getConnectionWrapper( StudioProgressMonitor monitor )
-    {
-        // simple auth without principal and credential
-        ConnectionParameter connectionParameter = new ConnectionParameter( null, LOCALHOST, ldapServer.getPort(),
-            EncryptionMethod.NONE, AuthenticationMethod.SIMPLE, "uid=admin,ou=system", "secret", null, false,
-            null, 30000L );
-
-        Connection connection = new Connection( connectionParameter );
-
-        connectionWrapper = connection.getConnectionWrapper();
-        connectionWrapper.connect( monitor );
-        connectionWrapper.bind( monitor );
-
-        assertTrue( connectionWrapper.isConnected() );
-
-        IReferralHandler referralHandler = referralUrls -> {
-            return connection;
-        };
-        ConnectionCorePlugin.getDefault().setReferralHandler( referralHandler );
-
-        assertTrue( connectionWrapper.isConnected() );
-        assertNull( monitor.getException() );
-
-        return connectionWrapper;
-    }
-
-
-    @Test
-    public void testPasswordModifyRequestExtendedOperation() throws Exception
-    {
-        String dn = "uid=user.X,ou=users,ou=system";
-
-        // create target entry
-        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), dn,
-            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X", "userPassword:  secret" ) );
-
-        // modify password
-        LdapApiService ldapApiService = LdapApiServiceFactory.getSingleton();
-        PasswordModifyRequest request = ( PasswordModifyRequest ) ldapApiService.getExtendedRequestFactories()
-            .get( PasswordModifyRequest.EXTENSION_OID ).newRequest();
-        request.setUserIdentity( Strings.getBytesUtf8( dn ) );
-        request.setOldPassword( Strings.getBytesUtf8( "secret" ) );
-        request.setNewPassword( Strings.getBytesUtf8( "s3cre3t" ) );
-        StudioProgressMonitor monitor = getProgressMonitor();
-        ExtendedResponse response = getConnectionWrapper( monitor ).extended( request, monitor );
-
-        // should have modified password of the target entry
-        assertEquals( ResultCodeEnum.SUCCESS, response.getLdapResult().getResultCode() );
-        assertFalse( monitor.isCanceled() );
-        assertFalse( monitor.errorsReported() );
-        Entry entry = service.getAdminSession().lookup( new Dn( dn ) );
-        assertEquals( "s3cre3t", Strings.utf8ToString( entry.get( "userPassword" ).getBytes() ) );
-    }
-
-}
diff --git a/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/DirectoryApiConnectionWrapperTest.java b/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/DirectoryApiConnectionWrapperTest.java
index 313abaf..b17eb59 100644
--- a/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/DirectoryApiConnectionWrapperTest.java
+++ b/tests/test.integration.core/src/main/java/org/apache/directory/studio/test/integration/core/DirectoryApiConnectionWrapperTest.java
@@ -26,9 +26,15 @@ import static org.hamcrest.CoreMatchers.hasItems;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import java.net.ConnectException;
+import java.nio.channels.UnresolvedAddressException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
@@ -36,23 +42,65 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
 
 import javax.naming.directory.SearchControls;
 
+import org.apache.directory.api.ldap.codec.api.LdapApiService;
+import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
+//import org.apache.directory.api.ldap.extras.extended.endTransaction.EndTransactionRequest;
+//import org.apache.directory.api.ldap.extras.extended.endTransaction.EndTransactionResponse;
+import org.apache.directory.api.ldap.extras.extended.pwdModify.PasswordModifyRequest;
+//import org.apache.directory.api.ldap.extras.extended.startTransaction.StartTransactionRequest;
+//import org.apache.directory.api.ldap.extras.extended.startTransaction.StartTransactionResponse;
+import org.apache.directory.api.ldap.extras.extended.whoAmI.WhoAmIRequest;
+import org.apache.directory.api.ldap.extras.extended.whoAmI.WhoAmIResponse;
+import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.entry.DefaultModification;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.entry.Modification;
+import org.apache.directory.api.ldap.model.entry.ModificationOperation;
+import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
+import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.api.ldap.model.exception.LdapLoopDetectedException;
+import org.apache.directory.api.ldap.model.message.ExtendedResponse;
+import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.directory.api.util.Strings;
+import org.apache.directory.ldap.client.api.exception.InvalidConnectionException;
+import org.apache.directory.server.annotations.CreateLdapServer;
+import org.apache.directory.server.annotations.CreateTransport;
+import org.apache.directory.server.core.annotations.ApplyLdifFiles;
+import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
+import org.apache.directory.server.core.integ.FrameworkRunner;
+//import org.apache.directory.server.ldap.handlers.extended.EndTransactionHandler;
+import org.apache.directory.server.ldap.handlers.extended.PwdModifyHandler;
+//import org.apache.directory.server.ldap.handlers.extended.StartTransactionHandler;
+import org.apache.directory.server.ldap.handlers.extended.WhoAmIHandler;
 import org.apache.directory.studio.common.core.jobs.StudioProgressMonitor;
 import org.apache.directory.studio.connection.core.Connection;
+import org.apache.directory.studio.connection.core.ConnectionCorePlugin;
 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.ConnectionParameter;
+import org.apache.directory.studio.connection.core.IReferralHandler;
 import org.apache.directory.studio.connection.core.ConnectionParameter.AuthenticationMethod;
 import org.apache.directory.studio.connection.core.ConnectionParameter.EncryptionMethod;
 import org.apache.directory.studio.connection.core.event.ConnectionEventRegistry;
+import org.apache.directory.studio.connection.core.io.ConnectionWrapper;
 import org.apache.directory.studio.connection.core.io.api.DirectoryApiConnectionWrapper;
+import org.apache.directory.studio.connection.core.io.api.StudioSearchResult;
 import org.apache.directory.studio.connection.core.io.api.StudioSearchResultEnumeration;
 import org.apache.directory.studio.ldapbrowser.core.jobs.InitializeRootDSERunnable;
 import org.apache.directory.studio.ldapbrowser.core.model.impl.BrowserConnection;
+import org.apache.mina.util.AvailablePortFinder;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 
 /**
@@ -61,9 +109,639 @@ import org.junit.Test;
  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
  * @version $Rev$, $Date$
  */
-public class DirectoryApiConnectionWrapperTest extends ConnectionWrapperTestBase
+@RunWith(FrameworkRunner.class)
+@CreateLdapServer(transports =
+    { @CreateTransport(protocol = "LDAP"), @CreateTransport(protocol = "LDAPS") }, extendedOpHandlers =
+    { PwdModifyHandler.class, WhoAmIHandler.class })
+@ApplyLdifFiles(clazz = DirectoryApiConnectionWrapperTest.class, value = "org/apache/directory/studio/test/integration/core/TestData.ldif")
+public class DirectoryApiConnectionWrapperTest extends AbstractLdapTestUnit
 {
-    // see tests in super class
+
+    protected ConnectionWrapper connectionWrapper;
+
+
+    @Before
+    public void setUp() throws Exception
+    {
+        // create referral entries
+        Entry referralsOu = new DefaultEntry( getService().getSchemaManager() );
+        referralsOu.setDn( new Dn( "ou=referrals,ou=system" ) );
+        referralsOu.add( "objectClass", "top", "organizationalUnit" );
+        referralsOu.add( "ou", "referrals" );
+        service.getAdminSession().add( referralsOu );
+
+        // direct referral
+        Entry r1 = new DefaultEntry( getService().getSchemaManager() );
+        r1.setDn( new Dn( "cn=referral1,ou=referrals,ou=system" ) );
+        r1.add( "objectClass", "top", "referral", "extensibleObject" );
+        r1.add( "cn", "referral1" );
+        r1.add( "ref", "ldap://" + LOCALHOST + ":" + ldapServer.getPort() + "/ou=users,ou=system" );
+        service.getAdminSession().add( r1 );
+
+        // referral via another immediate referral
+        Entry r2 = new DefaultEntry( getService().getSchemaManager() );
+        r2.setDn( new Dn( "cn=referral2,ou=referrals,ou=system" ) );
+        r2.add( "objectClass", "top", "referral", "extensibleObject" );
+        r2.add( "cn", "referral2" );
+        r2.add( "ref", "ldap://" + LOCALHOST + ":" + ldapServer.getPort() + "/cn=referral1,ou=referrals,ou=system" );
+        service.getAdminSession().add( r2 );
+
+        // referral to parent which contains this referral
+        Entry r3 = new DefaultEntry( getService().getSchemaManager() );
+        r3.setDn( new Dn( "cn=referral3,ou=referrals,ou=system" ) );
+        r3.add( "objectClass", "top", "referral", "extensibleObject" );
+        r3.add( "cn", "referral3" );
+        r3.add( "ref", "ldap://" + LOCALHOST + ":" + ldapServer.getPort() + "/ou=referrals,ou=system" );
+        service.getAdminSession().add( r3 );
+
+        // referrals pointing to each other (loop)
+        Entry r4a = new DefaultEntry( getService().getSchemaManager() );
+        r4a.setDn( new Dn( "cn=referral4a,ou=referrals,ou=system" ) );
+        r4a.add( "objectClass", "top", "referral", "extensibleObject" );
+        r4a.add( "cn", "referral4a" );
+        r4a.add( "ref", "ldap://" + LOCALHOST + ":" + ldapServer.getPort() + "/cn=referral4b,ou=referrals,ou=system" );
+        service.getAdminSession().add( r4a );
+        Entry r4b = new DefaultEntry( getService().getSchemaManager() );
+        r4b.setDn( new Dn( "cn=referral4b,ou=referrals,ou=system" ) );
+        r4b.add( "objectClass", "top", "referral", "extensibleObject" );
+        r4b.add( "cn", "referral4b" );
+        r4b.add( "ref", "ldap://" + LOCALHOST + ":" + ldapServer.getPort() + "/cn=referral4a,ou=referrals,ou=system" );
+        service.getAdminSession().add( r4b );
+    }
+
+
+    @After
+    public void tearDown() throws Exception
+    {
+        if ( connectionWrapper != null )
+        {
+            connectionWrapper.disconnect();
+        }
+    }
+
+
+    /**
+     * Tests connecting to the server.
+     */
+    @Test
+    public void testConnect()
+    {
+        StudioProgressMonitor monitor = getProgressMonitor();
+        ConnectionParameter connectionParameter = new ConnectionParameter( null, LOCALHOST, ldapServer.getPort(),
+            EncryptionMethod.NONE, AuthenticationMethod.NONE, null, null, null, true, null, 30000L );
+        Connection connection = new Connection( connectionParameter );
+        ConnectionWrapper connectionWrapper = connection.getConnectionWrapper();
+
+        assertFalse( connectionWrapper.isConnected() );
+
+        connectionWrapper.connect( monitor );
+        assertTrue( connectionWrapper.isConnected() );
+        assertNull( monitor.getException() );
+
+        connectionWrapper.disconnect();
+        assertFalse( connectionWrapper.isConnected() );
+
+        // TODO: SSL, StartTLS
+    }
+
+
+    /**
+     * Test failed connections to the server.
+     */
+    @Test
+    public void testConnectFailures()
+    {
+        StudioProgressMonitor monitor = null;
+        ConnectionParameter connectionParameter = null;
+        Connection connection = null;
+        ConnectionWrapper connectionWrapper = null;
+
+        // invalid port
+        monitor = getProgressMonitor();
+        connectionParameter = new ConnectionParameter( null, LOCALHOST, AvailablePortFinder.getNextAvailable(),
+            EncryptionMethod.NONE, AuthenticationMethod.NONE, null, null, null, true, null, 30000L );
+        connection = new Connection( connectionParameter );
+        connectionWrapper = connection.getConnectionWrapper();
+        connectionWrapper.connect( monitor );
+        assertFalse( connectionWrapper.isConnected() );
+        assertNotNull( monitor.getException() );
+        assertTrue( monitor.getException() instanceof InvalidConnectionException );
+        assertNotNull( monitor.getException().getCause() );
+        assertTrue( monitor.getException().getCause() instanceof ConnectException );
+
+        // unknown host
+        monitor = getProgressMonitor();
+        connectionParameter = new ConnectionParameter( null, "555.555.555.555", ldapServer.getPort(),
+            EncryptionMethod.NONE, AuthenticationMethod.NONE, null, null, null, true, null, 30000L );
+        connection = new Connection( connectionParameter );
+        connectionWrapper = connection.getConnectionWrapper();
+        connectionWrapper.connect( monitor );
+        assertFalse( connectionWrapper.isConnected() );
+        assertNotNull( monitor.getException() );
+        assertTrue( monitor.getException() instanceof InvalidConnectionException );
+        assertNotNull( monitor.getException().getCause() );
+        assertTrue( monitor.getException().getCause() instanceof UnresolvedAddressException );
+
+        // TODO: SSL, StartTLS
+    }
+
+
+    /**
+     * Test binding to the server.
+     */
+    @Test
+    public void testBind()
+    {
+        StudioProgressMonitor monitor = getProgressMonitor();
+        ConnectionParameter connectionParameter = new ConnectionParameter( null, LOCALHOST, ldapServer.getPort(),
+            EncryptionMethod.NONE, AuthenticationMethod.SIMPLE, "uid=admin,ou=system", "secret", null, true,
+            null, 30000L );
+        Connection connection = new Connection( connectionParameter );
+        ConnectionWrapper connectionWrapper = connection.getConnectionWrapper();
+
+        assertFalse( connectionWrapper.isConnected() );
+
+        connectionWrapper.connect( monitor );
+        connectionWrapper.bind( monitor );
+        assertTrue( connectionWrapper.isConnected() );
+        assertNull( monitor.getException() );
+
+        connectionWrapper.unbind();
+        connectionWrapper.disconnect();
+        assertFalse( connectionWrapper.isConnected() );
+    }
+
+
+    /**
+     * Test failed binds to the server.
+     */
+    @Test
+    public void testBindFailures()
+    {
+        StudioProgressMonitor monitor = null;
+        ConnectionParameter connectionParameter = null;
+        Connection connection = null;
+        ConnectionWrapper connectionWrapper = null;
+
+        // simple auth without principal and credential
+        monitor = getProgressMonitor();
+        connectionParameter = new ConnectionParameter( null, LOCALHOST, ldapServer.getPort(), EncryptionMethod.NONE,
+            AuthenticationMethod.SIMPLE, "uid=admin", "invalid", null, true, null, 30000L );
+        connection = new Connection( connectionParameter );
+        connectionWrapper = connection.getConnectionWrapper();
+        connectionWrapper.connect( monitor );
+        connectionWrapper.bind( monitor );
+        assertFalse( connectionWrapper.isConnected() );
+        assertNotNull( monitor.getException() );
+        assertTrue( monitor.getException() instanceof LdapAuthenticationException );
+        assertTrue( monitor.getException().getMessage().contains( "INVALID_CREDENTIALS" ) );
+
+        // simple auth with invalid principal and credential
+        monitor = getProgressMonitor();
+        connectionParameter = new ConnectionParameter( null, LOCALHOST, ldapServer.getPort(), EncryptionMethod.NONE,
+            AuthenticationMethod.SIMPLE, "uid=admin,ou=system", "bar", null, true, null, 30000L );
+        connection = new Connection( connectionParameter );
+        connectionWrapper = connection.getConnectionWrapper();
+        connectionWrapper.connect( monitor );
+        connectionWrapper.bind( monitor );
+        assertFalse( connectionWrapper.isConnected() );
+        assertNotNull( monitor.getException() );
+        assertTrue( monitor.getException() instanceof LdapAuthenticationException );
+        assertTrue( monitor.getException().getMessage().contains( "INVALID_CREDENTIALS" ) );
+    }
+
+
+    /**
+     * Test searching.
+     */
+    @Test
+    public void testSearch() throws Exception
+    {
+        StudioProgressMonitor monitor = getProgressMonitor();
+        SearchControls searchControls = new SearchControls();
+        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search( "ou=system", "(objectClass=*)",
+            searchControls, AliasDereferencingMethod.NEVER, ReferralHandlingMethod.IGNORE, null, monitor, null );
+
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertNotNull( result );
+        assertTrue( result.hasMore() );
+        assertNotNull( result.next() );
+    }
+
+
+    /**
+     * Test binary attributes.
+     */
+    @Test
+    public void testSearchBinaryAttributes() throws Exception
+    {
+        StudioProgressMonitor monitor = getProgressMonitor();
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope( SearchControls.OBJECT_SCOPE );
+        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search( "uid=admin,ou=system",
+            "(objectClass=*)",
+            searchControls, AliasDereferencingMethod.NEVER, ReferralHandlingMethod.IGNORE, null, monitor, null );
+
+        assertNotNull( result );
+        assertTrue( result.hasMore() );
+        StudioSearchResult entry = result.next();
+        assertNotNull( entry );
+
+        Object userPasswordValue = entry.getEntry().get( "userPassword" ).getBytes();
+        assertEquals( byte[].class, userPasswordValue.getClass() );
+        assertEquals( "secret", new String( ( byte[] ) userPasswordValue, StandardCharsets.UTF_8 ) );
+    }
+
+
+    @Test
+    public void testSearchContinuation_Follow_DirectReferral() throws Exception
+    {
+        StudioProgressMonitor monitor = getProgressMonitor();
+        SearchControls searchControls = new SearchControls();
+        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search(
+            "cn=referral1,ou=referrals,ou=system", "(objectClass=*)", searchControls, AliasDereferencingMethod.NEVER,
+            ReferralHandlingMethod.FOLLOW, null, monitor, null );
+
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertNotNull( result );
+
+        List<String> dns = consume( result, sr -> sr.getDn().getName() );
+        assertEquals( 1, dns.size() );
+        assertEquals( "uid=user.1,ou=users,ou=system", dns.get( 0 ) );
+    }
+
+
+    @Test
+    public void testSearchContinuation_Follow_IntermediateReferral() throws Exception
+    {
+        StudioProgressMonitor monitor = getProgressMonitor();
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search(
+            "cn=referral2,ou=referrals,ou=system", "(objectClass=*)", searchControls, AliasDereferencingMethod.NEVER,
+            ReferralHandlingMethod.FOLLOW, null, monitor, null );
+
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertNotNull( result );
+
+        List<String> dns = consume( result, sr -> sr.getDn().getName() );
+        assertEquals( 2, dns.size() );
+        assertThat( dns, hasItems( "ou=users,ou=system", "uid=user.1,ou=users,ou=system" ) );
+    }
+
+
+    @Test
+    public void testSearchContinuation_Follow_ReferralToParent() throws Exception
+    {
+        StudioProgressMonitor monitor = getProgressMonitor();
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search(
+            "cn=referral3,ou=referrals,ou=system", "(objectClass=*)", searchControls, AliasDereferencingMethod.NEVER,
+            ReferralHandlingMethod.FOLLOW, null, monitor, null );
+
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertNotNull( result );
+
+        List<String> dns = consume( result, sr -> sr.getDn().getName() );
+        assertEquals( 3, dns.size() );
+        assertThat( dns, hasItems( "ou=referrals,ou=system", "ou=users,ou=system", "uid=user.1,ou=users,ou=system" ) );
+    }
+
+
+    @Test
+    public void testSearchContinuation_Follow_ReferralLoop() throws Exception
+    {
+        StudioProgressMonitor monitor = getProgressMonitor();
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search(
+            "cn=referral4a,ou=referrals,ou=system", "(objectClass=*)", searchControls, AliasDereferencingMethod.NEVER,
+            ReferralHandlingMethod.FOLLOW, null, monitor, null );
+
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertNotNull( result );
+
+        List<String> dns = consume( result, sr -> sr.getDn().getName() );
+        assertEquals( 0, dns.size() );
+    }
+
+
+    @Test
+    public void testSearchContinuationFollowManually() throws Exception
+    {
+        StudioProgressMonitor monitor = getProgressMonitor();
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+        ConnectionWrapper connectionWrapper = getConnectionWrapper( monitor );
+        ConnectionCorePlugin.getDefault().setReferralHandler( null );
+        StudioSearchResultEnumeration result = connectionWrapper.search( "ou=referrals,ou=system", "(objectClass=*)",
+            searchControls, AliasDereferencingMethod.NEVER, ReferralHandlingMethod.FOLLOW_MANUALLY, null, monitor,
+            null );
+
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertNotNull( result );
+
+        List<String> dns = consume( result, sr -> sr.getDn().getName() );
+        assertEquals( 6, dns.size() );
+        assertThat( dns,
+            hasItems( "ou=referrals,ou=system", "ou=users,ou=system", "cn=referral1,ou=referrals,ou=system",
+                "cn=referral4a,ou=referrals,ou=system", "cn=referral4b,ou=referrals,ou=system" ) );
+    }
+
+
+    @Test
+    public void testSearchContinuationIgnore() throws Exception
+    {
+        StudioProgressMonitor monitor = getProgressMonitor();
+        SearchControls searchControls = new SearchControls();
+        searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+        StudioSearchResultEnumeration result = getConnectionWrapper( monitor ).search( "ou=referrals,ou=system",
+            "(objectClass=*)", searchControls, AliasDereferencingMethod.NEVER, ReferralHandlingMethod.IGNORE, null,
+            monitor, null );
+
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertNotNull( result );
+
+        List<String> dns = consume( result, sr -> sr.getDn().getName() );
+        assertEquals( 1, dns.size() );
+        assertThat( dns, hasItems( "ou=referrals,ou=system" ) );
+    }
+
+
+    protected <T> List<T> consume( StudioSearchResultEnumeration result, Function<StudioSearchResult, T> fn )
+        throws LdapException
+    {
+        List<T> list = new ArrayList<>();
+        while ( result.hasMore() )
+        {
+            StudioSearchResult sr = result.next();
+            list.add( fn.apply( sr ) );
+        }
+        return list;
+    }
+
+
+    @Test
+    public void testAdd() throws Exception
+    {
+        String dn = "uid=user.X,ou=users,ou=system";
+
+        StudioProgressMonitor monitor = getProgressMonitor();
+        Entry entry = new DefaultEntry( dn, "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" );
+        getConnectionWrapper( monitor ).createEntry( entry, null, monitor, null );
+
+        // should have created entry
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertTrue( service.getAdminSession().exists( dn ) );
+    }
+
+
+    @Test
+    public void testAddFollowsReferral_DirectReferral() throws Exception
+    {
+        String targetDn = "uid=user.X,ou=users,ou=system";
+        String referralDn = "uid=user.X,cn=referral1,ou=referrals,ou=system";
+
+        // create entry under referral
+        StudioProgressMonitor monitor = getProgressMonitor();
+        Entry entry = new DefaultEntry( referralDn, "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" );
+        getConnectionWrapper( monitor ).createEntry( entry, null, monitor, null );
+
+        // should have created target entry
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertTrue( service.getAdminSession().exists( targetDn ) );
+    }
+
+
+    @Test
+    public void testAddFollowsReferral_IntermediateReferral() throws Exception
+    {
+        String targetDn = "uid=user.X,ou=users,ou=system";
+        String referralDn = "uid=user.X,cn=referral2,ou=referrals,ou=system";
+
+        // create entry under referral
+        StudioProgressMonitor monitor = getProgressMonitor();
+        Entry entry = new DefaultEntry( referralDn, "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" );
+        getConnectionWrapper( monitor ).createEntry( entry, null, monitor, null );
+
+        // should have created target entry
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertTrue( service.getAdminSession().exists( targetDn ) );
+    }
+
+
+    @Test
+    public void testAddFollowsReferral_ReferralLoop() throws Exception
+    {
+        String referralDn = "uid=user.X,cn=referral4a,ou=referrals,ou=system";
+
+        // create entry under referral
+        StudioProgressMonitor monitor = getProgressMonitor();
+        Entry entry = new DefaultEntry( referralDn, "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" );
+        getConnectionWrapper( monitor ).createEntry( entry, null, monitor, null );
+
+        // should not have created target entry
+        assertFalse( monitor.isCanceled() );
+        assertTrue( monitor.errorsReported() );
+        assertNotNull( monitor.getException() );
+        assertTrue( monitor.getException() instanceof LdapLoopDetectedException );
+    }
+
+
+    @Test
+    public void testModify() throws Exception
+    {
+        String dn = "uid=user.X,ou=users,ou=system";
+
+        // create entry
+        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), dn,
+            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
+
+        // modify entry
+        StudioProgressMonitor monitor = getProgressMonitor();
+        List<Modification> modifications = Collections.singletonList(
+            new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
+                new DefaultAttribute( "sn", "modified" ) ) );
+        getConnectionWrapper( monitor ).modifyEntry( new Dn( dn ), modifications, null, monitor, null );
+
+        // should have modified the entry
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        Entry entry = service.getAdminSession().lookup( new Dn( dn ) );
+        assertEquals( "modified", entry.get( "sn" ).getString() );
+    }
+
+
+    @Test
+    public void testModifyFollowsReferral_DirectReferral() throws Exception
+    {
+        String targetDn = "uid=user.X,ou=users,ou=system";
+        String referralDn = "uid=user.X,cn=referral1,ou=referrals,ou=system";
+
+        // create target entry
+        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
+            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
+
+        // modify referral entry 
+        StudioProgressMonitor monitor = getProgressMonitor();
+        List<Modification> modifications = Collections.singletonList(
+            new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
+                new DefaultAttribute( "sn", "modified" ) ) );
+        getConnectionWrapper( monitor ).modifyEntry( new Dn( referralDn ), modifications, null, monitor, null );
+
+        // should have modified the target entry
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        Entry entry = service.getAdminSession().lookup( new Dn( targetDn ) );
+        assertEquals( "modified", entry.get( "sn" ).getString() );
+    }
+
+
+    @Test
+    public void testModifyFollowsReferral_IntermediateReferral() throws Exception
+    {
+        String targetDn = "uid=user.X,ou=users,ou=system";
+        String referralDn = "uid=user.X,cn=referral2,ou=referrals,ou=system";
+
+        // create target entry
+        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
+            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
+
+        // modify referral entry 
+        StudioProgressMonitor monitor = getProgressMonitor();
+        List<Modification> modifications = Collections.singletonList(
+            new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
+                new DefaultAttribute( "sn", "modified" ) ) );
+        getConnectionWrapper( monitor ).modifyEntry( new Dn( referralDn ), modifications, null, monitor, null );
+
+        // should have modified the target entry
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        Entry entry = service.getAdminSession().lookup( new Dn( targetDn ) );
+        assertEquals( "modified", entry.get( "sn" ).getString() );
+    }
+
+
+    @Test
+    public void testModifyFollowsReferral_ReferralLoop() throws Exception
+    {
+        String targetDn = "uid=user.X,ou=users,ou=system";
+        String referralDn = "uid=user.X,cn=referral4a,ou=referrals,ou=system";
+
+        // create target entry
+        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
+            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
+
+        // modify referral entry 
+        StudioProgressMonitor monitor = getProgressMonitor();
+        List<Modification> modifications = Collections.singletonList(
+            new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
+                new DefaultAttribute( "sn", "modified" ) ) );
+        getConnectionWrapper( monitor ).modifyEntry( new Dn( referralDn ), modifications, null, monitor, null );
+
+        // should not have modified the target entry
+        assertFalse( monitor.isCanceled() );
+        assertTrue( monitor.errorsReported() );
+        assertNotNull( monitor.getException() );
+        assertTrue( monitor.getException() instanceof LdapLoopDetectedException );
+    }
+
+
+    @Test
+    public void testDelete() throws Exception
+    {
+        String dn = "uid=user.X,ou=users,ou=system";
+
+        // create entry
+        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), dn,
+            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
+
+        // delete entry
+        StudioProgressMonitor monitor = getProgressMonitor();
+        getConnectionWrapper( monitor ).deleteEntry( new Dn( dn ), null, monitor, null );
+
+        // should have deleted the entry
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertFalse( service.getAdminSession().exists( dn ) );
+    }
+
+
+    @Test
+    public void testDeleteFollowsReferral_DirectReferral() throws Exception
+    {
+        String targetDn = "uid=user.X,ou=users,ou=system";
+        String referralDn = "uid=user.X,cn=referral1,ou=referrals,ou=system";
+
+        // create target entry
+        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
+            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
+
+        // delete referral entry 
+        StudioProgressMonitor monitor = getProgressMonitor();
+        getConnectionWrapper( monitor ).deleteEntry( new Dn( referralDn ), null, monitor, null );
+
+        // should have deleted the target entry
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertFalse( service.getAdminSession().exists( targetDn ) );
+    }
+
+
+    @Test
+    public void testDeleteFollowsReferral_IntermediateReferral() throws Exception
+    {
+        String targetDn = "uid=user.X,ou=users,ou=system";
+        String referralDn = "uid=user.X,cn=referral2,ou=referrals,ou=system";
+
+        // create target entry
+        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
+            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
+
+        // delete referral entry 
+        StudioProgressMonitor monitor = getProgressMonitor();
+        getConnectionWrapper( monitor ).deleteEntry( new Dn( referralDn ), null, monitor, null );
+
+        // should have deleted the target entry
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertFalse( service.getAdminSession().exists( targetDn ) );
+    }
+
+
+    @Test
+    public void testDeleteFollowsReferral_ReferralLoop() throws Exception
+    {
+        String targetDn = "uid=user.X,ou=users,ou=system";
+        String referralDn = "uid=user.X,cn=referral4a,ou=referrals,ou=system";
+
+        // create target entry
+        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), targetDn,
+            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X" ) );
+
+        // delete referral entry 
+        StudioProgressMonitor monitor = getProgressMonitor();
+        getConnectionWrapper( monitor ).deleteEntry( new Dn( referralDn ), null, monitor, null );
+
+        // should not have deleted the target entry
+        assertFalse( monitor.isCanceled() );
+        assertTrue( monitor.errorsReported() );
+        assertNotNull( monitor.getException() );
+        assertTrue( monitor.getException() instanceof LdapLoopDetectedException );
+        assertTrue( service.getAdminSession().exists( targetDn ) );
+    }
+
 
     @Test
     public void testSearchContinuationFollowParent() throws Exception
@@ -178,4 +856,164 @@ public class DirectoryApiConnectionWrapperTest extends ConnectionWrapperTestBase
         assertTrue( closeFuture.isDone() );
     }
 
+
+    @Test
+    public void testPasswordModifyRequestExtendedOperation_AdminChangesUserPassword() throws Exception
+    {
+        String dn = "uid=user.X,ou=users,ou=system";
+
+        // create target entry
+        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), dn,
+            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X", "userPassword:  secret" ) );
+
+        // modify password
+        LdapApiService ldapApiService = LdapApiServiceFactory.getSingleton();
+        PasswordModifyRequest request = ( PasswordModifyRequest ) ldapApiService.getExtendedRequestFactories()
+            .get( PasswordModifyRequest.EXTENSION_OID ).newRequest();
+        request.setUserIdentity( Strings.getBytesUtf8( dn ) );
+        request.setNewPassword( Strings.getBytesUtf8( "s3cre3t" ) );
+        StudioProgressMonitor monitor = getProgressMonitor();
+        ExtendedResponse response = getConnectionWrapper( monitor ).extended( request, monitor );
+
+        // should have modified password of the target entry
+        assertEquals( ResultCodeEnum.SUCCESS, response.getLdapResult().getResultCode() );
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        Entry entry = service.getAdminSession().lookup( new Dn( dn ) );
+        assertEquals( "s3cre3t", Strings.utf8ToString( entry.get( "userPassword" ).getBytes() ) );
+    }
+
+
+    @Test
+    public void testPasswordModifyRequestExtendedOperation_UserChangesOwnPassword() throws Exception
+    {
+        LdapApiService ldapApiService = LdapApiServiceFactory.getSingleton();
+        String dn = "uid=user.X,ou=users,ou=system";
+
+        // create target entry
+        service.getAdminSession().add( new DefaultEntry( service.getSchemaManager(), dn,
+            "objectClass: inetOrgPerson", "sn: X", "cn: X", "uid: user.X", "userPassword:  secret" ) );
+
+        // modify password with wrong old password
+        PasswordModifyRequest request1 = ( PasswordModifyRequest ) ldapApiService.getExtendedRequestFactories()
+            .get( PasswordModifyRequest.EXTENSION_OID ).newRequest();
+        request1.setUserIdentity( Strings.getBytesUtf8( dn ) );
+        request1.setOldPassword( Strings.getBytesUtf8( "wrong" ) );
+        request1.setNewPassword( Strings.getBytesUtf8( "s3cre3t" ) );
+        StudioProgressMonitor monitor1 = getProgressMonitor();
+        ExtendedResponse response1 = getConnectionWrapper( monitor1, dn, "secret" ).extended( request1, monitor1 );
+
+        // should not have modified password of the target entry
+        assertEquals( ResultCodeEnum.INVALID_CREDENTIALS, response1.getLdapResult().getResultCode() );
+        assertFalse( monitor1.isCanceled() );
+        assertTrue( monitor1.errorsReported() );
+        Entry entry1 = service.getAdminSession().lookup( new Dn( dn ) );
+        assertEquals( "secret", Strings.utf8ToString( entry1.get( "userPassword" ).getBytes() ) );
+
+        // modify password with correct old password
+        PasswordModifyRequest request2 = ( PasswordModifyRequest ) ldapApiService.getExtendedRequestFactories()
+            .get( PasswordModifyRequest.EXTENSION_OID ).newRequest();
+        request2.setUserIdentity( Strings.getBytesUtf8( dn ) );
+        request2.setOldPassword( Strings.getBytesUtf8( "secret" ) );
+        request2.setNewPassword( Strings.getBytesUtf8( "s3cre3t" ) );
+        StudioProgressMonitor monitor2 = getProgressMonitor();
+        ExtendedResponse response2 = getConnectionWrapper( monitor2, dn, "secret" ).extended( request2, monitor2 );
+
+        // should have modified password of the target entry
+        assertEquals( ResultCodeEnum.SUCCESS, response2.getLdapResult().getResultCode() );
+        assertFalse( monitor2.isCanceled() );
+        assertFalse( monitor2.errorsReported() );
+        Entry entry2 = service.getAdminSession().lookup( new Dn( dn ) );
+        assertEquals( "s3cre3t", Strings.utf8ToString( entry2.get( "userPassword" ).getBytes() ) );
+    }
+
+
+    @Ignore
+    @Test
+    public void testWhoAmIExtendedOperation() throws Exception
+    {
+        LdapApiService ldapApiService = LdapApiServiceFactory.getSingleton();
+        WhoAmIRequest request = ( WhoAmIRequest ) ldapApiService.getExtendedRequestFactories()
+            .get( WhoAmIRequest.EXTENSION_OID ).newRequest();
+        StudioProgressMonitor monitor = getProgressMonitor();
+        WhoAmIResponse response = ( WhoAmIResponse ) getConnectionWrapper( monitor ).extended( request, monitor );
+
+        assertEquals( ResultCodeEnum.SUCCESS, response.getLdapResult().getResultCode() );
+        assertFalse( monitor.isCanceled() );
+        assertFalse( monitor.errorsReported() );
+        assertTrue( response.isDnAuthzId() );
+        assertEquals( "uid=admin,ou=system", response.getDn().toString() );
+    }
+
+
+    /*
+    @Ignore
+    @Test
+    public void testStartEndTransactionExtendedOperation() throws Exception
+    {
+        LdapApiService ldapApiService = LdapApiServiceFactory.getSingleton();
+
+        StartTransactionRequest request1 = ( StartTransactionRequest ) ldapApiService.getExtendedRequestFactories()
+            .get( StartTransactionRequest.EXTENSION_OID ).newRequest();
+        StudioProgressMonitor monitor1 = getProgressMonitor();
+        StartTransactionResponse response1 = ( StartTransactionResponse ) getConnectionWrapper( monitor1 )
+            .extended( request1, monitor1 );
+
+        assertEquals( ResultCodeEnum.SUCCESS, response1.getLdapResult().getResultCode() );
+        assertFalse( monitor1.isCanceled() );
+        assertFalse( monitor1.errorsReported() );
+
+        EndTransactionRequest request2 = ( EndTransactionRequest ) ldapApiService.getExtendedRequestFactories()
+            .get( EndTransactionRequest.EXTENSION_OID ).newRequest();
+        request2.setTransactionId( response1.getTransactionId() );
+        request2.setCommit( true );
+        StudioProgressMonitor monitor2 = getProgressMonitor();
+        EndTransactionResponse response2 = ( EndTransactionResponse ) getConnectionWrapper( monitor2 )
+            .extended( request2, monitor2 );
+
+        assertEquals( ResultCodeEnum.SUCCESS, response2.getLdapResult().getResultCode() );
+        assertFalse( monitor2.isCanceled() );
+        assertFalse( monitor2.errorsReported() );
+    }
+    */
+
+
+    protected StudioProgressMonitor getProgressMonitor()
+    {
+        StudioProgressMonitor monitor = new StudioProgressMonitor( new NullProgressMonitor() );
+        return monitor;
+    }
+
+
+    protected ConnectionWrapper getConnectionWrapper( StudioProgressMonitor monitor )
+    {
+        return getConnectionWrapper( monitor, "uid=admin,ou=system", "secret" );
+    }
+
+
+    protected ConnectionWrapper getConnectionWrapper( StudioProgressMonitor monitor, String dn, String password )
+    {
+        // simple auth without principal and credential
+        ConnectionParameter connectionParameter = new ConnectionParameter( null, LOCALHOST, ldapServer.getPort(),
+            EncryptionMethod.NONE, AuthenticationMethod.SIMPLE, dn, password, null, false, null, 30000L );
+
+        Connection connection = new Connection( connectionParameter );
+
+        connectionWrapper = connection.getConnectionWrapper();
+        connectionWrapper.connect( monitor );
+        connectionWrapper.bind( monitor );
+
+        assertTrue( connectionWrapper.isConnected() );
+
+        IReferralHandler referralHandler = referralUrls -> {
+            return connection;
+        };
+        ConnectionCorePlugin.getDefault().setReferralHandler( referralHandler );
+
+        assertTrue( connectionWrapper.isConnected() );
+        assertNull( monitor.getException() );
+
+        return connectionWrapper;
+    }
+
 }
diff --git a/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/ExtendedOperationsTest.java b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/ExtendedOperationsTest.java
new file mode 100644
index 0000000..ea9e09b
--- /dev/null
+++ b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/ExtendedOperationsTest.java
@@ -0,0 +1,228 @@
+/*
+ *  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.test.integration.ui;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.directory.server.annotations.CreateLdapServer;
+import org.apache.directory.server.annotations.CreateTransport;
+import org.apache.directory.server.core.annotations.ApplyLdifFiles;
+import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
+import org.apache.directory.server.ldap.handlers.extended.PwdModifyHandler;
+import org.apache.directory.studio.test.integration.ui.bots.BotUtils;
+import org.apache.directory.studio.test.integration.ui.bots.BrowserViewBot;
+import org.apache.directory.studio.test.integration.ui.bots.ConnectionsViewBot;
+import org.apache.directory.studio.test.integration.ui.bots.EntryEditorBot;
+import org.apache.directory.studio.test.integration.ui.bots.ErrorDialogBot;
+import org.apache.directory.studio.test.integration.ui.bots.PasswordEditorDialogBot;
+import org.apache.directory.studio.test.integration.ui.bots.PasswordModifyExtendedOperationDialogBot;
+import org.apache.directory.studio.test.integration.ui.bots.StudioBot;
+import org.apache.directory.studio.test.integration.ui.bots.utils.Assertions;
+import org.apache.directory.studio.test.integration.ui.bots.utils.FrameworkRunnerWithScreenshotCaptureListener;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/**
+ * Tests the extended operations.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+@RunWith(FrameworkRunnerWithScreenshotCaptureListener.class)
+@CreateLdapServer(transports =
+    { @CreateTransport(protocol = "LDAP") }, extendedOpHandlers =
+    { PwdModifyHandler.class })
+@ApplyLdifFiles(clazz = ExtendedOperationsTest.class, value = "org/apache/directory/studio/test/integration/ui/BrowserTest.ldif")
+public class ExtendedOperationsTest extends AbstractLdapTestUnit
+{
+    private StudioBot studioBot;
+    private ConnectionsViewBot connectionsViewBot;
+    private BrowserViewBot browserViewBot;
+
+
+    @Before
+    public void setUp() throws Exception
+    {
+        studioBot = new StudioBot();
+        studioBot.resetLdapPerspective();
+        connectionsViewBot = studioBot.getConnectionView();
+        connectionsViewBot.createTestConnection( "BrowserTest", ldapServer.getPort() );
+        browserViewBot = studioBot.getBrowserView();
+    }
+
+
+    @After
+    public void tearDown() throws Exception
+    {
+        connectionsViewBot.deleteTestConnections();
+        Assertions.genericTearDownAssertions();
+    }
+
+
+    @Test
+    public void testPasswordModifyExtendedOperationDialogValidation()
+    {
+        browserViewBot.selectEntry( "DIT", "Root DSE", "ou=system", "ou=users", "uid=user.1" );
+
+        // Open dialog
+        PasswordModifyExtendedOperationDialogBot dialogBot = browserViewBot.openPasswordModifyExtendedOperationDialog();
+        assertTrue( dialogBot.isVisible() );
+
+        // Verify default UI state
+        assertEquals( "uid=user.1,ou=users,ou=system", dialogBot.getUserIdentity() );
+        assertFalse( dialogBot.useBindUserIdentity() );
+        assertEquals( "", dialogBot.getOldPassword() );
+        assertFalse( dialogBot.noOldPassword() );
+        assertEquals( "", dialogBot.getNewPassword() );
+        assertFalse( dialogBot.generateNewPassword() );
+        assertFalse( dialogBot.isOkButtonEnabled() );
+
+        // Enter passwords, should enable OK button
+        dialogBot.setOldPassword( "old123" );
+        dialogBot.setNewPassword( "new456" );
+        assertTrue( dialogBot.isOkButtonEnabled() );
+
+        // Check "Use bind user identity"
+        dialogBot.useBindUserIdentity( true );
+        assertEquals( "", dialogBot.getUserIdentity() );
+        assertTrue( dialogBot.isOkButtonEnabled() );
+        dialogBot.useBindUserIdentity( false );
+        assertEquals( "", dialogBot.getUserIdentity() );
+        assertFalse( dialogBot.isOkButtonEnabled() );
+        dialogBot.useBindUserIdentity( true );
+
+        // Check "No old password"
+        dialogBot.noOldPassword( true );
+        assertEquals( "", dialogBot.getOldPassword() );
+        assertTrue( dialogBot.isOkButtonEnabled() );
+        dialogBot.noOldPassword( false );
+        assertEquals( "", dialogBot.getOldPassword() );
+        assertFalse( dialogBot.isOkButtonEnabled() );
+        dialogBot.noOldPassword( true );
+
+        // Check "Generate new password"
+        dialogBot.generateNewPassword( true );
+        assertEquals( "", dialogBot.getNewPassword() );
+        assertTrue( dialogBot.isOkButtonEnabled() );
+        dialogBot.generateNewPassword( false );
+        assertEquals( "", dialogBot.getNewPassword() );
+        assertFalse( dialogBot.isOkButtonEnabled() );
+        dialogBot.generateNewPassword( true );
+
+        // Uncheck all again
+        dialogBot.useBindUserIdentity( false );
+        dialogBot.noOldPassword( false );
+        dialogBot.generateNewPassword( false );
+        assertFalse( dialogBot.isOkButtonEnabled() );
+
+        // Fill data
+        dialogBot.setNewPassword( "new123" );
+        dialogBot.setOldPassword( "old456" );
+        dialogBot.setUserIdentity( "foo=bar" );
+        assertTrue( dialogBot.isOkButtonEnabled() );
+
+        dialogBot.clickCancelButton();
+    }
+
+
+    @Test
+    public void testPasswordModifyExtendedOperationDialogOk()
+    {
+        String random = RandomStringUtils.random( 20 );
+        browserViewBot.selectEntry( "DIT", "Root DSE", "ou=system", "ou=users", "uid=user.1" );
+        String dn = "uid=user.1,ou=users,ou=system";
+
+        // Open dialog
+        PasswordModifyExtendedOperationDialogBot dialogBot = browserViewBot.openPasswordModifyExtendedOperationDialog();
+        assertTrue( dialogBot.isVisible() );
+        assertEquals( dn, dialogBot.getUserIdentity() );
+
+        // Change password
+        dialogBot.noOldPassword( true );
+        dialogBot.setNewPassword( random );
+        dialogBot.clickOkButton();
+
+        // Verify and bind with the correct password
+        browserViewBot.refresh();
+        BotUtils.sleep( 1000L );
+        EntryEditorBot entryEditorBot = studioBot.getEntryEditorBot( dn );
+        entryEditorBot.activate();
+        PasswordEditorDialogBot pwdEditorBot = entryEditorBot.editValueExpectingPasswordEditor( "userPassword",
+            "Plain text password" );
+        pwdEditorBot.activateCurrentPasswordTab();
+        pwdEditorBot.setVerifyPassword( random );
+        assertNull( pwdEditorBot.clickVerifyButton() );
+        assertNull( pwdEditorBot.clickBindButton() );
+        pwdEditorBot.clickCancelButton();
+    }
+
+
+    @Test
+    public void testPasswordModifyExtendedOperationDialogError()
+    {
+        String random = RandomStringUtils.random( 20 );
+        browserViewBot.selectEntry( "DIT", "Root DSE", "ou=system", "ou=users", "uid=user.1" );
+        String dn = "uid=user.1,ou=users,ou=system";
+
+        // Open dialog
+        PasswordModifyExtendedOperationDialogBot dialogBot = browserViewBot.openPasswordModifyExtendedOperationDialog();
+        assertTrue( dialogBot.isVisible() );
+        assertEquals( dn, dialogBot.getUserIdentity() );
+
+        // Wrong old password
+        dialogBot.activate();
+        dialogBot.setUserIdentity( dn );
+        dialogBot.setOldPassword( "wrong password" );
+        dialogBot.setNewPassword( random );
+        ErrorDialogBot errorBot = dialogBot.clickOkButtonExpectingErrorDialog();
+        assertTrue( errorBot.getErrorMessage().contains( "invalid credentials" ) );
+        errorBot.clickOkButton();
+
+        // Not existing entry
+        dialogBot.activate();
+        dialogBot.setUserIdentity( "cn=non-existing-entry" );
+        dialogBot.noOldPassword( true );
+        dialogBot.setNewPassword( random );
+        errorBot = dialogBot.clickOkButtonExpectingErrorDialog();
+        assertTrue( errorBot.getErrorMessage().contains( "The entry does not exist" ) );
+        errorBot.clickOkButton();
+
+        // ApacheDS does not support password generation
+        dialogBot.activate();
+        dialogBot.setUserIdentity( dn );
+        dialogBot.noOldPassword( true );
+        dialogBot.generateNewPassword( true );
+        errorBot = dialogBot.clickOkButtonExpectingErrorDialog();
+        assertTrue( errorBot.getErrorMessage().contains( "null new password" ) );
+        errorBot.clickOkButton();
+
+        dialogBot.activate();
+        dialogBot.clickCancelButton();
+    }
+}
diff --git a/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/NewEntryWizardTest.java b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/NewEntryWizardTest.java
index 436a763..7b73db8 100644
--- a/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/NewEntryWizardTest.java
+++ b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/NewEntryWizardTest.java
@@ -79,8 +79,6 @@ public class NewEntryWizardTest extends AbstractLdapTestUnit
     @Before
     public void setUp() throws Exception
     {
-        ErrorDialog.AUTOMATED_MODE = false;
-
         // enable krb5kdc and nis schemas
         ApacheDsUtils.enableSchema( ldapServer, "krb5kdc" );
         ApacheDsUtils.enableSchema( ldapServer, "nis" );
diff --git a/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/BrowserViewBot.java b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/BrowserViewBot.java
index 7f1f09e..2fb4712 100644
--- a/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/BrowserViewBot.java
+++ b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/BrowserViewBot.java
@@ -236,6 +236,13 @@ public class BrowserViewBot
     }
 
 
+    public PasswordModifyExtendedOperationDialogBot openPasswordModifyExtendedOperationDialog()
+    {
+        ContextMenuHelper.clickContextMenu( browserBot.getTree(), "Extended Operations", "Password Modify..." );
+        return new PasswordModifyExtendedOperationDialogBot();
+    }
+
+
     public void typeQuickSearchAttributeType( String attributeType )
     {
         bot.comboBox( 0 ).setText( attributeType );
diff --git a/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/DialogBot.java b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/DialogBot.java
index 57fa02a..029b5aa 100644
--- a/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/DialogBot.java
+++ b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/DialogBot.java
@@ -52,6 +52,20 @@ public abstract class DialogBot
     }
 
 
+    public boolean isOkButtonEnabled()
+    {
+        return isButtonEnabled( "OK" );
+    }
+
+
+    private boolean isButtonEnabled( String buttonTitle )
+    {
+        activate();
+        final SWTBotButton button = bot.button( buttonTitle );
+        return button.isEnabled();
+    }
+
+
     public void clickOkButton()
     {
         clickButton( "OK" );
diff --git a/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/PasswordModifyExtendedOperationDialogBot.java b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/PasswordModifyExtendedOperationDialogBot.java
new file mode 100644
index 0000000..3d18eda
--- /dev/null
+++ b/tests/test.integration.ui/src/main/java/org/apache/directory/studio/test/integration/ui/bots/PasswordModifyExtendedOperationDialogBot.java
@@ -0,0 +1,122 @@
+package org.apache.directory.studio.test.integration.ui.bots;
+
+
+import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
+
+
+public class PasswordModifyExtendedOperationDialogBot extends DialogBot
+{
+    public PasswordModifyExtendedOperationDialogBot()
+    {
+        super( "Password Modify Extended Operation (RFC 3062)" );
+    }
+
+
+    public String getUserIdentity()
+    {
+        return bot.comboBox().getText();
+    }
+
+
+    public void setUserIdentity( String text )
+    {
+        bot.comboBox().setText( text );
+    }
+
+
+    public boolean useBindUserIdentity()
+    {
+        return bot.checkBox( 0 ).isChecked();
+    }
+
+
+    public void useBindUserIdentity( boolean selected )
+    {
+        if ( selected )
+        {
+            bot.checkBox( 0 ).select();
+        }
+        else
+        {
+            bot.checkBox( 0 ).deselect();
+        }
+    }
+
+
+    public String getOldPassword()
+    {
+        return bot.text( 0 ).getText();
+    }
+
+
+    public void setOldPassword( String text )
+    {
+        bot.text( 0 ).setText( text );
+    }
+
+
+    public boolean noOldPassword()
+    {
+        return bot.checkBox( 1 ).isChecked();
+    }
+
+
+    public void noOldPassword( boolean selected )
+    {
+        if ( selected )
+        {
+            bot.checkBox( 1 ).select();
+        }
+        else
+        {
+            bot.checkBox( 1 ).deselect();
+        }
+    }
+
+
+    public String getNewPassword()
+    {
+        return bot.text( 1 ).getText();
+    }
+
+
+    public void setNewPassword( String text )
+    {
+        bot.text( 1 ).setText( text );
+    }
+
+
+    public boolean generateNewPassword()
+    {
+        return bot.checkBox( 2 ).isChecked();
+    }
+
+
+    public void generateNewPassword( boolean selected )
+    {
+        if ( selected )
+        {
+            bot.checkBox( 2 ).select();
+        }
+        else
+        {
+            bot.checkBox( 2 ).deselect();
+        }
+    }
+
+
+    public ErrorDialogBot clickOkButtonExpectingErrorDialog()
+    {
+        SWTBotShell shell = BotUtils.shell( new Runnable()
+        {
+            public void run()
+            {
+                clickOkButton();
+            }
+        }, "Error" );
+        String shellText = shell.getText();
+
+        return new ErrorDialogBot( shellText );
+    }
+
+}


Mime
View raw message