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