开发者

How to parse a hierarchical data structure containing inner references and possible circular references?

开发者 https://www.devze.com 2023-02-01 13:12 出处:网络
I\'m implementing a GUI for setting up user roles, where the admin of the application has the possibility to define nested roles. Roles in this particular case are defined by simple key-value pairs, t

I'm implementing a GUI for setting up user roles, where the admin of the application has the possibility to define nested roles. Roles in this particular case are defined by simple key-value pairs, the key identifying the role, and the value being either a "child role" that the current role extends, or a path to some part of the application. Here are some arbitrary examples of the possible structures:

user => path::/articles/read
user => path::/settings/edit/my

moderator => extend::user
moderator => path::/articles/edit

siteadmin => extend::moderator
siteadmin => extend::some-other-role
siteadmin => path::/user/ban

As you can see, roles can be nested, and a single role can extend multiple roles.

My objective is to

  • detect and warn about circular references caused by extending roles
  • flatten out the whole data structure, so rules will only contain "path" type definitions.

How would you approach this problem? I'm implementing this in php, but any solution (pseudo code, java, et开发者_开发技巧c) is appreciated (given the algorithm is right).


Code below should do the trick. You will need to provide a simple parser to parse values to the left and to the right of '=>' sign and feed it the Flatten.update() method below.

The code works in two stages:

1) Build a mapping for each role to the list of paths declared with "path::" and list of parent roles declared with "extend::"

2) Flatten all paths for each role, by traversing the mapping while keeping a map of all visited parent nodes to avoid circular dependencies.

class ChildValue {
    Set<String> paths = new HashSet<String>();
    Set<String> extend = new HashSet<String>();
}

/*
 * Logic to flatten interpret and flatten out
 * roles data
 */
class Flatten {
    // logical mapping store
    Map<String, ChildValue> mapping = new HashMap<String, ChildValue>();

    // Stage 1. Build logical mappin
    //
    // Call this method for every "role" => "path::/path" or "extend::role" pair
    void update(String roleName, String value) {
        ChildValue child = mapping.get(roleName);
        if (null == child) {
            // create new child node
            child = new ChildValue();
            mapping.put(roleName, child);
        }

        if (value.startsWith("path::")) {
            String path = value.substring(6);
            child.paths.add(path);
        } else {// assume "extend::"
            String extend = value.substring(8);
            child.extend.add(extend);
        }
    }

    // Stage 2.
    // After the mapping is build, call this method to
    // find all flattened paths for the role you need
    Set<String> getPathsFor(String roleName) {
        Set<String> visited = new HashSet<String>();
        return getPathsFor(roleName, visited);
    }

    // this method is called recursively
    private Set<String> getPathsFor(String roleName, Set<String> visited) {
        Set<String> paths = new HashSet<String>();
        ChildValue child = mapping.get(roleName);
        if (child != null) {
            // first add all paths directly declared with "path::"
            paths.addAll(child.paths);
            // then add all transitive paths
            for (String extendedRole : child.extend) {
                // check if parent node has not yet been visited
                // to avoid circular dependencies
                if (!visited.contains(extendedRole)) {
                    // not yet visited here, add all extended paths
                    visited.add(extendedRole);
                    paths.addAll(getPathsFor(extendedRole, visited));
                }else{
                        // node already visited, seems like we
                        // we have a circular dependency
                        throw new RuntimeException("Detected circular dep");
                }
            }
        }
        return paths;
    }
}

Here is a simple test for the code above

public class Roles {
    public static void main(String[] args) {
        Flatten ft = new Flatten();
        ft.update("user", "path::/path1");
        ft.update("user", "path::/path2");
        ft.update("user", "extend::parent");

        ft.update("parent", "path::/parent/path1");
        ft.update("parent", "path::/parent/path2");
        ft.update("parent", "extend::user");// circular dep

        System.out.println("paths for user => " + ft.getPathsFor("user"));
        System.out.println("paths for parent => " + ft.getPathsFor("parent"));
        System.out.println("paths for unknown => " + ft.getPathsFor("unknown"));

            //will print
            // paths for user => [/path2, /path1, /parent/path2, /parent/path1]
            // paths for parent => [/path2, /path1, /parent/path2, /parent/path1]
            // paths for unknown => []
    }
}

Hope this captures the general idea.

0

精彩评论

暂无评论...
验证码 换一张
取 消