001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.security;
029
030import org.opencms.file.CmsGroup;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsRequestContext;
033import org.opencms.file.CmsResource;
034import org.opencms.main.CmsException;
035import org.opencms.main.OpenCms;
036import org.opencms.util.CmsStringUtil;
037import org.opencms.util.CmsUUID;
038
039import java.util.ArrayList;
040import java.util.Arrays;
041import java.util.Collections;
042import java.util.HashMap;
043import java.util.HashSet;
044import java.util.Iterator;
045import java.util.List;
046import java.util.Locale;
047import java.util.Map;
048import java.util.Set;
049
050import com.google.common.collect.HashMultimap;
051import com.google.common.collect.Sets;
052
053/**
054 * A role is used in the OpenCms security system to check if a user has access to a certain system function.<p>
055 *
056 * Roles are used to ensure access permissions to system function that are not file based.
057 * For example, roles are used to check permissions to functions like "the user can schedule a
058 * job in the <code>{@link org.opencms.scheduler.CmsScheduleManager}</code>" or "the user can export (or import)
059 * the OpenCms database".<p>
060 *
061 * All roles are based on <code>{@link org.opencms.file.CmsGroup}</code>. This means to have access to a role,
062 * the user has to be a member in a certain predefined system group. Each role has exactly one group that
063 * contains all "direct" members of this role.<p>
064 *
065 * All roles have (optional) parent roles. If a user not a member of the role group of a role, but he is
066 * a member of at last one of the parent role groups, he/she also has full access to this role. This is called
067 * "indirect" membership to the role.<p>
068 *
069 * Please note that "indirect" membership does grant the user the same full access to a role that "direct"
070 * membership does. For example, the <code>{@link #ROOT_ADMIN}</code> role is a parent group of all other roles.
071 * So all users that are members of <code>{@link #ROOT_ADMIN}</code> have access to the functions of all other roles.<p>
072 *
073 * Please do not perform automated sorting of members on this compilation unit. That leads
074 * to NPE's<p>
075 *
076 * @since 6.0.0
077 */
078public final class CmsRole {
079
080    /** The "ACCOUNT_MANAGER" role. */
081    public static final CmsRole ACCOUNT_MANAGER;
082
083    /** The "ADMINISTRATOR" role, which is a parent to all organizational unit roles. */
084    public static final CmsRole ADMINISTRATOR;
085
086    /** The "CATEGORY_EDITOR" role. */
087    public static final CmsRole CATEGORY_EDITOR;
088
089    /** Prefix for individual user confirmation runtime property. */
090    public static final String CONFIRM_ROLE_PREFIX = "confirm.role.";
091
092    /** The "EXPORT_DATABASE" role. */
093    public static final CmsRole DATABASE_MANAGER;
094
095    /** The "DEVELOPER" role. */
096    public static final CmsRole DEVELOPER;
097
098    /** The "EDITOR" role. */
099    public static final CmsRole EDITOR;
100
101    /** The "ELEMENT_AUTHOR" role. */
102    public static final CmsRole ELEMENT_AUTHOR;
103
104    /** The "GALLERY_EDITOR" role. */
105    public static final CmsRole GALLERY_EDITOR;
106
107    /** The "LIST_EDITOR" role. */
108    public static final CmsRole LIST_EDITOR;
109
110    /** Identifier for role principals. */
111    public static final String PRINCIPAL_ROLE = "ROLE";
112
113    /** The "PROJECT_MANAGER" role. */
114    public static final CmsRole PROJECT_MANAGER;
115
116    /** The "ROOT_ADMIN" role, which is a parent to all other roles. */
117    public static final CmsRole ROOT_ADMIN;
118
119    /** The "VFS_MANAGER" role. */
120    public static final CmsRole VFS_MANAGER;
121
122    /** The "WORKPLACE_MANAGER" role. */
123    public static final CmsRole WORKPLACE_MANAGER;
124
125    /** The "WORKPLACE_USER" role. */
126    public static final CmsRole WORKPLACE_USER;
127
128    /** The list of system roles. */
129    private static final List<CmsRole> SYSTEM_ROLES;
130
131    /**
132     * Initializes the system roles with the configured OpenCms system group names.<p>
133     */
134    static {
135
136        ROOT_ADMIN = new CmsRole("ROOT_ADMIN", null, "/RoleRootAdmins");
137        WORKPLACE_MANAGER = new CmsRole("WORKPLACE_MANAGER", CmsRole.ROOT_ADMIN, "/RoleWorkplaceManager");
138        DATABASE_MANAGER = new CmsRole("DATABASE_MANAGER", CmsRole.ROOT_ADMIN, "/RoleDatabaseManager");
139
140        ADMINISTRATOR = new CmsRole("ADMINISTRATOR", CmsRole.ROOT_ADMIN, "RoleAdministrators");
141        PROJECT_MANAGER = new CmsRole("PROJECT_MANAGER", CmsRole.ADMINISTRATOR, "RoleProjectmanagers");
142        ACCOUNT_MANAGER = new CmsRole("ACCOUNT_MANAGER", CmsRole.ADMINISTRATOR, "RoleAccountManagers");
143        VFS_MANAGER = new CmsRole("VFS_MANAGER", CmsRole.ADMINISTRATOR, "RoleVfsManagers");
144        DEVELOPER = new CmsRole("DEVELOPER", CmsRole.VFS_MANAGER, "RoleDevelopers");
145        WORKPLACE_USER = new CmsRole("WORKPLACE_USER", CmsRole.DEVELOPER, "RoleWorkplaceUsers");
146
147        // the following roles all include the workplace user role
148        PROJECT_MANAGER.m_children.add(WORKPLACE_USER);
149        ACCOUNT_MANAGER.m_children.add(WORKPLACE_USER);
150
151        LIST_EDITOR = new CmsRole("LIST_EDITOR", CmsRole.WORKPLACE_USER, "RoleListEditor");
152        GALLERY_EDITOR = new CmsRole("GALLERY_EDITOR", CmsRole.WORKPLACE_USER, "RoleGalleryEditor");
153        CATEGORY_EDITOR = new CmsRole("CATEGORY_EDITOR", CmsRole.WORKPLACE_USER, "RoleCategoryEditor");
154        EDITOR = new CmsRole("EDITOR", CmsRole.GALLERY_EDITOR, "RoleEditor");
155
156        // the category editor role also includes the editor role
157        CATEGORY_EDITOR.m_children.add(EDITOR);
158
159        LIST_EDITOR.m_children.add(EDITOR);
160
161        ELEMENT_AUTHOR = new CmsRole("ELEMENT_AUTHOR", CmsRole.EDITOR, "RoleElementAuthor");
162
163        // create a lookup list for the system roles
164        SYSTEM_ROLES = Collections.unmodifiableList(
165            Arrays.asList(
166                new CmsRole[] {
167                    ROOT_ADMIN,
168                    WORKPLACE_MANAGER,
169                    DATABASE_MANAGER,
170                    ADMINISTRATOR,
171                    PROJECT_MANAGER,
172                    ACCOUNT_MANAGER,
173                    VFS_MANAGER,
174                    DEVELOPER,
175                    WORKPLACE_USER,
176                    LIST_EDITOR,
177                    GALLERY_EDITOR,
178                    CATEGORY_EDITOR,
179                    EDITOR,
180                    ELEMENT_AUTHOR}));
181
182        // now initialize all system roles
183        for (int i = 0; i < SYSTEM_ROLES.size(); i++) {
184            (SYSTEM_ROLES.get(i)).initialize();
185        }
186    }
187
188    /** The child roles of this role. */
189    private final List<CmsRole> m_children = new ArrayList<CmsRole>();
190
191    /** The distinct group names of this role. */
192    private List<String> m_distictGroupNames = new ArrayList<String>();
193
194    /** The name of the group this role is mapped to in the OpenCms database.*/
195    private final String m_groupName;
196
197    /** The id of the role, does not differentiate for organizational units. */
198    private final CmsUUID m_id;
199
200    /** Indicates if this role is organizational unit dependent. */
201    private boolean m_ouDependent;
202
203    /** The organizational unit this role applies to. */
204    private String m_ouFqn;
205
206    /** The parent role of this role. */
207    private final CmsRole m_parentRole;
208
209    /** The name of this role. */
210    private final String m_roleName;
211
212    /** Indicates if this role is a system role or a user defined role. */
213    private boolean m_systemRole;
214
215    /**
216     * Creates a user defined role.<p>
217     *
218     * @param roleName the name of this role
219     * @param groupName the name of the group the members of this role are stored in
220     * @param parentRole the parent role of this role
221     * @param ouDependent if the role is organizational unit dependent
222     */
223    public CmsRole(String roleName, CmsRole parentRole, String groupName, boolean ouDependent) {
224
225        this(roleName, parentRole, groupName);
226        m_ouDependent = ouDependent;
227        m_systemRole = false;
228        initialize();
229    }
230
231    /**
232     * Copy constructor.<p>
233     *
234     * @param role the role to copy
235     */
236    private CmsRole(CmsRole role) {
237
238        m_roleName = role.m_roleName;
239        m_id = role.m_id;
240        m_groupName = role.m_groupName;
241        m_parentRole = role.m_parentRole;
242        m_systemRole = role.m_systemRole;
243        m_ouDependent = role.m_ouDependent;
244        m_children.addAll(role.m_children);
245        m_distictGroupNames.addAll(Collections.unmodifiableList(role.m_distictGroupNames));
246    }
247
248    /**
249     * Creates a system role.<p>
250     *
251     * @param roleName the name of this role
252     * @param parentRole the parent role of this role
253     * @param groupName the related group name
254     */
255    private CmsRole(String roleName, CmsRole parentRole, String groupName) {
256
257        m_roleName = roleName;
258        m_id = CmsUUID.getConstantUUID(m_roleName);
259        m_ouDependent = !groupName.startsWith(CmsOrganizationalUnit.SEPARATOR);
260        m_parentRole = parentRole;
261        m_systemRole = true;
262        if (!m_ouDependent) {
263            m_groupName = groupName.substring(1);
264        } else {
265            m_groupName = groupName;
266        }
267        if (parentRole != null) {
268            parentRole.m_children.add(this);
269        }
270    }
271
272    /**
273     * Applies the system role order to a list of roles.<p>
274     *
275     * @param roles the roles
276     */
277    public static void applySystemRoleOrder(List<CmsRole> roles) {
278
279        Map<String, CmsRole> ouRoles = new HashMap<String, CmsRole>();
280        for (CmsRole role : roles) {
281            ouRoles.put(role.getRoleName(), role);
282        }
283        roles.clear();
284        for (CmsRole sysRole : CmsRole.getSystemRoles()) {
285            if (ouRoles.containsKey(sysRole.getRoleName())) {
286                roles.add(ouRoles.get(sysRole.getRoleName()));
287            }
288        }
289    }
290
291    /**
292     * Returns the list of system defined roles (instances of <code>{@link CmsRole}</code>).<p>
293     *
294     * @return the list of system defined roles
295     */
296    public static List<CmsRole> getSystemRoles() {
297
298        return SYSTEM_ROLES;
299    }
300
301    /**
302     * Checks if the given String starts with {@link #PRINCIPAL_ROLE} followed by a dot.<p>
303     *
304     * <ul>
305     * <li>Works if the given String is <code>null</code>.
306     * <li>Removes white spaces around the String before the check.
307     * <li>Also works with prefixes not being in upper case.
308     * <li>Does not check if the role after the prefix actually exists.
309     * </ul>
310     *
311     * @param principalName the potential role name to check
312     *
313     * @return <code>true</code> in case the String starts with {@link #PRINCIPAL_ROLE}
314     */
315    public static boolean hasPrefix(String principalName) {
316
317        return CmsStringUtil.isNotEmptyOrWhitespaceOnly(principalName)
318            && (principalName.trim().toUpperCase().startsWith(PRINCIPAL_ROLE + "."));
319    }
320
321    /**
322     * Removes the prefix if the given String starts with {@link #PRINCIPAL_ROLE} followed by a dot.<p>
323     *
324     * <ul>
325     * <li>Works if the given String is <code>null</code>.
326     * <li>If the given String does not start with {@link #PRINCIPAL_ROLE} followed by a dot it is returned unchanged.
327     * <li>Removes white spaces around the role name.
328     * <li>Also works with prefixes not being in upper case.
329     * <li>Does not check if the role after the prefix actually exists.
330     * </ul>
331     *
332     * @param principalName the role name to remove the prefix from
333     *
334     * @return the given String with the prefix {@link #PRINCIPAL_ROLE} and the following dot removed
335     */
336    public static String removePrefix(String principalName) {
337
338        String result = principalName;
339        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(principalName)) {
340            if (hasPrefix(principalName)) {
341                result = principalName.trim().substring(PRINCIPAL_ROLE.length() + 1);
342            }
343        }
344        return result;
345    }
346
347    /**
348     * Returns the role for the given group.<p>
349     *
350     * @param group a group to check for role representation
351     *
352     * @return the role for the given group
353     */
354    public static CmsRole valueOf(CmsGroup group) {
355
356        // check groups for internal representing the roles
357        if (group.isRole()) {
358            CmsRole role = valueOfGroupName(group.getName());
359            if (role != null) {
360                return role;
361            }
362        }
363        // check virtual groups mapping a role
364        if (group.isVirtual()) {
365            int index = (group.getFlags() & (I_CmsPrincipal.FLAG_CORE_LIMIT - 1));
366            index = index / (I_CmsPrincipal.FLAG_GROUP_VIRTUAL * 2);
367            CmsRole role = getSystemRoles().get(index);
368            return role.forOrgUnit(group.getOuFqn());
369        }
370        return null;
371    }
372
373    /**
374     * Returns the role for the given group name.<p>
375     *
376     * @param groupName a group name to check for role representation
377     *
378     * @return the role for the given group name
379     */
380    public static CmsRole valueOfGroupName(String groupName) {
381
382        String groupOu = CmsOrganizationalUnit.getParentFqn(groupName);
383        Iterator<CmsRole> it = SYSTEM_ROLES.iterator();
384        while (it.hasNext()) {
385            CmsRole role = it.next();
386            // direct check
387            if (groupName.equals(role.getGroupName())) {
388                return role.forOrgUnit(groupOu);
389            }
390            if (!role.isOrganizationalUnitIndependent()) {
391                // the role group name does not start with "/", but the given group fqn does
392                if (groupName.endsWith(CmsOrganizationalUnit.SEPARATOR + role.getGroupName())) {
393                    return role.forOrgUnit(groupOu);
394                }
395            }
396        }
397        return null;
398    }
399
400    /**
401     * Returns the role for the given id.<p>
402     *
403     * @param roleId the id to check for role representation
404     *
405     * @return the role for the given role id
406     */
407    public static CmsRole valueOfId(CmsUUID roleId) {
408
409        Iterator<CmsRole> it = SYSTEM_ROLES.iterator();
410        while (it.hasNext()) {
411            CmsRole role = it.next();
412            if (roleId.equals(role.getId())) {
413                return role;
414            }
415        }
416        return null;
417    }
418
419    /**
420     * Returns the role for the given role name.<p>
421     *
422     * @param roleName a role name to check for role representation
423     *
424     * @return the role for the given role name
425     */
426    public static CmsRole valueOfRoleName(String roleName) {
427
428        String roleOu = CmsOrganizationalUnit.getParentFqn(roleName);
429        Iterator<CmsRole> it = SYSTEM_ROLES.iterator();
430        while (it.hasNext()) {
431            CmsRole role = it.next();
432            // direct check
433            if (roleName.equals(role.getRoleName())) {
434                return role.forOrgUnit(roleOu);
435            }
436            if (!role.isOrganizationalUnitIndependent()) {
437                // the role name does not start with "/", but the given role fqn does
438                if (roleName.endsWith(CmsOrganizationalUnit.SEPARATOR + role.getRoleName())) {
439                    return role.forOrgUnit(roleOu);
440                }
441            }
442        }
443        return null;
444    }
445
446    /**
447     * Returns a role violation exception configured with a localized, role specific message
448     * for this role.<p>
449     *
450     * @param requestContext the current users OpenCms request context
451     *
452     * @return a role violation exception configured with a localized, role specific message
453     *      for this role
454     */
455    public CmsRoleViolationException createRoleViolationException(CmsRequestContext requestContext) {
456
457        return new CmsRoleViolationException(
458            Messages.get().container(
459                Messages.ERR_USER_NOT_IN_ROLE_2,
460                requestContext.getCurrentUser().getName(),
461                getName(requestContext.getLocale())));
462    }
463
464    /**
465     * Returns a role violation exception configured with a localized, role specific message
466     * for this role.<p>
467     *
468     * @param requestContext the current users OpenCms request context
469     * @param orgUnitFqn the organizational unit used for the role check, it may be <code>null</code>
470     *
471     * @return a role violation exception configured with a localized, role specific message
472     *      for this role
473     */
474    public CmsRoleViolationException createRoleViolationExceptionForOrgUnit(
475        CmsRequestContext requestContext,
476        String orgUnitFqn) {
477
478        return new CmsRoleViolationException(
479            Messages.get().container(
480                Messages.ERR_USER_NOT_IN_ROLE_FOR_ORGUNIT_3,
481                requestContext.getCurrentUser().getName(),
482                getName(requestContext.getLocale()),
483                orgUnitFqn));
484    }
485
486    /**
487     * Returns a role violation exception configured with a localized, role specific message
488     * for this role.<p>
489     *
490     * @param requestContext the current users OpenCms request context
491     * @param resource the resource used for the role check, it may be <code>null</code>
492     *
493     * @return a role violation exception configured with a localized, role specific message
494     *      for this role
495     */
496    public CmsRoleViolationException createRoleViolationExceptionForResource(
497        CmsRequestContext requestContext,
498        CmsResource resource) {
499
500        return new CmsRoleViolationException(
501            Messages.get().container(
502                Messages.ERR_USER_NOT_IN_ROLE_FOR_RESOURCE_3,
503                requestContext.getCurrentUser().getName(),
504                getName(requestContext.getLocale()),
505                requestContext.removeSiteRoot(resource.getRootPath())));
506    }
507
508    /**
509     * @see java.lang.Object#equals(java.lang.Object)
510     */
511    @Override
512    public boolean equals(Object obj) {
513
514        if (obj == this) {
515            return true;
516        }
517        if (obj instanceof CmsRole) {
518            CmsRole that = (CmsRole)obj;
519            // first check name
520            if (m_roleName.equals(that.m_roleName)) {
521                if (isOrganizationalUnitIndependent()) {
522                    // if ou independent ignore ou info
523                    return true;
524                }
525                // then check the org unit
526                if (m_ouFqn == null) {
527                    // if org unit not set
528                    return (that.m_ouFqn == null);
529                } else {
530                    // if org unit set
531                    return (m_ouFqn.equals(that.m_ouFqn));
532                }
533            }
534        }
535        return false;
536    }
537
538    /**
539     * Creates a new role based on this one for the given organizational unit.<p>
540     *
541     * @param ouFqn fully qualified name of the organizational unit
542     *
543     * @return a new role based on this one for the given organizational unit
544     */
545    public CmsRole forOrgUnit(String ouFqn) {
546
547        CmsRole newRole = new CmsRole(this);
548        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(ouFqn)) {
549            if (!ouFqn.endsWith(CmsOrganizationalUnit.SEPARATOR)) {
550                ouFqn += CmsOrganizationalUnit.SEPARATOR;
551            }
552        }
553        newRole.m_ouFqn = ouFqn;
554        return newRole;
555    }
556
557    /**
558     * Returns a list of all sub roles.<p>
559     *
560     * @param recursive if not set just direct children are returned
561     *
562     * @return all sub roles as a list of {@link CmsRole} objects
563     */
564    public List<CmsRole> getChildren(boolean recursive) {
565
566        List<CmsRole> children = new ArrayList<CmsRole>();
567        Iterator<CmsRole> itChildren = m_children.iterator();
568        while (itChildren.hasNext()) {
569            CmsRole child = itChildren.next();
570            if (child.isOrganizationalUnitIndependent()) {
571                child = child.forOrgUnit(null);
572            } else {
573                child = child.forOrgUnit(m_ouFqn);
574            }
575            children.add(child);
576            if (recursive) {
577                for (CmsRole grandChild : child.getChildren(true)) {
578                    if (!children.contains(grandChild)) {
579                        children.add(grandChild);
580                    }
581                }
582            }
583        }
584        return children;
585    }
586
587    /**
588     * Returns a localized role description.<p>
589     *
590     * @param locale the locale
591     *
592     * @return the localized role description
593     */
594    public String getDescription(Locale locale) {
595
596        if (m_systemRole) {
597            // localize role names for system roles
598            return Messages.get().getBundle(locale).key("GUI_ROLE_DESCRIPTION_" + m_roleName + "_0");
599        } else {
600            return getName(locale);
601        }
602    }
603
604    /**
605     * Returns the display name of this role including the organizational unit.<p>
606     *
607     * @param cms the cms context
608     * @param locale the locale
609     *
610     * @return the display name of this role including the organizational unit
611     *
612     * @throws CmsException if the organizational unit could not be read
613     */
614    public String getDisplayName(CmsObject cms, Locale locale) throws CmsException {
615
616        return Messages.get().getBundle(locale).key(
617            Messages.GUI_PRINCIPAL_DISPLAY_NAME_2,
618            getName(locale),
619            OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, getOuFqn()).getDisplayName(locale));
620    }
621
622    /**
623     * Returns the distinct group names of this role.<p>
624     *
625     * This group names are not fully qualified (organizational unit dependent).<p>
626     *
627     * @return the distinct group names of this role
628     */
629    public List<String> getDistinctGroupNames() {
630
631        return m_distictGroupNames;
632    }
633
634    /**
635     * Returns the fully qualified name of this role.<p>
636     *
637     * @return the fqn of this role
638     */
639    public String getFqn() {
640
641        if (getOuFqn() == null) {
642            return getRoleName();
643        }
644        return getOuFqn() + getRoleName();
645    }
646
647    /**
648     * Returns the name of the group this role is mapped to in the OpenCms database.<p>
649     *
650     * Here the fully qualified group name is returned.<p>
651     *
652     * @return the name of the group this role is mapped to in the OpenCms database
653     */
654    public String getGroupName() {
655
656        if ((m_ouFqn == null) || isOrganizationalUnitIndependent()) {
657            return m_groupName;
658        }
659        return m_ouFqn + m_groupName;
660    }
661
662    /**
663     * Returns the id of this role.<p>
664     *
665     * Does not differentiate for organizational units.<p>
666     *
667     * @return the id of this role
668     */
669    public CmsUUID getId() {
670
671        return m_id;
672    }
673
674    /**
675     * Returns a localized role name.<p>
676     *
677     * @param locale the locale
678     *
679     * @return the localized role name
680     */
681    public String getName(Locale locale) {
682
683        if (m_systemRole) {
684            // localize role names for system roles
685            return Messages.get().getBundle(locale).key("GUI_ROLENAME_" + m_roleName + "_0");
686        } else {
687            return getRoleName();
688        }
689    }
690
691    /**
692     * Returns the fully qualified name of the organizational unit.<p>
693     *
694     * @return the fully qualified name of the organizational unit
695     */
696    public String getOuFqn() {
697
698        return CmsOrganizationalUnit.removeLeadingSeparator(m_ouFqn);
699    }
700
701    /**
702     * Returns the parent role of this role.<p>
703     *
704     * @return the parent role of this role
705     */
706    public CmsRole getParentRole() {
707
708        if (m_parentRole == null) {
709            return null;
710        }
711        return m_parentRole.forOrgUnit(m_ouFqn);
712    }
713
714    /**
715     * Returns the name of the role.<p>
716     *
717     * @return the name of the role
718     */
719    public String getRoleName() {
720
721        return m_roleName;
722    }
723
724    /**
725     * Returns the flags needed for a group to emulate this role.<p>
726     *
727     * @return the flags needed for a group to emulate this role
728     */
729    public int getVirtualGroupFlags() {
730
731        int flags = I_CmsPrincipal.FLAG_GROUP_VIRTUAL;
732        flags += I_CmsPrincipal.FLAG_GROUP_VIRTUAL * 2 * getSystemRoles().indexOf(forOrgUnit(null));
733        return flags;
734    }
735
736    /**
737     * @see java.lang.Object#hashCode()
738     */
739    @Override
740    public int hashCode() {
741
742        return m_roleName.hashCode()
743            + (((m_ouFqn == null) || isOrganizationalUnitIndependent()) ? 13 : m_ouFqn.hashCode());
744    }
745
746    /**
747     * Checks if this role is organizational unit independent.<p>
748     *
749     * @return <code>true</code> if this role is organizational unit independent
750     */
751    public boolean isOrganizationalUnitIndependent() {
752
753        return !m_ouDependent;
754    }
755
756    /**
757     * Check if this role is a system role.<p>
758     *
759     * @return <code>true</code> if this role is a system role
760     */
761    public boolean isSystemRole() {
762
763        return m_systemRole;
764    }
765
766    /**
767     * @see java.lang.Object#toString()
768     */
769    @Override
770    public String toString() {
771
772        StringBuffer result = new StringBuffer();
773
774        result.append("[");
775        result.append(this.getClass().getName());
776        result.append(", role: ");
777        result.append(getRoleName());
778        result.append(", org unit: ");
779        result.append(getOuFqn());
780        result.append(", group: ");
781        result.append(getGroupName());
782        result.append("]");
783
784        return result.toString();
785    }
786
787    /**
788     * Returns a set of all roles group names.<p>
789     *
790     * @return a set of all roles group names
791     */
792    private Set<String> getAllGroupNames() {
793
794        // First get the topmost role (should be root admin)
795
796        CmsRole root = this;
797        while (root.getParentRole() != null) {
798            root = root.getParentRole();
799        }
800        List<CmsRole> allRoles = root.getChildren(true);
801        allRoles.add(root);
802
803        // now build multimap from children to parent roles for all roles reachable from root
804
805        HashMultimap<CmsRole, CmsRole> mapOfParents = HashMultimap.create();
806        for (CmsRole parent : allRoles) {
807            for (CmsRole child : parent.getChildren(false)) {
808                mapOfParents.put(child, parent);
809            }
810        }
811
812        // now traverse the parent map, starting from this role, to find all parent roles
813        // (we can't just use the getParentRole() method instead of the parent map here,
814        // since roles may have multiple logical parents (roles which have them as a child).
815
816        Set<CmsRole> visited = Sets.newHashSet();
817        Set<CmsRole> workingSet = Sets.newHashSet();
818        Set<String> result = Sets.newHashSet();
819        workingSet.add(this);
820        while (!workingSet.isEmpty()) {
821            CmsRole current = workingSet.iterator().next();
822            result.add(current.getGroupName());
823            workingSet.remove(current);
824            for (CmsRole parent : mapOfParents.get(current)) {
825                if (!visited.contains(parent)) {
826                    workingSet.add(parent);
827                }
828            }
829            visited.add(current);
830        }
831        return result;
832    }
833
834    /**
835     * Initializes this role, creating an optimized data structure for
836     * the lookup of the role group names.<p>
837     */
838    private void initialize() {
839
840        // calculate the distinct groups of this role
841        Set<String> distinctGroups = new HashSet<String>(getAllGroupNames());
842        // by using a set first we eliminate duplicate names
843        m_distictGroupNames = Collections.unmodifiableList(new ArrayList<String>(distinctGroups));
844    }
845}