001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2016 ForgeRock AS. 015 */ 016package org.forgerock.opendj.examples; 017 018import static org.forgerock.util.Utils.closeSilently; 019import org.forgerock.opendj.ldap.Connection; 020import org.forgerock.opendj.ldap.LDAPConnectionFactory; 021import org.forgerock.opendj.ldap.LdapException; 022import org.forgerock.opendj.ldap.ModificationType; 023import org.forgerock.opendj.ldap.ResultCode; 024import org.forgerock.opendj.ldap.RootDSE; 025import org.forgerock.opendj.ldap.controls.PermissiveModifyRequestControl; 026import org.forgerock.opendj.ldap.requests.Requests; 027import org.forgerock.opendj.ldap.responses.BindResult; 028import org.forgerock.opendj.ldap.responses.CompareResult; 029import org.forgerock.opendj.ldap.responses.Responses; 030import org.forgerock.opendj.ldap.responses.Result; 031import org.forgerock.util.AsyncFunction; 032import org.forgerock.util.promise.ExceptionHandler; 033import org.forgerock.util.promise.Promise; 034import org.forgerock.util.promise.Promises; 035import org.forgerock.util.promise.ResultHandler; 036 037import java.util.concurrent.CountDownLatch; 038 039/** 040 * This command-line client demonstrates adding and removing a member from a 041 * (potentially large) static group using the asynchronous APIs. 042 * 043 * The client takes as arguments the host and port of the directory server, the 044 * group DN, the member DN, and whether to "add" or "del" the specified member 045 * from the group. The client uses the Permissive Modify control if it is 046 * available to avoid having to check whether the member belongs to the group or 047 * not. 048 * 049 * This client expects a group that is a <code>groupOfNames</code> such as: 050 * 051 * <pre> 052 * dn: cn=My Static Group,ou=Groups,dc=example,dc=com 053 * cn: My Static Group 054 * objectClass: groupOfNames 055 * objectClass: top 056 * ou: Groups 057 * member: uid=ahunter,ou=People,dc=example,dc=com 058 * member: uid=bjensen,ou=People,dc=example,dc=com 059 * member: uid=tmorris,ou=People,dc=example,dc=com 060 * </pre> 061 * 062 * This client connects as <code>cn=Directory Manager</code> with password 063 * <code>password</code>. Not a best practice; in real code use application 064 * specific credentials to connect, and ensure that your application has access 065 * to use the Permissive Modify control if your directory server supports it. 066 */ 067public final class UpdateGroupAsync { 068 /** Connection to the LDAP server. */ 069 private static Connection connection; 070 /** Result for the operation. */ 071 private static int resultCode; 072 /** Count down latch to wait for modify operation to complete. */ 073 private static final CountDownLatch COMPLETION_LATCH = new CountDownLatch(1); 074 075 /** 076 * Updates the group as necessary. 077 * 078 * @param args 079 * The command line arguments: host, port, group-dn, member-dn, (add|del) 080 */ 081 public static void main(String[] args) { 082 if (args.length != 5) { 083 printUsage(); 084 } 085 final String host = args[0]; 086 final int port = Integer.parseInt(args[1]); 087 final String groupDn = args[2]; 088 final String memberDn = args[3]; 089 final ModificationType modType = getModificationType(args[4]); 090 091 // Connect, bind, update group. 092 new LDAPConnectionFactory(host, port) 093 .getConnectionAsync() 094 .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() { 095 @Override 096 public Promise<BindResult, LdapException> apply(Connection connection) 097 throws LdapException { 098 UpdateGroupAsync.connection = connection; 099 return connection.bindAsync( 100 Requests.newSimpleBindRequest("cn=Directory Manager", "password".toCharArray())); 101 } 102 }) 103 .thenAsync(new AsyncFunction<BindResult, RootDSE, LdapException>() { 104 @Override 105 public Promise<RootDSE, LdapException> apply(BindResult bindResult) 106 throws LdapException { 107 return RootDSE.readRootDSEAsync(connection); 108 } 109 }) 110 .thenAsync(new AsyncFunction<RootDSE, Result, LdapException>() { 111 @Override 112 public Promise<Result, LdapException> apply(RootDSE rootDSE) throws LdapException { 113 // If the directory supports the Permissive Modify request control, 114 // then the modification type does not matter. 115 if (rootDSE.getSupportedControls().contains(PermissiveModifyRequestControl.OID)) { 116 log("Updating group membership."); 117 return connection.modifyAsync( 118 Requests.newModifyRequest(groupDn) 119 .addControl(PermissiveModifyRequestControl.newControl(true)) 120 .addModification(modType, "member", memberDn)); 121 } else { 122 return connection 123 // Check whether the member is present. 124 .compareAsync(Requests.newCompareRequest(groupDn, "member", memberDn)) 125 .thenAsync(new AsyncFunction<CompareResult, Result, LdapException>() { 126 @Override 127 public Promise<Result, LdapException> apply(CompareResult compareResult) 128 throws LdapException { 129 ResultCode rc = compareResult.getResultCode(); 130 // Only add the member if missing from the group. 131 if (modType.equals(ModificationType.ADD) 132 && rc.equals(ResultCode.COMPARE_FALSE)) { 133 log("Adding " + memberDn + " to " + groupDn + "."); 134 return connection.modifyAsync( 135 Requests.newModifyRequest(groupDn) 136 .addModification(modType, "member", memberDn)); 137 // Only delete if present in the group. 138 } else if (modType.equals(ModificationType.DELETE) 139 && rc.equals(ResultCode.COMPARE_TRUE)) { 140 log("Deleting " + memberDn + " from " + groupDn + "."); 141 return connection.modifyAsync( 142 Requests.newModifyRequest(groupDn) 143 .addModification(modType, "member", memberDn)); 144 } else { 145 return Promises.newResultPromise( 146 Responses.newResult(ResultCode.SUCCESS)); 147 } 148 } 149 }); 150 } 151 } 152 }) 153 .thenOnResult(new ResultHandler<Result>() { 154 @Override 155 public void handleResult(Result result) { 156 final String op = (modType == ModificationType.ADD) ? "added to" : "deleted from"; 157 log(memberDn + " has been " + op + " the group " + groupDn + "."); 158 resultCode = result.getResultCode().intValue(); 159 COMPLETION_LATCH.countDown(); 160 } 161 }) 162 .thenOnException(new ExceptionHandler<LdapException>() { 163 @Override 164 public void handleException(LdapException e) { 165 System.err.println(e.getMessage()); 166 resultCode = e.getResult().getResultCode().intValue(); 167 COMPLETION_LATCH.countDown(); 168 } 169 }); 170 171 try { 172 COMPLETION_LATCH.await(); 173 } catch (InterruptedException e) { 174 System.err.println(e.getMessage()); 175 System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue()); 176 return; 177 } 178 179 closeSilently(connection); 180 System.exit(resultCode); 181 } 182 183 /** Print usage then exit. */ 184 private static void printUsage() { 185 System.err.println("Usage: host port group-dn member-dn {add|del}"); 186 System.err.println("For example: localhost 1389 " 187 + "cn=Static,ou=Groups,dc=example,dc=com " 188 + "uid=user.5150,ou=People,dc=example,dc=com " 189 + "del"); 190 System.exit(1); 191 } 192 193 /** 194 * Return the modification type for the update operation. 195 * @param operation Operation specified as an argument (add or del). 196 */ 197 private static ModificationType getModificationType(String operation) { 198 final boolean isAdd = "add".equalsIgnoreCase(operation); 199 if (!isAdd && !"del".equalsIgnoreCase(operation)) { 200 printUsage(); 201 } 202 return isAdd ? ModificationType.ADD : ModificationType.DELETE; 203 } 204 205 /** 206 * Log a message to System.out. 207 * 208 * @param message The message to write to the console. 209 */ 210 private static void log(final String message) { 211 System.out.println(message); 212 } 213 214 /** Constructor not used. */ 215 private UpdateGroupAsync() { 216 // Not used. 217 } 218}