com.dynatrace.openkit.core.communication.BeaconSendingContext Maven / Gradle / Ivy
Show all versions of openkit-java Show documentation
/**
* Copyright 2018-2021 Dynatrace LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dynatrace.openkit.core.communication;
import com.dynatrace.openkit.api.Logger;
import com.dynatrace.openkit.core.configuration.HTTPClientConfiguration;
import com.dynatrace.openkit.core.configuration.ServerConfiguration;
import com.dynatrace.openkit.core.objects.SessionImpl;
import com.dynatrace.openkit.core.objects.SessionState;
import com.dynatrace.openkit.protocol.AdditionalQueryParameters;
import com.dynatrace.openkit.protocol.HTTPClient;
import com.dynatrace.openkit.protocol.ResponseAttribute;
import com.dynatrace.openkit.protocol.ResponseAttributes;
import com.dynatrace.openkit.protocol.ResponseAttributesImpl;
import com.dynatrace.openkit.protocol.StatusResponse;
import com.dynatrace.openkit.providers.HTTPClientProvider;
import com.dynatrace.openkit.providers.TimingProvider;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* State context for beacon sending states.
*
*
* All package-private methods in this class shall only be accessed from the beacon sending thread,
* since they are not thread safe.
* All public methods are thread safe, unless explicitly stated.
*
*/
public class BeaconSendingContext implements AdditionalQueryParameters {
/**
* Default sleep time in milliseconds (used by {@link #sleep()}).
*/
static final long DEFAULT_SLEEP_TIME_MILLISECONDS = TimeUnit.SECONDS.toMillis(1);
private final Logger logger;
/** synchronization object for updating and reading server configuration and last response attributes */
private final Object lockObject = new Object();
/**
* Configuration storing last valid server side configuration.
*/
private ServerConfiguration serverConfiguration;
/**
* Represents the last status response received from the server.
*
*
* This field will be initially filled with the first response from the server when OpenKit initializes.
* Subsequent server responses (e.g. from session requests) will update the last server response by merging
* received fields.
*
*/
private ResponseAttributes lastResponseAttributes;
/**
* Configuration storing last valid HTTP client configuration, independent of a session.
*/
private HTTPClientConfiguration httpClientConfiguration;
private final HTTPClientProvider httpClientProvider;
private final TimingProvider timingProvider;
/**
* container storing all sessions
*/
private final LinkedBlockingQueue sessions = new LinkedBlockingQueue<>();
/**
* boolean indicating whether shutdown was requested or not
*/
private final AtomicBoolean shutdown = new AtomicBoolean(false);
/**
* countdown latch updated when init was done - which can either be success or failure
*/
private final CountDownLatch initCountDownLatch = new CountDownLatch(1);
/**
* current state of beacon sender
*/
private AbstractBeaconSendingState currentState;
/**
* state following after current state, nextState is usually set by doExecute of the current state
*/
private AbstractBeaconSendingState nextState;
/**
* timestamp when open sessions were last sent
*/
private long lastOpenSessionBeaconSendTime;
/**
* timestamp when last status check was done
*/
private long lastStatusCheckTime;
/**
* boolean indicating whether init was successful or not
*/
private volatile boolean initSucceeded = false;
/**
* Constructor.
*
*
* The state is initialized to {@link BeaconSendingInitState},
*
*/
public BeaconSendingContext(Logger logger,
HTTPClientConfiguration httpClientConfiguration,
HTTPClientProvider httpClientProvider,
TimingProvider timingProvider) {
this(logger, httpClientConfiguration, httpClientProvider, timingProvider, new BeaconSendingInitState());
}
/**
* Constructor.
*
*
* The initial state is provided. This constructor is intended for unit testing.
*
*/
BeaconSendingContext(Logger logger,
HTTPClientConfiguration httpClientConfiguration,
HTTPClientProvider httpClientProvider,
TimingProvider timingProvider,
AbstractBeaconSendingState initialState) {
this.logger = logger;
this.httpClientConfiguration = httpClientConfiguration;
this.serverConfiguration = ServerConfiguration.DEFAULT;
this.httpClientProvider = httpClientProvider;
this.timingProvider = timingProvider;
this.lastResponseAttributes = ResponseAttributesImpl.withUndefinedDefaults().build();
currentState = initialState;
}
/**
* Executes the current state.
*
*
* This method must only be called from the beacon sending thread, since it's not thread safe.
*
*/
public void executeCurrentState() {
nextState = null;
currentState.execute(this);
if (nextState != null && nextState != currentState) { // currentState.execute(...) can trigger state changes
if (logger.isInfoEnabled()) {
logger.info(getClass().getSimpleName() + " executeCurrentState() - State change from '" + currentState + "' to '" + nextState + "'");
}
currentState = nextState;
}
}
/**
* Requests a shutdown.
*/
public void requestShutdown() {
shutdown.set(true);
}
/**
* Gets a boolean flag indicating whether shutdown was requested before or not.
*/
public boolean isShutdownRequested() {
return shutdown.get();
}
/**
* Wait until OpenKit has been fully initialized.
*
*
* If initialization is interrupted (e.g. {@link #requestShutdown()} was called), then this method also returns.
*
*
* @return {@code true} OpenKit is fully initialized, {@code false} OpenKit init got interrupted.
*/
public boolean waitForInit() {
try {
initCountDownLatch.await();
} catch (InterruptedException e) {
requestShutdown();
Thread.currentThread().interrupt();
}
return initSucceeded;
}
/**
* Wait until OpenKit has been fully initialized or timeout expired.
*
*
* If initialization is interrupted (e.g. {@link #requestShutdown()} was called), then this method also returns.
*
*
* @param timeoutMillis The maximum number of milliseconds to wait for initialization being completed.
* @return {@code true} if OpenKit is fully initialized, {@code false} if OpenKit init got interrupted or time to wait expired.
*/
public boolean waitForInit(long timeoutMillis) {
try {
if (!initCountDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
return false; // timeout expired
}
} catch (InterruptedException e) {
requestShutdown();
Thread.currentThread().interrupt();
}
return initSucceeded;
}
/**
* Get a boolean indicating whether OpenKit is initialized or not.
*
* @return {@code true} if OpenKit is initialized, {@code false} otherwise.
*/
public boolean isInitialized() {
return initSucceeded;
}
/**
* Gets a boolean indicating whether the current state is a terminal state or not.
*
*
* This method must only be called from the beacon sending thread, since it's not thread safe.
*
*
* @return {@code true} if the current state is a terminal state, {@code false} otherwise.
*/
public boolean isInTerminalState() {
return currentState.isTerminalState();
}
/**
* Gets a boolean flag indicating whether capturing is turned on or off.
*
* @return {@code true} if capturing is turned on, {@code false} otherwise.
*/
boolean isCaptureOn() {
synchronized (lockObject) {
return serverConfiguration.isCaptureEnabled();
}
}
/**
* Gets the current state.
*
* @return current state.
*/
AbstractBeaconSendingState getCurrentState() {
return currentState;
}
/**
* Sets the next state.
*
* @param nextState Next state when state transition is performed.
*/
void setNextState(AbstractBeaconSendingState nextState) {
this.nextState = nextState;
}
/**
* Returns the next state.
*
* @return the nextState
*/
AbstractBeaconSendingState getNextState() {
return nextState;
}
/**
* Complete OpenKit initialization.
*
*
* This will wake up every caller waiting in the {@link #waitForInit()} method.
*
*
* @param success {@code true} if OpenKit was successfully initialized, {@code false} if it was interrupted.
*/
void initCompleted(boolean success) {
initSucceeded = success;
initCountDownLatch.countDown();
}
/**
* Gets the HTTP client provider.
*
* @return A class responsible for retrieving an instance of {@link HTTPClient}.
*/
HTTPClientProvider getHTTPClientProvider() {
return httpClientProvider;
}
/**
* Convenience method to retrieve an {@link HTTPClient} instance with {@link #httpClientConfiguration}
*
*
* This method is only allowed to be called from within the beacon sending thread.
*
*
* @return HTTP client received from {@link HTTPClientProvider}.
*/
HTTPClient getHTTPClient() {
return getHTTPClient(httpClientConfiguration);
}
/**
* Convenience method to retrieve an HTTP client.
*
* @return HTTP client received from {@link HTTPClientProvider}.
*/
HTTPClient getHTTPClient(HTTPClientConfiguration httpClientConfiguration) {
return httpClientProvider.createClient(httpClientConfiguration);
}
/**
* Gets the current timestamp.
*
* @return current timestamp as milliseconds elapsed since epoch (1970-01-01T00:00:00.000)
*/
long getCurrentTimestamp() {
return timingProvider.provideTimestampInMilliseconds();
}
/**
* Sleep some time ({@link #DEFAULT_SLEEP_TIME_MILLISECONDS}.
*
* @throws InterruptedException When sleeping thread got interrupted.
*/
void sleep() throws InterruptedException {
sleep(DEFAULT_SLEEP_TIME_MILLISECONDS);
}
/**
* Sleep given amount of milliseconds.
*
* @param millis The number of milliseconds to sleep.
* @throws InterruptedException When sleeping thread got interrupted.
*/
void sleep(long millis) throws InterruptedException {
timingProvider.sleep(millis);
}
/**
* Get timestamp when open sessions were sent last.
*/
long getLastOpenSessionBeaconSendTime() {
return lastOpenSessionBeaconSendTime;
}
/**
* Set timestamp when open sessions were sent last.
*/
void setLastOpenSessionBeaconSendTime(long timestamp) {
lastOpenSessionBeaconSendTime = timestamp;
}
/**
* Get timestamp when last status check was performed.
*/
long getLastStatusCheckTime() {
return lastStatusCheckTime;
}
/**
* Set timestamp when last status check was performed.
*/
void setLastStatusCheckTime(long timestamp) {
lastStatusCheckTime = timestamp;
}
/**
* Get the send interval for open sessions.
*/
int getSendInterval() {
synchronized (lockObject) {
return lastResponseAttributes.getSendIntervalInMilliseconds();
}
}
/**
* Returns the last {@link ResponseAttributes} received from the server.
*/
ResponseAttributes getLastResponseAttributes() {
synchronized (lockObject) {
return lastResponseAttributes;
}
}
/**
* Returns the last known {@link ServerConfiguration}.
*/
public ServerConfiguration getLastServerConfiguration() {
synchronized (lockObject) {
return serverConfiguration;
}
}
/**
* Disable data capturing and clears all session data. Finished sessions are removed from the beacon.
*/
void disableCaptureAndClear() {
// first disable in configuration, so no further data will get collected
disableCapture();
clearAllSessionData();
}
/**
* Disables data capturing
*/
private void disableCapture() {
synchronized (lockObject) {
serverConfiguration = new ServerConfiguration.Builder(serverConfiguration)
.withCapture(false)
.build();
}
}
/**
* Handle the status response received from the server.
*/
void handleStatusResponse(StatusResponse receivedResponse) {
if (receivedResponse == null || receivedResponse.isErroneousResponse()) {
disableCaptureAndClear();
return;
}
updateFrom(receivedResponse);
if (!isCaptureOn()) {
// capturing was turned off
clearAllSessionData();
}
}
/**
* Updates the {@link #getLastResponseAttributes() last known response attributes} as well as the
* {@link ServerConfiguration} and {@link HTTPClientConfiguration} in case the given status response
* {@link BeaconSendingResponseUtil#isSuccessfulResponse(StatusResponse) is succesful}.
*
* @param statusResponse the status response from which to update the last response attributes.
* @return in case the given status response was successful the updated response attributes are returned. Otherwise
* the current response attributes are returned.
*/
ResponseAttributes updateFrom(StatusResponse statusResponse) {
synchronized (lockObject) {
if (!BeaconSendingResponseUtil.isSuccessfulResponse(statusResponse)) {
return lastResponseAttributes;
}
lastResponseAttributes = lastResponseAttributes.merge(statusResponse.getResponseAttributes());
ServerConfiguration.Builder builder = new ServerConfiguration.Builder(lastResponseAttributes);
if (isApplicationIdMismatch(lastResponseAttributes)) {
builder.withCapture(false);
}
serverConfiguration = builder.build();
int serverId = serverConfiguration.getServerID();
if (serverId != httpClientConfiguration.getServerID()) {
httpClientConfiguration = createHttpClientConfigurationWith(serverId);
}
return lastResponseAttributes;
}
}
/**
* Ensure that the application id coming with the response matches the one that was configured for OpenKit.
*
*
* Mismatch check prevents a rare Jetty bug, where responses might be dispatched to the wrong receiver.
*
*
* @param lastResponseAttributes The last response attributes received from Dynatrace.
*
* @return {@code false} if application id is matching, {@code true} if a mismatch occurred.
*/
boolean isApplicationIdMismatch(ResponseAttributes lastResponseAttributes) {
if (lastResponseAttributes.isAttributeSet(ResponseAttribute.APPLICATION_ID)) {
return !httpClientConfiguration.getApplicationID().equals(lastResponseAttributes.getApplicationId());
}
// if it's not set it's either the old response format, or an older Dynatrace version
// in this case no mismatch is happening and everything is fine
return false;
}
HTTPClientConfiguration createHttpClientConfigurationWith(int serverId) {
return HTTPClientConfiguration.modifyWith(httpClientConfiguration).withServerID(serverId).build();
}
/**
* Clear captured data from all sessions.
*/
private void clearAllSessionData() {
// iterate over the elements
Iterator iterator = sessions.iterator();
while (iterator.hasNext()) {
SessionImpl session = iterator.next();
session.clearCapturedData();
SessionState state = session.getState();
if (state.isFinished()) {
iterator.remove();
}
}
}
/**
* Get all sessions that are not yet configured.
*
*
* A session is considered as not configured if it did not receive a server configuration update (either
* when receiving a successful for the first new session request or when capturing for the session got
* disabled due to an unsuccessful response).
*
*
*
* The returned list is a snapshot and might change during traversal.
*
*
* @return A list of new sessions.
*/
List getAllNotConfiguredSessions() {
List notConfiguredSessions = new LinkedList<>();
for (SessionImpl session : sessions) {
SessionState state = session.getState();
if (!state.isConfigured()) {
notConfiguredSessions.add(session);
}
}
return notConfiguredSessions;
}
/**
* Get a list of all sessions that have been configured and are currently open.
*/
List getAllOpenAndConfiguredSessions() {
List openSessions = new LinkedList<>();
for (SessionImpl session : sessions) {
SessionState state = session.getState();
if (state.isConfiguredAndOpen()) {
openSessions.add(session);
}
}
return openSessions;
}
/**
* Get a list of all sessions that have been configured and are currently finished.
*/
List getAllFinishedAndConfiguredSessions() {
List finishedSessions = new LinkedList<>();
for (SessionImpl session : sessions) {
SessionState state = session.getState();
if (state.isConfiguredAndFinished()) {
finishedSessions.add(session);
}
}
return finishedSessions;
}
/**
* Returns the number of sessions currently known to this context
*/
int getSessionCount() {
return sessions.size();
}
/**
* Returns the current server ID to be used for creating new sessions
*/
public int getCurrentServerId() {
return httpClientConfiguration.getServerID();
}
/**
* Adds the given session to the internal container of sessions.
*
* @param session The new session to add.
*/
public void addSession(SessionImpl session) {
sessions.add(session);
}
/**
* Removes the given {@link SessionImpl session} from the sessions known by this context.
*
* @param session the session to be removed.
*/
boolean removeSession(SessionImpl session) {
return sessions.remove(session);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// AdditionalQueryParameters
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public long getConfigurationTimestamp() {
synchronized (lockObject) {
return lastResponseAttributes.getTimestampInMilliseconds();
}
}
}