com.epam.reportportal.service.ReportPortal Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of client-java Show documentation
Show all versions of client-java Show documentation
A application used as an example on how to set up pushing its components to the Central Repository .
The newest version!
/*
* Copyright 2019 EPAM Systems
*
* 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.epam.reportportal.service;
import com.epam.reportportal.exception.InternalReportPortalClientException;
import com.epam.reportportal.listeners.ListenerParameters;
import com.epam.reportportal.message.ReportPortalMessage;
import com.epam.reportportal.message.TypeAwareByteSource;
import com.epam.reportportal.service.launch.PrimaryLaunch;
import com.epam.reportportal.service.launch.SecondaryLaunch;
import com.epam.reportportal.utils.SslUtils;
import com.epam.reportportal.utils.http.HttpRequestUtils;
import com.epam.reportportal.utils.properties.ListenerProperty;
import com.epam.reportportal.utils.properties.PropertiesLoader;
import com.epam.ta.reportportal.ws.model.launch.StartLaunchRQ;
import com.epam.ta.reportportal.ws.model.log.SaveLogRQ;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.reactivex.Maybe;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.apache.commons.lang3.BooleanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.jackson.JacksonConverterFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.net.ssl.*;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import static com.epam.reportportal.service.LaunchLoggingContext.DEFAULT_LAUNCH_KEY;
import static com.epam.reportportal.utils.MimeTypeDetector.detect;
import static com.epam.reportportal.utils.files.Utils.readFileToBytes;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* Default ReportPortal Reporter implementation. Uses
* {@link retrofit2.Retrofit} as REST WS Client
*
* @author Andrei Varabyeu
*/
public class ReportPortal {
private static final Logger LOGGER = LoggerFactory.getLogger(ReportPortal.class);
private final ListenerParameters parameters;
private final LaunchIdLock launchIdLock;
private final ReportPortalClient rpClient;
private final ExecutorService executor;
/**
* @param rpClient ReportPortal client
* @param parameters Listener Parameters
*/
ReportPortal(@Nullable ReportPortalClient rpClient, @Nonnull ExecutorService executor, @Nonnull ListenerParameters parameters,
@Nullable LaunchIdLock launchIdLock) {
this.rpClient = rpClient;
this.executor = executor;
this.parameters = Objects.requireNonNull(parameters);
this.launchIdLock = launchIdLock;
}
/**
* Starts launch in ReportPortal
*
* @param rq Request Data
* @return Launch
*/
@Nonnull
public Launch newLaunch(@Nonnull StartLaunchRQ rq) {
if (BooleanUtils.isNotTrue(parameters.getEnable()) || rpClient == null) {
return Launch.NOOP_LAUNCH;
}
if (launchIdLock == null) {
// do not use multi-client mode
return new LaunchImpl(rpClient, parameters, rq, executor);
}
final String instanceUuid = UUID.randomUUID().toString();
final String uuid = launchIdLock.obtainLaunchUuid(instanceUuid);
if (uuid == null) {
// timeout locking on file or interrupted, anyway it should be logged already
// we continue to operate normally, since this flag is set by default and we shouldn't fail launches because of it
return new LaunchImpl(rpClient, parameters, rq, executor);
}
if (instanceUuid.equals(uuid)) {
// We got our own UUID as launch UUID, that means we are primary launch.
ObjectMapper objectMapper = new ObjectMapper();
try {
StartLaunchRQ rqCopy = objectMapper.readValue(objectMapper.writeValueAsString(rq), StartLaunchRQ.class);
rqCopy.setUuid(uuid);
return new PrimaryLaunch(rpClient, parameters, rqCopy, executor, launchIdLock, instanceUuid);
} catch (IOException e) {
throw new InternalReportPortalClientException("Unable to clone start launch request:", e);
}
} else {
Maybe launch = Maybe.create(emitter -> {
emitter.onSuccess(uuid);
emitter.onComplete();
});
return new SecondaryLaunch(rpClient, parameters, launch, executor, launchIdLock, instanceUuid);
}
}
/**
* Factory method for {@link ReportPortal} that uses already started launch
*
* @param launchUuid Launch to be used
* @return This instance for chaining
*/
@Nonnull
public Launch withLaunch(@Nonnull Maybe launchUuid) {
return ofNullable(rpClient).map(c -> (Launch) new LaunchImpl(c, parameters, launchUuid, executor)).orElse(Launch.NOOP_LAUNCH);
}
/**
* @return Configuration parameters
*/
@Nonnull
public ListenerParameters getParameters() {
return parameters;
}
/**
* @return ReportPortal client
*/
@Nullable
public ReportPortalClient getClient() {
return this.rpClient;
}
/**
* Creates new builder for {@link ReportPortal}
*
* @return builder for {@link ReportPortal}
*/
@Nonnull
public static Builder builder() {
return new Builder();
}
private static LaunchIdLock getLaunchLock(ListenerParameters parameters) {
return parameters.getClientJoin() ? parameters.getClientJoinMode().getInstance(parameters) : null;
}
/**
* Creates new ReportPortal based on already built dependencies
*
* @param client Report Portal Client
* @param params {@link ListenerParameters}
* @return builder for {@link ReportPortal}
*/
@Nonnull
public static ReportPortal create(ReportPortalClient client, ListenerParameters params) {
return create(client, params, buildExecutorService(params));
}
/**
* Creates new ReportPortal based on already built dependencies
*
* @param client Report Portal Client
* @param params {@link ListenerParameters}
* @param executor An executor service which will be used for internal request / response queue
* @return builder for {@link ReportPortal}
*/
@Nonnull
public static ReportPortal create(@Nonnull final ReportPortalClient client, @Nonnull final ListenerParameters params,
@Nonnull final ExecutorService executor) {
return new ReportPortal(client, executor, params, getLaunchLock(params));
}
/**
* Emits log message if there is any active context attached to the current thread
*
* @param logSupplier Log supplier. Converts current Item ID to the {@link SaveLogRQ} object
* @return true if log has been emitted
*/
public static boolean emitLog(final Function logSupplier) {
final LoggingContext loggingContext = LoggingContext.context();
if (null != loggingContext) {
loggingContext.emit(logSupplier);
return true;
}
return false;
}
/**
* Emits log message on Launch level if there is any active context attached to the current thread
*
* @param logSupplier Log supplier. Converts current Item ID to the {@link SaveLogRQ} object
* @return true if log has been emitted
*/
public static boolean emitLaunchLog(final Function logSupplier) {
final LaunchLoggingContext launchLoggingContext = LaunchLoggingContext.context(DEFAULT_LAUNCH_KEY);
if (null != launchLoggingContext) {
launchLoggingContext.emit(logSupplier);
return true;
}
return false;
}
/**
* Emits log message if there is any active context attached to the current thread
*
* @param itemUuid Test Item ID promise
* @param logSupplier Log supplier. Converts current Item ID to the {@link SaveLogRQ} object
* @return true if log has been emitted
*/
public static boolean emitLog(Maybe itemUuid, final Function logSupplier) {
final LoggingContext loggingContext = LoggingContext.context();
if (null != loggingContext) {
loggingContext.emit(itemUuid, logSupplier);
return true;
}
return false;
}
/**
* Emits log message if there is any active context attached to the current thread
*
* @param message Log message
* @param level Log level
* @param time Log time
* @return true if log has been emitted
*/
public static boolean emitLog(final String message, final String level, final Date time) {
return emitLog(itemUuid -> {
SaveLogRQ rq = new SaveLogRQ();
rq.setLevel(level);
rq.setLogTime(time);
rq.setItemUuid(itemUuid);
rq.setMessage(message);
return rq;
});
}
/**
* Emits log message on Launch level if there is any active context attached to the current thread
*
* @param message Log message
* @param level Log level
* @param time Log time
* @return true if log has been emitted
*/
public static boolean emitLaunchLog(final String message, final String level, final Date time) {
return emitLaunchLog(launchUuid -> {
SaveLogRQ rq = new SaveLogRQ();
rq.setLevel(level);
rq.setLogTime(time);
rq.setLaunchUuid(launchUuid);
rq.setMessage(message);
return rq;
});
}
private static void fillSaveLogRQ(final SaveLogRQ rq, final String message, final String level, final Date time, final File file) {
rq.setMessage(message);
rq.setLevel(level);
rq.setLogTime(time);
try {
SaveLogRQ.File f = new SaveLogRQ.File();
f.setContentType(detect(file));
f.setContent(readFileToBytes(file));
f.setName(UUID.randomUUID().toString());
rq.setFile(f);
} catch (IOException e) {
// seems like there is some problem. Do not report an file
LOGGER.error("Cannot send file to ReportPortal", e);
}
}
/**
* Emits log message if there is any active context attached to the current thread
*
* @param message Log message
* @param level Log level
* @param time Log time
* @param file a file to attach to the log message
* @return true if log has been emitted
*/
public static boolean emitLog(final String message, final String level, final Date time, final File file) {
return emitLog(itemUuid -> {
SaveLogRQ rq = new SaveLogRQ();
rq.setItemUuid(itemUuid);
fillSaveLogRQ(rq, message, level, time, file);
return rq;
});
}
/**
* Emits log message on Launch level if there is any active context attached to the current thread
*
* @param message Log message
* @param level Log level
* @param time Log time
* @param file a file to attach to the log message
* @return true if log has been emitted
*/
public static boolean emitLaunchLog(final String message, final String level, final Date time, final File file) {
return emitLaunchLog(launchUuid -> {
SaveLogRQ rq = new SaveLogRQ();
rq.setLaunchUuid(launchUuid);
fillSaveLogRQ(rq, message, level, time, file);
return rq;
});
}
private static void fillSaveLogRQ(final SaveLogRQ rq, final String level, final Date time, final ReportPortalMessage message) {
rq.setLevel(level);
rq.setLogTime(time);
rq.setMessage(message.getMessage());
try {
final TypeAwareByteSource data = message.getData();
SaveLogRQ.File file = new SaveLogRQ.File();
file.setContent(data.read());
file.setContentType(data.getMediaType());
file.setName(UUID.randomUUID().toString());
rq.setFile(file);
} catch (Exception e) {
// seems like there is some problem. Do not report an file
LOGGER.error("Cannot send file to ReportPortal", e);
}
}
public static boolean emitLog(final ReportPortalMessage message, final String level, final Date time) {
return emitLog(itemUuid -> {
SaveLogRQ rq = new SaveLogRQ();
rq.setItemUuid(itemUuid);
fillSaveLogRQ(rq, level, time, message);
return rq;
});
}
public static boolean emitLaunchLog(final ReportPortalMessage message, final String level, final Date time) {
return emitLaunchLog(launchUuid -> {
SaveLogRQ rq = new SaveLogRQ();
rq.setLaunchUuid(launchUuid);
fillSaveLogRQ(rq, level, time, message);
return rq;
});
}
public static class Builder {
static final String API_PATH = "api/";
private static final String HTTPS = "https";
private OkHttpClient.Builder httpClient;
private ListenerParameters parameters;
private ExecutorService executor;
public Builder withHttpClient(OkHttpClient.Builder client) {
this.httpClient = client;
return this;
}
public Builder withParameters(ListenerParameters parameters) {
this.parameters = parameters;
return this;
}
public Builder withExecutorService(ExecutorService executor) {
this.executor = executor;
return this;
}
public ReportPortal build() {
ListenerParameters params = ofNullable(this.parameters).orElse(new ListenerParameters(defaultPropertiesLoader()));
ExecutorService executorService = executor == null ? buildExecutorService(params) : executor;
Class extends ReportPortalClient> clientType = params.isAsyncReporting() ?
ReportPortalClientV2.class :
ReportPortalClient.class;
return new ReportPortal(buildClient(clientType, params, executorService), executorService, params, buildLaunchLock(params));
}
/**
* @param clientType a class to instantiate
* @param params {@link ListenerParameters} Report Portal parameters
* @param Report Portal Client interface class
* @return a Report Portal Client instance
*/
public T buildClient(@Nonnull final Class clientType, @Nonnull final ListenerParameters params) {
return buildClient(clientType, params, buildExecutorService(params));
}
/**
* @param clientType a class to instantiate
* @param params {@link ListenerParameters} Report Portal parameters
* @param Report Portal Client interface class
* @param executor {@link ExecutorService} an Executor which will be used for internal request / response queue processing
* @return a Report Portal Client instance
*/
public T buildClient(@Nonnull final Class clientType, @Nonnull final ListenerParameters params,
@Nonnull final ExecutorService executor) {
OkHttpClient client = ofNullable(this.httpClient).map(c -> c.addInterceptor(new BearerAuthInterceptor(params.getApiKey()))
.build()).orElseGet(() -> defaultClient(params));
return ofNullable(client).map(c -> buildRestEndpoint(params, c, executor).create(clientType)).orElse(null);
}
/**
* @param parameters {@link ListenerParameters} Report Portal parameters
* @param client {@link OkHttpClient} an HTTP client instance
* @return a ReportPortal endpoint description class
*/
protected Retrofit buildRestEndpoint(@Nonnull final ListenerParameters parameters, @Nonnull final OkHttpClient client) {
return buildRestEndpoint(parameters, client, buildExecutorService(parameters));
}
/**
* @param parameters {@link ListenerParameters} Report Portal parameters
* @param client {@link OkHttpClient} an HTTP client instance
* @param executor {@link ExecutorService} an Executor which will be used for internal request / response queue processing
* @return a ReportPortal endpoint description class
*/
protected Retrofit buildRestEndpoint(@Nonnull final ListenerParameters parameters, @Nonnull final OkHttpClient client,
@Nonnull final ExecutorService executor) {
String baseUrl = (parameters.getBaseUrl().endsWith("/") ? parameters.getBaseUrl() : parameters.getBaseUrl() + "/") + API_PATH;
Retrofit.Builder builder = new Retrofit.Builder().client(client);
try {
builder.baseUrl(baseUrl);
} catch (NoSuchMethodError e) {
throw new InternalReportPortalClientException("Unable to initialize OkHttp client. "
+ "Report Portal client supports OkHttp version 3.11.0 as minimum.\n"
+ "Please update OkHttp dependency.\n"
+ "Besides this usually happens due to old selenium-java version (it overrides our dependency), "
+ "please use selenium-java 3.141.0 as minimum.",
e
);
}
return builder.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.from(executor)))
.addConverterFactory(JacksonConverterFactory.create(HttpRequestUtils.MAPPER))
.build();
}
@Nullable
protected OkHttpClient defaultClient(@Nonnull ListenerParameters parameters) {
String baseUrlStr = parameters.getBaseUrl();
if (baseUrlStr == null) {
LOGGER.warn("Base url for Report Portal server is not set!");
return null;
}
URL baseUrl;
try {
baseUrl = new URL(baseUrlStr);
} catch (MalformedURLException e) {
LOGGER.warn("Unable to parse Report Portal URL", e);
return null;
}
String keyStore = parameters.getKeystore();
String keyStorePassword = parameters.getKeystorePassword();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (HTTPS.equals(baseUrl.getProtocol()) && keyStore != null) {
if (null == keyStorePassword) {
String error = "You should provide keystore password parameter [" + ListenerProperty.KEYSTORE_PASSWORD
+ "] if you use HTTPS protocol";
LOGGER.error(error);
throw new InternalReportPortalClientException(error);
}
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(SslUtils.loadKeyStore(keyStore, keyStorePassword));
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
X509TrustManager trustManager = (X509TrustManager) ofNullable(trustManagers).flatMap(managers -> Arrays.stream(managers)
.filter(m -> m instanceof X509TrustManager)
.findAny())
.orElseThrow(() -> new InternalReportPortalClientException(
"Unable to find X509 trust manager, managers:" + Arrays.toString(trustManagers)));
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(sslSocketFactory, trustManager);
} catch (KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) {
String error = "Unable to load trust store";
LOGGER.error(error, e);
throw new InternalReportPortalClientException(error, e);
}
}
String proxyStr = parameters.getProxyUrl();
if (isNotBlank(proxyStr)) {
try {
URL proxyUrl = new URL(proxyStr);
int port = proxyUrl.getPort();
builder.proxy(new Proxy(Proxy.Type.HTTP,
InetSocketAddress.createUnresolved(proxyUrl.getHost(), port >= 0 ? port : proxyUrl.getDefaultPort())
));
} catch (MalformedURLException e) {
LOGGER.warn("Unable to parse proxy URL", e);
return null;
}
}
builder.addInterceptor(new BearerAuthInterceptor(parameters.getApiKey()));
builder.addInterceptor(new PathParamInterceptor("projectName", parameters.getProjectName()));
if (parameters.isHttpLogging()) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(logging);
}
ofNullable(parameters.getHttpCallTimeout()).ifPresent(builder::callTimeout);
ofNullable(parameters.getHttpConnectTimeout()).ifPresent(builder::connectTimeout);
ofNullable(parameters.getHttpReadTimeout()).ifPresent(builder::readTimeout);
ofNullable(parameters.getHttpWriteTimeout()).ifPresent(builder::writeTimeout);
builder.retryOnConnectionFailure(true).cookieJar(new CookieJar() {
private final Map> STORAGE = new ConcurrentHashMap<>();
@Override
public void saveFromResponse(@Nonnull HttpUrl url, @Nonnull List cookies) {
STORAGE.computeIfAbsent(url.url().getHost(), u -> new CopyOnWriteArrayList<>()).addAll(cookies);
}
@Override
@Nonnull
public List loadForRequest(@Nonnull HttpUrl url) {
return STORAGE.computeIfAbsent(url.url().getHost(), u -> new CopyOnWriteArrayList<>());
}
});
return builder.build();
}
/**
* @param parameters Report Portal parameters
* @return Launch lock instance
* @deprecated use {@link #buildLaunchLock(ListenerParameters)}
*/
@Deprecated
protected LaunchIdLock buildLockFile(ListenerParameters parameters) {
return buildLaunchLock(parameters);
}
protected LaunchIdLock buildLaunchLock(ListenerParameters parameters) {
return getLaunchLock(parameters);
}
protected PropertiesLoader defaultPropertiesLoader() {
return PropertiesLoader.load();
}
protected ExecutorService buildExecutorService(ListenerParameters params) {
return ReportPortal.buildExecutorService(params);
}
}
private static ExecutorService buildExecutorService(ListenerParameters params) {
return Executors.newFixedThreadPool(params.getIoPoolSize(),
new ThreadFactoryBuilder().setNameFormat("rp-io-%s").setDaemon(true).build()
);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy