diff --git a/dotCMS/src/main/java/com/dotcms/filters/interceptor/SimpleWebInterceptorDelegateImpl.java b/dotCMS/src/main/java/com/dotcms/filters/interceptor/SimpleWebInterceptorDelegateImpl.java index 4989e10a76fb..1f03413a8fa2 100644 --- a/dotCMS/src/main/java/com/dotcms/filters/interceptor/SimpleWebInterceptorDelegateImpl.java +++ b/dotCMS/src/main/java/com/dotcms/filters/interceptor/SimpleWebInterceptorDelegateImpl.java @@ -1,7 +1,11 @@ package com.dotcms.filters.interceptor; import com.dotcms.repackage.com.google.common.annotations.VisibleForTesting; +import com.dotcms.rest.config.DotRestApplication; +import com.dotmarketing.util.Config; +import com.dotmarketing.util.Logger; import com.dotmarketing.util.RegEX; +import io.vavr.Lazy; import org.apache.commons.collections.iterators.ReverseListIterator; import javax.servlet.http.HttpServletRequest; @@ -30,6 +34,9 @@ public class SimpleWebInterceptorDelegateImpl implements WebInterceptorDelegate private final AtomicBoolean reverseOrderForPostInvoke = new AtomicBoolean(false); + private static final Lazy ENABLE_TELEMETRY_FROM_CORE = Lazy.of(() -> + Config.getBooleanProperty("FEATURE_FLAG_TELEMETRY_CORE_ENABLED", false)); + @Override public void addBefore(final String webInterceptorName, final WebInterceptor webInterceptor) { @@ -83,7 +90,11 @@ public void add(final int order, final WebInterceptor webInterceptor) { @Override public void addFirst(final WebInterceptor webInterceptor) { - + if (Boolean.TRUE.equals(ENABLE_TELEMETRY_FROM_CORE.get()) && + webInterceptor.getClass().getName().equalsIgnoreCase("com.dotcms.experience.collectors.api.ApiMetricWebInterceptor")) { + Logger.warn(DotRestApplication.class, "Bypassing addition of API Metric Web Interceptor from OSGi"); + return; + } this.interceptors.add(0, webInterceptor); this.init(webInterceptor); } // addFirst. diff --git a/dotCMS/src/main/java/com/dotcms/rest/config/DotRestApplication.java b/dotCMS/src/main/java/com/dotcms/rest/config/DotRestApplication.java index f8163c40798b..1c155bc5823b 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/config/DotRestApplication.java +++ b/dotCMS/src/main/java/com/dotcms/rest/config/DotRestApplication.java @@ -1,20 +1,25 @@ package com.dotcms.rest.config; import com.dotcms.cdi.CDIUtils; +import com.dotcms.telemetry.rest.TelemetryResource; +import com.dotmarketing.util.Config; +import com.dotmarketing.util.Logger; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; -import io.swagger.v3.jaxrs2.integration.resources.AcceptHeaderOpenApiResource; -import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource; -import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.servers.Server; import io.swagger.v3.oas.annotations.tags.Tag; +import io.vavr.Lazy; +import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; +import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.glassfish.jersey.server.ResourceConfig; + +import javax.ws.rs.ApplicationPath; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import javax.ws.rs.ApplicationPath; -import org.glassfish.jersey.media.multipart.MultiPartFeature; -import org.glassfish.jersey.server.ResourceConfig; /** * This class provides the list of all the REST end-points in dotCMS. Every new @@ -47,18 +52,24 @@ ) public class DotRestApplication extends ResourceConfig { - public DotRestApplication() { + private static final Lazy ENABLE_TELEMETRY_FROM_CORE = Lazy.of(() -> + Config.getBooleanProperty("FEATURE_FLAG_TELEMETRY_CORE_ENABLED", false)); + public DotRestApplication() { + final List packages = new ArrayList<>(List.of( + "com.dotcms.rest", + "com.dotcms.contenttype.model.field", + "com.dotcms.rendering.js", + "com.dotcms.ai.rest", + "io.swagger.v3.jaxrs2")); + if (Boolean.TRUE.equals(ENABLE_TELEMETRY_FROM_CORE.get())) { + packages.add(TelemetryResource.class.getPackageName()); + } register(MultiPartFeature.class). register(JacksonJaxbJsonProvider.class). registerClasses(customClasses.keySet()). - packages( - "com.dotcms.rest", - "com.dotcms.contenttype.model.field", - "com.dotcms.rendering.js", - "com.dotcms.ai.rest", - "io.swagger.v3.jaxrs2" - ).register(CdiComponentProvider.class); + packages(packages.toArray(new String[0])). + register(CdiComponentProvider.class); } /** @@ -70,10 +81,15 @@ public DotRestApplication() { * adds a class and reloads * @param clazz the class ot add */ - public static synchronized void addClass(Class clazz) { + public static synchronized void addClass(final Class clazz) { if(clazz==null){ return; } + if (Boolean.TRUE.equals(ENABLE_TELEMETRY_FROM_CORE.get()) + && clazz.getName().equalsIgnoreCase("com.dotcms.experience.TelemetryResource")) { + Logger.warn(DotRestApplication.class, "Bypassing activation of Telemetry REST Endpoint from OSGi"); + return; + } if (Boolean.TRUE.equals(customClasses.computeIfAbsent(clazz,c -> true))) { final Optional reloader = CDIUtils.getBean(ContainerReloader.class); reloader.ifPresent(ContainerReloader::reload); diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricFactory.java b/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricFactory.java deleted file mode 100644 index 0c3ef97a621a..000000000000 --- a/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.dotcms.telemetry.business; - -import com.dotcms.business.CloseDBIfOpened; -import com.dotmarketing.common.db.DotConnect; -import com.dotmarketing.db.DbConnectionFactory; -import com.dotmarketing.exception.DotDataException; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * Utility class tha provide methods to run SQL Query into dotCMS DataBase - */ -public enum MetricFactory { - - INSTANCE; - - @CloseDBIfOpened - public Optional getValue(final String sqlQuery) throws DotDataException { - final DotConnect dotConnect = new DotConnect(); - final List> loadObjectResults = dotConnect.setSQL(sqlQuery) - .loadObjectResults(); - - if (loadObjectResults.isEmpty()) { - return Optional.empty(); - } - - return Optional.ofNullable(loadObjectResults.get(0).get("value")); - } - - /** - * Execute a query that returns a list of String objects. The query should retrieve a field - * called value, as shown in the example below: - *
-     * {@code
-     * SELECT identifier as value FROM template
-     * }
-     * 
- *

- * This method will iterate through the results returned by the query and use the values from - * the value field to create a Collection of Strings. - * - * @param sqlQuery the query to be executed. - * - * @return a Collection of Strings with the values returned by the query. - * - * @throws DotDataException if an error occurs while executing the query. - */ - @CloseDBIfOpened - public Optional> getList(final String sqlQuery) throws DotDataException { - final DotConnect dotConnect = new DotConnect(); - final List> loadObjectResults = dotConnect.setSQL(sqlQuery) - .loadObjectResults(); - - if (loadObjectResults.isEmpty()) { - return Optional.empty(); - } - - return Optional.of( - loadObjectResults.stream().map(item -> item.get("value").toString()).collect(Collectors.toList()) - ); - } - - @CloseDBIfOpened - @SuppressWarnings("unchecked") - public int getSchemaDBVersion() throws DotDataException { - final ArrayList> results = new DotConnect() - .setSQL("SELECT max(db_version) AS version FROM db_version") - .loadResults(); - - return Integer.parseInt(results.get(0).get("version").toString()); - } - -} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsAPI.java b/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsAPI.java index f5d46f953f45..bb592bc040db 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsAPI.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsAPI.java @@ -1,196 +1,56 @@ package com.dotcms.telemetry.business; import com.dotcms.telemetry.MetricsSnapshot; -import com.dotcms.telemetry.util.JsonUtil; -import com.dotcms.http.CircuitBreakerUrl; -import com.dotmarketing.business.APILocator; -import com.dotmarketing.db.LocalTransaction; import com.dotmarketing.exception.DotDataException; -import com.dotmarketing.exception.DotRuntimeException; -import com.dotmarketing.util.Config; -import com.dotmarketing.util.Logger; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; /** * API for all the Metrics related operations */ -public enum MetricsAPI { - - INSTANCE; - - final String endPointUrl = Config.getStringProperty("TELEMETRY_PERSISTENCE_ENDPOINT", - "https://dotcms-prod-1.analytics.dotcms.cloud/m"); - - final String customCategory = Config.getStringProperty("TELEMETRY_CLIENT_CATEGORY", - null); - - final String clientNameFromConfig = Config.getStringProperty("TELEMETRY_CLIENT_NAME", - null); - - final int clientEnvVersionFromConfig = Config.getIntProperty("TELEMETRY_CLIENT_VERSION", - -1); - - final String clientEnvFromConfig = Config.getStringProperty("TELEMETRY_CLIENT_ENV", - null); - - final int maxAttemptsToFail = Config.getIntProperty("TELEMETRY_MAX_ATTEMPTS_TO_FAIL", 3); - final int tryAgainDelay = Config.getIntProperty("TELEMETRY_TRY_AGAIN_DELAY", 30); - final int requestTimeout = Config.getIntProperty("TELEMETRY_REQUEST_TIMEOUT", 4000); - - private static int getSchemaDBVersion() throws DotDataException { - try { - return LocalTransaction.wrapReturn(MetricFactory.INSTANCE::getSchemaDBVersion); - } catch (DotDataException e) { - throw e; - } catch (Exception e) { - throw new DotRuntimeException(e); - } +public interface MetricsAPI { + enum ClientCategory { + DOTCMS, + CLIENT } /** - * Persists the given {@link MetricsSnapshot} + * Saves all the information collected in the {@link MetricsSnapshot} object to a specified + * server. + * + * @param metricsSnapshot The {@link MetricsSnapshot} containing the snapshot of the metrics to + * be persisted. * - * @param metricsSnapshot the snapshot to persist + * @throws DotDataException An error occurred while persisting the metrics snapshot. */ - public void persistMetricsSnapshot(final MetricsSnapshot metricsSnapshot) throws DotDataException { - Logger.debug(this, "Persisting the snapshot"); - - final Client client = getClient(); - - sendMetric(new MetricEndpointPayload.Builder() - .clientName(client.getClientName()) - .clientEnv(client.getEnvironment()) - .clientVersion(client.getVersion()) - .clientCategory(client.getCategory()) - .schemaVersion(getSchemaDBVersion()) - .insertDate(Instant.now()) - .snapshot(metricsSnapshot) - .build() - ); - } + void persistMetricsSnapshot(final MetricsSnapshot metricsSnapshot) throws DotDataException; /** - * Use the {@link MetricFactory#getList(String)} method to execute a Query and return a + * Use the {@link MetricsFactory#getList(String)} method to execute a Query and return a * Collection of String * * @param sqlQuery the query to be executed * * @return a Collection of Strings with the values returned by the query * - * @see MetricFactory#getList(String) + * @see MetricsFactory#getList(String) */ - public List getList(final String sqlQuery) { - try { - return LocalTransaction.wrapReturn(() -> MetricFactory.INSTANCE.getList(sqlQuery)) - .orElse(Collections.emptyList()); - } catch (Exception e) { - throw new DotRuntimeException(e); - } - } - - public Optional getValue(final String sqlQuery) { - try { - return LocalTransaction.wrapReturn(() -> MetricFactory.INSTANCE.getValue(sqlQuery)); - } catch (Exception e) { - throw new DotRuntimeException(e); - } - } - - public Client getClient() throws DotDataException { - - Client client = getClientMetaDataFromHostName(); - - return new Client.Builder() - .clientName(clientNameFromConfig != null ? clientNameFromConfig : - client.getClientName()) - .category(customCategory != null ? customCategory : client.getCategory()) - .version(clientEnvVersionFromConfig != -1 ? clientEnvVersionFromConfig : - client.getVersion()) - .environment(clientEnvFromConfig != null ? clientEnvFromConfig : - client.getEnvironment()) - .build(); - } - - private Client getClientMetaDataFromHostName() throws DotDataException { - final String hostname = APILocator.getServerAPI().getCurrentServer().getName(); - final String[] split = hostname.split("-"); + List getList(final String sqlQuery); - final Client.Builder builder = new Client.Builder(); + Optional getValue(final String sqlQuery); - if (split.length < 4) { - builder.clientName(hostname) - .environment(hostname) - .version(0); - } else { - final String clientName = String.join("-", Arrays.copyOfRange(split, 1, - split.length - 2)); + Client getClient() throws DotDataException; - builder.clientName(clientName) - .environment(split[split.length - 2]) - .version(Integer.parseInt(split[split.length - 1])); + class Client { - } - - getCategoryFromHostName(hostname).map(ClientCategory::name).ifPresent(builder::category); - - return builder.build(); - } - - private Optional getCategoryFromHostName(final String hostname) { - if (hostname.startsWith("dotcms-corpsites")) { - return Optional.of(ClientCategory.DOTCMS); - } else if (hostname.startsWith("dotcms-")) { - return Optional.of(ClientCategory.CLIENT); - } - - return Optional.empty(); - } - - - private void sendMetric(final MetricEndpointPayload metricEndpointPayload) { - - final CircuitBreakerUrl circuitBreakerUrl = CircuitBreakerUrl.builder() - .setMethod(CircuitBreakerUrl.Method.POST) - .setUrl(endPointUrl) - .setHeaders(Map.of("Content-Type", "application/json")) - .setRawData(JsonUtil.INSTANCE.getAsJson(metricEndpointPayload)) - .setFailAfter(maxAttemptsToFail) - .setTryAgainAfterDelaySeconds(tryAgainDelay) - .setTimeout(requestTimeout) - .build(); - - try { - circuitBreakerUrl.doString(); - final int response = circuitBreakerUrl.response(); - if (response != 201) { - Logger.debug(this, - "ERROR: Unable to save the Metric. HTTP error code: " + response); - } - } catch (Exception e) { - Logger.debug(this, "ERROR: Unable to save the Metric."); - } - } - - private enum ClientCategory { - DOTCMS, - CLIENT - } - - public static class Client { final String clientName; final String environment; final int version; - final String category; - public Client(final Builder builder) { + public Client(final Client.Builder builder) { this.clientName = builder.clientName; this.environment = builder.environment; this.version = builder.version; @@ -224,28 +84,28 @@ public String getCategory() { } public static class Builder { + String clientName; String environment; int version; - String category; - Builder clientName(final String clientName) { + Client.Builder clientName(final String clientName) { this.clientName = clientName; return this; } - Builder environment(final String environment) { + Client.Builder environment(final String environment) { this.environment = environment; return this; } - Builder version(final int version) { + Client.Builder version(final int version) { this.version = version; return this; } - Builder category(final String category) { + Client.Builder category(final String category) { this.category = category; return this; } @@ -253,7 +113,9 @@ Builder category(final String category) { Client build() { return new Client(this); } + } + } } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsAPIImpl.java b/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsAPIImpl.java new file mode 100644 index 000000000000..6131f7a2e3ef --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsAPIImpl.java @@ -0,0 +1,176 @@ +package com.dotcms.telemetry.business; + +import com.dotcms.business.CloseDBIfOpened; +import com.dotcms.business.WrapInTransaction; +import com.dotcms.http.CircuitBreakerUrl; +import com.dotcms.telemetry.MetricsSnapshot; +import com.dotcms.telemetry.util.JsonUtil; +import com.dotmarketing.business.APILocator; +import com.dotmarketing.exception.DotDataException; +import com.dotmarketing.exception.DotRuntimeException; +import com.dotmarketing.util.Config; +import com.dotmarketing.util.Logger; +import io.vavr.Lazy; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.servlet.http.HttpServletResponse; +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * This class provides the default implementation of the {@link MetricsAPI} interface. + */ +@ApplicationScoped +public class MetricsAPIImpl implements MetricsAPI { + + private final Lazy endPointUrl = Lazy.of(() -> Config.getStringProperty( + "TELEMETRY_PERSISTENCE_ENDPOINT", null)); + + private final Lazy customCategory = Lazy.of(() -> Config.getStringProperty( + "TELEMETRY_CLIENT_CATEGORY", null)); + + private final Lazy clientNameFromConfig = Lazy.of(() -> Config.getStringProperty( + "TELEMETRY_CLIENT_NAME", null)); + + private final Lazy clientEnvVersionFromConfig = Lazy.of(() -> Config.getIntProperty( + "TELEMETRY_CLIENT_VERSION", -1)); + + private final Lazy clientEnvFromConfig = Lazy.of(() -> Config.getStringProperty( + "TELEMETRY_CLIENT_ENV", null)); + + private final Lazy maxAttemptsToFail = Lazy.of(() -> Config.getIntProperty( + "TELEMETRY_MAX_ATTEMPTS_TO_FAIL", 3)); + private final Lazy tryAgainDelay = Lazy.of(() -> Config.getIntProperty( + "TELEMETRY_TRY_AGAIN_DELAY", 30)); + private final Lazy requestTimeout = Lazy.of(() -> Config.getIntProperty( + "TELEMETRY_REQUEST_TIMEOUT", 4000)); + + private final MetricsFactory metricsFactory; + + @Inject + public MetricsAPIImpl(final MetricsFactory metricsFactory) { + this.metricsFactory = metricsFactory; + } + + @WrapInTransaction + private int getSchemaDBVersion() throws DotDataException { + try { + return metricsFactory.getSchemaDBVersion(); + } catch (final DotDataException e) { + Logger.debug(this, "Error getting the Metrics schema version from the database", e); + throw e; + } catch (final Exception e) { + Logger.debug(this, "Generic error getting the Metrics schema version", e); + throw new DotRuntimeException(e); + } + } + + @Override + public void persistMetricsSnapshot(final MetricsSnapshot metricsSnapshot) throws DotDataException { + Logger.debug(this, "Persisting the snapshot"); + final Client client = getClient(); + sendMetric(new MetricEndpointPayload.Builder() + .clientName(client.getClientName()) + .clientEnv(client.getEnvironment()) + .clientVersion(client.getVersion()) + .clientCategory(client.getCategory()) + .schemaVersion(getSchemaDBVersion()) + .insertDate(Instant.now()) + .snapshot(metricsSnapshot) + .build() + ); + } + + @Override + @CloseDBIfOpened + public List getList(final String sqlQuery) { + try { + return metricsFactory.getList(sqlQuery).orElse(Collections.emptyList()); + } catch (final Exception e) { + Logger.debug(this, "Error getting the Metrics list from the database", e); + throw new DotRuntimeException(e); + } + } + + @Override + @CloseDBIfOpened + public Optional getValue(final String sqlQuery) { + try { + return metricsFactory.getValue(sqlQuery); + } catch (final Exception e) { + Logger.debug(this, "Error getting the Metrics value from the database", e); + throw new DotRuntimeException(e); + } + } + + @Override + public Client getClient() throws DotDataException { + final Client client = this.getClientMetaDataFromHostName(); + return new Client.Builder() + .clientName(clientNameFromConfig.get() != null ? clientNameFromConfig.get() : + client.getClientName()) + .category(customCategory.get() != null ? customCategory.get() : client.getCategory()) + .version(clientEnvVersionFromConfig.get() != -1 ? clientEnvVersionFromConfig.get() : + client.getVersion()) + .environment(clientEnvFromConfig.get() != null ? clientEnvFromConfig.get() : + client.getEnvironment()) + .build(); + } + + private Client getClientMetaDataFromHostName() throws DotDataException { + final String hostname = APILocator.getServerAPI().getCurrentServer().getName(); + final String[] split = hostname.split("-"); + final Client.Builder builder = new Client.Builder(); + + if (split.length < 4) { + builder.clientName(hostname) + .environment(hostname) + .version(0); + } else { + final String clientName = String.join("-", Arrays.copyOfRange(split, 1, + split.length - 2)); + builder.clientName(clientName) + .environment(split[split.length - 2]) + .version(Integer.parseInt(split[split.length - 1])); + } + + this.getCategoryFromHostName(hostname).map(ClientCategory::name).ifPresent(builder::category); + return builder.build(); + } + + private Optional getCategoryFromHostName(final String hostname) { + if (hostname.startsWith("dotcms-corpsites")) { + return Optional.of(ClientCategory.DOTCMS); + } else if (hostname.startsWith("dotcms-")) { + return Optional.of(ClientCategory.CLIENT); + } + return Optional.empty(); + } + + private void sendMetric(final MetricEndpointPayload metricEndpointPayload) { + final CircuitBreakerUrl circuitBreakerUrl = CircuitBreakerUrl.builder() + .setMethod(CircuitBreakerUrl.Method.POST) + .setUrl(endPointUrl.get()) + .setHeaders(Map.of("Content-Type", "application/json")) + .setRawData(JsonUtil.INSTANCE.getAsJson(metricEndpointPayload)) + .setFailAfter(maxAttemptsToFail.get()) + .setTryAgainAfterDelaySeconds(tryAgainDelay.get()) + .setTimeout(requestTimeout.get()) + .build(); + try { + circuitBreakerUrl.doString(); + final int response = circuitBreakerUrl.response(); + if (response != HttpServletResponse.SC_CREATED) { + Logger.debug(this, "ERROR: Unable to save the Metric. HTTP error code: " + response); + } + } catch (final Exception e) { + Logger.debug(this, "Failed to save the Metric to Telemetry persistence endpoint", e); + } + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsFactory.java b/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsFactory.java new file mode 100644 index 000000000000..c5ad0c311f8c --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsFactory.java @@ -0,0 +1,37 @@ +package com.dotcms.telemetry.business; + +import com.dotmarketing.exception.DotDataException; + +import java.util.List; +import java.util.Optional; + +/** + * This class provides low-level database access to Metrics-related information. + */ +public interface MetricsFactory { + + Optional getValue(final String sqlQuery) throws DotDataException; + + /** + * Execute a query that returns a list of String objects. The query should retrieve a field + * called value, as shown in the example below: + *
+     * {@code
+     * SELECT identifier as value FROM template
+     * }
+     * 
+ *

+ * This method will iterate through the results returned by the query and use the values from + * the value field to create a Collection of Strings. + * + * @param sqlQuery the query to be executed. + * + * @return a Collection of Strings with the values returned by the query. + * + * @throws DotDataException if an error occurs while executing the query. + */ + Optional> getList(final String sqlQuery) throws DotDataException; + + int getSchemaDBVersion() throws DotDataException; + +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsFactoryImpl.java b/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsFactoryImpl.java new file mode 100644 index 000000000000..41c38b0f4502 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricsFactoryImpl.java @@ -0,0 +1,47 @@ +package com.dotcms.telemetry.business; + +import com.dotmarketing.common.db.DotConnect; +import com.dotmarketing.exception.DotDataException; + +import javax.enterprise.context.ApplicationScoped; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * This class provides the default implementation of the {@link MetricsFactory} interface. + */ +@ApplicationScoped +public class MetricsFactoryImpl implements MetricsFactory { + + @Override + public Optional getValue(final String sqlQuery) throws DotDataException { + final List> loadObjectResults = new DotConnect().setSQL(sqlQuery).loadObjectResults(); + if (loadObjectResults.isEmpty()) { + return Optional.empty(); + } + return Optional.ofNullable(loadObjectResults.get(0).get("value")); + } + + @Override + public Optional> getList(final String sqlQuery) throws DotDataException { + final List> loadObjectResults = new DotConnect().setSQL(sqlQuery).loadObjectResults(); + if (loadObjectResults.isEmpty()) { + return Optional.empty(); + } + return Optional.of(loadObjectResults.stream() + .map(item -> item.get("value").toString()).collect(Collectors.toList())); + } + + @Override + @SuppressWarnings("unchecked") + public int getSchemaDBVersion() throws DotDataException { + final ArrayList> results = new DotConnect() + .setSQL("SELECT max(db_version) AS version FROM db_version") + .loadResults(); + return Integer.parseInt(results.get(0).get("version").toString()); + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/DBMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/DBMetricType.java index 24f3b45c61c6..5ebc1eef8cb3 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/DBMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/DBMetricType.java @@ -2,7 +2,7 @@ import com.dotcms.telemetry.MetricType; import com.dotcms.telemetry.MetricValue; -import com.dotcms.telemetry.business.MetricsAPI; +import com.dotmarketing.business.APILocator; import java.util.Optional; @@ -17,14 +17,12 @@ public interface DBMetricType extends MetricType { @Override default Optional getValue() { - return MetricsAPI.INSTANCE.getValue(getSqlQuery()); + return APILocator.getMetricsAPI().getValue(getSqlQuery()); } @Override default Optional getStat() { - return getValue() - .map(value -> new MetricValue(this.getMetric(), value)); - + return getValue().map(value -> new MetricValue(this.getMetric(), value)); } } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/MetricStatsCollector.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/MetricStatsCollector.java index 8bdf3347cb70..b63565ea1cd2 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/MetricStatsCollector.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/MetricStatsCollector.java @@ -1,9 +1,11 @@ package com.dotcms.telemetry.collectors; +import com.dotcms.cdi.CDIUtils; import com.dotcms.telemetry.MetricCalculationError; import com.dotcms.telemetry.MetricType; import com.dotcms.telemetry.MetricValue; import com.dotcms.telemetry.MetricsSnapshot; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotcms.telemetry.collectors.api.ApiMetricAPI; import com.dotcms.telemetry.collectors.api.ApiMetricTypes; import com.dotcms.telemetry.collectors.container.TotalFileContainersInLivePageDatabaseMetricType; @@ -17,33 +19,33 @@ import com.dotcms.telemetry.collectors.content.RecentlyEditedContentDatabaseMetricType; import com.dotcms.telemetry.collectors.content.TotalContentsDatabaseMetricType; import com.dotcms.telemetry.collectors.content.WorkingNotDefaultLanguageContentsDatabaseMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfCategoryFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfConstantFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfDateFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfDateTimeFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfHiddenFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfPermissionsFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfRelationshipFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfSiteOrFolderFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfTagFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfTextAreaFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfTextFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfTimeFieldsMetricType; -import com.dotcms.telemetry.collectors.contenttype.CountOfWYSIWYGFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfBinaryFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfBlockEditorFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfCategoryFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfCheckboxFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfColumnsFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfConstantFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfDateFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfDateTimeFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfFileFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfHiddenFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfImageFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfJSONFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfKeyValueFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfLineDividersFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfMultiselectFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfPermissionsFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfRadioFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfRelationshipFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfRowsFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfSelectFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfSiteOrFolderFieldsMetricType; import com.dotcms.telemetry.collectors.contenttype.CountOfTabFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfTagFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfTextAreaFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfTextFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfTimeFieldsMetricType; +import com.dotcms.telemetry.collectors.contenttype.CountOfWYSIWYGFieldsMetricType; import com.dotcms.telemetry.collectors.language.HasChangeDefaultLanguagesDatabaseMetricType; import com.dotcms.telemetry.collectors.language.OldStyleLanguagesVarialeMetricType; import com.dotcms.telemetry.collectors.language.TotalLanguagesDatabaseMetricType; @@ -108,7 +110,7 @@ */ public final class MetricStatsCollector { - public static final ApiMetricAPI apiStatAPI = new ApiMetricAPI(); + public static final ApiMetricAPI apiStatAPI = CDIUtils.getBeanThrows(ApiMetricAPI.class); static final Collection metricStatsCollectors; private MetricStatsCollector() { @@ -180,13 +182,18 @@ private MetricStatsCollector() { metricStatsCollectors.add(new TotalLiveContainerDatabaseMetricType()); metricStatsCollectors.add(new TotalWorkingContainerDatabaseMetricType()); - metricStatsCollectors.add(new TotalStandardContainersInLivePageDatabaseMetricType()); - metricStatsCollectors.add(new TotalFileContainersInLivePageDatabaseMetricType()); - metricStatsCollectors.add(new TotalStandardContainersInWorkingPageDatabaseMetricType()); - metricStatsCollectors.add(new TotalFileContainersInWorkingPageDatabaseMetricType()); - - metricStatsCollectors.add(new TotalFileContainersInLiveTemplatesDatabaseMetricType()); - metricStatsCollectors.add(new TotalStandardContainersInLiveTemplatesDatabaseMetricType()); + if (CDIUtils.getBean(MetricsAPI.class).isPresent()) { + final MetricsAPI metricsAPI = CDIUtils.getBean(MetricsAPI.class).get(); + metricStatsCollectors.add(new TotalStandardContainersInLivePageDatabaseMetricType(metricsAPI)); + metricStatsCollectors.add(new TotalFileContainersInLivePageDatabaseMetricType(metricsAPI)); + metricStatsCollectors.add(new TotalStandardContainersInWorkingPageDatabaseMetricType(metricsAPI)); + metricStatsCollectors.add(new TotalFileContainersInWorkingPageDatabaseMetricType(metricsAPI)); + + metricStatsCollectors.add(new TotalFileContainersInLiveTemplatesDatabaseMetricType(metricsAPI)); + metricStatsCollectors.add(new TotalStandardContainersInLiveTemplatesDatabaseMetricType(metricsAPI)); + } else { + Logger.debug(MetricStatsCollector.class, () -> "MetricsAPI could not be injected via CDI"); + } metricStatsCollectors.add(new CountOfCategoryFieldsMetricType()); metricStatsCollectors.add(new CountOfConstantFieldsMetricType()); @@ -222,7 +229,7 @@ private MetricStatsCollector() { public static MetricsSnapshot getStatsAndCleanUp() { final MetricsSnapshot stats = getStats(); - apiStatAPI.flushTemporalTable(); + apiStatAPI.flushTemporaryTable(); return stats; } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricAPI.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricAPI.java index a918f79373fc..6e10c96f877a 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricAPI.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricAPI.java @@ -1,10 +1,5 @@ package com.dotcms.telemetry.collectors.api; -import com.dotmarketing.db.HibernateUtil; -import com.dotmarketing.db.LocalTransaction; -import com.dotmarketing.exception.DotRuntimeException; - -import java.time.Instant; import java.util.Collection; import java.util.Map; @@ -17,9 +12,7 @@ * wish to track. Later, the data in this table is summarized and stored as part of the * MetricSnapshot. */ -public class ApiMetricAPI { - - final RequestHashCalculator requestHashCalculator = new RequestHashCalculator(); +public interface ApiMetricAPI { /** * Return all the summary from the temporal table @@ -29,13 +22,7 @@ public class ApiMetricAPI { * @see ApiMetricFactory * @see ApiMetricAPI */ - public static Collection> getMetricTemporaryTableData() { - try { - return ApiMetricFactory.INSTANCE.getMetricTemporaryTableData(); - } finally { - HibernateUtil.closeSessionSilently(); - } - } + Collection> getMetricTemporaryTableData(); /** * Save an Endpoint request to the metric_temporally_table. @@ -45,63 +32,25 @@ public static Collection> getMetricTemporaryTableData() { * @param apiMetricType Metric to be saved * @param request Request data */ - public void save(final ApiMetricType apiMetricType, - final ApiMetricWebInterceptor.RereadInputStreamRequest request) { - - final String requestHash = requestHashCalculator.calculate(apiMetricType, request); - final ApiMetricRequest metricAPIHit = new ApiMetricRequest.Builder() - .setMetric(apiMetricType.getMetric()) - .setTime(Instant.now()) - .setHash(requestHash) - .build(); - - ApiMetricFactorySubmitter.INSTANCE.saveAsync(metricAPIHit); - } + void save(final ApiMetricType apiMetricType, final ApiMetricWebInterceptor.RereadInputStreamRequest request); /** * Before beginning to collect endpoint requests, this function must be called. It saves a * starting register to the metric_temporally_table, indicating the initiation of data * collection */ - public void startCollecting() { - try { - LocalTransaction.wrap(ApiMetricFactory.INSTANCE::saveStartEvent); - } catch (Exception e) { - throw new RuntimeException(e); - } - } + void startCollecting(); - public void flushTemporalTable() { - try { - LocalTransaction.wrap(() -> { - ApiMetricFactory.INSTANCE.flushTemporaryTable(); - startCollecting(); - }); - } catch (Exception e) { - throw new DotRuntimeException(e); - } - } + void flushTemporaryTable(); /** * Create the metrics_temp table */ - public void createTemporaryTable() { - try { - LocalTransaction.wrap(ApiMetricFactory.INSTANCE::createTemporaryTable); - } catch (Exception e) { - throw new DotRuntimeException(e); - } - } + void createTemporaryTable(); /** * Drop the metrics_temp table */ - public void dropTemporaryTable() { - try { - LocalTransaction.wrap(ApiMetricFactory.INSTANCE::dropTemporaryTable); - } catch (Exception e) { - throw new DotRuntimeException(e); - } - } + void dropTemporaryTable(); } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricAPIImpl.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricAPIImpl.java new file mode 100644 index 000000000000..e6aa1ad47b40 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricAPIImpl.java @@ -0,0 +1,88 @@ +package com.dotcms.telemetry.collectors.api; + +import com.dotcms.business.CloseDBIfOpened; +import com.dotcms.business.WrapInTransaction; +import com.dotmarketing.exception.DotRuntimeException; +import com.dotmarketing.util.Logger; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import java.time.Instant; +import java.util.Collection; +import java.util.Map; + +@ApplicationScoped +public class ApiMetricAPIImpl implements ApiMetricAPI { + + final RequestHashCalculator requestHashCalculator = new RequestHashCalculator(); + final ApiMetricFactory apiMetricFactory; + + @Inject + public ApiMetricAPIImpl(final ApiMetricFactory apiMetricFactory) { + this.apiMetricFactory = apiMetricFactory; + } + + @CloseDBIfOpened + @Override + public Collection> getMetricTemporaryTableData() { + return this.apiMetricFactory.getMetricTemporaryTableData(); + } + + @Override + public void save(final ApiMetricType apiMetricType, + final ApiMetricWebInterceptor.RereadInputStreamRequest request) { + final String requestHash = requestHashCalculator.calculate(apiMetricType, request); + final ApiMetricRequest metricAPIHit = new ApiMetricRequest.Builder() + .setMetric(apiMetricType.getMetric()) + .setTime(Instant.now()) + .setHash(requestHash) + .build(); + ApiMetricFactorySubmitter.INSTANCE.saveAsync(metricAPIHit); + } + + @WrapInTransaction + @Override + public void startCollecting() { + try { + this.apiMetricFactory.saveStartEvent(); + } catch (final Exception e) { + Logger.debug(this, "Error saving start event", e); + throw new DotRuntimeException(e); + } + } + + @WrapInTransaction + @Override + public void flushTemporaryTable() { + try { + this.apiMetricFactory.flushTemporaryTable(); + startCollecting(); + } catch (final Exception e) { + Logger.debug(this, "Error flushing the temporary table", e); + throw new DotRuntimeException(e); + } + } + + @WrapInTransaction + @Override + public void createTemporaryTable() { + try { + this.apiMetricFactory.createTemporaryTable(); + } catch (final Exception e) { + Logger.debug(this, "Error creating the temporary table", e); + throw new DotRuntimeException(e); + } + } + + @WrapInTransaction + @Override + public void dropTemporaryTable() { + try { + this.apiMetricFactory.dropTemporaryTable(); + } catch (final Exception e) { + Logger.debug(this, "Error dropping the temporary table", e); + throw new DotRuntimeException(e); + } + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactory.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactory.java index 781c91de0972..585758a9089f 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactory.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactory.java @@ -1,16 +1,7 @@ package com.dotcms.telemetry.collectors.api; -import com.dotcms.business.CloseDBIfOpened; -import com.dotmarketing.common.db.DotConnect; import com.dotmarketing.exception.DotDataException; -import com.dotmarketing.exception.DotRuntimeException; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.vavr.control.Try; -import org.postgresql.util.PGobject; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; import java.util.Collection; import java.util.Map; @@ -24,11 +15,9 @@ * The metrics_temp is a special table designed to store the request to the endpoints we wish to * track. Later, the data in this table is summarized and stored as part of the MetricSnapshot. */ -public enum ApiMetricFactory { +public interface ApiMetricFactory { - INSTANCE; - - private static final String GET_DATA_FROM_TEMPORARY_METRIC_TABLE = + String GET_DATA_FROM_TEMPORARY_METRIC_TABLE = "SELECT " + "metric_type->>'feature' as feature, " + "metric_type->>'category' as category, " + @@ -40,119 +29,41 @@ public enum ApiMetricFactory { "metric_type->>'category', " + "metric_type->>'name' " + "HAVING metric_type->>'name' IS NOT null"; - private static final String OVERALL_QUERY = "SELECT (EXTRACT(epoch FROM now()) - EXTRACT" + + String OVERALL_QUERY = "SELECT (EXTRACT(epoch FROM now()) - EXTRACT" + "(epoch FROM MIN(timestamp)))/3600 " + "as overall FROM metrics_temp"; - final ObjectMapper jsonMapper = new ObjectMapper(); - - ApiMetricFactory() { - - } - /** * Save request on the metrics_temp * * @param apiMetricRequest request */ - public void save(final ApiMetricRequest apiMetricRequest) { - try { - final String jsonStr = jsonMapper.writeValueAsString(apiMetricRequest.getMetric()); - - final PGobject jsonObject = new PGobject(); - jsonObject.setType("json"); - Try.run(() -> jsonObject.setValue(jsonStr)).getOrElseThrow( - () -> new IllegalArgumentException("Invalid JSON")); - - new DotConnect() - .setSQL("INSERT INTO metrics_temp (timestamp, metric_type, hash) VALUES (?, " + - "?, ?)") - .addParam(OffsetDateTime.now(ZoneOffset.UTC)) - .addParam(jsonObject) - .addParam(apiMetricRequest.getHash()) - .loadResults(); - - } catch (JsonProcessingException | DotDataException e) { - throw new DotRuntimeException(e); - } - } - + void save(final ApiMetricRequest apiMetricRequest); /** * Save a register with just the current time as timestamp, it is used to mark when we start * collecting the data. */ - public void saveStartEvent() { - try { - new DotConnect() - .setSQL("INSERT INTO metrics_temp (timestamp) VALUES (?)") - .addParam(OffsetDateTime.now(ZoneOffset.UTC)) - .loadResults(); - - } catch (DotDataException e) { - throw new DotRuntimeException(e); - } - } + void saveStartEvent(); /** * Drop all the registers on the table */ - public void flushTemporaryTable() { - try { - new DotConnect() - .setSQL("DELETE from metrics_temp") - .loadResults(); - - } catch (DotDataException e) { - throw new DotRuntimeException(e); - } - } + void flushTemporaryTable(); /** * Create the metrics_temp table * * @throws DotDataException if something wrong happened */ - public void createTemporaryTable() throws DotDataException { - new DotConnect().setSQL("CREATE TABLE metrics_temp (\n" + - " timestamp TIMESTAMPTZ,\n" + - " metric_type JSON,\n" + - " hash VARCHAR(255)\n" + - ")") - .loadResults(); - - saveStartEvent(); - } + void createTemporaryTable() throws DotDataException; /** * Drop the metrics_temp table * * @throws DotDataException if something wrong happened */ - public void dropTemporaryTable() throws DotDataException { - new DotConnect().setSQL("DROP TABLE IF EXISTS metrics_temp").loadResults(); - } - - /** - * return the amount of hours between we start collecting the data until we are generating the - * summary - * - * @return the amount of hours - * - * @throws DotDataException An error occurred when accessing the database. - */ - @CloseDBIfOpened - @SuppressWarnings("unchecked") - private double getOverall() throws DotDataException { - final DotConnect dotConnect = new DotConnect(); - - return Double.parseDouble(((Map) dotConnect.setSQL(OVERALL_QUERY) - .loadResults() - .get(0)) - .get("overall") - .toString() - ); - } + void dropTemporaryTable() throws DotDataException; /** * Return all the summary from the temporal table @@ -162,19 +73,6 @@ private double getOverall() throws DotDataException { * @see ApiMetricFactory * @see ApiMetricAPI */ - @CloseDBIfOpened - @SuppressWarnings("unchecked") - public Collection> getMetricTemporaryTableData() { - try { - final DotConnect dotConnect = new DotConnect(); - - final double overall = getOverall(); - final String sql = String.format(GET_DATA_FROM_TEMPORARY_METRIC_TABLE, overall); - - return dotConnect.setSQL(sql).loadResults(); - } catch (Exception e) { - throw new DotRuntimeException(e); - } - } + Collection> getMetricTemporaryTableData(); } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactoryImpl.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactoryImpl.java new file mode 100644 index 000000000000..6389121adf38 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactoryImpl.java @@ -0,0 +1,149 @@ +package com.dotcms.telemetry.collectors.api; + +import com.dotmarketing.common.db.DotConnect; +import com.dotmarketing.exception.DotDataException; +import com.dotmarketing.exception.DotRuntimeException; +import com.dotmarketing.util.Logger; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.vavr.control.Try; +import org.postgresql.util.PGobject; + +import javax.enterprise.context.ApplicationScoped; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Collection; +import java.util.Map; + +@ApplicationScoped +public class ApiMetricFactoryImpl implements ApiMetricFactory { + + final ObjectMapper jsonMapper = new ObjectMapper(); + + /** + * Save request on the metrics_temp + * + * @param apiMetricRequest request + */ + @Override + public void save(final ApiMetricRequest apiMetricRequest) { + try { + final String jsonStr = jsonMapper.writeValueAsString(apiMetricRequest.getMetric()); + final PGobject jsonObject = new PGobject(); + jsonObject.setType("json"); + Try.run(() -> jsonObject.setValue(jsonStr)).getOrElseThrow( + () -> new IllegalArgumentException("Invalid JSON")); + + new DotConnect() + .setSQL("INSERT INTO metrics_temp (timestamp, metric_type, hash) VALUES (?, ?, ?)") + .addParam(OffsetDateTime.now(ZoneOffset.UTC)) + .addParam(jsonObject) + .addParam(apiMetricRequest.getHash()) + .loadResults(); + } catch (final JsonProcessingException | DotDataException e) { + Logger.debug(this, String.format("Error saving metric: %s", apiMetricRequest), e); + throw new DotRuntimeException(e); + } + } + + + /** + * Save a register with just the current time as timestamp, it is used to mark when we start + * collecting the data. + */ + @Override + public void saveStartEvent() { + try { + new DotConnect() + .setSQL("INSERT INTO metrics_temp (timestamp) VALUES (?)") + .addParam(OffsetDateTime.now(ZoneOffset.UTC)) + .loadResults(); + } catch (final DotDataException e) { + Logger.debug(this, "Error saving start event", e); + throw new DotRuntimeException(e); + } + } + + /** + * Drop all the registers on the table + */ + @Override + public void flushTemporaryTable() { + try { + new DotConnect() + .setSQL("DELETE from metrics_temp") + .loadResults(); + } catch (final DotDataException e) { + Logger.debug(this, "Error flushing the temporary table", e); + throw new DotRuntimeException(e); + } + } + + /** + * Create the metrics_temp table + * + * @throws DotDataException if something wrong happened + */ + @Override + public void createTemporaryTable() throws DotDataException { + new DotConnect().setSQL("CREATE TABLE metrics_temp (\n" + + " timestamp TIMESTAMPTZ,\n" + + " metric_type JSON,\n" + + " hash VARCHAR(255)\n" + + ")") + .loadResults(); + saveStartEvent(); + } + + /** + * Drop the metrics_temp table + * + * @throws DotDataException if something wrong happened + */ + @Override + public void dropTemporaryTable() throws DotDataException { + new DotConnect().setSQL("DROP TABLE IF EXISTS metrics_temp").loadResults(); + } + + /** + * return the amount of hours between we start collecting the data until we are generating the + * summary + * + * @return the amount of hours + * + * @throws DotDataException An error occurred when accessing the database. + */ + @SuppressWarnings("unchecked") + private double getOverall() throws DotDataException { + final DotConnect dotConnect = new DotConnect(); + return Double.parseDouble(((Map) dotConnect.setSQL(OVERALL_QUERY) + .loadResults() + .get(0)) + .get("overall") + .toString() + ); + } + + /** + * Return all the summary from the temporal table + * + * @return a collection of maps with the summary data. + * + * @see ApiMetricFactory + * @see ApiMetricAPI + */ + @SuppressWarnings("unchecked") + @Override + public Collection> getMetricTemporaryTableData() { + try { + final DotConnect dotConnect = new DotConnect(); + final double overall = getOverall(); + final String sql = String.format(GET_DATA_FROM_TEMPORARY_METRIC_TABLE, overall); + return dotConnect.setSQL(sql).loadResults(); + } catch (final Exception e) { + Logger.debug(this, "Error getting the temporary table data", e); + throw new DotRuntimeException(e); + } + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactorySubmitter.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactorySubmitter.java index c0f9ee01f553..98ec487d6399 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactorySubmitter.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricFactorySubmitter.java @@ -1,8 +1,9 @@ package com.dotcms.telemetry.collectors.api; +import com.dotcms.business.WrapInTransaction; +import com.dotcms.cdi.CDIUtils; import com.dotcms.concurrent.DotConcurrentFactory; import com.dotcms.concurrent.DotSubmitter; -import com.dotmarketing.db.LocalTransaction; import com.dotmarketing.exception.DotRuntimeException; import com.dotmarketing.util.Config; @@ -50,10 +51,11 @@ public void saveAsync(final ApiMetricRequest metricAPIRequest) { submitter.submit(() -> save(metricAPIRequest)); } + @WrapInTransaction private void save(final ApiMetricRequest metricAPIRequest) { try { - LocalTransaction.wrap(() -> ApiMetricFactory.INSTANCE.save(metricAPIRequest)); - } catch (Exception e) { + CDIUtils.getBeanThrows(ApiMetricFactory.class).save(metricAPIRequest); + } catch (final Exception e) { throw new DotRuntimeException(e); } } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricWebInterceptor.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricWebInterceptor.java index fca8932ad538..efaf13ea1588 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricWebInterceptor.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/api/ApiMetricWebInterceptor.java @@ -1,5 +1,6 @@ package com.dotcms.telemetry.collectors.api; +import com.dotcms.cdi.CDIUtils; import com.dotcms.filters.interceptor.Result; import com.dotcms.filters.interceptor.WebInterceptor; import com.liferay.util.servlet.ServletInputStreamWrapper; @@ -16,7 +17,7 @@ */ public class ApiMetricWebInterceptor implements WebInterceptor { - public static final ApiMetricAPI apiStatAPI = new ApiMetricAPI(); + public static final ApiMetricAPI apiStatAPI = CDIUtils.getBeanThrows(ApiMetricAPI.class); public ApiMetricWebInterceptor() { ApiMetricFactorySubmitter.INSTANCE.start(); @@ -27,7 +28,6 @@ public String[] getFilters() { return ApiMetricTypes.INSTANCE.get().stream() .map(ApiMetricType::getAPIUrl) .toArray(String[]::new); - } @Override @@ -39,8 +39,7 @@ public Result intercept(HttpServletRequest req, HttpServletResponse res) throws @Override public boolean afterIntercept(final HttpServletRequest req, final HttpServletResponse res) { - if (res.getStatus() == 200) { - + if (res.getStatus() == HttpServletResponse.SC_OK) { ApiMetricTypes.INSTANCE.interceptBy(req) .stream() .filter(apiMetricType -> apiMetricType.shouldCount(req, res)) @@ -48,7 +47,6 @@ public boolean afterIntercept(final HttpServletRequest req, final HttpServletRes apiStatAPI.save(apiMetricType, (RereadInputStreamRequest) req) ); } - return true; } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInLivePageDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInLivePageDatabaseMetricType.java index 3ec66940f38b..7b078a26f2e3 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInLivePageDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInLivePageDatabaseMetricType.java @@ -24,16 +24,19 @@ */ public abstract class TotalContainersInLivePageDatabaseMetricType extends TotalContainersInTemplateDatabaseMetricType { - private static final String LIVE_USED_TEMPLATES_INODES_QUERY = "SELECT " + "distinct " + - "contentlet_as_json -> 'fields' -> 'template' ->> 'value' as value " + "FROM " + - "contentlet INNER JOIN contentlet_version_info ON contentlet.inode = " + - "contentlet_version_info.live_inode " + "WHERE structure_inode IN (SELECT inode FROM " + - "structure where name = 'Page') AND " + "deleted = false"; + private static final String LIVE_USED_TEMPLATES_INODES_QUERY = "SELECT " + + "distinct contentlet_as_json -> 'fields' -> 'template' ->> 'value' as value " + + "FROM " + + "contentlet INNER JOIN contentlet_version_info ON contentlet.inode = contentlet_version_info.live_inode " + + "WHERE structure_inode IN (SELECT inode FROM structure where name = 'Page') AND " + "deleted = false"; + + protected MetricsAPI metricsAPI; private Collection getLiveUsedTemplatesInodes() { - return MetricsAPI.INSTANCE.getList(LIVE_USED_TEMPLATES_INODES_QUERY); + return metricsAPI.getList(LIVE_USED_TEMPLATES_INODES_QUERY); } + @Override Collection getTemplatesIds() { return getLiveUsedTemplatesInodes(); } @@ -44,4 +47,3 @@ final Template getTemplate(String id) throws DotDataException, DotSecurityExcept } } - diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInLiveTemplatesDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInLiveTemplatesDatabaseMetricType.java index 7f9e70dfcb44..acd35e4229fc 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInLiveTemplatesDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInLiveTemplatesDatabaseMetricType.java @@ -13,17 +13,17 @@ /** * This class count the amount of containers used in LIVE templates, in this case no matter the - * pages so this templates can be used or not on a page, this class extends from - * TotalContainersIntemplateDatabaseMetricType because it is a counter of the containers used in - * templates and it override the follow behavior: + * pages so this templates can be used or not on a page. This class extends from + * {@link TotalContainersInTemplateDatabaseMetricType} because it is a counter of the containers + * used in templates, and it overrides the follow behavior: *
    *
  • Searching Templates: Override the getTemplatesIds method to return only the Template * IDs for the LIVE templates. This is achieved using a SQL UNION query. The first part of the - * query retrieves standard templates from the template_version_info table, identifying those - * that have LIVE versions. The second part of the query retrieves file templates from the - * contenlet_version_info table, also focusing on those with live versions.
  • - *
  • Retrieve the Template Version: Override the getTemplate method to get the last LIVE - * version of the Template.
  • + * query retrieves standard templates from the {@code template_version_info} table, identifying + * those that have LIVE versions. The second part of the query retrieves file templates from the + * {@code contenlet_version_info} table, also focusing on those with live versions. + *
  • Retrieve the Template Version: Override the {@code getTemplate} method to get the last + * LIVE version of the Template.
  • *
*/ public abstract class TotalContainersInLiveTemplatesDatabaseMetricType extends TotalContainersInTemplateDatabaseMetricType { @@ -40,13 +40,12 @@ public abstract class TotalContainersInLiveTemplatesDatabaseMetricType extends T "WHERE id.parent_path LIKE '/application/templates/%' AND id.asset_name = 'body.vtl' " + "AND deleted = false AND live_inode is not null"; + protected MetricsAPI metricsAPI; + @Override Collection getTemplatesIds() { - - final List dataBaseTemplateInode = - MetricsAPI.INSTANCE.getList(LIVE_TEMPLATES_INODES_QUERY); - final List dataBaseFileTemplateInode = - MetricsAPI.INSTANCE.getList(LIVE_FILE_TEMPLATES_INODES_QUERY); + final List dataBaseTemplateInode = metricsAPI.getList(LIVE_TEMPLATES_INODES_QUERY); + final List dataBaseFileTemplateInode = metricsAPI.getList(LIVE_FILE_TEMPLATES_INODES_QUERY); return Stream.concat(dataBaseTemplateInode.stream(), dataBaseFileTemplateInode.stream()).collect(Collectors.toSet()); diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInWorkingPageDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInWorkingPageDatabaseMetricType.java index efd4f5c9f708..e48c6350e4e5 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInWorkingPageDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalContainersInWorkingPageDatabaseMetricType.java @@ -10,20 +10,22 @@ /** * Total of containers used in Working pages. - * - * This class count the amount of containers used in WORKING pages, it means all these pages that don’t have LIVE Version, - * this class extends from {@link TotalContainersInTemplateDatabaseMetricType} because it is a counter - * of the containers used in templates is this case we want to take account just the WORKING version of each one, - * so it override the follow behavior: - * - * - Searching Templates: Override the getTemplatesIds method to return only the Template IDs for - * the templates used on a WORKING page.This is achieved using a SQL UNION query. - * The first part of the query retrieves standard templates from the template_version_info table, - * identifying those that have only a working version. - * The second part of the query retrieves file templates from the contenlet_version_info table, - * also focusing on those with just a working version. - * - * - Retrieve the Template Version: Override the getTemplate method to get the last WORKING version of the Template. + *

+ * This class count the amount of containers used in WORKING pages, it means all these pages that + * don’t have LIVE Version. This class extends from + * {@link TotalContainersInTemplateDatabaseMetricType} because it is a counter of the containers + * used in templates is this case we want to take account just the WORKING version of each one, so + * it override the follow behavior: + *

    + *
  • Searching Templates: Override the {@code getTemplatesIds} method to return only the + * Template IDs for the templates used on a WORKING page.This is achieved using a SQL UNION + * query. The first part of the query retrieves standard templates from the + * {@code template_version_info} table, identifying those that have only a working version. The + * second part of the query retrieves file templates from the {@code contenlet_version_info} + * table, also focusing on those with just a working version.
  • + *
  • Retrieve the Template Version: Override the getTemplate method to get the last WORKING + * version of the Template.
  • + *
*/ public abstract class TotalContainersInWorkingPageDatabaseMetricType extends TotalContainersInTemplateDatabaseMetricType { @@ -70,8 +72,10 @@ public abstract class TotalContainersInWorkingPageDatabaseMetricType extends Tot "OR page.live_inode IS NULL " + ")"; + protected MetricsAPI metricsAPI; + private Collection getWorkingUsedTemplatesInodes() { - return MetricsAPI.INSTANCE.getList(WORKING_USED_TEMPLATES_INODES_QUERY + " UNION " + + return metricsAPI.getList(WORKING_USED_TEMPLATES_INODES_QUERY + " UNION " + WORKING_USED_FILE_TEMPLATES_INODES_QUERY); } @@ -85,5 +89,6 @@ Template getTemplate(String id) throws DotDataException, DotSecurityException { return APILocator.getTemplateAPI() .findWorkingTemplate(id, APILocator.systemUser(), false); } + } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLivePageDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLivePageDatabaseMetricType.java index 2b5058cc60bf..638afcb0a2d9 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLivePageDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLivePageDatabaseMetricType.java @@ -1,11 +1,19 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.inject.Inject; + /** * Total of FILE containers used in LIVE pages */ -public class TotalFileContainersInLivePageDatabaseMetricType extends TotalContainersInLivePageDatabaseMetricType { +public class TotalFileContainersInLivePageDatabaseMetricType extends TotalContainersInLivePageDatabaseMetricType { + + @Inject + public TotalFileContainersInLivePageDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -21,5 +29,5 @@ public String getDescription() { boolean filterContainer(final String containerId) { return FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } -} +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLiveTemplatesDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLiveTemplatesDatabaseMetricType.java index dd30ec4fe6c7..c36fc056dec5 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLiveTemplatesDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLiveTemplatesDatabaseMetricType.java @@ -1,11 +1,21 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + /** * Total of FILE containers used in LIVE templates */ -public class TotalFileContainersInLiveTemplatesDatabaseMetricType extends TotalContainersInLiveTemplatesDatabaseMetricType { +@ApplicationScoped +public class TotalFileContainersInLiveTemplatesDatabaseMetricType extends TotalContainersInLiveTemplatesDatabaseMetricType { + + @Inject + public TotalFileContainersInLiveTemplatesDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -16,9 +26,11 @@ public String getName() { public String getDescription() { return "Total of FILE containers used in LIVE templates"; } + @Override boolean filterContainer(final String containerId) { return FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } + } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInWorkingPageDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInWorkingPageDatabaseMetricType.java index 40d1cabd2095..b454fdbc6256 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInWorkingPageDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInWorkingPageDatabaseMetricType.java @@ -1,11 +1,19 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.inject.Inject; + /** * Total of FILE containers used in LIVE pages */ -public class TotalFileContainersInWorkingPageDatabaseMetricType extends TotalContainersInWorkingPageDatabaseMetricType { +public class TotalFileContainersInWorkingPageDatabaseMetricType extends TotalContainersInWorkingPageDatabaseMetricType { + + @Inject + public TotalFileContainersInWorkingPageDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -21,5 +29,5 @@ public String getDescription() { boolean filterContainer(final String containerId) { return FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } -} +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLivePageDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLivePageDatabaseMetricType.java index e0d09c899944..066b3621c615 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLivePageDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLivePageDatabaseMetricType.java @@ -1,11 +1,19 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.inject.Inject; + /** * Total of STANDARD containers used in LIVE pages */ -public class TotalStandardContainersInLivePageDatabaseMetricType extends TotalContainersInLivePageDatabaseMetricType { +public class TotalStandardContainersInLivePageDatabaseMetricType extends TotalContainersInLivePageDatabaseMetricType { + + @Inject + public TotalStandardContainersInLivePageDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -21,5 +29,5 @@ public String getDescription() { boolean filterContainer(final String containerId) { return !FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } -} +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLiveTemplatesDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLiveTemplatesDatabaseMetricType.java index a29555de8e2d..b1ff647c7687 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLiveTemplatesDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLiveTemplatesDatabaseMetricType.java @@ -1,11 +1,21 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + /** * Total of STANDARD containers used in LIVE templates */ -public class TotalStandardContainersInLiveTemplatesDatabaseMetricType extends TotalContainersInLiveTemplatesDatabaseMetricType { +@ApplicationScoped +public class TotalStandardContainersInLiveTemplatesDatabaseMetricType extends TotalContainersInLiveTemplatesDatabaseMetricType { + + @Inject + public TotalStandardContainersInLiveTemplatesDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -16,9 +26,11 @@ public String getName() { public String getDescription() { return "Total of STANDARD containers used in LIVE templates"; } + @Override boolean filterContainer(final String containerId) { return !FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } + } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInWorkingPageDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInWorkingPageDatabaseMetricType.java index 27a1aaf75e2c..2fe1bc9f4093 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInWorkingPageDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInWorkingPageDatabaseMetricType.java @@ -1,11 +1,19 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.inject.Inject; + /** * Total of STANDARD containers used in WORKING pages */ -public class TotalStandardContainersInWorkingPageDatabaseMetricType extends TotalContainersInWorkingPageDatabaseMetricType { +public class TotalStandardContainersInWorkingPageDatabaseMetricType extends TotalContainersInWorkingPageDatabaseMetricType { + + @Inject + public TotalStandardContainersInWorkingPageDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -16,9 +24,10 @@ public String getName() { public String getDescription() { return "Count of STANDARD containers used in WORKING pages"; } + @Override boolean filterContainer(final String containerId) { return !FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } -} +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/theme/TotalFilesInThemeMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/theme/TotalFilesInThemeMetricType.java index 381958e7fff5..0a7c00069550 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/theme/TotalFilesInThemeMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/theme/TotalFilesInThemeMetricType.java @@ -3,12 +3,12 @@ import com.dotcms.telemetry.MetricCategory; import com.dotcms.telemetry.MetricFeature; import com.dotcms.telemetry.collectors.DBMetricType; -import com.dotmarketing.util.Logger; /** * Collects the total of Number of LIVE/WORKING files in themes */ public class TotalFilesInThemeMetricType implements DBMetricType { + @Override public String getName() { return "TOTAL_FILES_IN_THEMES"; @@ -38,4 +38,5 @@ public String getSqlQuery() { "FROM contentlet_version_info cvi INNER JOIN identifier id ON cvi.identifier = id.id " + "WHERE id.parent_path LIKE '/application/themes/%' AND id.asset_name = 'template.vtl')"; } + } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/job/MetricsStatsJob.java b/dotCMS/src/main/java/com/dotcms/telemetry/job/MetricsStatsJob.java index ee726513c68c..33361ce7605a 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/job/MetricsStatsJob.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/job/MetricsStatsJob.java @@ -1,10 +1,9 @@ package com.dotcms.telemetry.job; -import com.dotcms.concurrent.lock.ClusterLockManager; import com.dotcms.exception.ExceptionUtil; import com.dotcms.telemetry.MetricsSnapshot; -import com.dotcms.telemetry.business.MetricsAPI; import com.dotcms.telemetry.collectors.MetricStatsCollector; +import com.dotmarketing.business.APILocator; import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; import io.vavr.Lazy; @@ -22,7 +21,7 @@ public class MetricsStatsJob implements StatefulJob { public static final String JOB_GROUP = "MetricsStatsJobGroup"; public static final String ENABLED_PROP = "TELEMETRY_SAVE_SCHEDULE_JOB_ENABLED"; public static final String CRON_EXPR_PROP = "TELEMETRY_SAVE_SCHEDULE"; - private static final String CRON_EXPRESSION_DEFAULT = "0 0 22 * * ?"; + public static final String CRON_EXPRESSION_DEFAULT = "0 0 22 * * ?"; public static final Lazy ENABLED = Lazy.of(() -> Config.getBooleanProperty(ENABLED_PROP, true)); @@ -34,15 +33,11 @@ public void execute(final JobExecutionContext jobExecutionContext) throws JobExe final MetricsSnapshot metricsSnapshot; try { metricsSnapshot = MetricStatsCollector.getStatsAndCleanUp(); - MetricsAPI.INSTANCE.persistMetricsSnapshot(metricsSnapshot); + APILocator.getMetricsAPI().persistMetricsSnapshot(metricsSnapshot); } catch (final Throwable e) { - Logger.debug(this, String.format("Error occurred during job execution: %s", + Logger.error(this, String.format("An error occurred during job execution: %s", ExceptionUtil.getErrorMessage(e)), e); - } } - - public void run(ClusterLockManager lockManager) { - } } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/osgi/Activator.java b/dotCMS/src/main/java/com/dotcms/telemetry/osgi/Activator.java deleted file mode 100644 index 397501ce346e..000000000000 --- a/dotCMS/src/main/java/com/dotcms/telemetry/osgi/Activator.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.dotcms.telemetry.osgi; - -import com.dotcms.concurrent.DotConcurrentFactory; -import com.dotcms.concurrent.lock.ClusterLockManager; -import com.dotcms.telemetry.rest.TelemetryResource; -import com.dotcms.telemetry.collectors.api.ApiMetricAPI; -import com.dotcms.telemetry.collectors.api.ApiMetricFactorySubmitter; -import com.dotcms.telemetry.collectors.api.ApiMetricWebInterceptor; -import com.dotcms.telemetry.job.MetricsStatsJob; -import com.dotcms.filters.interceptor.FilterWebInterceptorProvider; -import com.dotcms.filters.interceptor.WebInterceptor; -import com.dotcms.filters.interceptor.WebInterceptorDelegate; -import com.dotcms.rest.config.RestServiceUtil; -import com.dotmarketing.filters.InterceptorFilter; -import com.dotmarketing.osgi.GenericBundleActivator; -import com.dotmarketing.util.Config; -import com.dotmarketing.util.Logger; -import io.vavr.Lazy; -import org.apache.logging.log4j.core.util.CronExpression; -import org.osgi.framework.BundleContext; - -import java.text.ParseException; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -public class Activator extends GenericBundleActivator { - public static String version; - - private final WebInterceptorDelegate delegate = FilterWebInterceptorProvider - .getInstance(Config.CONTEXT) - .getDelegate(InterceptorFilter.class); - - private final WebInterceptor apiCallWebInterceptor = new ApiMetricWebInterceptor(); - private final MetricsStatsJob metricsStatsJob = new MetricsStatsJob(); - - private static final String METRICS_JOB_LOCK_KEY = "metrics_job_lock"; - private ScheduledFuture scheduledFuture; - private final Lazy enableTelemetry = Lazy.of(() -> - Config.getBooleanProperty("FEATURE_FLAG_TELEMETRY", false)); - - private final Lazy enableAPIMetrics = Lazy.of(() -> - Config.getBooleanProperty("TELEMETRY_API_METRICS_ENABLED", false)); - - public static final ApiMetricAPI apiStatAPI = new ApiMetricAPI(); - - @Override - public void start(final BundleContext context) { - - PluginVersionUtil.init(context); - - if(Boolean.TRUE.equals(enableTelemetry.get())) { - Logger.debug(Activator.class.getName(), "Starting the Telemetry plugin"); - - RestServiceUtil.addResource(TelemetryResource.class); - - try { - apiStatAPI.dropTemporaryTable(); - apiStatAPI.createTemporaryTable(); - - if(Boolean.TRUE.equals(enableAPIMetrics.get())) { - Logger.debug(Activator.class.getName(), "API metrics enabled"); - delegate.addFirst(apiCallWebInterceptor); - ApiMetricFactorySubmitter.INSTANCE.start(); - } - Logger.debug(Activator.class.getName(), "Scheduling Telemetry Job"); - scheduleMetricsJob(); - Logger.debug(Activator.class.getName(), "The Telemetry plugin was started"); - } catch (Throwable t) { - Logger.debug(this, "Error starting the Telemetry plugin.", t); - } - } - } - - private void scheduleMetricsJob() throws ParseException { - final ClusterLockManager lockManager = DotConcurrentFactory.getInstance() - .getClusterLockManager(METRICS_JOB_LOCK_KEY); - - CronExpression cron = new CronExpression(Config - .getStringProperty("TELEMETRY_SAVE_SCHEDULE", "0 0 22 * * ?")) ; - - final Instant now = Instant.now(); - final Instant previousRun = cron.getPrevFireTime(Date.from(now)).toInstant(); - final Instant nextRun = cron.getNextValidTimeAfter(Date.from(previousRun)).toInstant(); - final Duration delay = Duration.between(now, nextRun); - final Duration runEvery = Duration.between(previousRun, nextRun); - - scheduledFuture = DotConcurrentFactory.getScheduledThreadPoolExecutor().scheduleAtFixedRate( - () -> metricsStatsJob.run(lockManager) - , delay.get(ChronoUnit.SECONDS), - runEvery.get(ChronoUnit.SECONDS), - TimeUnit.SECONDS); - } - - @Override - public void stop(BundleContext context) throws Exception { - if(Boolean.TRUE.equals(enableTelemetry.get())) { - RestServiceUtil.removeResource(TelemetryResource.class); - scheduledFuture.cancel(false); - apiStatAPI.dropTemporaryTable(); - - if(Boolean.TRUE.equals(enableAPIMetrics.get())) { - delegate.remove(apiCallWebInterceptor.getName(), true); - ApiMetricFactorySubmitter.INSTANCE.shutdownNow(); - } - } - } -} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/osgi/PluginVersionUtil.java b/dotCMS/src/main/java/com/dotcms/telemetry/osgi/PluginVersionUtil.java deleted file mode 100644 index 15173b777101..000000000000 --- a/dotCMS/src/main/java/com/dotcms/telemetry/osgi/PluginVersionUtil.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.dotcms.telemetry.osgi; - -import org.osgi.framework.BundleContext; - -public class PluginVersionUtil { - - private static String version; - - private PluginVersionUtil(){} - - public static void init(BundleContext context) { - version = context.getBundle().getHeaders().get("Bundle-Version"); - } - - public static String getVersion(){ - return version; - } -} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/rest/TelemetryResource.java b/dotCMS/src/main/java/com/dotcms/telemetry/rest/TelemetryResource.java index cb50f24c37fa..1e4e677c2809 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/rest/TelemetryResource.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/rest/TelemetryResource.java @@ -1,8 +1,8 @@ package com.dotcms.telemetry.rest; -import com.dotcms.telemetry.collectors.MetricStatsCollector; import com.dotcms.rest.WebResource; import com.dotcms.rest.annotation.NoCache; +import com.dotcms.telemetry.collectors.MetricStatsCollector; import com.dotmarketing.business.Role; import com.dotmarketing.util.Logger; import com.fasterxml.jackson.jaxrs.json.annotation.JSONP; @@ -37,6 +37,7 @@ public class TelemetryResource { ResponseEntityMetricsSnapshotView.class)))}) public final Response getData(@Context final HttpServletRequest request, @Context final HttpServletResponse response) { + Logger.debug(this, () -> "Generating dotCMS Telemetry data"); new WebResource.InitBuilder(new WebResource()) .requestAndResponse(request, response) .requiredBackendUser(true) diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/util/MetricCaches.java b/dotCMS/src/main/java/com/dotcms/telemetry/util/MetricCaches.java index 4044048424e9..c37142fc9a72 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/util/MetricCaches.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/util/MetricCaches.java @@ -1,5 +1,6 @@ package com.dotcms.telemetry.util; +import com.dotcms.cdi.CDIUtils; import com.dotcms.telemetry.collectors.api.ApiMetricAPI; import java.util.Arrays; @@ -10,7 +11,7 @@ public enum MetricCaches { SITE_SEARCH_INDICES(new MetricCache<>(IndicesSiteSearchUtil.INSTANCE::getESIndices)), - TEMPORARY_TABLA_DATA(new MetricCache<>(ApiMetricAPI::getMetricTemporaryTableData)); + TEMPORARY_TABLA_DATA(new MetricCache<>(() -> CDIUtils.getBeanThrows(ApiMetricAPI.class).getMetricTemporaryTableData())); private final MetricCache metricCache; diff --git a/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java b/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java index 2acb7cb49e27..758031be4879 100644 --- a/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java +++ b/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java @@ -94,6 +94,7 @@ import com.dotcms.storage.FileStorageAPIImpl; import com.dotcms.system.event.local.business.LocalSystemEventsAPI; import com.dotcms.system.event.local.business.LocalSystemEventsAPIFactory; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotcms.timemachine.business.TimeMachineAPI; import com.dotcms.timemachine.business.TimeMachineAPIImpl; import com.dotcms.util.FileWatcherAPI; @@ -1163,6 +1164,11 @@ public static AnalyticsAPI getAnalyticsAPI() { return (AnalyticsAPI) getInstance(APIIndex.ANALYTICS_API); } + /** + * Returns a single instance of the {@link ContentTypeDestroyAPI} class. + * + * @return The {@link ContentTypeDestroyAPI} instance. + */ public static ContentTypeDestroyAPI getContentTypeDestroyAPI() { return (ContentTypeDestroyAPI) getInstance(APIIndex.CONTENT_TYPE_DESTROY_API); } @@ -1193,6 +1199,15 @@ public static ContentAnalyticsAPI getContentAnalyticsAPI() { return (ContentAnalyticsAPI) getInstance(APIIndex.CONTENT_ANALYTICS_API); } + /** + * Returns a single instance of the {@link MetricsAPI} class via CDI. + * + * @return The {@link MetricsAPI} instance. + */ + public static MetricsAPI getMetricsAPI() { + return CDIUtils.getBeanThrows(MetricsAPI.class); + } + /** * Generates a unique instance of the specified dotCMS API. * diff --git a/dotCMS/src/main/java/com/dotmarketing/filters/InterceptorFilter.java b/dotCMS/src/main/java/com/dotmarketing/filters/InterceptorFilter.java index b1d69259343a..9b3390ec46c0 100644 --- a/dotCMS/src/main/java/com/dotmarketing/filters/InterceptorFilter.java +++ b/dotCMS/src/main/java/com/dotmarketing/filters/InterceptorFilter.java @@ -10,20 +10,31 @@ import com.dotcms.jitsu.EventLogWebInterceptor; import com.dotcms.prerender.PreRenderSEOWebInterceptor; import com.dotcms.security.multipart.MultiPartRequestSecurityWebInterceptor; +import com.dotcms.telemetry.collectors.api.ApiMetricWebInterceptor; import com.dotcms.variant.business.web.CurrentVariantWebInterceptor; import com.dotmarketing.business.APILocator; import com.dotmarketing.util.Config; +import io.vavr.Lazy; import javax.servlet.FilterConfig; import javax.servlet.ServletException; /** - * This empty filter is useful to attach {@link com.dotcms.filters.interceptor.WebInterceptor}, it is the first one on the - * filter pipeline and maps everything. + * This empty filter is useful to attach {@link com.dotcms.filters.interceptor.WebInterceptor} + * objects to it. This is the first one in the filter pipeline and maps everything. This way, it's + * not necessary to modify the web.xml file to add any new interceptors, and they can even be added + * programmatically via OSGi plug-ins. + * * @author jsanca */ public class InterceptorFilter extends AbstractWebInterceptorSupportFilter { + private static final Lazy ENABLE_TELEMETRY_FROM_CORE = Lazy.of(() -> + Config.getBooleanProperty("FEATURE_FLAG_TELEMETRY_CORE_ENABLED", false)); + + private static final Lazy TELEMETRY_API_METRICS_ENABLED = Lazy.of(() -> + Config.getBooleanProperty("TELEMETRY_API_METRICS_ENABLED", false)); + @Override public void init(final FilterConfig config) throws ServletException { @@ -34,8 +45,12 @@ public void init(final FilterConfig config) throws ServletException { super.init(config); } // init. + /** + * Adds the interceptors to the delegate. You can add more to the list, as required. + * + * @param config The current instance of the {@link FilterConfig} object. + */ private void addInterceptors(final FilterConfig config) { - final WebInterceptorDelegate delegate = this.getDelegate(config.getServletContext()); @@ -48,7 +63,9 @@ private void addInterceptors(final FilterConfig config) { delegate.add(new EventLogWebInterceptor()); delegate.add(new CurrentVariantWebInterceptor()); delegate.add(analyticsTrackWebInterceptor); - + if (Boolean.TRUE.equals(ENABLE_TELEMETRY_FROM_CORE.get()) && Boolean.TRUE.equals(TELEMETRY_API_METRICS_ENABLED.get())) { + delegate.add(new ApiMetricWebInterceptor()); + } APILocator.getLocalSystemEventsAPI().subscribe(SystemTableUpdatedKeyEvent.class, analyticsTrackWebInterceptor); } // addInterceptors. diff --git a/dotCMS/src/main/java/com/dotmarketing/init/DotInitScheduler.java b/dotCMS/src/main/java/com/dotmarketing/init/DotInitScheduler.java index 5b6ca319aecd..c35632c9de27 100644 --- a/dotCMS/src/main/java/com/dotmarketing/init/DotInitScheduler.java +++ b/dotCMS/src/main/java/com/dotmarketing/init/DotInitScheduler.java @@ -5,10 +5,22 @@ import com.dotcms.job.system.event.DeleteOldSystemEventsJob; import com.dotcms.job.system.event.SystemEventsJob; import com.dotcms.publisher.business.PublisherQueueJob; +import com.dotcms.telemetry.job.MetricsStatsJob; import com.dotcms.workflow.EscalationThread; import com.dotmarketing.exception.DotDataException; import com.dotmarketing.quartz.QuartzUtils; -import com.dotmarketing.quartz.job.*; +import com.dotmarketing.quartz.job.AccessTokenRenewJob; +import com.dotmarketing.quartz.job.BinaryCleanupJob; +import com.dotmarketing.quartz.job.CleanUnDeletedUsersJob; +import com.dotmarketing.quartz.job.DeleteInactiveLiveWorkingIndicesJob; +import com.dotmarketing.quartz.job.DeleteSiteSearchIndicesJob; +import com.dotmarketing.quartz.job.DropOldContentVersionsJob; +import com.dotmarketing.quartz.job.EsReadOnlyMonitorJob; +import com.dotmarketing.quartz.job.FreeServerFromClusterJob; +import com.dotmarketing.quartz.job.PruneTimeMachineBackupJob; +import com.dotmarketing.quartz.job.ServerHeartbeatJob; +import com.dotmarketing.quartz.job.StartEndScheduledExperimentsJob; +import com.dotmarketing.quartz.job.UsersToDeleteThread; import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; import com.dotmarketing.util.UtilMethods; @@ -36,19 +48,15 @@ public class DotInitScheduler { public static final String DOTCMS_JOB_GROUP_NAME = "dotcms_jobs"; - public static final String CRON_EXPRESSION_EVERY_5_MINUTES = "0 */5 * ? * *"; - private static void deleteOldJobs() throws DotDataException { - // remove unused old jobs QuartzUtils.deleteJobDB("WebDavCleanupJob", DOTCMS_JOB_GROUP_NAME); QuartzUtils.deleteJobDB("TrashCleanupJob", DOTCMS_JOB_GROUP_NAME); QuartzUtils.deleteJobDB("DeleteOldClickstreams", DOTCMS_JOB_GROUP_NAME); QuartzUtils.deleteJobDB("linkchecker", DOTCMS_JOB_GROUP_NAME); QuartzUtils.deleteJobDB("ContentReindexerJob", DOTCMS_JOB_GROUP_NAME); - } /** @@ -66,12 +74,9 @@ public static void start() throws Exception { Calendar calendar; boolean isNew; - - // remove unused old jobs deleteOldJobs(); - if(Config.getBooleanProperty("ENABLE_USERS_TO_DELETE_THREAD", false)) { try { isNew = false; @@ -298,34 +303,26 @@ public static void start() throws Exception { } addDropOldContentVersionsJob(); - if ( !Config.getBooleanProperty(DOTCMS_DISABLE_WEBSOCKET_PROTOCOL, false) ) { // Enabling the System Events Job addSystemEventsJob(); } - // start the server heartbeat job addServerHeartbeatJob(); - // Enabling the Delete Old System Events Job addDeleteOldSystemEvents(sched); - if ( !Config.getBooleanProperty(DOTCMS_DISABLE_ELASTIC_READONLY_MONITOR, false) ) { // Enabling the Read only monitor addElasticReadyOnlyMonitor(sched); } - //Enable the delete old ES Indices Job addDeleteOldESIndicesJob(sched); - //Enable the delete old SS Indices Job addDeleteOldSiteSearchIndicesJob(sched); - AccessTokenRenewJob.AccessTokensRenewJobScheduler.schedule(); - addStartEndScheduledExperimentsJob(sched); - addPruneOldTimeMachineBackups(sched); + addTelemetryMetricsStatsJob(sched); //Starting the sequential and standard Schedulers QuartzUtils.startSchedulers(); @@ -537,6 +534,39 @@ private static void addPruneOldTimeMachineBackups (final Scheduler scheduler) { } } + /** + * Adds the {@link MetricsStatsJob} Quartz Job to the scheduler during the startup + * process. + */ + private static void addTelemetryMetricsStatsJob(final Scheduler scheduler) { + if (Config.getBooleanProperty("FEATURE_FLAG_TELEMETRY_CORE_ENABLED", false)) { + final String triggerName = "trigger36"; + final String triggerGroup = "group36"; + final JobBuilder telemetryMetricsStatsJob = new JobBuilder() + .setJobClass(MetricsStatsJob.class) + .setJobName(MetricsStatsJob.JOB_NAME) + .setJobGroup(DOTCMS_JOB_GROUP_NAME) + .setTriggerName(triggerName) + .setTriggerGroup(triggerGroup) + .setCronExpressionProp(MetricsStatsJob.CRON_EXPR_PROP) + .setCronExpressionPropDefault(MetricsStatsJob.CRON_EXPRESSION.get()) + .setCronMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING); + if (Boolean.FALSE.equals(MetricsStatsJob.ENABLED.get())) { + telemetryMetricsStatsJob.enabled(false); + } + scheduleJob(telemetryMetricsStatsJob); + } else { + try { + // If the job is not enabled, delete it from the scheduler if it exists + if (null != (scheduler.getJobDetail(MetricsStatsJob.JOB_NAME, DOTCMS_JOB_GROUP_NAME))) { + scheduler.deleteJob(MetricsStatsJob.JOB_NAME, DOTCMS_JOB_GROUP_NAME); + } + } catch (final SchedulerException e) { + Logger.warn(DotInitScheduler.class, e.toString()); + } + } + } + private static void addServerHeartbeatJob () { final int initialDelay = Config.getIntProperty("SERVER_HEARTBEAT_INITIAL_DELAY_SECONDS", 60);