Skip to content

Commit a0227a1

Browse files
authored
Merge pull request #534 from Netcentric/feature/503-improve-performance-of-applying-users-and-groups
#503 Improve the performance of applying groups and users
2 parents ecfbab2 + 70e4158 commit a0227a1

File tree

5 files changed

+367
-64
lines changed

5 files changed

+367
-64
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package biz.netcentric.cq.tools.actool.authorizableinstaller.impl;
2+
3+
import java.security.Principal;
4+
import java.util.Set;
5+
6+
import javax.jcr.RepositoryException;
7+
8+
import org.apache.jackrabbit.api.security.user.Authorizable;
9+
import org.apache.jackrabbit.api.security.user.Group;
10+
import org.apache.jackrabbit.api.security.user.User;
11+
import org.apache.jackrabbit.api.security.user.UserManager;
12+
13+
/** Replicates the methods from {@link UserManager} but does not extend this interface because {@link UserManager} is a provider type that
14+
* may receive additional methods in the future (and hence it would break this code). See {@link AuthInstallerUserManagerPrefetchingImpl}
15+
* for the motivation to use this special user manager (performance). */
16+
interface AuthInstallerUserManager {
17+
18+
// -- AC Tool special methods
19+
Set<String> getDeclaredIsMemberOf(String id) throws RepositoryException;
20+
21+
Set<String> getDeclaredMembersWithoutRegularUsers(String id);
22+
23+
UserManager getOakUserManager();
24+
25+
// -- methods from oak UserManager that the AC tool uses (delegated, only the relevant methods are listed here)
26+
27+
Authorizable getAuthorizable(String id) throws RepositoryException;
28+
29+
User createUser(String userID, String password, Principal principal, String intermediatePath) throws RepositoryException;
30+
31+
User createSystemUser(String userID, String intermediatePath) throws RepositoryException;
32+
33+
Group createGroup(Principal principal) throws RepositoryException;
34+
35+
Group createGroup(Principal principal, String intermediatePath) throws RepositoryException;
36+
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package biz.netcentric.cq.tools.actool.authorizableinstaller.impl;
2+
3+
import static biz.netcentric.cq.tools.actool.history.PersistableInstallationLogger.msHumanReadable;
4+
5+
import java.security.Principal;
6+
import java.util.Collections;
7+
import java.util.HashMap;
8+
import java.util.HashSet;
9+
import java.util.Iterator;
10+
import java.util.Map;
11+
import java.util.Set;
12+
13+
import javax.jcr.RepositoryException;
14+
import javax.jcr.ValueFactory;
15+
16+
import org.apache.jackrabbit.JcrConstants;
17+
import org.apache.jackrabbit.api.security.user.Authorizable;
18+
import org.apache.jackrabbit.api.security.user.Group;
19+
import org.apache.jackrabbit.api.security.user.Query;
20+
import org.apache.jackrabbit.api.security.user.QueryBuilder;
21+
import org.apache.jackrabbit.api.security.user.User;
22+
import org.apache.jackrabbit.api.security.user.UserManager;
23+
import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
import biz.netcentric.cq.tools.actool.helper.Constants;
28+
import biz.netcentric.cq.tools.actool.history.InstallationLogger;
29+
30+
/**
31+
* <p>
32+
* Special user manager that prefetches
33+
* <ul>
34+
* <li>all groups, system users and anonymous (but not regular users)</li>
35+
* <li>all memberships between the preloaded authorizables</li>
36+
* <ul>
37+
* upon creation.
38+
* </p>
39+
* <p>
40+
* The membership relationships are cached in lookup maps to quickly be able to query the repository state for both
41+
* authorizable.declaredMemberOf() (this we call in this class also) and group.getDeclaredMembers(). Having called declaredMemberOf() for
42+
* all groups of the repository has also retrieved all memberships (expect regular user memberships), hence the method getDeclaredMembers()
43+
* does not have to be called anymore. This is particularly useful because that way the AC tool does not have to iterate over a potentially
44+
* huge number of users in production to then only filter out a few relevant groups in the end (the AC Tool does not touch user memberships
45+
* to groups).
46+
* </p>
47+
*/
48+
class AuthInstallerUserManagerPrefetchingImpl implements AuthInstallerUserManager {
49+
50+
private static final Logger LOG = LoggerFactory.getLogger(AuthInstallerUserManagerPrefetchingImpl.class);
51+
52+
private final UserManager delegate;
53+
private final Map<String, Authorizable> authorizableCache = new HashMap<>();
54+
55+
private final Map<String, Set<String>> nonRegularUserMembersByAuthorizableId = new HashMap<>();
56+
private final Map<String, Set<String>> isMemberOfByAuthorizableId = new HashMap<>();
57+
58+
public AuthInstallerUserManagerPrefetchingImpl(UserManager delegate, final ValueFactory valueFactory, InstallationLogger installLog)
59+
throws RepositoryException {
60+
this.delegate = delegate;
61+
62+
long startPrefetch = System.currentTimeMillis();
63+
Iterator<Authorizable> authorizablesToPrefetchIt = delegate.findAuthorizables(new Query() {
64+
public <T> void build(QueryBuilder<T> builder) {
65+
builder.setCondition(
66+
builder.or( //
67+
builder.neq("@" + JcrConstants.JCR_PRIMARYTYPE, valueFactory.createValue(UserConstants.NT_REP_USER)),
68+
builder.eq("@" + UserConstants.REP_AUTHORIZABLE_ID, valueFactory.createValue(Constants.USER_ANONYMOUS))) //
69+
);
70+
}
71+
});
72+
while (authorizablesToPrefetchIt.hasNext()) {
73+
Authorizable auth = authorizablesToPrefetchIt.next();
74+
String authId = auth.getID();
75+
authorizableCache.put(authId, auth);
76+
77+
// init lists
78+
nonRegularUserMembersByAuthorizableId.put(authId, new HashSet<String>());
79+
isMemberOfByAuthorizableId.put(authId, new HashSet<String>());
80+
}
81+
82+
installLog.addMessage(LOG, "Prefetched " + authorizableCache.size() + " authorizables in "
83+
+ msHumanReadable(System.currentTimeMillis() - startPrefetch));
84+
85+
long startPrefetchMemberships = System.currentTimeMillis();
86+
int membershipCount = 0;
87+
for (Authorizable authorizable : authorizableCache.values()) {
88+
String authId = authorizable.getID();
89+
Iterator<Group> declaredMemberOf = authorizable.declaredMemberOf();
90+
while (declaredMemberOf.hasNext()) {
91+
Group memberOfGroup = declaredMemberOf.next();
92+
String memberOfGroupId = memberOfGroup.getID();
93+
isMemberOfByAuthorizableId.get(authId).add(memberOfGroupId);
94+
nonRegularUserMembersByAuthorizableId.get(memberOfGroupId).add(authId);
95+
membershipCount++;
96+
}
97+
}
98+
99+
installLog.addMessage(LOG, "Prefetched " + membershipCount + " memberships in "
100+
+ msHumanReadable(System.currentTimeMillis() - startPrefetchMemberships));
101+
}
102+
103+
public Authorizable getAuthorizable(String id) throws RepositoryException {
104+
Authorizable authorizable = authorizableCache.get(id);
105+
if (authorizable == null) {
106+
authorizable = delegate.getAuthorizable(id);
107+
authorizableCache.put(id, authorizable);
108+
}
109+
return authorizable;
110+
}
111+
112+
public Set<String> getDeclaredIsMemberOf(String id) throws RepositoryException {
113+
114+
if (isMemberOfByAuthorizableId.containsKey(id)) {
115+
return isMemberOfByAuthorizableId.get(id);
116+
} else {
117+
// for users fall back to retrieve on demand
118+
Set<String> memberOfSet = new HashSet<String>();
119+
Iterator<Group> memberOfIt = getAuthorizable(id).declaredMemberOf();
120+
while (memberOfIt.hasNext()) {
121+
Authorizable memberOfGroup = memberOfIt.next();
122+
memberOfSet.add(memberOfGroup.getID());
123+
}
124+
return memberOfSet;
125+
}
126+
}
127+
128+
public Set<String> getDeclaredMembersWithoutRegularUsers(String id) {
129+
return nonRegularUserMembersByAuthorizableId.containsKey(id) ? nonRegularUserMembersByAuthorizableId.get(id)
130+
: Collections.<String> emptySet();
131+
}
132+
133+
public int getCacheSize() {
134+
return authorizableCache.size();
135+
}
136+
137+
// --- delegated methods
138+
139+
public UserManager getOakUserManager() {
140+
return delegate;
141+
}
142+
143+
public User createUser(String userID, String password, Principal principal, String intermediatePath) throws RepositoryException {
144+
return delegate.createUser(userID, password, principal, intermediatePath);
145+
}
146+
147+
public User createSystemUser(String userID, String intermediatePath) throws RepositoryException {
148+
return delegate.createSystemUser(userID, intermediatePath);
149+
}
150+
151+
public Group createGroup(Principal principal) throws RepositoryException {
152+
return delegate.createGroup(principal);
153+
}
154+
155+
public Group createGroup(Principal principal, String intermediatePath) throws RepositoryException {
156+
return delegate.createGroup(principal, intermediatePath);
157+
}
158+
159+
}

0 commit comments

Comments
 (0)