// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.aad.msal4j; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Predicate; class TelemetryManager implements ITelemetryManager, ITelemetry { private final ConcurrentHashMap> completedEvents = new ConcurrentHashMap<>(); private final ConcurrentHashMap eventsInProgress = new ConcurrentHashMap<>(); private final ConcurrentHashMap> eventCount = new ConcurrentHashMap<>(); private boolean onlySendFailureTelemetry; private Consumer>> telemetryConsumer; public TelemetryManager(Consumer>> telemetryConsumer, boolean onlySendFailureTelemetry) { this.telemetryConsumer = telemetryConsumer; this.onlySendFailureTelemetry = onlySendFailureTelemetry; } public TelemetryHelper createTelemetryHelper(String requestId, String clientId, Event eventToStart, Boolean shouldFlush) { return new TelemetryHelper(this, requestId, clientId, eventToStart, shouldFlush); } public String generateRequestId() { return UUID.randomUUID().toString(); } @Override public void startEvent(String requestId, Event eventToStart) { if (hasConsumer() && !StringHelper.isBlank(requestId)) { eventsInProgress.put(new EventKey(requestId, eventToStart), eventToStart); } } @Override public void stopEvent(String requestId, Event eventToStop) { if (!hasConsumer() || StringHelper.isBlank(requestId)) return; EventKey eventKey = new EventKey(requestId, eventToStop); Event eventStarted = eventsInProgress.getOrDefault(eventKey, null); if (eventStarted == null) { return; } eventToStop.stop(); incrementEventCount(requestId, eventToStop); if (!completedEvents.containsKey(requestId)) { List eventList = new ArrayList<>(Arrays.asList(eventToStop)); completedEvents.put(requestId, eventList); } else { List eventList = completedEvents.get(requestId); eventList.add(eventToStop); } eventsInProgress.remove(eventKey); } @Override public void flush(String requestId, String clientId) { if (!hasConsumer()) { return; } if (!completedEvents.containsKey(requestId)) { return; } completedEvents.get(requestId).addAll(collateOrphanedEvents(requestId)); List eventsToFlush = completedEvents.remove(requestId); Map eventCountToFlush = eventCount.remove(requestId); eventCountToFlush = !(eventCountToFlush == null) ? eventCountToFlush : new ConcurrentHashMap<>(); Predicate isSuccessfulPredicate = event -> event instanceof ApiEvent && ((ApiEvent) event).getWasSuccessful(); if (onlySendFailureTelemetry && eventsToFlush.stream().anyMatch(isSuccessfulPredicate)) { eventsToFlush.clear(); } if (eventsToFlush.size() <= 0) { return; } eventsToFlush.add(0, new DefaultEvent(clientId, eventCountToFlush)); telemetryConsumer.accept(Collections.unmodifiableList(eventsToFlush)); } private Collection collateOrphanedEvents(String requestId) { List orphanedEvents = new ArrayList<>(); for (EventKey key : eventsInProgress.keySet()) { if (key.getRequestId().equalsIgnoreCase(requestId)) { orphanedEvents.add(eventsInProgress.remove(key)); } } return orphanedEvents; } private void incrementEventCount(String requestId, Event eventToIncrement) { String eventName = eventToIncrement.get(Event.EVENT_NAME_KEY); ConcurrentHashMap eventNameCount = eventCount.getOrDefault( requestId, new ConcurrentHashMap() { { put(eventName, 0); } }); eventNameCount.put(eventName, eventNameCount.getOrDefault(eventName, 0) + 1); eventCount.put(requestId, eventNameCount); } private boolean hasConsumer() { return telemetryConsumer != null; } }