6

I have a list of roles in a database. They are of the form

application.Role1.read
application.Role1.write
application.Role2.read
application.Role3.read

So each role has an entry based on read/write permission. I want to convert the roles into a POJO which I can then send as JSON to a UI. Each POJO would have the role name, and a boolean for read or write permission.

Here is the RolePermission class:

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class RolePermission {
    private String roleName;
    private boolean readAllowed;
    private boolean writeAllowed;

    public String getRoleName() {
        return roleName;
    }

    public RolePermission setRoleName(String roleName) {
        this.roleName = roleName;
        return this;
    }

    public boolean isReadAllowed() {
        return readAllowed;
    }

    public RolePermission setReadAllowed(boolean readAllowed) {
        this.readAllowed = readAllowed;
        return this;
    }

    public boolean isWriteAllowed() {
        return writeAllowed;
    }

    public RolePermission setWriteAllowed(boolean writeAllowed) {
        this.writeAllowed = writeAllowed;
        return this;
    }
}

I am doing the transformation like so:

public static final String ROLE_PREFIX = "application.";
public static final String ROLE_READ_PERMISSION = "read";
public static final String ROLE_WRITE_PERMISSION = "write";

@Override
public List<RolePermission> getRoles(Backend backend) {
    List<String> allRoles = backend.getRoles()
            .stream()
            .map(s -> s.replace(ROLE_PREFIX, ""))
            .sorted()
            .collect(Collectors.toList());
    Map<String, RolePermission> roleMap = new HashMap<>();
    for (String role : allRoles) {
        String[] tokens = role.split(".");
        String roleName = tokens[0];
        String permission = tokens[1];
        if (!roleMap.containsKey(roleName))
            roleMap.put(roleName, new RolePermission().setRoleName(roleName));
        RolePermission permission = roleMap.get(roleName);
        if (ROLE_READ_PERMISSION.equals(permission))
            permission.setReadAllowed(true);
        if (ROLE_WRITE_PERMISSION.equals(permission))
            permission.setWriteAllowed(true);
    }
    return new LinkedList<>(roleMap.values());
}

Is there a way to do the foreach loop above using Java 8 streams?

This is a mock Backend instance that just returns a list of roles:

public class Backend {
    public List<String> getRoles() {
        return Arrays.asList(
            "application.Role1.read",
            "application.Role1.write",
            "application.Role2.read",
            "application.Role3.read"
        );
    }
}
1
  • Not sure who keeps voting down answers, but not providing one in their place... :-( Commented Nov 6, 2017 at 19:07

3 Answers 3

4

You can use groupingBy to join the different permissions for the same roleName together.

public static final String ROLE_PREFIX = "application.";
public static final String ROLE_READ_PERMISSION = "read";
public static final String ROLE_WRITE_PERMISSION = "write";

@Override
public List<RolePermission> getRoles(Backend backend) {
    Map<String, List<String[]>> allRoles = backend.getRoles()
            .stream()
            .map(s -> s.replace(ROLE_PREFIX, "")) // something like "Role1.read"
            .map(s -> s.split("\\.")) // something like ["Role1", "read"]
            .collect(Collectors.groupingBy(split -> split[0]));
    return allRoles.values()
                   .stream()
                   .map(this::buildPermission)
                   .collect(Collectors.toList());
}

private RolePermission buildPermission(List<String[]> roleEntries) {
    RolePermission permission = new RolePermission().setRoleName(roleEntries.get(0)[0]);
    roleEntries.stream()
               .forEach(entry -> {
                   if (ROLE_READ_PERMISSION.equals(entry[1]))
                       permission.setReadAllowed(true);
                   if (ROLE_WRITE_PERMISSION.equals(entry[1]))
                       permission.setWriteAllowed(true);
               });
    return permission;
}

I also think that your String.split was using an incorrect regex in the original post, because . is a special regex character. I've tested this and it works correctly.

Output:

[RolePermission(roleName=Role3, readAllowed=true, writeAllowed=false), 
 RolePermission(roleName=Role2, readAllowed=true, writeAllowed=false),
 RolePermission(roleName=Role1, readAllowed=true, writeAllowed=true)]
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks @durron597. Can you help me figure out what this line is doing? .map(s -> s.replace(ROLE_PREFIX, "").split("\\."))
@JoseChavez it's the same as what you had, except I moved the String.split up. You have to use the \` because .` is a regex expression that means "any character". I'll edit a link to that reference into my answer.
I guess I would like to know what would be the result of that "map" method call? Is that returning an array of strings, but grouped by the role name?
Awesome, I think the groupingBy call is what glues it together :-)
0

Your for loop can be replaced with second call of map in your existing stream and toMap collector:

public List<RolePermission> getRoles(Backend backend)
{
    Map<String, RolePermission> allRoles = backend.getRoles()
            .stream()
            .map(s -> s.replace(ROLE_PREFIX, ""))
            .sorted()
            .map(this::mapStringToRolePermission)
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, RolePermission::merge));
    return new ArrayList<>(allRoles.values());
}

where mapStringToRolePermission:

private static Map.Entry<String, RolePermission> mapStringToRolePermission(String role)
{
    String roleName = role.substring(0, role.indexOf('.'));
    RolePermission rolePermission = new RolePermission();
    rolePermission.setRoleName(roleName);
    rolePermission.setReadAllowed(role.endsWith(ROLE_READ_PERMISSION));
    rolePermission.setWriteAllowed(role.endsWith(ROLE_WRITE_PERMISSION));
    return new AbstractMap.SimpleEntry<>(roleName, rolePermission);
}

and an additional method merge for RolePermission:

public RolePermission merge(RolePermission another)
{
    if (another.isReadAllowed())
        setReadAllowed(true);
    if (another.isWriteAllowed())
        setWriteAllowed(true);
    return this;
}

Comments

-1

The Java stream has a map function so you could use that to map String to RolePermission (or whatever object you want) and then collect the results to a list. Would something like this work for you? It looks like you more or less already have the String to RolePermission conversion done

final List<RolePermission> roles = getRoles().stream().map(roleString -> { <your conversion code> }).collect(Collectors.toList());

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.