in languagetool-server/src/main/java/org/languagetool/server/TextChecker.java [353:828]
void checkText(AnnotatedText aText, HttpExchange httpExchange, Map<String, String> params, ErrorRequestLimiter errorRequestLimiter,
String remoteAddress) throws Exception {
checkParams(params);
long timeStart = System.currentTimeMillis();
String authHeader = ServerTools.getAuthHeader(httpExchange.getRequestHeaders());
UserLimits limits = ServerTools.getUserLimits(params, config, authHeader);
if (Premium.isPremiumStatusCheck(aText)) {
Language premiumStatusCheckLang = Languages.getLanguageForShortCode("en-US");
List<RuleMatch> matches = new ArrayList<>();
if (limits.hasPremium() || config.isPremiumAlways()) {
matches.add(new RuleMatch(new Rule() {
@Override
public String getId() {
return "PREMIUM_FAKE_RULE";
}
@Override
public String getDescription() {
return "PREMIUM_FAKE_RULE";
}
@Override
public RuleMatch[] match(AnalyzedSentence sentence) throws IOException {
return RuleMatch.EMPTY_ARRAY;
}
}, null,0,1,""));
}
DetectedLanguage detectedLanguage = new DetectedLanguage(premiumStatusCheckLang, premiumStatusCheckLang, 0.99999076F, "ngram");
int compactMode = Integer.parseInt(params.getOrDefault("c", "0"));
String response = getResponse(aText, premiumStatusCheckLang, detectedLanguage, premiumStatusCheckLang, Collections.singletonList(new CheckResults(matches, Collections.emptyList())), Collections.emptyList(), null, compactMode,
!limits.hasPremium(), JLanguageTool.Mode.ALL);
setHeaders(httpExchange);
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.getBytes(ENCODING).length);
httpExchange.getResponseBody().write(response.getBytes(ENCODING));
return;
}
String requestId = httpExchange.getRequestHeaders().getFirst("X-Request-ID");
// logging information
String agent = params.get("useragent") != null ? params.get("useragent") : "-";
Long agentId = null, userId = null;
if (databaseLogger.isLogging()) {
DatabaseAccess db = DatabaseAccess.getInstance();
agentId = db.getOrCreateClientId(params.get("useragent"));
userId = limits.getPremiumUid();
}
String referrer = httpExchange.getRequestHeaders().getFirst("Referer");
String userAgent = httpExchange.getRequestHeaders().getFirst("User-Agent");
if (!config.isAnonymousAccessAllowed() && limits.getPremiumUid() == null) {
throw new AuthException("Anonymous access is prohibited on this server, please provide authentication.");
}
int length = aText.getPlainText().length();
if ("true".equals(params.get("languageChanged"))) {
log.info("languageChanged to " + params.get("language") + " for text with length " + aText.getPlainText().trim().length());
}
if (length > limits.getMaxTextLength()) {
ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.MAX_TEXT_SIZE);
throw new TextTooLongException("Your text exceeds the limit of " + limits.getMaxTextLength() +
" characters (it's " + length + " characters). Please submit a shorter text.");
}
// static because we can't rely on errorRequestLimiter, null when timeoutRequestLimit option not set
if (!config.isLocalApiMode()) {
try {
RequestLimiter.checkUserLimit(referrer, userAgent, limits);
} catch (TooManyRequestsException e) {
String response = "Error: Access denied: " + e.getMessage();
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_FORBIDDEN, response.getBytes(ENCODING).length);
httpExchange.getResponseBody().write(response.getBytes(ENCODING));
String message = "Blocked request from uid:" + userId + " because user limit is reached: ";
message += "limit = " + limits.getRequestsPerDay() + ", mode = " + limits.getLimitEnforcementMode() + ". ";
message += "Access from " + remoteAddress + ", ";
message += "HTTP user agent: " + userAgent + ", ";
message += "User agent param: " + params.get("useragent") + ", ";
message += "Referrer: " + referrer + ", ";
message += "language: " + params.get("language") + ", ";
message += "h: " + reqCounter.getHandleCount() + ", ";
message += "r: " + reqCounter.getRequestCount();
if (params.get("username") != null) {
message += ", user: " + params.get("username");
}
if (params.get("apiKey") != null) {
message += ", apiKey: " + params.get("apiKey");
}
String text = params.get("text");
if (text != null) {
message += ", text length: " + text.length();
}
log.warn(message);
return;
}
}
List<String> dictGroups = null;
String dictName = "default";
if (params.containsKey("dicts")) {
dictGroups = Arrays.asList(params.get("dicts").split(","));
dictGroups.sort(Comparator.naturalOrder());
dictName = "groups_" + String.join(",", dictGroups);
}
final List<String> finalDictGroups = dictGroups;
List<String> dictWords = limits.getPremiumUid() != null ?
TelemetryProvider.INSTANCE.createSpan(SPAN_NAME_PREFIX +"GetUserDictWords", Attributes.empty(), () -> getUserDictWords(limits, finalDictGroups)) : Collections.emptyList();
boolean filterDictionaryMatches = "true".equals(params.getOrDefault("filterDictionaryMatches", "true"));
Long textSessionId = computeTextSessionID(params.get("textSessionId"), remoteAddress);
List<String> abTest = AB_TEST_SERVICE.getActiveAbTestForClient(params, config);
boolean enableHiddenRules = "true".equals(params.get("enableHiddenRules"));
if (limits.hasPremium()) {
enableHiddenRules = false;
}
boolean autoDetectLanguage = getLanguageAutoDetect(params);
List<String> preferredVariants = getPreferredVariants(params);
if (params.get("noopLanguages") != null && !autoDetectLanguage) {
ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.INVALID_REQUEST);
throw new BadRequestException("You can specify 'noopLanguages' only when also using 'language=auto'");
}
List<String> noopLangs = params.get("noopLanguages") != null ?
Arrays.asList(params.get("noopLanguages").split(",")) : Collections.emptyList();
List<String> preferredLangs = params.get("preferredLanguages") != null ?
Arrays.asList(params.get("preferredLanguages").split(",")) : Collections.emptyList();
DetectedLanguage detLang = TelemetryProvider.INSTANCE.createSpan(SPAN_NAME_PREFIX + "DetetectLanguage", Attributes.empty(), () -> getLanguage(aText.getPlainText(), params, preferredVariants, noopLangs, preferredLangs,
params.getOrDefault("ld", "control").equalsIgnoreCase("test")));
Language lang = detLang.getGivenLanguage();
List<Rule> userRules = TelemetryProvider.INSTANCE.createSpan(SPAN_NAME_PREFIX + "GetUserRules", Attributes.empty(), () -> getUserRules(limits, lang, finalDictGroups));
String ltAgent = params.getOrDefault("useragent", "unknown");
Pattern trustedSourcesPattern = config.getTrustedSources();
boolean trustedSource = trustedSourcesPattern == null || (limits.hasPremium() || trustedSourcesPattern.matcher(ltAgent).matches());
boolean optInThirdPartyAI = isOptInThirdPartyAI(limits, params, config);
// set default value for tokenType
UserConfig.TokenType tokenType = authHeader == null ? UserConfig.TokenType.NO_TOKEN : UserConfig.TokenType.INVALID_TOKEN;
JwtContent jwtContent = limits.getJwtContent();
if (jwtContent != null && jwtContent.isValid()) {
tokenType = jwtContent.isPremium() ? UserConfig.TokenType.TRIAL_TOKEN : UserConfig.TokenType.TEST_TOKEN;
}
UserConfig userConfig =
new UserConfig(dictWords, userRules,
getRuleValues(params), config.getMaxSpellingSuggestions(),
limits.getPremiumUid(), dictName, limits.getDictCacheSize(),
null, filterDictionaryMatches, abTest, textSessionId,
!limits.hasPremium() && enableHiddenRules, preferredLangs, trustedSource, optInThirdPartyAI, limits.hasPremium(), tokenType);
//print("Check start: " + text.length() + " chars, " + langParam);
// == temporary counting code ======================================
/*
if (httpExchange.getRequestHeaders() != null && httpExchange.getRequestHeaders().get("Accept-Language") != null) {
List<String> langs = httpExchange.getRequestHeaders().get("Accept-Language");
if (langs.size() > 0) {
String[] split = langs.get(0).split(",");
if (split.length > 0 && detLang.getDetectedLanguage() != null && detLang.getDetectedLanguage().getShortCode().equals("en")) {
int theCount1 = StringUtils.countMatches(aText.toString(), " the ");
int theCount2 = StringUtils.countMatches(aText.toString(), "The ");
String browserLang = split[0];
System.out.println("STAT\t" + detLang.getDetectedLanguage().getShortCode() + "\t" + detLang.getDetectionConfidence() + "\t" + aText.toString().length() + "\t" + browserLang + "\t" + theCount1 + "\t" + theCount2);
}
}
}
*/
// ========================================
Integer count = languageCheckCounts.get(lang.getShortCodeWithCountryAndVariant());
if (count == null) {
count = 1;
} else {
count++;
}
//print("Starting check: " + aText.getPlainText().length() + " chars, #" + count);
String motherTongueParam = params.get("motherTongue");
Language motherTongue = motherTongueParam != null ? parseLanguage(motherTongueParam) : null;
boolean useEnabledOnly = "yes".equals(params.get("enabledOnly")) || "true".equals(params.get("enabledOnly"));
List<Language> altLanguages = new ArrayList<>();
if (params.get("altLanguages") != null) {
String[] altLangParams = COMMA_WHITESPACE_PATTERN.split(params.get("altLanguages"));
for (String langCode : altLangParams) {
Language altLang = parseLanguage(langCode);
altLanguages.add(altLang);
if (altLang.hasVariant() && !altLang.isVariant()) {
ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.INVALID_REQUEST);
throw new BadRequestException("You specified altLanguage '" + langCode + "', but for this language you need to specify a variant, e.g. 'en-GB' instead of just 'en'");
}
}
}
List<String> enabledRules = getEnabledRuleIds(params);
List<String> disabledRules = getDisabledRuleIds(params);
List<CategoryId> enabledCategories = getCategoryIds("enabledCategories", params);
List<CategoryId> disabledCategories = getCategoryIds("disabledCategories", params);
if (shouldRunRestrictedRulesTest(params, agent, lang, abTest)) {
log.info("Running test with restricted rules for user: {}, language: {}, client: {}",
params.getOrDefault("username", ""), lang.getShortCodeWithCountryAndVariant(), agent);
useEnabledOnly = true;
enabledRules = onlyTestRules;
// need to clear these settings, leads to conflict with useEnabledOnly otherwise
disabledRules = Collections.emptyList();
disabledCategories = Collections.emptyList();
}
if ((disabledRules.size() > 0 || disabledCategories.size() > 0) && useEnabledOnly) {
ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.INVALID_REQUEST);
throw new BadRequestException("You cannot specify disabled rules or categories using enabledOnly=true");
}
if (enabledRules.isEmpty() && enabledCategories.isEmpty() && useEnabledOnly) {
ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.INVALID_REQUEST);
throw new BadRequestException("You must specify enabled rules or categories when using enabledOnly=true");
}
boolean enableTempOffRules = "true".equals(params.get("enableTempOffRules"));
boolean useQuerySettings = enabledRules.size() > 0 || disabledRules.size() > 0 ||
enabledCategories.size() > 0 || disabledCategories.size() > 0 || enableTempOffRules;
boolean allowIncompleteResults = "true".equals(params.get("allowIncompleteResults"));
JLanguageTool.Mode mode = ServerTools.getMode(params);
JLanguageTool.Level level = ServerTools.getLevel(params);
String[] toneTagNames = params.get("toneTags") != null ? params.get("toneTags").split(",") : null;
Set<ToneTag> toneTags = new HashSet<>(ToneTag.values().length);
if (toneTagNames != null) {
if (toneTagNames.length == 1 && toneTagNames[0].isEmpty()) { //&toneTags=
toneTags.add(ToneTag.ALL_WITHOUT_GOAL_SPECIFIC);
} else {
for (String toneTagName : toneTagNames) {
if (toneTagNames.length > 1 && (toneTagName.equals("NO_TONE_RULE") || toneTagName.equals("ALL_TONE_RULES"))) { //&toneTags=ALL_TONE_RULES or //&toneTags=NO_TONE_RULE
log.warn("NO_TONE_RULE and ALL_TONE_RULES will be ignored if more than one toneTag is in params.");
continue;
}
try {
toneTags.add(ToneTag.valueOf(toneTagName));
} catch (IllegalArgumentException ex) {
//just ignore unsupported toneTags
log.warn("Unsupported toneTag found in params: {}", toneTagName);
}
}
}
} else {
toneTags.add(ToneTag.ALL_WITHOUT_GOAL_SPECIFIC); //No toneTags param in request
}
String rIp = LanguageToolHttpHandler.getRealRemoteAddressOrNull(httpExchange,config);
if (rIp != null && mode.equals(JLanguageTool.Mode.ALL_BUT_TEXTLEVEL_ONLY)) {
log.info("L-IP{}:{}", lang.getShortCodeWithCountryAndVariant(), rIp);
}
String callback = params.get("callback");
// allowed to log input on errors?
boolean inputLogging = !params.getOrDefault("inputLogging", "").equals("no");
boolean premiumStatus = limits.hasPremium();
QueryParams qParams = new QueryParams(altLanguages, enabledRules, disabledRules,
enabledCategories, disabledCategories, useEnabledOnly,
useQuerySettings, allowIncompleteResults, enableHiddenRules, premiumStatus, enableTempOffRules, mode, level, toneTags, callback, inputLogging);
int textSize = length;
List<CheckResults> ruleMatchesSoFar = Collections.synchronizedList(new ArrayList<>());
Future<List<CheckResults>> future;
try {
future = executorService.submit(() -> {
try (MDC.MDCCloseable c = MDC.putCloseable("rID", LanguageToolHttpHandler.getRequestId(httpExchange))) {
log.debug("Starting text check on {} chars; params: {}", length, qParams);
long time = System.currentTimeMillis();
List<CheckResults> results = getRuleMatches(aText, lang, motherTongue, params, qParams, userConfig, f -> ruleMatchesSoFar.add(new CheckResults(Collections.singletonList(f), Collections.emptyList())));
log.debug("Finished text check in {}ms. Starting suggestion generation.", System.currentTimeMillis() - time);
time = System.currentTimeMillis();
// generate suggestions, otherwise this is not part of the timeout logic and not properly measured in the metrics
results.stream().flatMap(r -> r.getRuleMatches().stream()).forEach(RuleMatch::computeLazySuggestedReplacements);
log.debug("Finished suggestion generation in {}ms, returning results.", System.currentTimeMillis() - time);
return results;
}
});
} catch (RejectedExecutionException e) {
throw new UnavailableException("Server overloaded, please try again later", e);
}
String incompleteResultReason = null;
List<CheckResults> res;
Attributes textCheckingAttributes = Attributes.builder()
.put("text.language", lang.getShortCode())
.put("text.size", textSize)
.put("userRules.size", userRules.size())
.put("dictionary.size", dictWords.size())
.build();
Integer finalCount = count;
Map.Entry<List<CheckResults>, String> resAndReason = TelemetryProvider.INSTANCE.createSpan(SPAN_NAME_PREFIX + "GetRuleMatches", textCheckingAttributes, (span) -> {
List<CheckResults> localRes;
String localReason = null;
try {
if (limits.getMaxCheckTimeMillis() < 0) {
localRes = future.get();
} else {
localRes = future.get(limits.getMaxCheckTimeMillis(), TimeUnit.MILLISECONDS);
}
} catch (ExecutionException e) {
future.cancel(true);
if (ExceptionUtils.getRootCause(e) instanceof ErrorRateTooHighException) {
ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.TOO_MANY_ERRORS);
}
if (qParams.allowIncompleteResults && ExceptionUtils.getRootCause(e) instanceof ErrorRateTooHighException) {
log.warn(e.getMessage() + " - returning " + ruleMatchesSoFar.size() + " matches found so far. " +
"Detected language: " + detLang + ", " + ServerTools.getLoggingInfo(remoteAddress, null, -1, httpExchange,
params, System.currentTimeMillis() - timeStart, reqCounter));
localRes = new ArrayList<>(ruleMatchesSoFar); // threads might still be running, so make a copy
localReason = "Results are incomplete: " + ExceptionUtils.getRootCause(e).getMessage();
} else if (e.getCause() != null && e.getCause() instanceof OutOfMemoryError) {
throw (OutOfMemoryError) e.getCause();
} else {
throw new RuntimeException(ServerTools.cleanUserTextFromMessage(e.getMessage(), params) + ", detected: " + detLang, e);
}
} catch (TimeoutException e) {
boolean cancelled = future.cancel(true);
Path loadFile = Paths.get("/proc/loadavg"); // works in Linux only(?)
String loadInfo = loadFile.toFile().exists() ? Files.readAllLines(loadFile).toString() : "(unknown)";
if (errorRequestLimiter != null) {
errorRequestLimiter.logAccess(remoteAddress, httpExchange.getRequestHeaders(), params);
}
String message = "Text checking took longer than allowed maximum of " + limits.getMaxCheckTimeMillis() +
" milliseconds (cancelled: " + cancelled +
", lang: " + lang.getShortCodeWithCountryAndVariant() +
", detected: " + detLang +
", #" + finalCount +
", " + length + " characters of text" +
", mode: " + mode.toString().toLowerCase() +
", h: " + reqCounter.getHandleCount() +
", r: " + reqCounter.getRequestCount() +
", requestId: " + requestId +
", system load: " + loadInfo + ")";
if (qParams.allowIncompleteResults) {
log.info(message + " - returning " + ruleMatchesSoFar.size() + " matches found so far");
localRes = new ArrayList<>(ruleMatchesSoFar); // threads might still be running, so make a copy
localReason = "Results are incomplete: text checking took longer than allowed maximum of " +
String.format(Locale.ENGLISH, "%.2f", limits.getMaxCheckTimeMillis() / 1000.0) + " seconds";
span.setAttribute("incompleteResults", true);
} else {
ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.MAX_CHECK_TIME);
throw new RuntimeException(message, e);
}
}
return new AbstractMap.SimpleEntry(localRes, localReason);
});
res = resAndReason.getKey();
incompleteResultReason = resAndReason.getValue();
// no lazy computation at later points (outside of timeout enforcement)
// e.g. ruleMatchesSoFar can have matches without computeLazySuggestedReplacements called yet
res.forEach(checkResults -> checkResults.getRuleMatches().forEach(RuleMatch::discardLazySuggestedReplacements));
setHeaders(httpExchange);
List<RuleMatch> hiddenMatches = new ArrayList<>();
boolean temporaryPremiumDisabledRuleMatch = false;
Set<String> temporaryPremiumDisabledRuleMatchedIds = new HashSet<>();
// filter computed premium matches, convert to hidden matches - no separate hidden matches server needed
if (!qParams.premium && qParams.enableHiddenRules) {
List<RuleMatch> allMatches = new ArrayList<>(); // for filtering out overlapping matches, collect across CheckResults
List<RuleMatch> premiumMatches = new ArrayList<>();
for (CheckResults result : res) {
List<RuleMatch> filteredMatches = new ArrayList<>();
for (RuleMatch match : result.getRuleMatches()) {
if (Premium.get().isPremiumRule(match.getRule()) && !Premium.isTempNotPremium(match.getRule())) {
premiumMatches.add(match);
} else if (userConfig.getAbTest() != null && userConfig.getAbTest().equals("ALLOW_PREMIUM_IN_BASIC") && Premium.get().isPremiumRule(match.getRule()) && Premium.isTempNotPremium(match.getRule())) {
System.out.println("Rule: " + match.getRule().getId() + " is premium but temporary available in basic");
filteredMatches.add(match);
allMatches.add(match);
temporaryPremiumDisabledRuleMatch = true;
temporaryPremiumDisabledRuleMatchedIds.add(match.getRule().getId());
} else {
// filter out premium matches
filteredMatches.add(match);
// keep track for filtering out overlapping matches
allMatches.add(match);
}
// need to replace list, can't iterate and remove since some rules may return unmodifiable lists
result.setRuleMatches(filteredMatches);
}
}
hiddenMatches.addAll(ResultExtender.getAsHiddenMatches(allMatches, premiumMatches));
}
int compactMode = Integer.parseInt(params.getOrDefault("c", "0"));
String response = getResponse(aText, lang, detLang, motherTongue, res, hiddenMatches, incompleteResultReason, compactMode,
limits.getPremiumUid() == null, qParams.mode);
if (qParams.callback != null) {
// JSONP - still needed today for the special case of hosting your own on-premise LT without SSL
// and using it from a local MS Word (not Online Word) - issue #89 in the add-in repo:
response = qParams.callback + "(" + response + ");";
}
String messageSent = "sent";
String languageMessage = lang.getShortCodeWithCountryAndVariant();
try {
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.getBytes(ENCODING).length);
httpExchange.getResponseBody().write(response.getBytes(ENCODING));
ServerMetricsCollector.getInstance().logResponse(HttpURLConnection.HTTP_OK);
} catch (IOException exception) {
// the client is disconnected
messageSent = "notSent: " + exception.getMessage();
}
if (motherTongue != null) {
languageMessage += " (mother tongue: " + motherTongue.getShortCodeWithCountryAndVariant() + ")";
}
if (autoDetectLanguage) {
languageMessage += "[auto]";
}
languageCheckCounts.put(lang.getShortCodeWithCountryAndVariant(), count);
int computationTime = (int) (System.currentTimeMillis() - timeStart);
Premium premium = Premium.get();
List<String> premiumMatchRuleIds = res.stream().
flatMap(r -> r.getRuleMatches().stream()).
filter(k -> premium.isPremiumRule(k.getRule())).
map(k -> k.getRule().getId()).
collect(Collectors.toList());
Map<String, Integer> ruleMatchCount = getRuleMatchCount(res);
int matchCount = ruleMatchCount.size();
String version = params.get("v") != null ? ", version: " + params.get("v") : "";
String skipLimits = limits.getSkipLimits() ? ", skipLimits" : "";
log.info("Check done: " + length + " chars, " + languageMessage +
", requestId: " + requestId + ", #" + count + ", " + referrer + ", "
+ premiumMatchRuleIds.size() + "/"
+ matchCount + " matches, "
+ computationTime + "ms, agent:" + agent + version
+ ", " + messageSent + ", q:" + (workQueue != null ? workQueue.size() : "?")
+ ", h:" + reqCounter.getHandleCount() + ", dH:" + reqCounter.getDistinctIps()
+ ", r:" + reqCounter.getRequestCount()
+ ", m:" + ServerTools.getModeForLog(mode) + skipLimits
+ ", premium: " + premiumStatus
//+ ", temporaryPremiumDisabledRuleMatches: " + temporaryPremiumDisabledRuleMatch //TODO activate if used
//+ ", temporaryPremiumDisabledRuleMatchedIds: " + temporaryPremiumDisabledRuleMatchedIds //TODO activate if used
+ (limits.getPremiumUid() != null ? ", uid:" + limits.getPremiumUid() : ""));
if (premiumMatchRuleIds.size() > 0) {
for (String premiumMatchRuleId : premiumMatchRuleIds) {
log.info("premium:" + lang.getShortCodeWithCountryAndVariant() + ":" + premiumMatchRuleId);
}
}
ServerMetricsCollector.getInstance().logCheck(
lang, computationTime, textSize, matchCount, mode);
if (!config.isSkipLoggingChecks()) {
// NOTE: Java/DB (not sure) can't keep up with logging the volume of new entries we've reached,
// so we limit it to enterprise customers where we actually pay attention to the request limits
if (limits.getRequestsPerDay() != null) {
DatabaseCheckLogEntry logEntry = new DatabaseCheckLogEntry(userId, agentId, logServerId, textSize, matchCount,
lang, detLang.getDetectedLanguage(), computationTime, textSessionId, mode.toString());
databaseLogger.log(logEntry);
}
}
if (databaseLogger.isLogging()) {
if (System.currentTimeMillis() - pingsCleanDateMillis > PINGS_CLEAN_MILLIS && pings.size() < PINGS_MAX_SIZE) {
log.info("Cleaning pings DB (" + pings.size() + " items)");
pings.clear();
pingsCleanDateMillis = System.currentTimeMillis();
}
if (agentId != null && userId != null) {
DatabasePingLogEntry ping = new DatabasePingLogEntry(agentId, userId);
if (!pings.contains(ping)) {
databaseLogger.log(ping);
if (pings.size() >= PINGS_MAX_SIZE) {
// prevent pings taking up unlimited amounts of memory
log.warn("Pings DB has reached max size: " + pings.size());
} else {
pings.add(ping);
}
}
}
}
}