ezbake.groups.graph.EzGroupsGraphImpl.java Source code

Java tutorial

Introduction

Here is the source code for ezbake.groups.graph.EzGroupsGraphImpl.java

Source

/*   Copyright (C) 2013-2015 Computer Sciences Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. */

package ezbake.groups.graph;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.thinkaurelius.titan.core.TitanGraph;
import com.tinkerpop.blueprints.*;
import com.tinkerpop.frames.FrameInitializer;
import com.tinkerpop.frames.FramedGraph;
import com.tinkerpop.frames.FramedGraphConfiguration;
import com.tinkerpop.frames.FramedGraphFactory;
import com.tinkerpop.frames.modules.AbstractModule;
import com.tinkerpop.frames.modules.javahandler.JavaHandlerModule;
import com.tinkerpop.gremlin.java.GremlinPipeline;
import com.tinkerpop.pipes.PipeFunction;
import com.tinkerpop.pipes.branch.LoopPipe;
import ezbake.groups.common.GroupNameHelper;
import ezbake.groups.graph.api.GroupIDProvider;
import ezbake.groups.graph.exception.*;
import ezbake.groups.graph.frames.edge.BaseEdge;
import ezbake.groups.graph.frames.vertex.BaseVertex;
import ezbake.groups.graph.frames.vertex.Group;
import ezbake.groups.graph.frames.vertex.User;
import ezbake.groups.graph.query.*;
import ezbake.groups.thrift.EzGroupsConstants;
import ezbake.groups.thrift.GroupInheritancePermissions;
import ezbake.groups.thrift.GroupQueryException;
import ezbake.groups.thrift.UserGroupPermissions;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;

import static ezbake.groups.graph.PermissionEnforcer.Permission;

import javax.annotation.Nullable;

public class EzGroupsGraphImpl implements GroupsGraph {
    private static final Logger logger = LoggerFactory.getLogger(EzGroupsGraphImpl.class);
    public static final String GROUP_NAME_SEP = EzGroupsConstants.GROUP_NAME_SEP;

    /**
     * Predicate for filtering out groups that should not be returned to the user, including 'root' and app access
     * related groups.
     */
    private static final com.google.common.base.Predicate<Group> NON_USER_FACING_GROUP = new com.google.common.base.Predicate<Group>() {

        private final String appAccessName = String.format("%s.%s", Group.COMMON_GROUP,
                EzGroupsConstants.APP_ACCESS_GROUP);

        @Override
        public boolean apply(@Nullable Group input) {
            if (input != null && input.getGroupName().equals(Group.COMMON_GROUP)
                    || input.getGroupName().equals(appAccessName)
                    || input.getGroupName().startsWith(appAccessName + '.')) {
                return true;
            }

            return false;
        }
    };

    private final GroupNameHelper gnh = new GroupNameHelper();
    private final TitanGraph graph;
    private final FramedGraphFactory framedGraphFactory;

    private GroupIDProvider idProvider;

    final Object commonGroupId;
    final Object appGroupId;
    final Object appAccessGroupId;

    @Inject
    public EzGroupsGraphImpl(TitanGraph graph, GroupIDProvider idProvider, GroupQuery query) {
        this.graph = graph;
        this.idProvider = idProvider;

        // Set up the framed factory
        framedGraphFactory = new FramedGraphFactory(new AbstractModule() {
            @Override
            public void doConfigure(FramedGraphConfiguration config) {
                config.addFrameInitializer(new FrameInitializer() {
                    @Override
                    public void initElement(Class<?> kind, FramedGraph<?> framedGraph, Element element) {
                        element.setProperty("class", kind.getName());
                    }
                });
            }
        }, new JavaHandlerModule());

        commonGroupId = createCommonGroup();
        appGroupId = addSpecialGroup(Group.APP_GROUP);
        appAccessGroupId = addSpecialGroup(Group.APP_ACCESS_GROUP);
    }

    public GroupIDProvider getIdProvider() {
        return this.idProvider;
    }

    public Graph getGraph() {
        return this.graph;
    }

    public FramedGraph<TitanGraph> getFramedGraph() {
        return framedGraphFactory.create(graph);
    }

    public Object getCommonGroupId() {
        return commonGroupId;
    }

    public Object getAppGroupId() {
        return appGroupId;
    }

    /**
     * Closes this stream and releases any system resources associated
     * with it. If the stream is already closed then invoking this
     * method has no effect.
     *
     * @throws java.io.IOException if an I/O error occurs
     */
    @Override
    public void close() throws IOException {
        graph.shutdown();
    }

    /**
     * Commit a transaction with the graph
     *
     * Transactions must be committed before changes made to the graph in other threads will be realized. If an
     * operation is doing many reads, it may be ideal to wait until the current transaction is fully finished
     * before committing the transaction. For this reason, read operations on this class do not automatically close
     * the transaction, and you must do so by calling this method
     */
    @Override
    public void commitTransaction() {
        commitTransaction(false);
    }

    /**
     * Transactions must be committed before changes made to the graph in other threads will be realized. If an
     * operation is doing many reads, it may be ideal to wait until the current transaction is fully finished
     * before committing the transaction. For this reason, read operations on this class do not automatically close
     * the transaction, and you must do so by calling this method
     *
     * @param rollback optionally, rollback the transaction instead of committing it
     */
    @Override
    public void commitTransaction(boolean rollback) {
        if (rollback) {
            graph.rollback();
        } else {
            graph.commit();
        }
    }

    /**
     * Set up the common group, or just get it's vertex ID. This is the default parent group, and must be created if not
     * present
     *
     * @return vertex id for the common group
     */
    public Object createCommonGroup() {
        FramedGraph<TitanGraph> framedGraph = getFramedGraph();
        Object commonId;

        Iterator<Vertex> v = graph.query().has(BaseVertex.INDEX, Compare.EQUAL, 0)
                .has(Group.GROUP_NAME, Compare.EQUAL, EzGroupsConstants.ROOT).limit(1).vertices().iterator();
        if (!v.hasNext()) {
            try {
                // create the common group
                Group cg = framedGraph.addVertex(null, Group.class);
                cg.setName(EzGroupsConstants.ROOT);
                cg.setGroupName(EzGroupsConstants.ROOT);
                cg.setGroupFriendlyName(EzGroupsConstants.ROOT);
                cg.setIndex(0l);
                cg.setType(BaseVertex.VertexType.GROUP);
                commonId = cg.asVertex().getId();
                graph.commit();
                logger.info("Created common group with name: {}, index:{}", cg.getGroupName(), cg.getIndex());
            } catch (Exception e) {
                graph.rollback();
                logger.error("Failed to create common group", e);
                throw new RuntimeException("Unable to set current ID for common group");
            }
        } else {
            commonId = v.next().getId();
        }

        return commonId;
    }

    public Object addSpecialGroup(String specialName) {
        Vertex parentGroup = graph.getVertex(commonGroupId);

        // Get the vertex/id
        Object specialId;
        String groupName = Joiner.on(GROUP_NAME_SEP).join(Group.COMMON_GROUP, specialName);
        Iterator<Vertex> groups = graph.query().has(Group.GROUP_NAME, Compare.EQUAL, groupName).limit(1).vertices()
                .iterator();
        if (groups.hasNext()) {
            specialId = groups.next().getId();
        } else {
            try {
                Group specialGroup = addGroupPrivileged(parentGroup, specialName,
                        new GroupInheritancePermissions(false, false, false, false, false), true, false);
                specialId = specialGroup.asVertex().getId();
            } catch (VertexExistsException e) {
                // Should not happen, but create as best we can
                try {
                    Group specialGroup = getFramedGraph().addVertex(null, Group.class);
                    specialGroup.setGroupName(new GroupNameHelper().addRootGroupPrefix(groupName));
                    specialGroup.setGroupFriendlyName(groupName);
                    specialGroup.setIndex(idProvider.nextID());
                    assignEdges(parentGroup, specialGroup.asVertex(), false, false, false, false, false);
                    specialId = specialGroup.asVertex().getId();
                    graph.commit();
                } catch (Exception e1) {
                    graph.rollback();
                    logger.error("Failed to create {} group", specialName, e1);
                    throw new RuntimeException("Unable to acquire ID for " + specialName);
                }
            } catch (IndexUnavailableException e) {
                // transaction rollback responsibility of thrower
                logger.error("Failed to create {} group. Problem getting id", specialName, e);
                throw new RuntimeException("Unable to acquire ID for " + specialName, e);
            } catch (InvalidGroupNameException e) {
                logger.error("Failed to create {} group. Invalid name", specialName, e);
                throw new RuntimeException("Invalid name for group " + specialName, e);
            }
        }

        return specialId;
    }

    /**
     * Add a new child group to the common group, with the default inheritances
     *
     * @param ownerType
     * @param ownerPrincipal
     * @param groupName
     * @return
     * @throws VertexExistsException
     * @throws UserNotFoundException
     * @throws AccessDeniedException
     */
    public Group addGroup(BaseVertex.VertexType ownerType, String ownerPrincipal, String groupName)
            throws VertexExistsException, UserNotFoundException, AccessDeniedException, IndexUnavailableException,
            InvalidGroupNameException {
        return addGroup(ownerType, ownerPrincipal, groupName, new GroupInheritancePermissions(),
                new UserGroupPermissionsWrapper(true, true, true, true, true), true, false);
    }

    /**
     * Add a new child group to the common group with the specified inheritances
     *
     * @param ownerType
     * @param ownerPrincipal
     * @param groupName
     * @return
     * @throws VertexExistsException
     * @throws UserNotFoundException
     * @throws AccessDeniedException
     */
    public Group addGroup(BaseVertex.VertexType ownerType, String ownerPrincipal, String groupName,
            GroupInheritancePermissions inheritance, UserGroupPermissions permissions, boolean requireOnlyUser,
            boolean requireOnlyApp) throws UserNotFoundException, AccessDeniedException, VertexExistsException,
            IndexUnavailableException, InvalidGroupNameException {
        return addGroup(ownerType, ownerPrincipal, groupName, graph.getVertex(commonGroupId), inheritance,
                permissions, requireOnlyUser, requireOnlyApp);
    }

    /**
     *
     * @param ownerType
     * @param ownerID
     * @param groupName
     * @param parentGroup
     * @return
     * @throws UserNotFoundException
     * @throws AccessDeniedException
     * @throws VertexExistsException
     * @throws VertexNotFoundException
     */
    public Group addGroup(BaseVertex.VertexType ownerType, String ownerID, String groupName, String parentGroup,
            GroupInheritancePermissions inheritance, UserGroupPermissions permissions, boolean requireOnlyUser,
            boolean requireOnlyApp) throws UserNotFoundException, AccessDeniedException, VertexExistsException,
            VertexNotFoundException, IndexUnavailableException, InvalidGroupNameException {
        Iterator<Vertex> parents = graph.query().has(Group.GROUP_NAME, parentGroup).limit(1).vertices().iterator();
        if (!parents.hasNext()) {
            graph.rollback();
            throw new VertexNotFoundException("Cannot find the parent group: " + parentGroup);
        }

        return addGroup(ownerType, ownerID, groupName, parents.next(), inheritance, permissions, requireOnlyUser,
                requireOnlyApp);
    }

    protected Group addGroup(BaseVertex.VertexType ownerType, String ownerPrincipal, String groupName,
            String parentGroup) throws VertexExistsException, UserNotFoundException, AccessDeniedException,
            IndexUnavailableException, VertexNotFoundException, InvalidGroupNameException {
        return addGroup(ownerType, ownerPrincipal, groupName, getGroup(parentGroup).asVertex(),
                new GroupInheritancePermissions(true, false, false, false, false),
                new UserGroupPermissionsWrapper(true, true, true, true, true), true, false);
    }

    protected Group addGroup(BaseVertex.VertexType ownerType, String ownerPrincipal, String groupName,
            Group parentGroup) throws VertexExistsException, UserNotFoundException, AccessDeniedException,
            IndexUnavailableException, InvalidGroupNameException {
        return addGroup(ownerType, ownerPrincipal, groupName, parentGroup.asVertex(),
                new GroupInheritancePermissions(true, false, false, false, false),
                new UserGroupPermissionsWrapper(true, true, true, true, true), true, false);
    }

    protected Group addGroup(BaseVertex.VertexType ownerType, String ownerPrincipal, String groupName,
            Vertex parent) throws VertexExistsException, UserNotFoundException, AccessDeniedException,
            IndexUnavailableException, InvalidGroupNameException {
        return addGroup(ownerType, ownerPrincipal, groupName, parent, new GroupInheritancePermissions(),
                new UserGroupPermissionsWrapper(true, true, true, true, true), true, false);
    }

    /**
     * Add a new Group vertex to the graph. A childGroup edge will run from the parent vertex to the new group. Direct
     * admin edges (all of them) will run from the owner to the new group vertex. The inherit flags are used to set up
     * admin edges from the parent group to the new group. If a sibling group with the same name exists, throw an error
     *
     * @param groupName
     * @param parent
     * @throws ezbake.groups.graph.exception.VertexExistsException if a sibling vertex with the same groupName
     * already exists
     * @return vertex id of the newly created group
     */
    protected Group addGroup(BaseVertex.VertexType ownerType, String ownerPrincipal, String groupName,
            Vertex parent, GroupInheritancePermissions inheritance, UserGroupPermissions permissions,
            boolean requireOnlyUser, boolean requireOnlyApp) throws AccessDeniedException, UserNotFoundException,
            VertexExistsException, IndexUnavailableException, InvalidGroupNameException {
        logger.info("Processing addGroup with groupName: {}, parent: {}", groupName,
                parent.getProperty(Group.GROUP_NAME));
        FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        if (isAppAccessGroup(parent)) {
            throw new AccessDeniedException("No child groups may be added to the " + Group.APP_ACCESS_GROUP);
        }

        // Find the owner
        User owner;
        Iterator<User> ownerit = framedGraph.query().has(User.TYPE, Compare.EQUAL, ownerType.toString())
                .has(User.PRINCIPAL, Compare.EQUAL, ownerPrincipal).limit(1).vertices(User.class).iterator();
        if (!ownerit.hasNext()) {
            graph.rollback();
            throw new UserNotFoundException("Requested owner for new group does not exist");
        }
        owner = ownerit.next();

        final GroupQuery q = new GroupQuery(framedGraphFactory.create(this.graph));
        // Owner must have admin create child on the parent group
        q.getPermissionEnforcer().validateAuthorized(owner, parent, Permission.CREATE_CHILD);

        return addGroupPrivileged(parent, owner.asVertex(), groupName, inheritance, permissions, requireOnlyUser,
                requireOnlyApp);
    }

    /**
     * Adds a group to a parent group, but does not do any access checks. This should only be used internally, and only
     * if you know what you are doing!
     *
     * @param parent
     * @param owner
     * @param groupName
     * @return
     * @throws VertexExistsException
     */
    private Group addGroupPrivileged(Vertex parent, Vertex owner, String groupName,
            GroupInheritancePermissions inheritance, UserGroupPermissions permissions, boolean requireOnlyUser,
            boolean requireOnlyApp)
            throws VertexExistsException, IndexUnavailableException, InvalidGroupNameException {
        Group group = addGroupPrivileged(parent, groupName, inheritance, requireOnlyUser, requireOnlyApp);

        // Add direct admin to the group from the owner
        assignEdges(owner, group.asVertex(), permissions.isDataAccess(), permissions.isAdminRead(),
                permissions.isAdminWrite(), permissions.isAdminManage(), permissions.isAdminCreateChild());
        graph.commit();

        return group;
    }

    /**
     * Adds a group to a parent group, but does not do any access checks. This should only be used internally, and only
     * if you know what you are doing!
     *
     * @param parent
     * @param groupName
     * @return
     * @throws VertexExistsException
     */
    private Group addGroupPrivileged(Vertex parent, String groupName, GroupInheritancePermissions inheritance,
            boolean requireOnlyUser, boolean requireOnlyApp)
            throws VertexExistsException, IndexUnavailableException, InvalidGroupNameException {
        if (groupName.contains(EzGroupsConstants.GROUP_NAME_SEP)) {
            throw new InvalidGroupNameException(
                    "Group name must not contain '" + EzGroupsConstants.GROUP_NAME_SEP + "'");
        }

        FramedGraph<TitanGraph> framedGraph = getFramedGraph();
        Group parentGroup = framedGraph.frame(parent, Group.class);

        // Append the parent group name:
        String groupNameWithPath = Joiner.on(GROUP_NAME_SEP).join(parentGroup.getGroupName(), groupName);

        // Check existence - query parent group for group of the same name
        GremlinPipeline siblingExists = new GremlinPipeline(parent).outE(Group.CHILD_GROUP).inV()
                .has(Group.FRIENDLY_GROUP_NAME, groupName);
        if (siblingExists.hasNext()) {
            logger.debug("Parent vertex: {} has children with groupName: {}", parentGroup.getGroupName(),
                    groupName);
            graph.rollback();
            throw new VertexExistsException(
                    parentGroup.getGroupName() + " already has child with name: " + groupName);
        }

        Group g = framedGraph.addVertex(null, Group.class);
        g.setType(BaseVertex.VertexType.GROUP);
        try {
            g.setIndex(idProvider.nextID());
        } catch (Exception e) {
            graph.rollback();
            throw new IndexUnavailableException("Unable to acquire an index for group");
        }
        g.setName(groupName);
        g.setGroupFriendlyName(groupName);
        g.setGroupName(groupNameWithPath);
        g.setRequireOnlyUser(requireOnlyUser);
        g.setRequireOnlyApp(requireOnlyApp);

        parentGroup.addChildGroup(g);
        assignEdges(parent, g.asVertex(), inheritance.isDataAccess(), inheritance.isAdminRead(),
                inheritance.isAdminWrite(), inheritance.isAdminManage(), inheritance.isAdminCreateChild());

        graph.commit();
        return g;
    }

    /**
     * Get group metadata with is ADMIN_READ permission check
     *
     * @param type of Vertex
     * @param userId of a user calling getGroup
     * @param groupName being searched for
     *
     * @return Group
     *
     * @throws VertexNotFoundException
     * @throws UserNotFoundException
     * @throws AccessDeniedException
     * @throws InvalidVertexTypeException
     */
    public Group getGroup(BaseVertex.VertexType type, String userId, String groupName)
            throws VertexNotFoundException, UserNotFoundException, AccessDeniedException,
            InvalidVertexTypeException {
        FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        // Get the group vertex
        Iterator<Group> g = framedGraph.query().has(Group.GROUP_NAME, Compare.EQUAL, groupName)
                .has(BaseVertex.TYPE, Compare.EQUAL, BaseVertex.VertexType.GROUP.toString()).limit(1)
                .vertices(Group.class).iterator();
        if (!g.hasNext()) {
            graph.rollback();
            throw new VertexNotFoundException("Group not found: " + groupName);
        }

        final Group group = g.next();
        final User user = getUser(type, userId);

        final GroupQuery q = new GroupQuery(framedGraphFactory.create(this.graph));
        q.getPermissionEnforcer().validateAuthorized(user, group, Permission.READ);

        return group;
    }

    /**
     * Returns true if group exists.
     *
     * @param name name of group to check
     * @return true if group exists, false if not
     */
    private boolean groupExists(String name) {
        try {
            getGroup(name);
            return true;
        } catch (VertexNotFoundException e) {
            return false;
        }
    }

    /**
     * Get a group with NO permission check.
     *
     * @param groupName name of group to get.
     * @return the requested group.
     * @throws VertexNotFoundException if the group could not be found
     */
    @Override
    public Group getGroup(String groupName) throws VertexNotFoundException {
        FramedGraph<TitanGraph> framedGraph = getFramedGraph();
        // Get the group vertex
        Iterator<Group> g = framedGraph.query().has(Group.GROUP_NAME, Compare.EQUAL, groupName)
                .has(BaseVertex.TYPE, Compare.EQUAL, BaseVertex.VertexType.GROUP.toString()).limit(1)
                .vertices(Group.class).iterator();
        if (!g.hasNext()) {
            graph.rollback();
            final String errMsg = "Group not found: " + groupName;
            logger.error(errMsg);
            throw new VertexNotFoundException(errMsg);
        }

        return g.next();
    }

    public Set<Group> getGroups(Set<String> groupNames) {
        Set<Group> groups = Sets.newHashSet();
        for (String groupName : groupNames) {
            try {
                groups.add(getGroup(groupName));
            } catch (VertexNotFoundException e) {
                // ignoring group
            }
        }
        return groups;
    }

    @Override
    public Set<User> getUsers(BaseVertex.VertexType type, Set<String> ezbakeUserIds) {
        if (type != BaseVertex.VertexType.APP_USER && type != BaseVertex.VertexType.USER) {
            final String errMsg = String
                    .format("Attempted to get invalid user type. Type '%s' is not a valid user type to get!", type);

            logger.error(errMsg);
            throw new IllegalArgumentException(errMsg);
        }

        Set<User> users = Sets.newHashSet();
        for (String userId : ezbakeUserIds) {
            try {
                users.add(getUserByEzBakeId(type, userId));
            } catch (VertexNotFoundException e) {
                // ignoring user
            }
        }

        return users;
    }

    /**
     * Gets all groups excluding any 'special' groups.
     *
     * @return all groups excluding any 'special' groups
     */
    @Override
    public Set<Group> getGroups() {
        final FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        final Iterable<Group> groups = framedGraph.frameVertices(
                framedGraph.query().has(BaseVertex.TYPE, BaseVertex.VertexType.GROUP.toString()).vertices(),
                Group.class);

        final Set<Group> retrievedGroups = Sets.newHashSet(groups);
        Iterables.removeIf(retrievedGroups, NON_USER_FACING_GROUP);

        return retrievedGroups;
    }

    @Override
    public Set<Group> getGroupsByIds(Set<Long> indices) {
        final Set<Group> foundGroups = Sets.newHashSet();

        for (Long id : indices) {
            final Group group = getGroupById(id);
            if (group != null) {
                foundGroups.add(group);
            }
        }

        return foundGroups;
    }

    @Override
    public Set<Group> getGroupsByIdsWithAuths(BaseVertex.VertexType type, String principal, Set<Long> indices)
            throws UserNotFoundException, InvalidVertexTypeException {
        final Set<Group> groups = Sets.newHashSet();

        final GroupQuery q = new GroupQuery(framedGraphFactory.create(this.graph));

        for (Group group : getGroupsByIds(indices)) {
            if (q.getPermissionEnforcer().hasAnyPermission(getUser(type, principal), group)) {
                groups.add(group);
            }
        }

        return groups;
    }

    /**
     * Gets a group by its EzGroups index.
     *
     * @param ezGroupsIndex EzGroups index
     * @return group with the given EzGroups index or null if a group with that index cant be found
     */
    private Group getGroupById(long ezGroupsIndex) {
        final FramedGraph<TitanGraph> framedGraph = getFramedGraph();
        // Get the group vertex
        final Iterator<Group> g = framedGraph.query().has(Group.INDEX, Compare.EQUAL, ezGroupsIndex)
                .has(BaseVertex.TYPE, Compare.EQUAL, BaseVertex.VertexType.GROUP.toString()).limit(1)
                .vertices(Group.class).iterator();
        if (!g.hasNext()) {
            return null;
        }

        return g.next();
    }

    /**
     * Gets an Ezbake User by the given EzBake ID.
     *
     * @param type whether the user is a USER or an APP_USER
     * @param ezBakeUserId user ID of the desired user
     * @return the requested User object
     * @throws VertexNotFoundException if the User could not be found
     */
    private User getUserByEzBakeId(BaseVertex.VertexType type, String ezBakeUserId) throws VertexNotFoundException {
        FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        // Get the user vertex
        Iterator<User> g = framedGraph.query().has(User.PRINCIPAL, Compare.EQUAL, ezBakeUserId)
                .has(BaseVertex.TYPE, Compare.EQUAL, type.toString()).limit(1).vertices(User.class).iterator();
        if (!g.hasNext()) {
            final String errMsg = "User not found: " + ezBakeUserId;
            logger.error(errMsg);
            throw new VertexNotFoundException(errMsg);
        }

        return g.next();
    }

    /**
     * Gets if an user exists in groups.
     *
     * @param type type of user
     * @param ezBakeUserId ezBake ID of user
     * @return true if the user exists, false if not
     */
    private boolean userExists(BaseVertex.VertexType type, String ezBakeUserId) {
        try {
            getUserByEzBakeId(type, ezBakeUserId);
            return true;
        } catch (VertexNotFoundException e) {
            return false;
        }
    }

    public void setGroupInheritance(String groupName, boolean dataAccess, boolean adminRead, boolean adminWrite,
            boolean adminManage, boolean adminCreateChild) throws VertexNotFoundException {
        Iterator<Vertex> gs = graph.query().has(Group.GROUP_NAME, Compare.EQUAL, groupName).limit(1).vertices()
                .iterator();
        if (!gs.hasNext()) {
            graph.rollback();
            throw new VertexNotFoundException("Cannot find the group: " + groupName);
        }
        setGroupInheritance(gs.next(), dataAccess, adminRead, adminWrite, adminManage, adminCreateChild);
    }

    protected void setGroupInheritance(Vertex group, boolean dataAccess, boolean adminRead, boolean adminWrite,
            boolean adminManage, boolean adminCreateChild) throws VertexNotFoundException {
        // Find the parent group
        GremlinPipeline<Vertex, Vertex> parentPipe = new GremlinPipeline<Vertex, Vertex>(group)
                .in(Group.CHILD_GROUP);
        if (!parentPipe.hasNext()) {
            graph.rollback();
            throw new VertexNotFoundException(
                    "Unable to find parent group of: " + group.getProperty(Group.GROUP_NAME));
        }
        Vertex parent = parentPipe.next();

        assignEdges(parent, group, dataAccess, adminRead, adminWrite, adminManage, adminCreateChild);
        graph.commit();
    }

    /**
     * Change the friendly name of a group and all of its children's fully qualified names as appropriate. Note that
     * there are NO checks on whether this operation should be permit here. For a change group name operation that
     * should have permission checked, see {@link #changeGroupName(ezbake.groups.graph.frames.vertex.User, String,
     * String)}
     * <p/>
     * For example: 'root.parentName.myName' could be a fully qualified group name with a friendly name of 'myName'. By
     * passing in this fully qualified group name and new friendly name 'newName' the fully qualified name would become
     * 'root.parentName.newName' with friendly name 'newName'.
     *
     * @param groupName name of the group whose name to change
     * @param newFriendlyName new friendly name for the given group
     * @return a map of old group names to new group names if any were changed
     * @throws VertexNotFoundException if the given group cannot be found
     * @throws VertexExistsException if a group by the new name already exists
     */
    public Map<String, String> changeGroupName(String groupName, String newFriendlyName)
            throws VertexNotFoundException, VertexExistsException {
        if (groupName.endsWith(newFriendlyName)) {
            return Maps.newHashMap();
        }

        final FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        final String newGroupName = gnh.changeGroupName(groupName, newFriendlyName);
        if (groupExists(newGroupName)) {
            final String errMsg = String.format(
                    "Cannot rename a group to a group that already exists! Changing group's name '%s to '%s' cannot "
                            + "be completed.",
                    groupName, newGroupName);

            logger.error(errMsg);
            throw new VertexExistsException(errMsg);
        }

        final Group group = getGroup(groupName);
        logger.info("Updating group name {} -> {}", groupName, newGroupName);
        group.setGroupName(newGroupName);
        group.setGroupFriendlyName(newFriendlyName);

        final Map<String, String> changedGroupNames = Maps.newHashMap();
        changedGroupNames.put(groupName, newGroupName);
        // Change the group name and all child groups
        final List<Vertex> groups = Lists.newArrayList();
        getChildGroups(true, framedGraph, group.asVertex(), true).store(groups).iterate();

        for (Vertex v : groups) {
            final Group currentGroup = framedGraph.frame(v, Group.class);
            final String cgName = currentGroup.getGroupName();
            final String cgNewName = cgName.replace(groupName, newGroupName);
            logger.info("Updating child group name {} -> {}", cgName, cgNewName);
            currentGroup.setGroupName(cgNewName);
            changedGroupNames.put(cgName, cgNewName);
        }
        graph.commit();

        return changedGroupNames;
    }

    /**
     * Change the friendly name of a group and all of its children's fully qualified names as appropriate. This
     * operation is only allowed if the given {@code User} is authorized to manage all affected groups.
     *
     * @param requesterType whether the requester is a USER or an APP_USER
     * @param userId ID/principal of user requesting the operation
     * @param fullyQualifiedGroupName name of the group whose name will be changed
     * @param newFriendlyName new friendly name for the given group
     * @return a map of names before the change to names after the change, or an empty map if none were changed
     * @throws UserNotFoundException if the user principal could not be mapped to a valid user
     * @throws AccessDeniedException if the given User does not have manage rights on all affected groups
     * @throws VertexNotFoundException if the given group cannot be found
     * @throws ezbake.groups.graph.exception.VertexExistsException if a group by the new name already exists
     * @throws IllegalArgumentException if {@code requesterType} is not USER or APP_USER
     */
    @Override
    public Map<String, String> changeGroupName(BaseVertex.VertexType requesterType, String userId,
            String fullyQualifiedGroupName, String newFriendlyName)
            throws UserNotFoundException, AccessDeniedException, VertexNotFoundException, VertexExistsException {
        if (requesterType == BaseVertex.VertexType.APP_USER || requesterType == BaseVertex.VertexType.USER) {
            final FramedGraph<TitanGraph> framedGraph = getFramedGraph();
            final User requester = findAndRetrieveUserById(requesterType, userId, framedGraph);

            return changeGroupName(requester, fullyQualifiedGroupName, newFriendlyName);
        } else {
            final String errMsg = String.format("Type of requester for a name change must be either '%s' or '%s'!",
                    BaseVertex.VertexType.APP_USER, BaseVertex.VertexType.USER);

            logger.error(errMsg);
            throw new IllegalArgumentException(errMsg);
        }
    }

    /**
     * Change the friendly name of a group and all of its children's fully qualified names as appropriate. This
     * operation is only allowed if the given {@code User} is authorized to manage all affected groups.
     *
     * @param requester User requesting the operation
     * @param fullyQualifiedGroupName name of the group whose name will be changed
     * @param newFriendlyName new friendly name for the given group
     * @return a Map of old group names to new group names or an empty map if none were changed
     * @throws AccessDeniedException if the given User does not have manage rights on all affected groups
     * @throws VertexNotFoundException if the given group cannot be found
     * @throws ezbake.groups.graph.exception.VertexExistsException if a group by the new name already exists
     */
    private Map<String, String> changeGroupName(final User requester, String fullyQualifiedGroupName,
            String newFriendlyName) throws AccessDeniedException, VertexNotFoundException, VertexExistsException {
        final FramedGraph<TitanGraph> framedGraph = getFramedGraph();
        final Group group = getGroup(fullyQualifiedGroupName);

        final GremlinPipeline<Vertex, Vertex> groupVertexPipe = getChildGroups(true, framedGraph, group.asVertex(),
                true);

        final List<Vertex> verticesToCheck = groupVertexPipe.toList();
        verticesToCheck.add(group.asVertex());

        final GroupQuery q = new GroupQuery(framedGraphFactory.create(this.graph));
        q.getPermissionEnforcer().validateAuthorized(requester, verticesToCheck, Permission.MANAGE);

        return changeGroupName(fullyQualifiedGroupName, newFriendlyName);
    }

    /**
     * Add a new user to the graph. Create a DataAccess edge, and all the admin edges to the common group
     * @param type
     * @param principal
     * @param name
     * @throws ezbake.groups.graph.exception.VertexExistsException
     * @return
     */
    @Override
    public User addUser(BaseVertex.VertexType type, String principal, String name)
            throws InvalidVertexTypeException, VertexExistsException, AccessDeniedException, UserNotFoundException,
            IndexUnavailableException, InvalidGroupNameException {
        logger.info("Processing addUser type: {}, principal: {}, name: {}", type, principal, name);

        if (type != BaseVertex.VertexType.APP_USER && type != BaseVertex.VertexType.USER) {
            throw new InvalidVertexTypeException("Cannot create user of type: " + type);
        }

        FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        Iterator<User> users = framedGraph.query().has(User.PRINCIPAL, Compare.EQUAL, principal)
                .has(BaseVertex.TYPE, Compare.EQUAL, type.toString()).limit(1).vertices(User.class).iterator();
        if (users.hasNext()) {
            logger.warn("User with principal: {} and type: {} already exists, not creating new user", principal,
                    type);
            graph.rollback();
            throw new VertexExistsException("A " + type + " with principal: " + principal + " already exists");
        }

        // Add the User
        User user = framedGraph.addVertex(null, User.class);
        user.setPrincipal(principal);
        user.setName(name);
        user.setType(type);
        user.setTerminator(false);
        try {
            user.setIndex(idProvider.nextID());
        } catch (Exception e) {
            graph.rollback();
            logger.error("Error getting the next group ID", e);
            throw new IndexUnavailableException("Unable to receive new index", e);
        }

        // Add edges for the User to the common group
        Vertex commonGroup = graph.getVertex(commonGroupId);
        assignEdges(user.asVertex(), commonGroup, true, false, false, false, true);

        switch (type) {
        case APP_USER:
            if (Strings.isNullOrEmpty(name)) {
                graph.rollback();
                throw new InvalidGroupNameException("App Users must have a name in order to create app groups");
            }
            // App users get direct data_access on appaccess group
            assignEdges(user.asVertex(), graph.getVertex(appAccessGroupId), true, false, false, false, false);

            // Add the app group and app access group
            Object appGroup = addGroupPrivileged(graph.getVertex(appGroupId), user.asVertex(), name,
                    new GroupInheritancePermissions(false, false, false, false, false),
                    new UserGroupPermissionsWrapper(true, true, true, true, true), true, false);
            Object appAccessGroup = addGroupPrivileged(graph.getVertex(appAccessGroupId), user.asVertex(), name,
                    new GroupInheritancePermissions(true, false, false, false, false),
                    new UserGroupPermissionsWrapper(true, true, true, true, true), true, false);
            break;
        case USER:
            break;
        default:
            logger.error("Adding user encountered type : {}", type);
        }

        graph.commit();
        return user;
    }

    /**
     * Gets an user by its Titan/ EzGroups ID.
     *
     * @param type whether the user is a USER or an APP_USER
     * @param id Titan ID/ vertex ID of the user
     * @return an User identified by the given ID and type
     * @throws InvalidVertexTypeException if the UserType is not USER or APP_USER
     * @throws UserNotFoundException if the user cannot be found
     */
    public User getUser(BaseVertex.VertexType type, String id)
            throws InvalidVertexTypeException, UserNotFoundException {
        if (type != BaseVertex.VertexType.APP_USER && type != BaseVertex.VertexType.USER) {
            throw new InvalidVertexTypeException(
                    String.format("'%s' is not an user type that can be retrieved!", type));
        }
        FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        return findAndRetrieveUserById(type, id, framedGraph);
    }

    /**
     * Updates an user's principal to the new principal.
     *
     * @param type whether the user is a USER or an APP_USER
     * @param principal user's original principal
     * @param newPrincipal new principal for the user
     * @throws UserNotFoundException if the user cannot be found
     * @throws InvalidVertexTypeException if the type is not USER or APP_USER
     * @throws VertexNotFoundException if the user is an APP user and associated groups (e.g. app access) are missing
     * @throws VertexExistsException if a vertex already exists with the new principal
     */
    @Override
    public void updateUser(BaseVertex.VertexType type, String principal, String newPrincipal)
            throws UserNotFoundException, InvalidVertexTypeException, VertexNotFoundException,
            VertexExistsException {

        updateUser(type, principal, newPrincipal, null);
    }

    /**
     * Updates an user's principal to the new principal.
     *
     * @param type whether the user is a USER or an APP_USER
     * @param principal user's original principal
     * @param newPrincipal new principal for the user
     * @param newName new name for the user - if this field is the same as original name or empty, name is not changed
     * @throws UserNotFoundException if the user cannot be found
     * @throws InvalidVertexTypeException if the type is not USER or APP_USER
     * @throws VertexNotFoundException if the user is an APP user and associated groups (e.g. app access) are missing
     * @throws VertexExistsException if a vertex already exists with the new principal
     */
    @Override
    public void updateUser(BaseVertex.VertexType type, String principal, String newPrincipal, String newName)
            throws UserNotFoundException, InvalidVertexTypeException, VertexNotFoundException,
            VertexExistsException {
        logger.info("Processing updateUser user type: {}, principal: {}, newPrincipal: {}", type, principal,
                newPrincipal);

        // only accept valid user types
        if (type != BaseVertex.VertexType.APP_USER && type != BaseVertex.VertexType.USER) {
            final String errMsg = "Cannot update user for type: " + type;
            logger.error("Cannot update user for type: " + type);
            throw new InvalidVertexTypeException(errMsg);
        }

        User user;

        // prevent principal change to a principal that already exists
        if (!principal.equals(newPrincipal) && userExists(type, newPrincipal)) {
            final String errMsg = String.format(
                    "Cannot update user with principal '%s' to new principal '%s', a vertex already exists with that "
                            + "principal!",
                    principal, newPrincipal);

            logger.error(errMsg);
            throw new VertexExistsException(errMsg);
        } else {
            // get the user and set its principal to the new value
            user = getUserByEzBakeId(type, principal);
            user.setPrincipal(newPrincipal);
        }

        // Only set the name if a value is provided
        if (!Strings.isNullOrEmpty(newName) && !newName.trim().isEmpty()) {
            // If app user, need to update the app groups
            if (type == BaseVertex.VertexType.APP_USER) {
                final String currentName = user.getName();

                // Update group name
                final GroupNameHelper helper = new GroupNameHelper();
                final String appGroupName = helper.getNamespacedAppGroup(currentName);
                final String appAccessGroupName = helper.getNamespacedAppAccessGroup(currentName);

                try {
                    changeGroupName(appGroupName, newName);
                    changeGroupName(appAccessGroupName, newName);
                } catch (VertexNotFoundException | VertexExistsException e) {
                    logger.error("Failed to change APP_USER(id:{}) Name {} -> {}", principal, currentName, newName);
                    graph.rollback();
                    throw e;
                }
            }

            // update the user name
            user.setName(newName);
        }

        graph.commit();
    }

    public void setUserActiveOrNot(BaseVertex.VertexType type, String principal, boolean active)
            throws InvalidVertexTypeException, UserNotFoundException {
        logger.info("Processing {} User user type: {}, principal: {}", active ? "activate" : "deactivate", type,
                principal);
        if (type != BaseVertex.VertexType.APP_USER && type != BaseVertex.VertexType.USER) {
            throw new InvalidVertexTypeException("Cannot add vertex of type: " + type + " to groups");
        }

        FramedGraph<TitanGraph> framedGraph = getFramedGraph();
        // Get the user vertex
        final User user = findAndRetrieveUserById(type, principal, framedGraph);
        user.setIsActive(active);
        graph.commit();
    }

    public void setGroupActiveOrNot(BaseVertex.VertexType type, String userId, final String groupName,
            boolean active) throws VertexNotFoundException, UserNotFoundException, AccessDeniedException {
        setGroupActiveOrNot(type, userId, groupName, active, false);
    }

    public void setGroupActiveOrNot(BaseVertex.VertexType type, String userId, final String groupName,
            boolean active, boolean andChildren)
            throws VertexNotFoundException, UserNotFoundException, AccessDeniedException {
        logger.info("Processing activate/deactivate group: {}, become active: {}", groupName, active);
        FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        // find the user
        final User user = findAndRetrieveUserById(type, userId, framedGraph);

        // find the group
        Iterator<Group> gs = framedGraph.query().has(Group.GROUP_NAME, groupName).limit(1).vertices(Group.class)
                .iterator();
        if (!gs.hasNext()) {
            graph.rollback();
            throw new VertexNotFoundException(
                    "Group: " + groupName + " does not exist. Cannot change active/deactivated");
        }
        Group g = gs.next();

        // Find the group by traversing from the user along admin manage
        GremlinPipeline<Vertex, Vertex> gi = new GremlinPipeline<Vertex, Vertex>(user.asVertex())
                .as("traversalLoop").outE(BaseEdge.EdgeType.A_MANAGE.toString()).gather().scatter().inV()
                .loop("traversalLoop", new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                    @Override
                    public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                        return !vertexLoopBundle.getObject().getProperty(Group.GROUP_NAME).equals(groupName);
                    }
                });
        if (!gi.hasNext()) {
            // Check if the vertex even exists
            if (!graph.query().has(Group.GROUP_NAME, groupName).vertices().iterator().hasNext()) {
                graph.rollback();
                throw new VertexNotFoundException(
                        "Group: " + groupName + " does not exist. Cannot change active/deactivated");
            } else {
                graph.rollback();
                throw new AccessDeniedException(
                        "User must have admin manage permissions on a group to " + "activate/deactivate it");
            }
        }
        Group g1 = framedGraph.frame(gi.next(), Group.class);
        g1.setIsActive(active);

        if (andChildren) {
            GremlinPipeline<Vertex, Vertex> pipe = new GremlinPipeline<Vertex, Vertex>(g.asVertex()).as("LOOPING")
                    // Make sure we can get back to the user
                    .inE(BaseEdge.EdgeType.A_MANAGE.toString()).gather().scatter().outV()
                    .loop("LOOPING", new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                        @Override
                        public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                            return !vertexLoopBundle.getObject().getId().equals(user.asVertex().getId());
                        }
                    }).back("LOOPING").as("NEWLOOP").outE(Group.CHILD_GROUP).inV()
                    .loop("NEWLOOP", new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                        @Override
                        public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                            return true;
                        }
                    }, new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                        @Override
                        public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                            return true;
                        }
                    }).as("child groups").inE(BaseEdge.EdgeType.A_MANAGE.toString()).outV()
                    .loop("child groups", new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                        @Override
                        public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                            return !vertexLoopBundle.getObject().getId().equals(user.asVertex().getId());
                        }
                    }).back("child groups").cast(Vertex.class);

            for (Vertex v : pipe) {
                Group childGroup = framedGraph.frame(v, Group.class);
                logger.info("Also {} child group {}", (active) ? "activating" : "deactivating",
                        childGroup.getGroupName());
                childGroup.setIsActive(active);
            }
        }
        graph.commit();
    }

    public void deleteUser(BaseVertex.VertexType type, String principal)
            throws InvalidVertexTypeException, UserNotFoundException {
        logger.info("Processing deleteUser user type: {}, principal: {}", type, principal);
        if (type != BaseVertex.VertexType.APP_USER && type != BaseVertex.VertexType.USER) {
            throw new InvalidVertexTypeException("Cannot add vertex of type: " + type + " to groups");
        }

        FramedGraph<TitanGraph> framedGraph = getFramedGraph();
        // Get the user vertex
        final User user = findAndRetrieveUserById(type, principal, framedGraph);

        graph.removeVertex(user.asVertex());
        graph.commit();
    }

    /**
     * Add a user to a group, which involves creating a direct DATA_ACCESS edge between the two
     *
     * @param type whether the user is a USER or an APP_USER
     * @param principal user principal
     * @param groupName String group name
     */
    public void addUserToGroup(BaseVertex.VertexType type, String principal, String groupName)
            throws VertexNotFoundException, UserNotFoundException, InvalidVertexTypeException {
        addUserToGroup(type, principal, groupName, true, false, false, false, false);
    }

    /**
     * Add a user to a group, which involves creating a direct DATA_ACCESS edge between the two
     *
     * @param type whether the user is a USER or an APP_USER
     * @param principal user principal
     * @param groupName String group name
     */
    public void addUserToGroup(BaseVertex.VertexType type, String principal, String groupName, boolean dataAccess,
            boolean adminRead, boolean adminWrite, boolean adminManage, boolean adminCreateChild)
            throws VertexNotFoundException, UserNotFoundException, InvalidVertexTypeException {
        // Get the group verted
        Iterator<Group> groups = getFramedGraph().query().has(Group.GROUP_NAME, Compare.EQUAL, groupName).limit(1)
                .vertices(Group.class).iterator();
        if (!groups.hasNext()) {
            graph.rollback();
            throw new VertexNotFoundException("Cannot find the group: " + groupName);
        }
        addUserToGroup(type, principal, groups.next(), dataAccess, adminRead, adminWrite, adminManage,
                adminCreateChild);
    }

    /**
     * Add a user to a group, which involves creating a direct DATA_ACCESS edge between the two
     *
     * @param type whether the user is a USER or an APP_USER
     * @param principal user principal
     * @param group Vertex of the group the user should be added to
     */
    public void addUserToGroup(BaseVertex.VertexType type, String principal, Group group)
            throws UserNotFoundException, InvalidVertexTypeException {
        addUserToGroup(type, principal, group, true, false, false, false, false);
    }

    /**
     * Add a user to a group, which involves creating a direct DATA_ACCESS edge between the two
     *
     * @param type whether the user is a USER or an APP_USER
     * @param principal user principal
     * @param group Vertex of the group the user should be added to
     */
    public void addUserToGroup(BaseVertex.VertexType type, String principal, Group group, boolean dataAcces,
            boolean adminRead, boolean adminWrite, boolean adminManage, boolean adminCreateChild)
            throws UserNotFoundException, InvalidVertexTypeException {
        logger.info("Processing addUserToGroup group: {}, user type: {}, principal: {}", group.getGroupName(), type,
                principal);
        if (type != BaseVertex.VertexType.APP_USER && type != BaseVertex.VertexType.USER) {
            graph.rollback();
            throw new InvalidVertexTypeException("Cannot add vertex of type: " + type + " to groups");
        }
        FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        // Get the user vertex
        final User user = findAndRetrieveUserById(type, principal, framedGraph);
        assignEdges(user.asVertex(), group.asVertex(), dataAcces, adminRead, adminWrite, adminManage,
                adminCreateChild);

        graph.commit();
    }

    public void removeUserFromGroup(BaseVertex.VertexType type, String principal, String groupName)
            throws UserNotFoundException, VertexNotFoundException, InvalidVertexTypeException {
        // Get the group verted
        Iterator<Vertex> groups = graph.query().has(Group.GROUP_NAME, groupName).limit(1).vertices().iterator();
        if (!groups.hasNext()) {
            graph.rollback();
            throw new VertexNotFoundException("Cannot find the group: " + groupName);
        }
        removeUserFromGroup(type, principal, groups.next().getId());
    }

    /**
     * o
     * Remove a user from a group. The user must have explict access to that group in order to be removed. This simply
     * removes the DATA_ACCESS edges from the user to the group
     *
     * @param type whether the user is a USER or an APP_USER
     * @param principal user principal
     * @param groupId Vertex ID of the group the user should be removed from
     */
    public void removeUserFromGroup(BaseVertex.VertexType type, String principal, Object groupId)
            throws UserNotFoundException, InvalidVertexTypeException {
        logger.info("Processing removeUserFromGroup group: {}, user type: {}, principal: {}", groupId, type,
                principal);
        if (type != BaseVertex.VertexType.APP_USER && type != BaseVertex.VertexType.USER) {
            graph.rollback();
            throw new InvalidVertexTypeException("Cannot remove vertex of type: " + type + " to groups");
        }

        FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        // Get the user vertex
        final User user = findAndRetrieveUserById(type, principal, framedGraph);
        Group group = framedGraph.getVertex(groupId, Group.class);

        removeEdges(user.asVertex(), group.asVertex(), true, false, false, false, false);
        graph.commit();
    }

    /**
     * Traverse the graph to determine all of the groups an user is a member of. Group membership is defined as having a
     * path along DATA_ACCESS edges from the user to the group.
     *
     * @param vertexID Titan ID of the user
     * @return the set of Group objects for all the groups the user is a member of
     */
    @VisibleForTesting
    Set<Group> userGroups(Object vertexID) {
        return userGroups(graph.getVertex(vertexID), false, true);
    }

    /**
     * Traverse the graph to determine all of the groups an user is a member of. Group membership is defined as having a
     * path along DATA_ACCESS edges from the user to the group.
     *
     * @param userID EzBake ID of the user for which to get the groups they are a member of
     * @return the set of all groups the user is a member of
     * @throws UserNotFoundException if the user whose groups were requested could not be found
     */
    @Override
    public Set<Group> userGroups(BaseVertex.VertexType userType, String userID) throws UserNotFoundException {
        return userGroups(userType, userID, false);
    }

    /**
     * Traverse the graph to determine all of the groups a user is a member of. Group membership is defined as having a
     * path along DATA_ACCESS edges from the user to the group.
     *
     * @param userID EzBake ID of the user for which to get the groups they are a member of
     * @return the set of all groups the user is a member of
     * @throws UserNotFoundException if the user could not be found
     */
    @Override
    public Set<Group> userGroups(BaseVertex.VertexType userType, String userID, boolean explicitGroupsOnly)
            throws UserNotFoundException {
        return userGroups(userType, userID, explicitGroupsOnly, true);
    }

    /**
     * Traverse the graph to determine all of the groups a user is a member of. Group membership is defined as having a
     * path along DATA_ACCESS edges from the user to the group.
     *
     * @param userType type of user, USER or APP_USER
     * @param userID EzBake ID of the user for which to get the groups they are a member of
     * @param explicitGroupsOnly if only groups that have a direct DATA_ACCESS edge to the user should be included in
     * the result
     * @param includeInactive if inactive groups should be included in the result
     * @return the set of all groups the user is a member of, or just the set of groups the user has a direct
     * DATA_ACCESS edge to if {@code explicitGroupsOnly} is true.
     * @throws UserNotFoundException if the user for which groups are requested cannot be found
     */
    @Override
    public Set<Group> userGroups(BaseVertex.VertexType userType, String userID, boolean explicitGroupsOnly,
            boolean includeInactive) throws UserNotFoundException {
        Iterator<Vertex> users = graph.query().has(BaseVertex.TYPE, userType.toString()).has(User.PRINCIPAL, userID)
                .limit(1).vertices().iterator();
        if (!users.hasNext()) {
            graph.rollback();
            throw new UserNotFoundException("No user found with ID: " + userID);
        }

        logger.trace("getting groups for {}", userID);
        return userGroups(users.next(), explicitGroupsOnly, includeInactive);
    }

    /**
     * Traverse the graph to determine all of the groups a user is a member of. Group membership is defined as having a
     * path along DATA_ACCESS edges from the user to the group. If retrieving explicit groups, only return groups that
     * have a direct DATA_ACCESS edge.
     *
     * @param user the user vertex from which to traverse down DATA_ACCESS edges
     * @param explicitPath if true, only return groups that have a direct DATA_ACCESS edge
     * @return the set of all groups the user is a member of
     */
    @VisibleForTesting
    Set<Group> userGroups(Vertex user, final boolean explicitPath, boolean includeInactive) {
        logger.trace("user: {}, explicitPath: {}, includeInactive: {}", user, explicitPath, includeInactive);

        final Set<Group> groups = new HashSet<>();

        GremlinPipeline<Object, Vertex> pipe = new GremlinPipeline<>(user).as("user_groups")
                .outE(BaseEdge.EdgeType.DATA_ACCESS.toString()).gather().scatter().inV()
                .loop("user_groups", new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                    @Override
                    public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                        return !explicitPath;
                    }
                }, new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                    @Override
                    public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                        return BaseVertex.VertexType.GROUP.toString()
                                .equals(vertexLoopBundle.getObject().getProperty(BaseVertex.TYPE));
                    }
                }).dedup();

        // Collect the results
        FramedGraph<TitanGraph> framedGraph = getFramedGraph();
        while (pipe.hasNext()) {
            Group g = framedGraph.frame(pipe.next(), Group.class);
            if (!includeInactive && !g.isActive()) {
                continue;
            }
            groups.add(g);
        }

        logger.trace("got user groups: {}", groups);
        return groups;
    }

    /**
     * Look up members of a group by first querying the graph for the group by group name. Collect the group members by
     * traversing the graph from a group vertex, along IN DATA_ACCESS edges, to find users who have access to that group
     *
     * @param groupName name of the group
     * @param explicityOnly only return group members who are explicitly included in the group
     * @return a set of User objects (both app and regular users)
     */
    public Set<User> groupMembers(BaseVertex.VertexType userType, String userId, String groupName,
            boolean explicityOnly) throws VertexNotFoundException, UserNotFoundException {
        return groupMembers(userType, userId, groupName, true, true, explicityOnly);
    }

    /**
     * Look up members of a group by first querying the graph for the group by group name. Collect the group members by
     * traversing the graph from a group vertex, along IN DATA_ACCESS edges, to find users who have access to that group
     *
     * @param groupName name of the group
     * @param explicityOnly only return group members who are explicitly included in the group
     * @param includeUsers whether or not users should be included in the query
     * @param includeApps whether or not apps should be included in the query
     * @return a set of User objects (both app and regular users)
     */
    @Override
    public Set<User> groupMembers(BaseVertex.VertexType userType, String userId, String groupName,
            final boolean includeUsers, final boolean includeApps, boolean explicityOnly)
            throws VertexNotFoundException, UserNotFoundException {
        // Find the owner
        Iterator<Vertex> users = graph.query().has(BaseVertex.TYPE, userType.toString()).has(User.PRINCIPAL, userId)
                .limit(1).vertices().iterator();
        if (!users.hasNext()) {
            graph.rollback();
            throw new UserNotFoundException("No user id: " + userId);
        }

        // Find the group
        Iterator<Vertex> groups = graph.query().has(Group.GROUP_NAME, groupName).limit(1).vertices().iterator();
        if (!groups.hasNext()) {
            graph.rollback();
            throw new VertexNotFoundException("No group found with name: " + groupName);
        }

        return groupMembers(users.next(), groups.next(), includeUsers, includeApps, explicityOnly);
    }

    @Override
    public Set<String> specialAppNamesQuery(String specialGroupName, Vertex user) throws VertexNotFoundException {
        final GroupQuery q = new GroupQuery(framedGraphFactory.create(this.graph));

        return q.getAppGroupQuery().specialAppNamesQuery(specialGroupName, user);
    }

    /**
     * Traverse the graph from a group vertex, along IN DATA_ACCESS edges, to find users who have access to that group
     *
     * @param group Vertex of group where the query should begin
     * @param explicitPath if true, only return users that have a direct edge
     * @param includeUsers whether group members of type USER are included in result
     * @param includeApps whether group members of type APP_USER are included in result
     * @return a set of User objects (both app and regular users)
     */
    public Set<User> groupMembers(final Vertex user, final Vertex group, final boolean includeUsers,
            final boolean includeApps, final boolean explicitPath) {
        final Set<User> users = new HashSet<>();

        GremlinPipeline<Object, Vertex> pipe = new GremlinPipeline<>(user).as("owner_access")
                .outE(BaseEdge.EdgeType.A_READ.toString()).inV()
                .loop("owner_access", new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                    @Override
                    public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                        return !vertexLoopBundle.getObject().getId().equals(group.getId());
                    }
                }).as("group").as("group_member_traversal").inE(BaseEdge.EdgeType.DATA_ACCESS.toString()).outV()
                .loop("group_member_traversal", new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                    @Override
                    public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                        return !explicitPath;
                    }
                }, new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                    @Override
                    public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                        boolean isUser = BaseVertex.VertexType.USER.toString()
                                .equals(vertexLoopBundle.getObject().getProperty(BaseVertex.TYPE));
                        boolean isApp = BaseVertex.VertexType.APP_USER.toString()
                                .equals(vertexLoopBundle.getObject().getProperty(BaseVertex.TYPE));

                        return (includeUsers && isUser) || (includeApps && isApp);
                    }
                }).dedup();

        FramedGraph<TitanGraph> framedGraph = getFramedGraph();
        while (pipe.hasNext()) {
            User member = framedGraph.frame(pipe.next(), User.class);
            users.add(member);
        }
        return users;
    }

    /**
     * Get child groups starting at a named parent group, as a user. The user must have ADMIN_READ permissions on the
     * parent group to get any results, and they will only see child groups they also have ADMIN_READ on. This will
     * only return direct children of the named parent group
     *
     * @param userType type of user, USER or APP_USER
     * @param id the user's unique external id
     * @param groupName name of the group at which to start the query
     * @return a set of all the child groups the user can view
     * @throws UserNotFoundException
     * @throws VertexNotFoundException
     * @throws AccessDeniedException
     */
    public Set<Group> getGroupChildren(BaseVertex.VertexType userType, String id, String groupName)
            throws UserNotFoundException, VertexNotFoundException, AccessDeniedException {
        return getGroupChildren(userType, id, groupName, false);
    }

    /**
     * Get child groups starting at a named parent group, as a user. The user must have ADMIN_READ permissions on the
     * parent group to get any results, and they will only see child groups they also have ADMIN_READ on.
     *
     * @param userType type of user, USER or APP_USER
     * @param id the user's unique external id
     * @param recurse if true, this will return child groups of the children
     * @return a set of all the child groups the user can view
     * @throws UserNotFoundException
     * @throws VertexNotFoundException
     * @throws AccessDeniedException
     */
    public Set<Group> getGroupChildren(BaseVertex.VertexType userType, String id, String groupName,
            final boolean recurse) throws UserNotFoundException, VertexNotFoundException, AccessDeniedException {
        return getGroupChildren(userType, id, groupName, recurse, false);
    }

    public Set<Group> getGroupChildren(BaseVertex.VertexType userType, String id, String groupName,
            final boolean recurse, boolean includeSelf)
            throws UserNotFoundException, VertexNotFoundException, AccessDeniedException {
        final FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        // find the user vertex
        final User owner = findAndRetrieveUserById(userType, id, framedGraph);

        // find the parent group
        Iterator<Vertex> parents = graph.query().has(Group.GROUP_NAME, groupName).limit(1).vertices().iterator();
        if (!parents.hasNext()) {
            graph.rollback();
            throw new VertexNotFoundException("Cannot find the parent group: " + groupName);
        }
        Vertex parent = parents.next();

        final GremlinPipeline<Vertex, Vertex> groupPath = getChildGroups(recurse, framedGraph, parent, true);

        groupPath.as("traverse_back_to_user").inE(BaseEdge.EdgeType.A_READ.toString()).outV()
                .loop("traverse_back_to_user", new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                    @Override
                    public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                        return !vertexLoopBundle.getObject().equals(owner.asVertex());
                    }
                }).back("traverse_back_to_user").cast(Vertex.class).enablePath();

        Set<Group> childGroups = Sets.newHashSet(framedGraph.frameVertices(groupPath, Group.class));
        if (includeSelf) {
            childGroups.add(framedGraph.frame(parent, Group.class));
        }
        return childGroups;
    }

    /**
     * Gets children of a given Vertex. A group-vertex is considered a child group of another group-vertex if it
     * has path of incoming {@link Group#CHILD_GROUP} edges connecting to that vertex. If {@code recurse} is false, only
     * the children one hop away are returned, otherwise all children are returned.
     *
     * @param recurse true if all children are desired, false if only the immediate children
     * @param framedGraph framed graph to traverse
     * @param parent vertex to get the children from
     * @param activeGroupsOnly whether or not to only return active groups
     * @return a gremlin pipeline with the requested child groups
     */
    private GremlinPipeline<Vertex, Vertex> getChildGroups(final boolean recurse,
            final FramedGraph<TitanGraph> framedGraph, Vertex parent, final boolean activeGroupsOnly) {
        final String findChildGroups = "findChildGroups";

        return new GremlinPipeline<Vertex, Vertex>(parent)
                // Traverse all the child groups, and emit as appropriate
                .as(findChildGroups).outE(Group.CHILD_GROUP).gather().scatter().inV().loop(findChildGroups,

                        new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                            @Override
                            public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {

                                return recurse;
                            }
                        }, new PipeFunction<LoopPipe.LoopBundle<Vertex>, Boolean>() {
                            @Override
                            public Boolean compute(LoopPipe.LoopBundle<Vertex> vertexLoopBundle) {
                                final Group g = framedGraph.frame(vertexLoopBundle.getObject(), Group.class);
                                if (activeGroupsOnly) {
                                    return g.isActive();
                                } else {
                                    return true;
                                }
                            }
                        })
                .dedup();
    }

    /**
     * Given group name, infer permissions based on all edges between given vertex (determined by group name) and its
     * parent
     *
     * @param groupName
     * @return inheritance permissions
     * @throws VertexNotFoundException
     */
    public GroupInheritancePermissions getGroupInheritancePermissions(String groupName)
            throws VertexNotFoundException {
        List<String> split = Splitter.on(EzGroupsConstants.GROUP_NAME_SEP).splitToList(groupName);

        String parentName;
        if (split.size() > 1) { // prevent array out of boundary exception
            parentName = Joiner.on(EzGroupsConstants.GROUP_NAME_SEP).join(split.subList(0, split.size() - 1));
        } else {
            parentName = groupName;
        }

        Iterator<Vertex> parents = graph.query().has(Group.GROUP_NAME, parentName).limit(1).vertices().iterator();
        Iterator<Vertex> children = graph.query().has(Group.GROUP_NAME, groupName).limit(1).vertices().iterator();

        if (!parents.hasNext() || !children.hasNext()) {
            logger.error("Cannot find group name: parent group {} | child group {}", parentName, groupName);
            graph.rollback();
            throw new VertexNotFoundException(
                    "Cannot find group name: parent group " + parentName + " | child group " + groupName);
        }

        Vertex parent = parents.next();
        Vertex child = children.next();
        final GroupQuery q = new GroupQuery(framedGraphFactory.create(this.graph));

        return new GroupInheritancePermissions(
                q.getBaseQuery().pathExists(parent, child.getId(), BaseEdge.EdgeType.DATA_ACCESS.toString()),
                q.getBaseQuery().pathExists(parent, child.getId(), BaseEdge.EdgeType.A_READ.toString()),
                q.getBaseQuery().pathExists(parent, child.getId(), BaseEdge.EdgeType.A_WRITE.toString()),
                q.getBaseQuery().pathExists(parent, child.getId(), BaseEdge.EdgeType.A_MANAGE.toString()),
                q.getBaseQuery().pathExists(parent, child.getId(), BaseEdge.EdgeType.A_CREATE_CHILD.toString()));
    }

    public Set<BaseEdge.EdgeType> userPermissionsOnGroup(BaseVertex.VertexType type, String userPrincipal,
            String groupName) throws UserNotFoundException, VertexNotFoundException {
        final FramedGraph<TitanGraph> framedGraph = getFramedGraph();

        // find the user vertex
        final User owner = findAndRetrieveUserById(type, userPrincipal, framedGraph);

        // find the group group
        Iterator<Group> groups = framedGraph.query().has(Group.GROUP_NAME, groupName).limit(1).vertices(Group.class)
                .iterator();
        if (!groups.hasNext()) {
            graph.rollback();
            throw new VertexNotFoundException("Cannot find the group: " + groupName);
        }
        Group group = groups.next();

        Set<BaseEdge.EdgeType> edges = new HashSet<>();
        final GroupQuery q = new GroupQuery(framedGraphFactory.create(this.graph));

        for (BaseEdge.EdgeType edge : BaseEdge.EdgeType.values()) {
            if (q.getBaseQuery().pathExists(owner.asVertex(), group.asVertex().getId(), edge.toString())) {
                edges.add(edge);
            }
        }
        graph.commit();

        return edges;
    }

    @Override
    public boolean pathExists(Vertex source, Vertex target, String... edgeLabel) {
        final GroupQuery q = new GroupQuery(framedGraphFactory.create(this.graph));

        return q.getBaseQuery().pathExists(source, target, edgeLabel);
    }

    /**
     * Finds and returns an User-vertex of the given type, by the given ID.
     *
     * @param userType type of user, USER or APP_USER
     * @param id principal/id of the user to retrieve
     * @param framedGraph graph to search for the user
     * @return an User-vertex by the given ID
     * @throws UserNotFoundException if the user cannot be found
     */
    private User findAndRetrieveUserById(BaseVertex.VertexType userType, String id,
            FramedGraph<TitanGraph> framedGraph) throws UserNotFoundException {
        Iterator<User> ownerit = framedGraph.query().limit(1).has(User.TYPE, userType.toString())
                .has(User.PRINCIPAL, id).vertices(User.class).iterator();

        if (!ownerit.hasNext()) {
            graph.rollback();
            final String errMsg = String.format(String.format("User: '%s' of type: '%s' not found.", id, userType));
            logger.error(errMsg);
            throw new UserNotFoundException(errMsg);
        }

        return ownerit.next();
    }

    /**
     * App Access groups cannot have children, this will return true if it is a child group of App access
     *
     * @param group the group that is being checked
     * @return true if the group is a child of app access
     */
    protected boolean isAppAccessGroup(Vertex group) {
        return group != null && new GremlinPipeline<Vertex, Vertex>(group).inE(Group.CHILD_GROUP).outV()
                .has("id", appAccessGroupId).hasNext();
    }

    /**
     * Helper function that will assign the various different edges between two vertices
     *  @param parent Vertex that will be the source of the edges
     * @param child Vertex that will be the destination of the edges
     * @param dataAccess if true, assign a data access edge
     * @param a_read if true, assign an admin read edge
     * @param a_write if true, assign an admin write edge
     * @param a_manage if true, assign an admin manage edge
     * @param a_create_child if true, assign an admin create child edge
     */
    protected void assignEdges(final Vertex parent, final Vertex child, boolean dataAccess, boolean a_read,
            boolean a_write, boolean a_manage, boolean a_create_child) {
        if (dataAccess) {
            parent.addEdge(BaseEdge.EdgeType.DATA_ACCESS.toString(), child);
        } else {
            removeSpecificEdge(parent.getId(),
                    child.query().labels(BaseEdge.EdgeType.DATA_ACCESS.toString()).direction(Direction.IN).edges());
        }
        if (a_create_child) {
            parent.addEdge(BaseEdge.EdgeType.A_CREATE_CHILD.toString(), child);
        } else {
            removeSpecificEdge(parent.getId(), child.query().labels(BaseEdge.EdgeType.A_CREATE_CHILD.toString())
                    .direction(Direction.IN).edges());
        }
        if (a_manage) {
            parent.addEdge(BaseEdge.EdgeType.A_MANAGE.toString(), child);
        } else {
            removeSpecificEdge(parent.getId(),
                    child.query().labels(BaseEdge.EdgeType.A_MANAGE.toString()).direction(Direction.IN).edges());
        }
        if (a_read) {
            parent.addEdge(BaseEdge.EdgeType.A_READ.toString(), child);
        } else {
            removeSpecificEdge(parent.getId(),
                    child.query().labels(BaseEdge.EdgeType.A_READ.toString()).direction(Direction.IN).edges());
        }
        if (a_write) {
            parent.addEdge(BaseEdge.EdgeType.A_WRITE.toString(), child);
        } else {
            removeSpecificEdge(parent.getId(),
                    child.query().labels(BaseEdge.EdgeType.A_WRITE.toString()).direction(Direction.IN).edges());
        }
    }

    private void removeSpecificEdge(Object id, Iterable<Edge> edges) {
        for (Edge e : edges) {
            if (e.getVertex(Direction.OUT).getId().equals(id)) {
                logger.debug("Removing edge: {} {}", e, e.getLabel());
                graph.removeEdge(e);
            }
        }
    }

    protected Iterable<Edge> getAllEdgesBetweenVertices(final Vertex parent, final Vertex child) {
        return new GremlinPipeline<Vertex, Edge>(parent)
                .outE(BaseEdge.EdgeType.DATA_ACCESS.toString(), BaseEdge.EdgeType.A_CREATE_CHILD.toString(),
                        BaseEdge.EdgeType.A_MANAGE.toString(), BaseEdge.EdgeType.A_READ.toString(),
                        BaseEdge.EdgeType.A_WRITE.toString())
                .as("edges").inV().has("id", child.getId()).back("edges").cast(Edge.class);
    }

    protected void removeEdges(final Vertex parent, final Vertex child, boolean dataAccess, boolean a_create_child,
            boolean a_manage, boolean a_read, boolean a_write) {
        Iterable<Edge> edges = getAllEdgesBetweenVertices(parent, child);
        for (Edge e : edges) {
            BaseEdge.EdgeType edgeType = BaseEdge.EdgeType.valueOf(e.getLabel());
            boolean needRemove = false;

            switch (edgeType) {
            case DATA_ACCESS:
                needRemove = dataAccess;
                break;
            case A_CREATE_CHILD:
                needRemove = a_create_child;
                break;
            case A_MANAGE:
                needRemove = a_manage;
                break;
            case A_WRITE:
                needRemove = a_write;
                break;
            case A_READ:
                needRemove = a_read;
                break;
            }

            // Remove edges that need it
            if (needRemove) {
                graph.removeEdge(e);
            }
        }
        graph.commit();
    }

    //TODO: test me.
    protected void manageEdges(final Vertex parent, final Vertex child, boolean dataAccess, boolean a_create_child,
            boolean a_manage, boolean a_read, boolean a_write) {
        // Get all edges that currently exist between them
        Iterable<Edge> edges = new GremlinPipeline<Vertex, Edge>(parent)
                .outE(BaseEdge.EdgeType.DATA_ACCESS.toString(), BaseEdge.EdgeType.A_CREATE_CHILD.toString(),
                        BaseEdge.EdgeType.A_MANAGE.toString(), BaseEdge.EdgeType.A_READ.toString(),
                        BaseEdge.EdgeType.A_WRITE.toString())
                .as("edges").inV().has("id", child.getId()).back("edges").cast(Edge.class);

        // This list will contain only edges that need to be created once the following loop is complete
        List<BaseEdge.EdgeType> needCreate = new ArrayList<>(Arrays.asList((BaseEdge.EdgeType.values())));

        // Remove ones that shouldn't exist, and remove edges from needCreate if they already exist (and should)
        for (Edge e : edges) {
            BaseEdge.EdgeType edgeType = BaseEdge.EdgeType.valueOf(e.getLabel());
            boolean needRemove = false;

            switch (edgeType) {
            case DATA_ACCESS:
                needRemove = !dataAccess;
                break;
            case A_CREATE_CHILD:
                needRemove = !a_create_child;
                break;
            case A_MANAGE:
                needRemove = !a_manage;
                break;
            case A_WRITE:
                needRemove = !a_write;
                break;
            case A_READ:
                needRemove = !a_read;
                break;
            }

            // Remove edges that need it
            if (needRemove) {
                graph.removeEdge(e);
            } else {
                needCreate.remove(edgeType);
            }
        }

        // Add those edges that didn't exist
        for (BaseEdge.EdgeType type : needCreate) {
            parent.addEdge(type.toString(), child);
        }
        graph.commit();
    }

    @Override
    public Set<Long> getAuthorizations(BaseVertex.VertexType userType, String userId, List<String> appFilter)
            throws GroupQueryException {
        return new AuthorizationQuery(this).execute(userType, userId, appFilter);
    }

    public Set<BaseEdge.EdgeType> edgesBetweenVertices(Vertex source, final Vertex destination,
            BaseEdge.EdgeType... edgeLabels) {
        Set<BaseEdge.EdgeType> edges = new HashSet<>();
        final GroupQuery q = new GroupQuery(framedGraphFactory.create(this.graph));

        for (BaseEdge.EdgeType edgeLabel : edgeLabels) {
            logger.trace("Checking edge between {} and {}", source, destination);
            if (q.getBaseQuery().pathExists(source, destination.getId(), edgeLabel.toString())) {
                logger.trace("Path existed {}", edgeLabel);
                edges.add(edgeLabel);
            }
        }

        return edges;
    }
}