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