-
Notifications
You must be signed in to change notification settings - Fork 12
Do not block access for ALL users when the license user limit is exceeded #192 #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
8dd0b11
47e64be
e4ac1ef
52f9e35
7fc507a
c0cbb9f
2de24c6
d3eb916
2d5a325
2ba4b4b
8051097
762ea39
a11d4e5
6b4cc16
46e0b37
4cb3e21
60db9d9
461281a
457509c
fe20bee
8c42396
6ef265a
82100e2
7433457
661e2ea
c28e399
dc64443
6b84ba7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,16 +21,20 @@ | |
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.SortedSet; | ||
| import java.util.TreeSet; | ||
|
|
||
| import javax.inject.Inject; | ||
| import javax.inject.Named; | ||
| import javax.inject.Singleton; | ||
|
|
||
| import org.apache.commons.lang3.builder.CompareToBuilder; | ||
| import org.slf4j.Logger; | ||
| import org.xwiki.bridge.event.DocumentCreatedEvent; | ||
| import org.xwiki.bridge.event.DocumentDeletedEvent; | ||
| import org.xwiki.bridge.event.DocumentUpdatedEvent; | ||
| import org.xwiki.component.annotation.Component; | ||
| import org.xwiki.model.reference.DocumentReference; | ||
| import org.xwiki.model.reference.LocalDocumentReference; | ||
| import org.xwiki.observation.AbstractEventListener; | ||
| import org.xwiki.observation.event.Event; | ||
|
|
@@ -46,14 +50,18 @@ | |
|
|
||
| /** | ||
| * Component used to count the existing active users. | ||
| * | ||
| * | ||
| * @version $Id$ | ||
| * @since 1.6 | ||
| */ | ||
| @Component(roles = UserCounter.class) | ||
| @Singleton | ||
| public class UserCounter | ||
| { | ||
| protected static final String BASE_USER_QUERY = ", BaseObject as obj, IntegerProperty as prop " | ||
| + "where doc.fullName = obj.name and obj.className = 'XWiki.XWikiUsers' and prop.id.id = obj.id " | ||
| + "and prop.id.name = 'active' and prop.value = '1'"; | ||
|
|
||
| @Inject | ||
| private Logger logger; | ||
|
|
||
|
|
@@ -73,6 +81,9 @@ public class UserCounter | |
|
|
||
| private Long cachedUserCount; | ||
|
|
||
| // A set of all users on the instance, from all subwikis, sorted by creation date. | ||
| private SortedSet<XWikiDocument> cachedSortedUsers; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you see how we could do some performance tests? We would need to know that this doesn't fail on an instance with a loot of users There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm guessing this depends on the JVM allocated memory. Iirc, this comment mentioned that the cache size should be ok (at least proportionally to the instance size). I'll try to create a test with ~10.000 users on the docker instance, but I'll have to see if the test instance can really support 10k users.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be nice if we could have it. I know there were some script to create multiple users / wikis |
||
|
|
||
| /** | ||
| * Event listener that invalidates the cached user count when an user is added, deleted or the active property's | ||
| * value is changed. | ||
|
|
@@ -124,11 +135,41 @@ public void onEvent(Event event, Object source, Object data) | |
|
|
||
| if (newDocumentIsUser != oldDocumentIsUser || newActive != oldActive) { | ||
| // The user object is either added/removed or set to active/inactive. Invalidate the cached user count. | ||
| this.userCounter.cachedUserCount = null; | ||
| this.userCounter.flushCache(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Flush the cache of the user counter. | ||
| * | ||
| * @since 1.33 | ||
| */ | ||
| public void flushCache() | ||
| { | ||
| this.cachedUserCount = null; | ||
| this.cachedSortedUsers = null; | ||
| } | ||
|
|
||
| /** | ||
| * Get all users on the instance, from all subwikis, sorted by creation date. | ||
| * | ||
| * @return the users, sorted by creation date. | ||
| * @since 1.33 | ||
| */ | ||
| public SortedSet<XWikiDocument> getSortedUsers() throws WikiManagerException, QueryException | ||
| { | ||
| if (cachedSortedUsers == null) { | ||
| cachedSortedUsers = new TreeSet<>( | ||
| (e1, e2) -> new CompareToBuilder().append(e1.getCreationDate(), e2.getCreationDate()).build()); | ||
| for (String wikiId : wikiDescriptorManager.getAllIds()) { | ||
| cachedSortedUsers.addAll(getUsersOnWiki(wikiId)); | ||
abrassat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| cachedUserCount = (long) cachedSortedUsers.size(); | ||
| } | ||
| return cachedSortedUsers; | ||
| } | ||
|
|
||
| /** | ||
| * Counts the existing active users. | ||
| * | ||
|
|
@@ -153,15 +194,46 @@ public long getUserCount() throws Exception | |
| return cachedUserCount; | ||
| } | ||
|
|
||
| private long getUserCountOnWiki(String wikiId) throws QueryException | ||
| /** | ||
| * Return whether the given user is under the specified license user limit. | ||
| * | ||
| * @param user the user to check | ||
| * @param userLimit the license max user limit | ||
| * @return whether the given user is under the specified license user limit | ||
| * @since 1.33 | ||
| */ | ||
| public boolean isUserUnderLimit(DocumentReference user, long userLimit) throws Exception | ||
| { | ||
| StringBuilder statement = new StringBuilder(", BaseObject as obj, IntegerProperty as prop "); | ||
| statement.append("where doc.fullName = obj.name and obj.className = 'XWiki.XWikiUsers' and "); | ||
| statement.append("prop.id.id = obj.id and prop.id.name = 'active' and prop.value = '1'"); | ||
| if (null == user) { | ||
| return false; | ||
| } | ||
| if (userLimit < 0 || getUserCount() <= userLimit) { | ||
| // Unlimited licenses should always return true. | ||
| // Also, skip the checks for instances with fewer users than the limit. | ||
| return true; | ||
| } | ||
| SortedSet<XWikiDocument> sortedUsers = getSortedUsers(); | ||
| XWikiDocument userDocument = | ||
| sortedUsers.stream().filter(userDoc -> userDoc.getDocumentReference().equals(user)).findFirst() | ||
| .orElse(null); | ||
| if (userDocument == null) { | ||
| return false; | ||
| } else { | ||
| return sortedUsers.headSet(userDocument).size() < userLimit; | ||
| } | ||
| } | ||
|
|
||
| Query query = this.queryManager.createQuery(statement.toString(), Query.HQL); | ||
| private long getUserCountOnWiki(String wikiId) throws QueryException | ||
| { | ||
| Query query = this.queryManager.createQuery(BASE_USER_QUERY, Query.HQL); | ||
| query.addFilter(this.uniqueFilter).addFilter(this.countFilter).setWiki(wikiId); | ||
| List<Long> results = query.execute(); | ||
| return results.get(0); | ||
| } | ||
|
|
||
| private List<XWikiDocument> getUsersOnWiki(String wikiId) throws QueryException | ||
| { | ||
| return this.queryManager.createQuery("select doc from XWikiDocument doc" + BASE_USER_QUERY, Query.HQL) | ||
| .setWiki(wikiId).execute(); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.