-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(jans-auth-server): added new stat response service with test
- Loading branch information
Showing
3 changed files
with
175 additions
and
99 deletions.
There are no files selected for viewing
131 changes: 131 additions & 0 deletions
131
...-auth-server/server/src/main/java/io/jans/as/server/service/stat/StatResponseService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package io.jans.as.server.service.stat; | ||
|
||
import com.google.common.cache.Cache; | ||
import com.google.common.cache.CacheBuilder; | ||
import io.jans.as.common.model.stat.StatEntry; | ||
import io.jans.as.server.ws.rs.stat.StatResponse; | ||
import io.jans.as.server.ws.rs.stat.StatResponseItem; | ||
import io.jans.orm.PersistenceEntryManager; | ||
import io.jans.orm.search.filter.Filter; | ||
import net.agkn.hll.HLL; | ||
import org.slf4j.Logger; | ||
|
||
import javax.ejb.DependsOn; | ||
import javax.enterprise.context.ApplicationScoped; | ||
import javax.inject.Inject; | ||
import javax.inject.Named; | ||
import java.util.Base64; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import static io.jans.as.model.util.Util.escapeLog; | ||
|
||
/** | ||
* @author Yuriy Zabrovarnyy | ||
*/ | ||
@ApplicationScoped | ||
@DependsOn("appInitializer") | ||
@Named | ||
public class StatResponseService { | ||
|
||
@Inject | ||
private Logger log; | ||
|
||
@Inject | ||
private PersistenceEntryManager entryManager; | ||
|
||
@Inject | ||
private StatService statService; | ||
|
||
private final Cache<String, StatResponse> responseCache = CacheBuilder | ||
.newBuilder() | ||
.expireAfterWrite(1, TimeUnit.HOURS) | ||
.build(); | ||
|
||
public StatResponse buildResponse(List<String> months) { | ||
final String cacheKey = months.toString(); | ||
final StatResponse cachedResponse = responseCache.getIfPresent(cacheKey); | ||
if (cachedResponse != null) { | ||
return cachedResponse; | ||
} | ||
|
||
StatResponse response = new StatResponse(); | ||
for (String month : months) { | ||
final StatResponseItem responseItem = buildItem(month); | ||
if (responseItem != null) { | ||
response.getResponse().put(month, responseItem); | ||
} | ||
} | ||
|
||
responseCache.put(cacheKey, response); | ||
return response; | ||
} | ||
|
||
private StatResponseItem buildItem(String month) { | ||
try { | ||
String monthlyDn = String.format("ou=%s,%s", escapeLog(month), statService.getBaseDn()); | ||
|
||
final List<StatEntry> entries = entryManager.findEntries(monthlyDn, StatEntry.class, Filter.createPresenceFilter("jansId")); | ||
if (entries == null || entries.isEmpty()) { | ||
log.trace("Can't find stat entries for month: {}", monthlyDn); | ||
return null; | ||
} | ||
|
||
final StatResponseItem responseItem = new StatResponseItem(); | ||
responseItem.setMonthlyActiveUsers(userCardinality(entries)); | ||
responseItem.setMonth(month); | ||
|
||
unionTokenMapIntoResponseItem(entries, responseItem); | ||
|
||
return responseItem; | ||
} catch (Exception e) { | ||
log.error(e.getMessage(), e); | ||
return null; | ||
} | ||
} | ||
|
||
|
||
private long userCardinality(List<StatEntry> entries) { | ||
HLL hll = decodeHll(entries.get(0)); | ||
|
||
// Union hll | ||
if (entries.size() > 1) { | ||
for (int i = 1; i < entries.size(); i++) { | ||
hll.union(decodeHll(entries.get(i))); | ||
} | ||
} | ||
return hll.cardinality(); | ||
} | ||
|
||
private HLL decodeHll(StatEntry entry) { | ||
try { | ||
return HLL.fromBytes(Base64.getDecoder().decode(entry.getUserHllData())); | ||
} catch (Exception e) { | ||
log.error("Failed to decode HLL data, entry dn: {}, data: {}", entry.getDn(), entry.getUserHllData()); | ||
return statService.newHll(); | ||
} | ||
} | ||
|
||
|
||
private void unionTokenMapIntoResponseItem(List<StatEntry> entries, StatResponseItem responseItem) { | ||
for (StatEntry entry : entries) { | ||
entry.getStat().getTokenCountPerGrantType().entrySet().stream().filter(en -> en.getValue() != null).forEach(en -> { | ||
final Map<String, Long> tokenMap = responseItem.getTokenCountPerGrantType().get(en.getKey()); | ||
if (tokenMap == null) { | ||
responseItem.getTokenCountPerGrantType().put(en.getKey(), en.getValue()); | ||
return; | ||
} | ||
for (Map.Entry<String, Long> tokenEntry : en.getValue().entrySet()) { | ||
final Long counter = tokenMap.get(tokenEntry.getKey()); | ||
if (counter == null) { | ||
tokenMap.put(tokenEntry.getKey(), tokenEntry.getValue()); | ||
continue; | ||
} | ||
|
||
tokenMap.put(tokenEntry.getKey(), counter + tokenEntry.getValue()); | ||
} | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
...h-server/server/src/test/java/io/jans/as/server/service/stat/StatResponseServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package io.jans.as.server.service.stat; | ||
|
||
import io.jans.orm.PersistenceEntryManager; | ||
import org.mockito.InjectMocks; | ||
import org.mockito.Mock; | ||
import org.mockito.testng.MockitoTestNGListener; | ||
import org.testng.annotations.Listeners; | ||
import org.testng.annotations.Test; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
|
||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.times; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.when; | ||
|
||
/** | ||
* @author Yuriy Zabrovarnyy | ||
*/ | ||
@Listeners(MockitoTestNGListener.class) | ||
public class StatResponseServiceTest { | ||
|
||
@InjectMocks | ||
private StatResponseService statResponseService; | ||
|
||
@Mock | ||
private PersistenceEntryManager entryManager; | ||
|
||
@Test | ||
public void buildResponse_whenCalled_shouldInvokeEntityManagerOneTimeBecauseSecondTimeResponseMustBeCached() { | ||
when(entryManager.findEntries(any(), any(), any())).thenReturn(new ArrayList<>()); | ||
|
||
statResponseService.buildResponse(Collections.singletonList("01")); | ||
statResponseService.buildResponse(Collections.singletonList("01")); | ||
statResponseService.buildResponse(Collections.singletonList("01")); | ||
|
||
// must be called exactly 1 time, all further calls should use cached response | ||
verify(entryManager, times(1)).findEntries(any(), any(), any()); | ||
} | ||
} |