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