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 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.extensions; 018 019import static com.forgerock.opendj.util.StaticUtils.getBytes; 020 021import java.io.UnsupportedEncodingException; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.HashSet; 025import java.util.LinkedHashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Set; 029import java.util.concurrent.atomic.AtomicReference; 030import java.util.concurrent.locks.ReadWriteLock; 031import java.util.concurrent.locks.ReentrantReadWriteLock; 032 033import org.forgerock.i18n.LocalizableMessage; 034import org.forgerock.i18n.LocalizedIllegalArgumentException; 035import org.forgerock.i18n.slf4j.LocalizedLogger; 036import org.forgerock.opendj.config.server.ConfigException; 037import org.forgerock.opendj.ldap.ByteString; 038import org.forgerock.opendj.ldap.DN; 039import org.forgerock.opendj.ldap.ModificationType; 040import org.forgerock.opendj.ldap.ResultCode; 041import org.forgerock.opendj.ldap.SearchScope; 042import org.forgerock.opendj.ldap.schema.AttributeType; 043import org.forgerock.opendj.server.config.server.GroupImplementationCfg; 044import org.forgerock.opendj.server.config.server.StaticGroupImplementationCfg; 045import org.forgerock.util.Reject; 046import org.forgerock.util.annotations.VisibleForTesting; 047import org.opends.server.api.Group; 048import org.opends.server.core.DirectoryServer; 049import org.opends.server.core.ModifyOperation; 050import org.opends.server.core.ModifyOperationBasis; 051import org.opends.server.core.ServerContext; 052import org.opends.server.protocols.ldap.LDAPControl; 053import org.opends.server.types.AcceptRejectWarn; 054import org.opends.server.types.Attribute; 055import org.opends.server.types.Attributes; 056import org.opends.server.types.Control; 057import org.opends.server.types.DirectoryException; 058import org.opends.server.types.Entry; 059import org.opends.server.types.InitializationException; 060import org.opends.server.types.MemberList; 061import org.opends.server.types.MembershipException; 062import org.opends.server.types.Modification; 063import org.opends.server.types.SearchFilter; 064 065import static org.forgerock.opendj.ldap.schema.CoreSchema.*; 066import static org.opends.messages.ExtensionMessages.*; 067import static org.opends.server.core.DirectoryServer.*; 068import static org.opends.server.protocols.internal.InternalClientConnection.*; 069import static org.opends.server.util.CollectionUtils.*; 070import static org.opends.server.util.ServerConstants.*; 071 072/** 073 * A static group implementation, in which the DNs of all members are explicitly 074 * listed. 075 * <p> 076 * There are three variants of static groups: 077 * <ul> 078 * <li>one based on the {@code groupOfNames} object class: which stores the 079 * member list in the {@code member} attribute</li> 080 * <li>one based on the {@code groupOfEntries} object class, which also stores 081 * the member list in the {@code member} attribute</li> 082 * <li>one based on the {@code groupOfUniqueNames} object class, which stores 083 * the member list in the {@code uniqueMember} attribute.</li> 084 * </ul> 085 */ 086public class StaticGroup extends Group<StaticGroupImplementationCfg> 087{ 088 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 089 090 /** The attribute type used to hold the membership list for this group. */ 091 private AttributeType memberAttributeType; 092 093 /** The DN of the entry that holds the definition for this group. */ 094 private DN groupEntryDN; 095 096 /** The set of the DNs of the members for this group. */ 097 private HashSet<CompactDn> memberDNs; 098 099 /** The list of nested group DNs for this group. */ 100 private LinkedList<DN> nestedGroups = new LinkedList<>(); 101 102 /** Passed to the group manager to see if the nested group list needs to be refreshed. */ 103 private long nestedGroupRefreshToken = DirectoryServer.getGroupManager().refreshToken(); 104 105 /** Read/write lock protecting memberDNs and nestedGroups. */ 106 private ReadWriteLock lock = new ReentrantReadWriteLock(); 107 108 private ServerContext serverContext; 109 110 /** 111 * Creates an uninitialized static group. This is intended for internal use 112 * only, to allow {@code GroupManager} to dynamically create a group. 113 */ 114 public StaticGroup() 115 { 116 super(); 117 } 118 119 /** 120 * Creates a new static group instance with the provided information. 121 * 122 * @param groupEntryDN The DN of the entry that holds the definition 123 * for this group. 124 * @param memberAttributeType The attribute type used to hold the membership 125 * list for this group. 126 * @param memberDNs The set of the DNs of the members for this 127 * group. 128 */ 129 private StaticGroup(ServerContext serverContext, DN groupEntryDN, AttributeType memberAttributeType, 130 LinkedHashSet<CompactDn> memberDNs) 131 { 132 super(); 133 Reject.ifNull(groupEntryDN, memberAttributeType, memberDNs); 134 135 this.serverContext = serverContext; 136 this.groupEntryDN = groupEntryDN; 137 this.memberAttributeType = memberAttributeType; 138 this.memberDNs = memberDNs; 139 } 140 141 @Override 142 public void initializeGroupImplementation(StaticGroupImplementationCfg configuration) 143 throws ConfigException, InitializationException 144 { 145 // No additional initialization is required. 146 } 147 148 @Override 149 public StaticGroup newInstance(ServerContext serverContext, Entry groupEntry) throws DirectoryException 150 { 151 Reject.ifNull(groupEntry); 152 153 // Determine whether it is a groupOfNames, groupOfEntries or 154 // groupOfUniqueNames entry. If not, then that's a problem. 155 AttributeType someMemberAttributeType; 156 boolean hasGroupOfEntriesClass = hasObjectClass(groupEntry, OC_GROUP_OF_ENTRIES_LC); 157 boolean hasGroupOfNamesClass = hasObjectClass(groupEntry, OC_GROUP_OF_NAMES_LC); 158 boolean hasGroupOfUniqueNamesClass = hasObjectClass(groupEntry, OC_GROUP_OF_UNIQUE_NAMES_LC); 159 if (hasGroupOfEntriesClass) 160 { 161 if (hasGroupOfNamesClass) 162 { 163 LocalizableMessage message = ERR_STATICGROUP_INVALID_OC_COMBINATION.get( 164 groupEntry.getName(), OC_GROUP_OF_ENTRIES, OC_GROUP_OF_NAMES); 165 throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message); 166 } 167 else if (hasGroupOfUniqueNamesClass) 168 { 169 LocalizableMessage message = ERR_STATICGROUP_INVALID_OC_COMBINATION.get( 170 groupEntry.getName(), OC_GROUP_OF_ENTRIES, OC_GROUP_OF_UNIQUE_NAMES); 171 throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message); 172 } 173 174 someMemberAttributeType = getMemberAttributeType(); 175 } 176 else if (hasGroupOfNamesClass) 177 { 178 if (hasGroupOfUniqueNamesClass) 179 { 180 LocalizableMessage message = ERR_STATICGROUP_INVALID_OC_COMBINATION.get( 181 groupEntry.getName(), OC_GROUP_OF_NAMES, OC_GROUP_OF_UNIQUE_NAMES); 182 throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message); 183 } 184 185 someMemberAttributeType = getMemberAttributeType(); 186 } 187 else if (hasGroupOfUniqueNamesClass) 188 { 189 someMemberAttributeType = getUniqueMemberAttributeType(); 190 } 191 else 192 { 193 LocalizableMessage message = 194 ERR_STATICGROUP_NO_VALID_OC.get(groupEntry.getName(), OC_GROUP_OF_NAMES, OC_GROUP_OF_UNIQUE_NAMES); 195 throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message); 196 } 197 198 List<Attribute> memberAttrList = groupEntry.getAttribute(someMemberAttributeType); 199 int membersCount = 0; 200 for (Attribute a : memberAttrList) 201 { 202 membersCount += a.size(); 203 } 204 LinkedHashSet<CompactDn> someMemberDNs = new LinkedHashSet<>(membersCount); 205 for (Attribute a : memberAttrList) 206 { 207 for (ByteString v : a) 208 { 209 try 210 { 211 someMemberDNs.add(new CompactDn(DN.valueOf(v.toString()))); 212 } 213 catch (LocalizedIllegalArgumentException e) 214 { 215 logger.traceException(e); 216 if (DirectoryServer.getSyntaxEnforcementPolicy() == AcceptRejectWarn.REJECT) 217 { 218 logger.error(ERR_STATICGROUP_CANNOT_DECODE_MEMBER_VALUE_AS_DN, 219 v, someMemberAttributeType.getNameOrOID(), groupEntry.getName(), e.getMessageObject()); 220 } 221 // else just ignore this value (issue OPENDJ-2833) 222 } 223 } 224 } 225 return new StaticGroup(serverContext, groupEntry.getName(), someMemberAttributeType, someMemberDNs); 226 } 227 228 @Override 229 public SearchFilter getGroupDefinitionFilter() 230 throws DirectoryException 231 { 232 // FIXME -- This needs to exclude enhanced groups once we have support for them. 233 String filterString = 234 "(&(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)" + 235 "(objectClass=groupOfEntries))" + 236 "(!(objectClass=ds-virtual-static-group)))"; 237 return SearchFilter.createFilterFromString(filterString); 238 } 239 240 @Override 241 public boolean isGroupDefinition(Entry entry) 242 { 243 Reject.ifNull(entry); 244 245 // FIXME -- This needs to exclude enhanced groups once we have support for them. 246 if (hasObjectClass(entry, OC_VIRTUAL_STATIC_GROUP)) 247 { 248 return false; 249 } 250 251 boolean hasGroupOfEntriesClass = hasObjectClass(entry, OC_GROUP_OF_ENTRIES_LC); 252 boolean hasGroupOfNamesClass = hasObjectClass(entry, OC_GROUP_OF_NAMES_LC); 253 boolean hasGroupOfUniqueNamesClass = hasObjectClass(entry, OC_GROUP_OF_UNIQUE_NAMES_LC); 254 if (hasGroupOfEntriesClass) 255 { 256 return !hasGroupOfNamesClass 257 && !hasGroupOfUniqueNamesClass; 258 } 259 else if (hasGroupOfNamesClass) 260 { 261 return !hasGroupOfUniqueNamesClass; 262 } 263 else 264 { 265 return hasGroupOfUniqueNamesClass; 266 } 267 } 268 269 private boolean hasObjectClass(Entry entry, String ocName) 270 { 271 return entry.hasObjectClass(DirectoryServer.getSchema().getObjectClass(ocName)); 272 } 273 274 @Override 275 public DN getGroupDN() 276 { 277 return groupEntryDN; 278 } 279 280 @Override 281 public void setGroupDN(DN groupDN) 282 { 283 groupEntryDN = groupDN; 284 } 285 286 @Override 287 public boolean supportsNestedGroups() 288 { 289 return true; 290 } 291 292 @Override 293 public List<DN> getNestedGroupDNs() 294 { 295 try 296 { 297 reloadIfNeeded(); 298 } 299 catch (DirectoryException ex) 300 { 301 return Collections.<DN>emptyList(); 302 } 303 lock.readLock().lock(); 304 try 305 { 306 return nestedGroups; 307 } 308 finally 309 { 310 lock.readLock().unlock(); 311 } 312 } 313 314 @Override 315 public void addNestedGroup(DN nestedGroupDN) 316 throws UnsupportedOperationException, DirectoryException 317 { 318 Reject.ifNull(nestedGroupDN); 319 320 lock.writeLock().lock(); 321 try 322 { 323 if (nestedGroups.contains(nestedGroupDN)) 324 { 325 LocalizableMessage msg = ERR_STATICGROUP_ADD_NESTED_GROUP_ALREADY_EXISTS.get(nestedGroupDN, groupEntryDN); 326 throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS, msg); 327 } 328 329 ModifyOperation modifyOperation = newModifyOperation(ModificationType.ADD, nestedGroupDN); 330 modifyOperation.run(); 331 if (modifyOperation.getResultCode() != ResultCode.SUCCESS) 332 { 333 LocalizableMessage msg = ERR_STATICGROUP_ADD_MEMBER_UPDATE_FAILED.get( 334 nestedGroupDN, groupEntryDN, modifyOperation.getErrorMessage()); 335 throw new DirectoryException(modifyOperation.getResultCode(), msg); 336 } 337 338 LinkedList<DN> newNestedGroups = new LinkedList<>(nestedGroups); 339 newNestedGroups.add(nestedGroupDN); 340 nestedGroups = newNestedGroups; 341 //Add it to the member DN list. 342 HashSet<CompactDn> newMemberDNs = new HashSet<>(memberDNs); 343 newMemberDNs.add(new CompactDn(nestedGroupDN)); 344 memberDNs = newMemberDNs; 345 } 346 finally 347 { 348 lock.writeLock().unlock(); 349 } 350 } 351 352 @Override 353 public void removeNestedGroup(DN nestedGroupDN) 354 throws UnsupportedOperationException, DirectoryException 355 { 356 Reject.ifNull(nestedGroupDN); 357 358 lock.writeLock().lock(); 359 try 360 { 361 if (! nestedGroups.contains(nestedGroupDN)) 362 { 363 throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, 364 ERR_STATICGROUP_REMOVE_NESTED_GROUP_NO_SUCH_GROUP.get(nestedGroupDN, groupEntryDN)); 365 } 366 367 ModifyOperation modifyOperation = newModifyOperation(ModificationType.DELETE, nestedGroupDN); 368 modifyOperation.run(); 369 if (modifyOperation.getResultCode() != ResultCode.SUCCESS) 370 { 371 LocalizableMessage message = ERR_STATICGROUP_REMOVE_MEMBER_UPDATE_FAILED.get( 372 nestedGroupDN, groupEntryDN, modifyOperation.getErrorMessage()); 373 throw new DirectoryException(modifyOperation.getResultCode(), message); 374 } 375 376 LinkedList<DN> newNestedGroups = new LinkedList<>(nestedGroups); 377 newNestedGroups.remove(nestedGroupDN); 378 nestedGroups = newNestedGroups; 379 //Remove it from the member DN list. 380 LinkedHashSet<CompactDn> newMemberDNs = new LinkedHashSet<>(memberDNs); 381 newMemberDNs.remove(new CompactDn(nestedGroupDN)); 382 memberDNs = newMemberDNs; 383 } 384 finally 385 { 386 lock.writeLock().unlock(); 387 } 388 } 389 390 @Override 391 public boolean isMember(DN userDN, AtomicReference<Set<DN>> examinedGroups) throws DirectoryException 392 { 393 reloadIfNeeded(); 394 CompactDn compactUserDN = new CompactDn(userDN); 395 lock.readLock().lock(); 396 try 397 { 398 if (memberDNs.contains(compactUserDN)) 399 { 400 return true; 401 } 402 if (nestedGroups.isEmpty()) { 403 return false; 404 } 405 406 // there are nested groups 407 Set<DN> groups = getExaminedGroups(examinedGroups); 408 if (!groups.add(getGroupDN())) 409 { 410 return false; 411 } 412 for (DN nestedGroupDN : nestedGroups) 413 { 414 Group<? extends GroupImplementationCfg> group = getGroupManager().getGroupInstance(nestedGroupDN); 415 if (group != null && group.isMember(userDN, examinedGroups)) 416 { 417 return true; 418 } 419 } 420 } 421 finally 422 { 423 lock.readLock().unlock(); 424 } 425 return false; 426 } 427 428 private Set<DN> getExaminedGroups(AtomicReference<Set<DN>> examinedGroups) 429 { 430 Set<DN> groups = examinedGroups.get(); 431 if (groups == null) 432 { 433 groups = new HashSet<DN>(); 434 examinedGroups.set(groups); 435 } 436 return groups; 437 } 438 439 @Override 440 public boolean isMember(Entry userEntry, AtomicReference<Set<DN>> examinedGroups) 441 throws DirectoryException 442 { 443 return isMember(userEntry.getName(), examinedGroups); 444 } 445 446 /** 447 * Check if the group manager has registered a new group instance or removed a 448 * a group instance that might impact this group's membership list. 449 */ 450 private void reloadIfNeeded() throws DirectoryException 451 { 452 //Check if group instances have changed by passing the group manager 453 //the current token. 454 if (DirectoryServer.getGroupManager().hasInstancesChanged(nestedGroupRefreshToken)) 455 { 456 lock.writeLock().lock(); 457 try 458 { 459 Group<?> thisGroup = DirectoryServer.getGroupManager().getGroupInstance(groupEntryDN); 460 // Check if the group itself has been removed 461 if (thisGroup == null) 462 { 463 throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, 464 ERR_STATICGROUP_GROUP_INSTANCE_INVALID.get(groupEntryDN)); 465 } 466 else if (thisGroup != this) 467 { 468 LinkedHashSet<CompactDn> newMemberDNs = new LinkedHashSet<>(); 469 MemberList memberList = thisGroup.getMembers(); 470 while (memberList.hasMoreMembers()) 471 { 472 try 473 { 474 newMemberDNs.add(new CompactDn(memberList.nextMemberDN())); 475 } 476 catch (MembershipException ex) 477 { 478 // TODO: should we throw an exception there instead of silently fail ? 479 } 480 } 481 memberDNs = newMemberDNs; 482 } 483 nestedGroups.clear(); 484 for (CompactDn compactDn : memberDNs) 485 { 486 DN dn = compactDn.toDn(serverContext); 487 Group<?> group = DirectoryServer.getGroupManager().getGroupInstance(dn); 488 if (group != null) 489 { 490 nestedGroups.add(group.getGroupDN()); 491 } 492 } 493 nestedGroupRefreshToken = DirectoryServer.getGroupManager().refreshToken(); 494 } 495 finally 496 { 497 lock.writeLock().unlock(); 498 } 499 } 500 } 501 502 @Override 503 public MemberList getMembers() throws DirectoryException 504 { 505 reloadIfNeeded(); 506 lock.readLock().lock(); 507 try 508 { 509 return new SimpleStaticGroupMemberList(serverContext, groupEntryDN, memberDNs); 510 } 511 finally 512 { 513 lock.readLock().unlock(); 514 } 515 } 516 517 @Override 518 public MemberList getMembers(DN baseDN, SearchScope scope, SearchFilter filter) throws DirectoryException 519 { 520 reloadIfNeeded(); 521 lock.readLock().lock(); 522 try 523 { 524 if (baseDN == null && filter == null) 525 { 526 return new SimpleStaticGroupMemberList(serverContext, groupEntryDN, memberDNs); 527 } 528 return new FilteredStaticGroupMemberList(serverContext, groupEntryDN, memberDNs, baseDN, scope, filter); 529 } 530 finally 531 { 532 lock.readLock().unlock(); 533 } 534 } 535 536 @Override 537 public boolean mayAlterMemberList() 538 { 539 return true; 540 } 541 542 @Override 543 public void updateMembers(List<Modification> modifications) 544 throws UnsupportedOperationException, DirectoryException 545 { 546 Reject.ifNull(memberDNs); 547 Reject.ifNull(nestedGroups); 548 549 reloadIfNeeded(); 550 lock.writeLock().lock(); 551 try 552 { 553 for (Modification mod : modifications) 554 { 555 Attribute attribute = mod.getAttribute(); 556 if (attribute.getAttributeDescription().getAttributeType().equals(memberAttributeType)) 557 { 558 switch (mod.getModificationType().asEnum()) 559 { 560 case ADD: 561 for (ByteString v : attribute) 562 { 563 DN member = DN.valueOf(v); 564 memberDNs.add(new CompactDn(member)); 565 if (DirectoryServer.getGroupManager().getGroupInstance(member) != null) 566 { 567 nestedGroups.add(member); 568 } 569 } 570 break; 571 case DELETE: 572 if (attribute.isEmpty()) 573 { 574 memberDNs.clear(); 575 nestedGroups.clear(); 576 } 577 else 578 { 579 for (ByteString v : attribute) 580 { 581 DN member = DN.valueOf(v); 582 memberDNs.remove(new CompactDn(member)); 583 nestedGroups.remove(member); 584 } 585 } 586 break; 587 case REPLACE: 588 memberDNs.clear(); 589 nestedGroups.clear(); 590 for (ByteString v : attribute) 591 { 592 DN member = DN.valueOf(v); 593 memberDNs.add(new CompactDn(member)); 594 if (DirectoryServer.getGroupManager().getGroupInstance(member) != null) 595 { 596 nestedGroups.add(member); 597 } 598 } 599 break; 600 } 601 } 602 } 603 } 604 finally { 605 lock.writeLock().unlock(); 606 } 607 } 608 609 @Override 610 public void addMember(Entry userEntry) throws UnsupportedOperationException, DirectoryException 611 { 612 Reject.ifNull(userEntry); 613 614 lock.writeLock().lock(); 615 try 616 { 617 DN userDN = userEntry.getName(); 618 CompactDn compactUserDN = new CompactDn(userDN); 619 620 if (memberDNs.contains(compactUserDN)) 621 { 622 LocalizableMessage message = ERR_STATICGROUP_ADD_MEMBER_ALREADY_EXISTS.get(userDN, groupEntryDN); 623 throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS, message); 624 } 625 626 ModifyOperation modifyOperation = newModifyOperation(ModificationType.ADD, userDN); 627 modifyOperation.run(); 628 if (modifyOperation.getResultCode() != ResultCode.SUCCESS) 629 { 630 throw new DirectoryException(modifyOperation.getResultCode(), 631 ERR_STATICGROUP_ADD_MEMBER_UPDATE_FAILED.get(userDN, groupEntryDN, modifyOperation.getErrorMessage())); 632 } 633 634 LinkedHashSet<CompactDn> newMemberDNs = new LinkedHashSet<CompactDn>(memberDNs); 635 newMemberDNs.add(compactUserDN); 636 memberDNs = newMemberDNs; 637 } 638 finally 639 { 640 lock.writeLock().unlock(); 641 } 642 } 643 644 @Override 645 public void removeMember(DN userDN) throws UnsupportedOperationException, DirectoryException 646 { 647 Reject.ifNull(userDN); 648 649 CompactDn compactUserDN = new CompactDn(userDN); 650 lock.writeLock().lock(); 651 try 652 { 653 if (! memberDNs.contains(compactUserDN)) 654 { 655 LocalizableMessage message = ERR_STATICGROUP_REMOVE_MEMBER_NO_SUCH_MEMBER.get(userDN, groupEntryDN); 656 throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, message); 657 } 658 659 ModifyOperation modifyOperation = newModifyOperation(ModificationType.DELETE, userDN); 660 modifyOperation.run(); 661 if (modifyOperation.getResultCode() != ResultCode.SUCCESS) 662 { 663 throw new DirectoryException(modifyOperation.getResultCode(), 664 ERR_STATICGROUP_REMOVE_MEMBER_UPDATE_FAILED.get(userDN, groupEntryDN, modifyOperation.getErrorMessage())); 665 } 666 667 LinkedHashSet<CompactDn> newMemberDNs = new LinkedHashSet<>(memberDNs); 668 newMemberDNs.remove(compactUserDN); 669 memberDNs = newMemberDNs; 670 //If it is in the nested group list remove it. 671 if (nestedGroups.contains(userDN)) 672 { 673 LinkedList<DN> newNestedGroups = new LinkedList<>(nestedGroups); 674 newNestedGroups.remove(userDN); 675 nestedGroups = newNestedGroups; 676 } 677 } 678 finally 679 { 680 lock.writeLock().unlock(); 681 } 682 } 683 684 private ModifyOperation newModifyOperation(ModificationType modType, DN userDN) 685 { 686 Attribute attr = Attributes.create(memberAttributeType, userDN.toString()); 687 LinkedList<Modification> mods = newLinkedList(new Modification(modType, attr)); 688 Control control = new LDAPControl(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE, false); 689 690 return new ModifyOperationBasis(getRootConnection(), nextOperationID(), nextMessageID(), 691 newLinkedList(control), groupEntryDN, mods); 692 } 693 694 @Override 695 public void toString(StringBuilder buffer) 696 { 697 buffer.append("StaticGroup("); 698 buffer.append(groupEntryDN); 699 buffer.append(")"); 700 } 701 702 /** 703 * A compact representation of a DN, suitable for equality and comparisons, and providing a natural hierarchical 704 * ordering. 705 * <p> 706 * The memory consumption compared to a regular DN object is minimal. 707 */ 708 static final class CompactDn implements Comparable<CompactDn> 709 { 710 /** Original string corresponding to the DN. */ 711 private final byte[] originalValue; 712 713 /** 714 * Normalized byte string, suitable for equality and comparisons, and providing a natural 715 * hierarchical ordering, but not usable as a valid DN. 716 */ 717 private final byte[] normalizedValue; 718 719 @VisibleForTesting 720 CompactDn(DN dn) 721 { 722 this.originalValue = getBytes(dn.toString()); 723 this.normalizedValue = dn.toNormalizedByteString().toByteArray(); 724 } 725 726 @Override 727 public int compareTo(final CompactDn other) 728 { 729 final int length1 = normalizedValue.length; 730 final int length2 = other.normalizedValue.length; 731 int count = Math.min(length1, length2); 732 int i = 0; 733 int j = 0; 734 while (count-- != 0) 735 { 736 final int firstByte = 0xFF & normalizedValue[i++]; 737 final int secondByte = 0xFF & other.normalizedValue[j++]; 738 if (firstByte != secondByte) 739 { 740 return firstByte - secondByte; 741 } 742 } 743 return length1 - length2; 744 } 745 746 /** 747 * Returns the DN corresponding to this compact representation. 748 * 749 * @param serverContext 750 * The server context. 751 * 752 * @return the DN 753 */ 754 public DN toDn(ServerContext serverContext) 755 { 756 return DN.valueOf(toString(), serverContext.getSchemaNG()); 757 } 758 759 @Override 760 public int hashCode() 761 { 762 return Arrays.hashCode(normalizedValue); 763 } 764 765 @Override 766 public boolean equals(Object obj) 767 { 768 if (this == obj) 769 { 770 return true; 771 } 772 else if (obj instanceof CompactDn) 773 { 774 final CompactDn other = (CompactDn) obj; 775 return Arrays.equals(normalizedValue, other.normalizedValue); 776 } 777 else 778 { 779 return false; 780 } 781 } 782 783 @Override 784 public String toString() 785 { 786 final int length = originalValue.length; 787 if (length == 0) { 788 return ""; 789 } 790 try { 791 return new String(originalValue, 0, length, "UTF-8"); 792 } catch (final UnsupportedEncodingException e) { 793 // TODO: I18N 794 throw new RuntimeException("Unable to decode bytes as UTF-8 string", e); 795 } 796 } 797 } 798}