From ad18396316480c7c5269097ac2df2695aab9555d Mon Sep 17 00:00:00 2001 From: Boubaker Khanfir Date: Fri, 7 Jun 2024 18:17:46 +0100 Subject: [PATCH] feat: Allow to purge automatically Analytics Indices - MEED-6999 - Meeds-io/meeds#2097 (#188) --- analytics-api/pom.xml | 11 +- .../api/service/AnalyticsService.java | 25 +- .../StatisticDataProcessorService.java | 93 ++-- .../service/StatisticDataQueueService.java | 14 +- .../StatisticWatcherImportService.java | 69 +++ .../websocket/AnalyticsWebSocketService.java | 79 +-- .../listener/AnalyticsWebSocketListener.java | 99 ++++ .../model}/AnalyticsWebSocketMessage.java | 11 +- .../meeds}/analytics/model/StatisticData.java | 26 +- .../model/StatisticDataQueueEntry.java | 9 +- .../model/StatisticFieldMapping.java | 9 +- .../analytics/model/StatisticFieldValue.java | 9 +- .../analytics/model}/StatisticWatcher.java | 9 +- .../model/StatisticWatchersDescriptor.java} | 30 +- .../meeds/analytics/model}/UserSettings.java | 11 +- .../model/chart/ChartAggregationLabel.java | 9 +- .../model/chart/ChartAggregationResult.java | 9 +- .../model/chart/ChartAggregationValue.java | 11 +- .../analytics/model/chart/ChartData.java | 21 +- .../analytics/model/chart/ChartDataList.java | 22 +- .../model/chart/PercentageChartResult.java | 9 +- .../model/chart/PercentageChartValue.java | 9 +- .../model/chart/TableColumnItemValue.java | 9 +- .../model/chart/TableColumnResult.java | 9 +- .../model/filter/AbstractAnalyticsFilter.java | 9 +- .../model/filter/AnalyticsFilter.java | 50 +- .../filter/AnalyticsPercentageFilter.java | 15 +- .../filter/AnalyticsPercentageItemFilter.java | 34 +- .../model/filter/AnalyticsPeriod.java | 9 +- .../model/filter/AnalyticsPeriodType.java | 9 +- .../AnalyticsTableColumnAggregation.java | 24 +- .../filter/AnalyticsTableColumnFilter.java | 9 +- .../model/filter/AnalyticsTableFilter.java | 37 +- .../aggregation/AnalyticsAggregation.java | 13 +- .../aggregation/AnalyticsAggregationType.java | 9 +- .../aggregation/AnalyticsPercentageLimit.java | 11 +- .../filter/search/AnalyticsFieldFilter.java | 11 +- .../search/AnalyticsFieldFilterType.java | 9 +- .../filter/search/AnalyticsSortField.java | 9 +- .../plugin}/StatisticDataProcessorPlugin.java | 12 +- .../analytics/utils/AnalyticsUtils.java | 16 +- .../ManagedStatisticDataQueueService.java | 70 --- analytics-listeners/pom.xml | 24 +- .../job/SpacesStatisticsCountJob.java | 63 +++ .../job/UsersStatisticsCountJob.java | 100 ++++ .../portal/LoginAnalyticsListener.java | 38 +- .../portal/LoginFailedAnalyticsListener.java | 39 +- .../listener/portal/PageAccessListener.java | 42 +- .../portal/UserAnalyticsEventListener.java | 52 +- .../ActivityAttachmentAnalyticsListener.java | 59 +- .../social/AnalyticsActivityListener.java | 48 +- .../social/AnalyticsActivityTagsListener.java | 38 +- .../social/AnalyticsProfileListener.java | 39 +- .../social/AnalyticsRelationshipListener.java | 34 +- .../social/AnalyticsSpaceListener.java | 33 +- ...AnalyticsSpaceWebNotificationListener.java | 47 +- .../BaseAttachmentAnalyticsListener.java | 62 +-- .../WebSocketUIStatisticListener.java | 45 +- .../job/SpacesStatisticsCountJob.java | 81 --- .../job/UsersStatisticsCountJob.java | 116 ---- analytics-packaging/pom.xml | 5 +- analytics-services/pom.xml | 33 +- .../elasticsearch/ElasticsearchConnector.java | 366 ++++++++++++ .../ElasticsearchSettingService.java | 261 +++++++++ .../elasticsearch/model/ElasticResponse.java | 34 ++ .../ElasticSearchStatisticDataProcessor.java | 36 +- .../ElasticsearchAnalyticsService.java} | 486 ++++++++-------- .../DummyStatisticDataQueueService.java | 84 +-- .../analytics/es/AnalyticsESClient.java | 429 -------------- ...AnalyticsElasticContentRequestBuilder.java | 48 -- .../es/AnalyticsIndexingServiceConnector.java | 178 ------ .../main/resources/analytics-ui-watchers.json | 14 + .../resources/conf/portal/configuration.xml | 122 ---- analytics-webapps/pom.xml | 36 +- .../meeds/analytics/AnalyticsApplication.java | 45 ++ .../portlet/AbstractAnalyticsPortlet.java | 27 +- .../analytics/portlet/AnalyticsPortlet.java | 17 +- .../portlet/AnalyticsRatePortlet.java | 13 +- .../portlet/AnalyticsTablePortlet.java | 19 +- .../StatisticDataCollectionPortlet.java | 42 +- .../META-INF/exo-conf/configuration.xml | 7 +- .../analytics-ui-watchers-configuration.xml | 525 ------------------ .../conf/analytics/cache-configuration.xml | 9 +- .../dynamic-container-configuration.xml | 7 +- .../analytics/listeners-configuration.xml | 209 ------- .../analytics/organization-configuration.xml | 9 +- .../webapp/WEB-INF/conf/configuration.xml | 9 +- .../main/webapp/WEB-INF/gatein-resources.xml | 20 + .../webapp/WEB-INF/jsp/analytics-rate.jsp | 7 +- .../webapp/WEB-INF/jsp/analytics-table.jsp | 7 +- .../src/main/webapp/WEB-INF/jsp/analytics.jsp | 7 +- .../main/webapp/WEB-INF/jsp/breadcrumb.jsp | 7 +- .../WEB-INF/jsp/statistics-collection.jsp | 7 +- .../src/main/webapp/WEB-INF/portlet.xml | 15 +- .../src/main/webapp/WEB-INF/web.xml | 14 +- .../UIPageDisplayStatisticCollection.gtmpl | 7 +- .../main/webapp/js/statistic-collection.js | 7 +- .../src/main/webapp/skin/less/analytics.less | 7 +- .../AnalyticsDashboardBreadcrumb.vue | 7 +- .../components/AnalyticsDrawerNavigation.vue | 7 +- .../breadcrumb-portlet/initComponents.js | 7 +- .../webapp/vue-app/breadcrumb-portlet/main.js | 7 +- .../components/common/SelectPeriod.vue | 7 +- .../samples/DateSampleItemAttribute.vue | 7 +- .../samples/DurationSampleItemAttribute.vue | 27 +- .../components/samples/ProfileChip.vue | 7 +- .../samples/ProfileSampleItemAttribute.vue | 7 +- .../components/samples/SampleItem.vue | 7 +- .../samples/SampleItemAttribute.vue | 7 +- .../components/samples/ViewSamplesDrawer.vue | 7 +- .../settings/AnalyticsChartSetting.vue | 7 +- .../components/settings/JsonPanelDialog.vue | 7 +- .../components/settings/form/FieldFilter.vue | 7 +- .../settings/form/FieldSelection.vue | 7 +- .../settings/form/IdentityFieldSelection.vue | 7 +- .../settings/form/LimitFilterForm.vue | 7 +- .../settings/form/MultipleValuesSelection.vue | 7 +- .../settings/form/SpaceFieldFilter.vue | 7 +- .../settings/form/TextValueSuggester.vue | 7 +- .../settings/form/TextValuesFilter.vue | 7 +- .../settings/form/UserFieldFilter.vue | 7 +- .../settings/form/XAxisAggregationField.vue | 7 +- .../settings/tabs/ColorsSettingForm.vue | 7 +- .../settings/tabs/GeneralSettingForm.vue | 7 +- .../tabs/MultipleChartsAggregationForm.vue | 7 +- .../settings/tabs/SearchFilterForm.vue | 7 +- .../settings/tabs/SettingColorPicker.vue | 7 +- .../settings/tabs/XAxisAggregationForm.vue | 7 +- .../settings/tabs/YAxisAggregationForm.vue | 7 +- .../vue-app/common-components/extensions.js | 7 +- .../common-components/initComponents.js | 7 +- .../vue-app/common-components/js/utils.js | 7 +- .../webapp/vue-app/common-components/main.js | 7 +- .../components/AnalyticsApplication.vue | 7 +- .../components/chart/AnalyticsChart.vue | 7 +- .../vue-app/generic-portlet/initComponents.js | 7 +- .../webapp/vue-app/generic-portlet/main.js | 7 +- .../components/AnalyticsRateApplication.vue | 7 +- .../chart/AnalyticsPercentageBarChart.vue | 7 +- .../chart/AnalyticsPercentageChart.vue | 7 +- .../vue-app/rate-portlet/initComponents.js | 7 +- .../main/webapp/vue-app/rate-portlet/main.js | 7 +- .../components/AnalyticsTableApplication.vue | 7 +- ...AnalyticsTableColumnAggregationSetting.vue | 7 +- .../settings/AnalyticsTableColumnSetting.vue | 7 +- .../settings/AnalyticsTableGeneralSetting.vue | 7 +- .../settings/AnalyticsTableSetting.vue | 7 +- .../components/table/AnalyticsTable.vue | 7 +- .../components/table/AnalyticsTableCell.vue | 7 +- .../table/AnalyticsTableCellSpaceValue.vue | 7 +- .../table/AnalyticsTableCellUserValue.vue | 7 +- .../table/AnalyticsTableCellValue.vue | 7 +- .../vue-app/table-portlet/extensions.js | 7 +- .../vue-app/table-portlet/initComponents.js | 7 +- .../main/webapp/vue-app/table-portlet/main.js | 7 +- analytics-webapps/webpack.prod.js | 7 +- analytics-webapps/webpack.watch.js | 7 +- pom.xml | 32 +- 158 files changed, 2741 insertions(+), 2827 deletions(-) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/api/service/AnalyticsService.java (91%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/api/service/StatisticDataProcessorService.java (71%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/api/service/StatisticDataQueueService.java (88%) create mode 100644 analytics-api/src/main/java/io/meeds/analytics/api/service/injection/StatisticWatcherImportService.java rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/api/websocket/AnalyticsWebSocketService.java (52%) create mode 100644 analytics-api/src/main/java/io/meeds/analytics/api/websocket/listener/AnalyticsWebSocketListener.java rename analytics-api/src/main/java/{org/exoplatform/analytics/api/websocket => io/meeds/analytics/model}/AnalyticsWebSocketMessage.java (91%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/StatisticData.java (90%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/StatisticDataQueueEntry.java (93%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/StatisticFieldMapping.java (95%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/StatisticFieldValue.java (89%) rename analytics-api/src/main/java/{org/exoplatform/analytics/api/service => io/meeds/analytics/model}/StatisticWatcher.java (94%) rename analytics-api/src/main/java/{org/exoplatform/analytics/api/service/StatisticUIWatcherPlugin.java => io/meeds/analytics/model/StatisticWatchersDescriptor.java} (51%) rename analytics-api/src/main/java/{org/exoplatform/analytics/api/websocket => io/meeds/analytics/model}/UserSettings.java (84%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/chart/ChartAggregationLabel.java (92%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/chart/ChartAggregationResult.java (92%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/chart/ChartAggregationValue.java (86%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/chart/ChartData.java (82%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/chart/ChartDataList.java (83%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/chart/PercentageChartResult.java (92%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/chart/PercentageChartValue.java (91%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/chart/TableColumnItemValue.java (89%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/chart/TableColumnResult.java (90%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/AbstractAnalyticsFilter.java (92%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/AnalyticsFilter.java (85%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/AnalyticsPercentageFilter.java (97%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/AnalyticsPercentageItemFilter.java (79%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/AnalyticsPeriod.java (96%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/AnalyticsPeriodType.java (97%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/AnalyticsTableColumnAggregation.java (71%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/AnalyticsTableColumnFilter.java (95%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/AnalyticsTableFilter.java (91%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/aggregation/AnalyticsAggregation.java (96%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/aggregation/AnalyticsAggregationType.java (92%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/aggregation/AnalyticsPercentageLimit.java (88%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/search/AnalyticsFieldFilter.java (90%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/search/AnalyticsFieldFilterType.java (88%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/model/filter/search/AnalyticsSortField.java (90%) rename analytics-api/src/main/java/{org/exoplatform/analytics/api/processor => io/meeds/analytics/plugin}/StatisticDataProcessorPlugin.java (90%) rename analytics-api/src/main/java/{org/exoplatform => io/meeds}/analytics/utils/AnalyticsUtils.java (98%) delete mode 100644 analytics-api/src/main/java/org/exoplatform/analytics/api/service/ManagedStatisticDataQueueService.java create mode 100644 analytics-listeners/src/main/java/io/meeds/analytics/job/SpacesStatisticsCountJob.java create mode 100644 analytics-listeners/src/main/java/io/meeds/analytics/job/UsersStatisticsCountJob.java rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/portal/LoginAnalyticsListener.java (66%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/portal/LoginFailedAnalyticsListener.java (64%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/portal/PageAccessListener.java (84%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/portal/UserAnalyticsEventListener.java (69%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/social/ActivityAttachmentAnalyticsListener.java (58%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/social/AnalyticsActivityListener.java (87%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/social/AnalyticsActivityTagsListener.java (81%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/social/AnalyticsProfileListener.java (81%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/social/AnalyticsRelationshipListener.java (83%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/social/AnalyticsSpaceListener.java (91%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/social/AnalyticsSpaceWebNotificationListener.java (74%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/social/BaseAttachmentAnalyticsListener.java (67%) rename analytics-listeners/src/main/java/{org/exoplatform => io/meeds}/analytics/listener/websocket/WebSocketUIStatisticListener.java (76%) delete mode 100644 analytics-listeners/src/main/java/org/exoplatform/analytics/job/SpacesStatisticsCountJob.java delete mode 100644 analytics-listeners/src/main/java/org/exoplatform/analytics/job/UsersStatisticsCountJob.java create mode 100644 analytics-services/src/main/java/io/meeds/analytics/elasticsearch/ElasticsearchConnector.java create mode 100644 analytics-services/src/main/java/io/meeds/analytics/elasticsearch/ElasticsearchSettingService.java create mode 100644 analytics-services/src/main/java/io/meeds/analytics/elasticsearch/model/ElasticResponse.java rename analytics-services/src/main/java/{org/exoplatform/analytics/es => io/meeds/analytics/elasticsearch}/processor/ElasticSearchStatisticDataProcessor.java (58%) rename analytics-services/src/main/java/{org/exoplatform/analytics/es/service/ESAnalyticsService.java => io/meeds/analytics/elasticsearch/service/ElasticsearchAnalyticsService.java} (78%) rename analytics-services/src/main/java/{org/exoplatform => io/meeds}/analytics/queue/service/DummyStatisticDataQueueService.java (71%) delete mode 100644 analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsESClient.java delete mode 100644 analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsElasticContentRequestBuilder.java delete mode 100644 analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsIndexingServiceConnector.java create mode 100644 analytics-services/src/main/resources/analytics-ui-watchers.json delete mode 100644 analytics-services/src/main/resources/conf/portal/configuration.xml create mode 100644 analytics-webapps/src/main/java/io/meeds/analytics/AnalyticsApplication.java rename analytics-webapps/src/main/java/{org/exoplatform/addon => io/meeds}/analytics/portlet/AbstractAnalyticsPortlet.java (95%) rename analytics-webapps/src/main/java/{org/exoplatform/addon => io/meeds}/analytics/portlet/AnalyticsPortlet.java (91%) rename analytics-webapps/src/main/java/{org/exoplatform/addon => io/meeds}/analytics/portlet/AnalyticsRatePortlet.java (95%) rename analytics-webapps/src/main/java/{org/exoplatform/addon => io/meeds}/analytics/portlet/AnalyticsTablePortlet.java (94%) rename analytics-webapps/src/main/java/{org/exoplatform/addon => io/meeds}/analytics/portlet/StatisticDataCollectionPortlet.java (62%) delete mode 100644 analytics-webapps/src/main/webapp/WEB-INF/conf/analytics/analytics-ui-watchers-configuration.xml delete mode 100644 analytics-webapps/src/main/webapp/WEB-INF/conf/analytics/listeners-configuration.xml diff --git a/analytics-api/pom.xml b/analytics-api/pom.xml index 0bd4fe9de..8bb65c590 100644 --- a/analytics-api/pom.xml +++ b/analytics-api/pom.xml @@ -1,16 +1,19 @@ 4.0.0 - org.exoplatform.addons.analytics + io.meeds.analytics analytics-parent 7.0.x-whitepaper-SNAPSHOT analytics-listeners - eXo Analytics - Listeners + Meeds:: Analytics - Listeners 0 diff --git a/analytics-listeners/src/main/java/io/meeds/analytics/job/SpacesStatisticsCountJob.java b/analytics-listeners/src/main/java/io/meeds/analytics/job/SpacesStatisticsCountJob.java new file mode 100644 index 000000000..749c5adc4 --- /dev/null +++ b/analytics-listeners/src/main/java/io/meeds/analytics/job/SpacesStatisticsCountJob.java @@ -0,0 +1,63 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.analytics.job; + +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import org.exoplatform.social.core.space.spi.SpaceService; + +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.utils.AnalyticsUtils; +import io.meeds.common.ContainerTransactional; + +import lombok.SneakyThrows; + +/** + * A job to collect statistics of users count + */ +@Configuration +@EnableScheduling +public class SpacesStatisticsCountJob { + + @Autowired + private SpaceService spaceService; + + @SneakyThrows + @ContainerTransactional + @Scheduled(initialDelay = 2, fixedDelay = 180, timeUnit = TimeUnit.MINUTES) + public void run() { + long startTime = System.currentTimeMillis(); + int allSpacesCount = spaceService.getAllSpacesWithListAccess().getSize(); + StatisticData statisticData = new StatisticData(); + statisticData.setModule("social"); + statisticData.setSubModule("space"); + statisticData.setOperation("spacesCount"); + statisticData.setDuration(System.currentTimeMillis() - startTime); + statisticData.addParameter("countType", "allSpaces"); + statisticData.addParameter("count", allSpacesCount); + AnalyticsUtils.addStatisticData(statisticData); + } + +} diff --git a/analytics-listeners/src/main/java/io/meeds/analytics/job/UsersStatisticsCountJob.java b/analytics-listeners/src/main/java/io/meeds/analytics/job/UsersStatisticsCountJob.java new file mode 100644 index 000000000..3ed634e0c --- /dev/null +++ b/analytics-listeners/src/main/java/io/meeds/analytics/job/UsersStatisticsCountJob.java @@ -0,0 +1,100 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.analytics.job; + +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import org.exoplatform.commons.utils.ListAccess; +import org.exoplatform.services.organization.Group; +import org.exoplatform.services.organization.Membership; +import org.exoplatform.services.organization.OrganizationService; +import org.exoplatform.services.organization.User; +import org.exoplatform.services.organization.UserStatus; +import org.exoplatform.social.core.identity.model.Identity; +import org.exoplatform.social.core.identity.provider.OrganizationIdentityProvider; +import org.exoplatform.social.core.manager.IdentityManager; + +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.utils.AnalyticsUtils; +import io.meeds.common.ContainerTransactional; + +import lombok.SneakyThrows; + +/** + * A job to collect statistics of users count + */ +@Configuration +@EnableScheduling +public class UsersStatisticsCountJob { + + @Autowired + private OrganizationService organizationService; + + @Autowired + private IdentityManager identityManager; + + @SneakyThrows + @ContainerTransactional + @Scheduled(initialDelay = 2, fixedDelay = 180, timeUnit = TimeUnit.MINUTES) + public void run() { + long startTime = System.currentTimeMillis(); + + ListAccess allUsers = organizationService.getUserHandler().findAllUsers(UserStatus.ANY); + int allUsersCount = allUsers.getSize(); + + ListAccess enabledIdentities = identityManager.getIdentitiesByProfileFilter(OrganizationIdentityProvider.NAME, + null, + false); + int enabledUsersCount = enabledIdentities.getSize(); + int disabledUsersCount = allUsersCount - enabledUsersCount; + + addUsersCountStatistic("allUsers", allUsersCount, startTime); + addUsersCountStatistic("enabledUsers", enabledUsersCount, startTime); + addUsersCountStatistic("disabledUsers", disabledUsersCount, startTime); + + startTime = System.currentTimeMillis(); + Group externalsGroup = organizationService.getGroupHandler().findGroupById("/platform/externals"); + int enabledExternalUsersCount = 0; + if (externalsGroup != null) { + ListAccess externalMemberships = organizationService.getMembershipHandler() + .findAllMembershipsByGroup(externalsGroup); + enabledExternalUsersCount = externalMemberships.getSize(); + } + addUsersCountStatistic("enabledExternalUsers", enabledExternalUsersCount, startTime); + addUsersCountStatistic("enabledInternalUsers", (enabledUsersCount - enabledExternalUsersCount), startTime); + } + + private void addUsersCountStatistic(String countType, int count, long startTime) { + StatisticData statisticData = new StatisticData(); + statisticData.setModule("portal"); + statisticData.setSubModule("account"); + statisticData.setOperation("usersCount"); + statisticData.setDuration(System.currentTimeMillis() - startTime); + statisticData.addParameter("countType", countType); + statisticData.addParameter("count", count); + AnalyticsUtils.addStatisticData(statisticData); + } + +} diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/LoginAnalyticsListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/LoginAnalyticsListener.java similarity index 66% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/LoginAnalyticsListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/LoginAnalyticsListener.java index 654bfe9d8..53aa21ed6 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/LoginAnalyticsListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/LoginAnalyticsListener.java @@ -1,36 +1,60 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.portal; +package io.meeds.analytics.listener.portal; -import static org.exoplatform.analytics.utils.AnalyticsUtils.addStatisticData; -import static org.exoplatform.analytics.utils.AnalyticsUtils.getUserIdentityId; +import static io.meeds.analytics.utils.AnalyticsUtils.addStatisticData; +import static io.meeds.analytics.utils.AnalyticsUtils.getUserIdentityId; + +import java.util.Arrays; +import java.util.List; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import org.exoplatform.analytics.model.StatisticData; import org.exoplatform.services.listener.*; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.services.security.ConversationRegistry; import org.exoplatform.services.security.ConversationState; +import io.meeds.analytics.model.StatisticData; + +import jakarta.annotation.PostConstruct; + @Asynchronous +@Component public class LoginAnalyticsListener extends Listener { - private static final Log LOG = ExoLogger.getLogger(LoginAnalyticsListener.class); + + private static final Log LOG = ExoLogger.getLogger(LoginAnalyticsListener.class); + + private static final List EVENT_NAMES = Arrays.asList("exo.core.security.ConversationRegistry.register", + "exo.core.security.ConversationRegistry.unregister"); + + @Autowired + private ListenerService listenerService; + + @PostConstruct + public void init() { + EVENT_NAMES.forEach(name -> listenerService.addListener(name, this)); + } @Override public void onEvent(Event event) throws Exception { diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/LoginFailedAnalyticsListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/LoginFailedAnalyticsListener.java similarity index 64% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/LoginFailedAnalyticsListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/LoginFailedAnalyticsListener.java index 4ed2ad14f..1a54327d0 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/LoginFailedAnalyticsListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/LoginFailedAnalyticsListener.java @@ -1,35 +1,58 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.portal; +package io.meeds.analytics.listener.portal; -import static org.exoplatform.analytics.utils.AnalyticsUtils.addStatisticData; +import static io.meeds.analytics.utils.AnalyticsUtils.addStatisticData; +import java.util.Arrays; +import java.util.List; import java.util.Map; -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.analytics.utils.AnalyticsUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + import org.exoplatform.commons.api.persistence.ExoTransactional; import org.exoplatform.services.listener.Asynchronous; import org.exoplatform.services.listener.Event; import org.exoplatform.services.listener.Listener; +import org.exoplatform.services.listener.ListenerService; + +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.utils.AnalyticsUtils; + +import jakarta.annotation.PostConstruct; @Asynchronous +@Component public class LoginFailedAnalyticsListener extends Listener> { + private static final List EVENT_NAMES = Arrays.asList("login.failed"); + + @Autowired + private ListenerService listenerService; + + @PostConstruct + public void init() { + EVENT_NAMES.forEach(name -> listenerService.addListener(name, this)); + } + @Override @ExoTransactional public void onEvent(Event> event) throws Exception { @@ -40,8 +63,8 @@ public void onEvent(Event> event) throws Exception { statisticData.setSubModule("login"); statisticData.setOperation("login"); statisticData.setUserId(AnalyticsUtils.getUserIdentityId(data.get("user_id"))); - statisticData.setStatus(data.get("status").equals("ko") ? StatisticData.StatisticStatus.KO - : StatisticData.StatisticStatus.OK); + statisticData.setStatus(data.get("status").equals("ko") ? StatisticData.StatisticStatus.KO : + StatisticData.StatisticStatus.OK); statisticData.addParameter("reason", data.get("reason")); addStatisticData(statisticData); } diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/PageAccessListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/PageAccessListener.java similarity index 84% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/PageAccessListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/PageAccessListener.java index c891cedaa..2f3afe94f 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/PageAccessListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/PageAccessListener.java @@ -1,30 +1,35 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.portal; - -import static org.exoplatform.analytics.utils.AnalyticsUtils.*; +package io.meeds.analytics.listener.portal; +import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.analytics.model.StatisticData.StatisticStatus; +import static io.meeds.analytics.utils.AnalyticsUtils.*; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + import org.exoplatform.container.component.BaseComponentPlugin; -import org.exoplatform.container.xml.InitParams; import org.exoplatform.portal.application.PortalRequestContext; import org.exoplatform.portal.mop.user.UserNode; import org.exoplatform.portal.webui.portal.UIPortal; @@ -36,18 +41,25 @@ import org.exoplatform.web.application.*; import org.exoplatform.webui.application.WebuiRequestContext; +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.model.StatisticData.StatisticStatus; + +@Component public class PageAccessListener extends BaseComponentPlugin implements ApplicationLifecycle { - private static final Log LOG = ExoLogger.getLogger(PageAccessListener.class); + private static final Log LOG = ExoLogger.getLogger(PageAccessListener.class); - private ThreadLocal operationStartTime = new ThreadLocal<>(); + @Autowired + private ApplicationLifecycleExtension applicationLifecycleExtension; - private boolean collectAjaxQueries = false; + @Value("${analytics.collectAjaxQueries:false}") + private boolean collectAjaxQueries = false; - public PageAccessListener(InitParams params) { - if (params != null && params.containsKey("collectAjaxQueries")) { - this.collectAjaxQueries = Boolean.parseBoolean(params.getValueParam("collectAjaxQueries").getValue()); - } + private ThreadLocal operationStartTime = new ThreadLocal<>(); + + @PostConstruct + public void init() { + applicationLifecycleExtension.addPortalApplicationLifecycle(this); } @Override diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/UserAnalyticsEventListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/UserAnalyticsEventListener.java similarity index 69% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/UserAnalyticsEventListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/UserAnalyticsEventListener.java index 62c303b2e..bd1770ae9 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/portal/UserAnalyticsEventListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/portal/UserAnalyticsEventListener.java @@ -1,70 +1,94 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.portal; +package io.meeds.analytics.listener.portal; -import static org.exoplatform.analytics.utils.AnalyticsUtils.*; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_SOCIAL_IDENTITY_ID; +import static io.meeds.analytics.utils.AnalyticsUtils.addStatisticData; +import static io.meeds.analytics.utils.AnalyticsUtils.getCurrentUserIdentityId; +import static io.meeds.analytics.utils.AnalyticsUtils.getUserIdentityId; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.commons.api.persistence.ExoTransactional; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; +import org.exoplatform.services.organization.OrganizationService; import org.exoplatform.services.organization.User; import org.exoplatform.services.organization.UserEventListener; +import io.meeds.analytics.model.StatisticData; +import io.meeds.common.ContainerTransactional; + +import jakarta.annotation.PostConstruct; +import lombok.SneakyThrows; + +@Component public class UserAnalyticsEventListener extends UserEventListener { - private static final Log LOG = ExoLogger.getLogger(UserAnalyticsEventListener.class); + private static final Log LOG = ExoLogger.getLogger(UserAnalyticsEventListener.class); - private ThreadLocal operationStartTime = new ThreadLocal<>(); + private ThreadLocal operationStartTime = new ThreadLocal<>(); + + @Autowired + private OrganizationService organizationService; + + @SneakyThrows + @PostConstruct + public void init() { + organizationService.addListenerPlugin(this); + } @Override - @ExoTransactional + @ContainerTransactional public void preSave(User user, boolean isNew) throws Exception { operationStartTime.set(System.currentTimeMillis()); } @Override - @ExoTransactional + @ContainerTransactional public void preSetEnabled(User user) throws Exception { operationStartTime.set(System.currentTimeMillis()); } @Override - @ExoTransactional + @ContainerTransactional public void preDelete(User user) throws Exception { operationStartTime.set(System.currentTimeMillis()); } @Override - @ExoTransactional + @ContainerTransactional public void postSave(User user, boolean isNew) throws Exception { StatisticData statisticData = buildStatisticData(isNew ? "createUser" : "saveUser", user); addStatisticData(statisticData); } @Override - @ExoTransactional + @ContainerTransactional public void postSetEnabled(User user) throws Exception { StatisticData statisticData = buildStatisticData("enableUser", user); addStatisticData(statisticData); } @Override - @ExoTransactional + @ContainerTransactional public void postDelete(User user) throws Exception { StatisticData statisticData = buildStatisticData("deleteUser", user); addStatisticData(statisticData); diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/ActivityAttachmentAnalyticsListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/ActivityAttachmentAnalyticsListener.java similarity index 58% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/ActivityAttachmentAnalyticsListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/social/ActivityAttachmentAnalyticsListener.java index efdaea73e..22a9104ba 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/ActivityAttachmentAnalyticsListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/ActivityAttachmentAnalyticsListener.java @@ -1,45 +1,71 @@ -/* +/** * This file is part of the Meeds project (https://meeds.io/). * - * Copyright (C) 2023 Meeds Association contact@meeds.io + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.social; +package io.meeds.analytics.listener.social; + +import static io.meeds.analytics.utils.AnalyticsUtils.addActivityStatisticsData; -import static org.exoplatform.analytics.utils.AnalyticsUtils.addActivityStatisticsData; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.container.xml.InitParams; import org.exoplatform.services.listener.Asynchronous; +import org.exoplatform.services.listener.ListenerService; import org.exoplatform.social.attachment.AttachmentService; import org.exoplatform.social.attachment.model.ObjectAttachmentId; import org.exoplatform.social.core.activity.model.ExoSocialActivity; import org.exoplatform.social.core.manager.ActivityManager; import org.exoplatform.social.core.space.spi.SpaceService; +import io.meeds.analytics.model.StatisticData; + +import jakarta.annotation.PostConstruct; +import lombok.Getter; + @Asynchronous +@Component public class ActivityAttachmentAnalyticsListener extends BaseAttachmentAnalyticsListener { - private ActivityManager activityManager; + private static final List SUPPORTED_OBJECT_TYPES = Collections.singletonList("activity"); + + private static final List EVENT_NAMES = Arrays.asList("attachment.created", "attachment.deleted"); + + @Getter + @Autowired + private AttachmentService attachmentService; - public ActivityAttachmentAnalyticsListener(AttachmentService attachmentService, - SpaceService spaceService, - ActivityManager activityManager, - InitParams initParam) { - super(attachmentService, spaceService, initParam); - this.activityManager = activityManager; + @Getter + @Autowired + private SpaceService spaceService; + + @Autowired + private ActivityManager activityManager; + + @Autowired + private ListenerService listenerService; + + @PostConstruct + public void init() { + EVENT_NAMES.forEach(name -> listenerService.addListener(name, this)); } @Override @@ -50,6 +76,11 @@ protected void extendStatisticData(StatisticData statisticData, ObjectAttachment } } + @Override + protected List getSupportedObjectType() { + return SUPPORTED_OBJECT_TYPES; + } + @Override protected String getModule(ObjectAttachmentId objectAttachment) { return "social"; diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsActivityListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsActivityListener.java similarity index 87% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsActivityListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsActivityListener.java index 8bacd200b..264f76187 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsActivityListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsActivityListener.java @@ -1,27 +1,35 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.social; +package io.meeds.analytics.listener.social; -import static org.exoplatform.analytics.utils.AnalyticsUtils.*; +import static io.meeds.analytics.utils.AnalyticsUtils.addActivityStatisticsData; +import static io.meeds.analytics.utils.AnalyticsUtils.addSpaceStatistics; +import static io.meeds.analytics.utils.AnalyticsUtils.addStatisticData; +import static io.meeds.analytics.utils.AnalyticsUtils.getCurrentUserIdentityId; +import static io.meeds.analytics.utils.AnalyticsUtils.getIdentity; +import static io.meeds.analytics.utils.AnalyticsUtils.getUserIdentityId; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.commons.utils.CommonsUtils; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.social.core.activity.ActivityLifeCycleEvent; @@ -36,9 +44,25 @@ import org.exoplatform.social.core.space.model.Space; import org.exoplatform.social.core.space.spi.SpaceService; +import io.meeds.analytics.model.StatisticData; + +import jakarta.annotation.PostConstruct; + +@Component public class AnalyticsActivityListener extends ActivityListenerPlugin { - private static final Log LOG = ExoLogger.getLogger(AnalyticsActivityListener.class); + private static final Log LOG = ExoLogger.getLogger(AnalyticsActivityListener.class); + + @Autowired + private ActivityManager activityManager; + + @Autowired + private SpaceService spaceService; + + @PostConstruct + public void init() { + activityManager.addActivityEventListener(this); + } @Override public void saveActivity(ActivityLifeCycleEvent event) { @@ -59,15 +83,17 @@ public void updateActivity(ActivityLifeCycleEvent event) { handleErrorProcessingOperation(event, e); } } + @Override public void deleteActivity(ActivityLifeCycleEvent event) { try { - StatisticData statisticData = addActivityStatisticEvent(event,"deleteActivity"); + StatisticData statisticData = addActivityStatisticEvent(event, "deleteActivity"); addStatisticData(statisticData); } catch (Exception e) { handleErrorProcessingOperation(event, e); } } + @Override public void shareActivity(ActivityLifeCycleEvent event) { try { @@ -97,15 +123,17 @@ public void updateComment(ActivityLifeCycleEvent event) { handleErrorProcessingOperation(event, e); } } + @Override public void deleteComment(ActivityLifeCycleEvent event) { try { - StatisticData statisticData = addActivityStatisticEvent(event,"deleteComment"); + StatisticData statisticData = addActivityStatisticEvent(event, "deleteComment"); addStatisticData(statisticData); } catch (Exception e) { handleErrorProcessingOperation(event, e); } } + @Override public void likeActivity(ActivityLifeCycleEvent event) { try { @@ -163,7 +191,6 @@ private StatisticData addActivityStatisticEvent(ActivityLifeCycleEvent event, St ActivityStream activityStream = activity.getActivityStream(); if ((activityStream == null || activityStream.getType() == null || activityStream.getPrettyId() == null) && StringUtils.isNotBlank(activity.getParentId())) { - ActivityManager activityManager = CommonsUtils.getService(ActivityManager.class); ExoSocialActivity parentActivity = activityManager.getActivity(activity.getParentId()); activityStream = parentActivity.getActivityStream(); } @@ -188,7 +215,6 @@ private StatisticData addActivityStatisticEvent(ActivityLifeCycleEvent event, St StatisticData statisticData = new StatisticData(); if (streamIdentity != null) { if (StringUtils.equals(streamIdentity.getProviderId(), SpaceIdentityProvider.NAME)) { - SpaceService spaceService = CommonsUtils.getService(SpaceService.class); Space space = spaceService.getSpaceByPrettyName(streamIdentity.getRemoteId()); addSpaceStatistics(statisticData, space); } diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsActivityTagsListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsActivityTagsListener.java similarity index 81% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsActivityTagsListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsActivityTagsListener.java index 597b9c85e..5f10f9981 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsActivityTagsListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsActivityTagsListener.java @@ -1,32 +1,37 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.social; +package io.meeds.analytics.listener.social; import java.util.Date; import java.util.Set; import org.apache.commons.lang3.Validate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import org.apache.commons.lang3.StringUtils; -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.analytics.utils.AnalyticsUtils; + import org.exoplatform.commons.api.persistence.ExoTransactional; import org.exoplatform.services.listener.Asynchronous; import org.exoplatform.services.listener.Event; import org.exoplatform.services.listener.Listener; +import org.exoplatform.services.listener.ListenerService; import org.exoplatform.social.core.activity.model.ExoSocialActivity; import org.exoplatform.social.core.activity.model.ExoSocialActivityImpl; import org.exoplatform.social.core.identity.model.Identity; @@ -35,16 +40,29 @@ import org.exoplatform.social.metadata.tag.model.TagName; import org.exoplatform.social.metadata.tag.model.TagObject; +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.utils.AnalyticsUtils; + +import jakarta.annotation.PostConstruct; + @Asynchronous +@Component public class AnalyticsActivityTagsListener extends Listener> { - private ActivityManager activityManager; + private static final String EVENT_NAME = "metadata.tag.added"; + + @Autowired + private ActivityManager activityManager; + + @Autowired + private IdentityManager identityManager; - private IdentityManager identityManager; + @Autowired + private ListenerService listenerService; - public AnalyticsActivityTagsListener(ActivityManager activityManager, IdentityManager identityManager) { - this.activityManager = activityManager; - this.identityManager = identityManager; + @PostConstruct + public void init() { + listenerService.addListener(EVENT_NAME, this); } @Override diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsProfileListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsProfileListener.java similarity index 81% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsProfileListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsProfileListener.java index a24e249c9..612fe68d7 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsProfileListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsProfileListener.java @@ -1,41 +1,60 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.social; +package io.meeds.analytics.listener.social; -import static org.exoplatform.analytics.utils.AnalyticsUtils.AVATAR; -import static org.exoplatform.analytics.utils.AnalyticsUtils.FIELD_SOCIAL_IDENTITY_ID; -import static org.exoplatform.analytics.utils.AnalyticsUtils.IMAGE_SIZE; -import static org.exoplatform.analytics.utils.AnalyticsUtils.IMAGE_TYPE; -import static org.exoplatform.analytics.utils.AnalyticsUtils.addStatisticData; -import static org.exoplatform.analytics.utils.AnalyticsUtils.getIdentity; +import static io.meeds.analytics.utils.AnalyticsUtils.AVATAR; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_SOCIAL_IDENTITY_ID; +import static io.meeds.analytics.utils.AnalyticsUtils.IMAGE_SIZE; +import static io.meeds.analytics.utils.AnalyticsUtils.IMAGE_TYPE; +import static io.meeds.analytics.utils.AnalyticsUtils.addStatisticData; +import static io.meeds.analytics.utils.AnalyticsUtils.getIdentity; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import org.exoplatform.analytics.model.StatisticData; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.social.core.identity.model.Identity; import org.exoplatform.social.core.identity.provider.OrganizationIdentityProvider; +import org.exoplatform.social.core.manager.IdentityManager; import org.exoplatform.social.core.model.AvatarAttachment; import org.exoplatform.social.core.profile.ProfileLifeCycleEvent; import org.exoplatform.social.core.profile.ProfileListenerPlugin; +import io.meeds.analytics.model.StatisticData; + +import jakarta.annotation.PostConstruct; + +@Component public class AnalyticsProfileListener extends ProfileListenerPlugin { private static final Log LOG = ExoLogger.getLogger(AnalyticsProfileListener.class); + @Autowired + private IdentityManager identityManager; + + @PostConstruct + public void init() { + identityManager.registerProfileListener(this); + } + @Override public void avatarUpdated(ProfileLifeCycleEvent event) { try { diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsRelationshipListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsRelationshipListener.java similarity index 83% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsRelationshipListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsRelationshipListener.java index e8cc5490c..0303b5630 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsRelationshipListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsRelationshipListener.java @@ -1,34 +1,54 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.social; +package io.meeds.analytics.listener.social; -import static org.exoplatform.analytics.utils.AnalyticsUtils.addStatisticData; -import static org.exoplatform.analytics.utils.AnalyticsUtils.getCurrentUserIdentityId; +import static io.meeds.analytics.utils.AnalyticsUtils.addStatisticData; +import static io.meeds.analytics.utils.AnalyticsUtils.getCurrentUserIdentityId; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import org.exoplatform.analytics.model.StatisticData; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; +import org.exoplatform.social.core.manager.RelationshipManager; +import org.exoplatform.social.core.manager.RelationshipManagerImpl; import org.exoplatform.social.core.relationship.RelationshipEvent; import org.exoplatform.social.core.relationship.RelationshipListenerPlugin; import org.exoplatform.social.core.relationship.model.Relationship; +import io.meeds.analytics.model.StatisticData; + +import jakarta.annotation.PostConstruct; + +@Component public class AnalyticsRelationshipListener extends RelationshipListenerPlugin { - private static final Log LOG = ExoLogger.getLogger(AnalyticsRelationshipListener.class); + private static final Log LOG = ExoLogger.getLogger(AnalyticsRelationshipListener.class); + + @Autowired + private RelationshipManager relationshipManager; + + @PostConstruct + public void init() { + ((RelationshipManagerImpl) relationshipManager).addListenerPlugin(this); + } @Override public void requested(RelationshipEvent event) { diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsSpaceListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsSpaceListener.java similarity index 91% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsSpaceListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsSpaceListener.java index d12dc077f..681becc9f 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsSpaceListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsSpaceListener.java @@ -1,36 +1,55 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.social; +package io.meeds.analytics.listener.social; -import static org.exoplatform.analytics.utils.AnalyticsUtils.addSpaceStatistics; -import static org.exoplatform.analytics.utils.AnalyticsUtils.addStatisticData; -import static org.exoplatform.analytics.utils.AnalyticsUtils.getCurrentUserIdentityId; +import static io.meeds.analytics.utils.AnalyticsUtils.addSpaceStatistics; +import static io.meeds.analytics.utils.AnalyticsUtils.addStatisticData; +import static io.meeds.analytics.utils.AnalyticsUtils.getCurrentUserIdentityId; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import org.exoplatform.analytics.model.StatisticData; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.social.core.space.SpaceListenerPlugin; import org.exoplatform.social.core.space.model.Space; import org.exoplatform.social.core.space.spi.SpaceLifeCycleEvent; +import org.exoplatform.social.core.space.spi.SpaceService; + +import io.meeds.analytics.model.StatisticData; + +import jakarta.annotation.PostConstruct; +@Component public class AnalyticsSpaceListener extends SpaceListenerPlugin { private static final Log LOG = ExoLogger.getLogger(AnalyticsSpaceListener.class); + @Autowired + private SpaceService spaceService; + + @PostConstruct + public void init() { + spaceService.registerSpaceListenerPlugin(this); + } + @Override public void spaceAccessEdited(SpaceLifeCycleEvent event) { try { diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsSpaceWebNotificationListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsSpaceWebNotificationListener.java similarity index 74% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsSpaceWebNotificationListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsSpaceWebNotificationListener.java index 896440abf..d7d5f141e 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/AnalyticsSpaceWebNotificationListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsSpaceWebNotificationListener.java @@ -1,41 +1,66 @@ -/* +/** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2020 - 2022 Meeds Association contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.social; +package io.meeds.analytics.listener.social; -import static org.exoplatform.analytics.utils.AnalyticsUtils.addSpaceStatistics; -import static org.exoplatform.analytics.utils.AnalyticsUtils.addStatisticData; +import static io.meeds.analytics.utils.AnalyticsUtils.addSpaceStatistics; +import static io.meeds.analytics.utils.AnalyticsUtils.addStatisticData; + +import java.util.Arrays; +import java.util.List; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import org.exoplatform.analytics.model.StatisticData; import org.exoplatform.commons.api.persistence.ExoTransactional; +import org.exoplatform.services.listener.Asynchronous; import org.exoplatform.services.listener.Event; import org.exoplatform.services.listener.Listener; +import org.exoplatform.services.listener.ListenerService; import org.exoplatform.social.core.space.model.Space; import org.exoplatform.social.core.space.spi.SpaceService; import org.exoplatform.social.notification.model.SpaceWebNotificationItem; import org.exoplatform.social.notification.model.SpaceWebNotificationItemUpdate; import org.exoplatform.social.notification.service.SpaceWebNotificationService; +import io.meeds.analytics.model.StatisticData; + +import jakarta.annotation.PostConstruct; + +@Asynchronous +@Component public class AnalyticsSpaceWebNotificationListener extends Listener { - private SpaceService spaceService; + private static final List EVENT_NAMES = Arrays.asList("notification.read.item", + "notification.unread.item", + "notification.read.allItems"); + + @Autowired + private SpaceService spaceService; + + @Autowired + private ListenerService listenerService; - public AnalyticsSpaceWebNotificationListener(SpaceService spaceService) { - this.spaceService = spaceService; + @PostConstruct + public void init() { + EVENT_NAMES.forEach(name -> listenerService.addListener(name, this)); } @Override diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/BaseAttachmentAnalyticsListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/BaseAttachmentAnalyticsListener.java similarity index 67% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/BaseAttachmentAnalyticsListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/social/BaseAttachmentAnalyticsListener.java index 6d1cab43a..e4e5dcee3 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/social/BaseAttachmentAnalyticsListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/BaseAttachmentAnalyticsListener.java @@ -1,27 +1,31 @@ -/* +/** * This file is part of the Meeds project (https://meeds.io/). * - * Copyright (C) 2023 Meeds Association contact@meeds.io + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.social; +package io.meeds.analytics.listener.social; + +import static io.meeds.analytics.utils.AnalyticsUtils.addSpaceStatistics; +import static io.meeds.analytics.utils.AnalyticsUtils.addStatisticData; + +import java.util.Date; +import java.util.List; +import java.util.Map; -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.analytics.utils.AnalyticsUtils; -import org.exoplatform.commons.api.persistence.ExoTransactional; -import org.exoplatform.container.xml.InitParams; import org.exoplatform.services.listener.Event; import org.exoplatform.services.listener.Listener; import org.exoplatform.social.attachment.AttachmentPlugin; @@ -30,46 +34,32 @@ import org.exoplatform.social.core.space.model.Space; import org.exoplatform.social.core.space.spi.SpaceService; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import static org.exoplatform.analytics.utils.AnalyticsUtils.addSpaceStatistics; -import static org.exoplatform.analytics.utils.AnalyticsUtils.addStatisticData; +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.utils.AnalyticsUtils; +import io.meeds.common.ContainerTransactional; public abstract class BaseAttachmentAnalyticsListener extends Listener { - public static final String STATISTICS_ATTACH_OPERATION = "attachImages"; - - public static final String STATISTICS_DETACH_OPERATION = "removeImageAttachments"; - - public static final String ATTACHMENT_CREATED_EVENT = "attachment.created"; - - public static final String ATTACHMENT_DELETED_EVENT = "attachment.deleted"; - private final AttachmentService attachmentService; + public static final String STATISTICS_ATTACH_OPERATION = "attachImages"; - private SpaceService spaceService; + public static final String STATISTICS_DETACH_OPERATION = "removeImageAttachments"; - private List supportedObjectType; + public static final String ATTACHMENT_CREATED_EVENT = "attachment.created"; - protected BaseAttachmentAnalyticsListener(AttachmentService attachmentService, SpaceService spaceService, InitParams initParams) { - this.attachmentService = attachmentService; - this.spaceService = spaceService; - this.supportedObjectType = initParams.getValuesParam("supported-type").getValues(); - } + public static final String ATTACHMENT_DELETED_EVENT = "attachment.deleted"; @Override - @ExoTransactional + @ContainerTransactional public void onEvent(Event event) throws Exception { String username = event.getSource(); ObjectAttachmentId objectAttachment = event.getData(); - if (objectAttachment == null || !supportedObjectType.contains(objectAttachment.getObjectType())) { + if (objectAttachment == null || !getSupportedObjectType().contains(objectAttachment.getObjectType())) { return; } - Map attachmentPlugins = attachmentService.getAttachmentPlugins(); + Map attachmentPlugins = getAttachmentService().getAttachmentPlugins(); AttachmentPlugin attachmentPlugin = attachmentPlugins.get(objectAttachment.getObjectType()); long spaceId = attachmentPlugin.getSpaceId(objectAttachment.getObjectId()); @@ -93,7 +83,7 @@ public void onEvent(Event event) throws Exception { } private StatisticData buildStatisticData(String operation, ObjectAttachmentId objectAttachment, long spaceId, long userId) { - Space space = spaceService.getSpaceById(String.valueOf(spaceId)); + Space space = getSpaceService().getSpaceById(String.valueOf(spaceId)); StatisticData statisticData = new StatisticData(); statisticData.setModule(getModule(objectAttachment)); statisticData.setSubModule(getSubModule(objectAttachment)); @@ -107,6 +97,12 @@ private StatisticData buildStatisticData(String operation, ObjectAttachmentId ob protected void extendStatisticData(StatisticData statisticData, ObjectAttachmentId objectAttachment) { } + protected abstract AttachmentService getAttachmentService(); + + protected abstract SpaceService getSpaceService(); + + protected abstract List getSupportedObjectType(); + protected abstract String getModule(ObjectAttachmentId objectAttachment); protected abstract String getSubModule(ObjectAttachmentId objectAttachment); diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/websocket/WebSocketUIStatisticListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/websocket/WebSocketUIStatisticListener.java similarity index 76% rename from analytics-listeners/src/main/java/org/exoplatform/analytics/listener/websocket/WebSocketUIStatisticListener.java rename to analytics-listeners/src/main/java/io/meeds/analytics/listener/websocket/WebSocketUIStatisticListener.java index bb2b6bd14..a678c9f17 100644 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/listener/websocket/WebSocketUIStatisticListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/websocket/WebSocketUIStatisticListener.java @@ -1,50 +1,67 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.listener.websocket; +package io.meeds.analytics.listener.websocket; -import static org.exoplatform.analytics.utils.AnalyticsUtils.*; +import static io.meeds.analytics.utils.AnalyticsUtils.*; import java.util.*; import java.util.Map.Entry; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import org.exoplatform.analytics.api.service.AnalyticsService; -import org.exoplatform.analytics.api.service.StatisticWatcher; -import org.exoplatform.analytics.api.websocket.AnalyticsWebSocketMessage; -import org.exoplatform.analytics.api.websocket.AnalyticsWebSocketService; -import org.exoplatform.analytics.model.StatisticData; import org.exoplatform.services.listener.*; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.social.core.space.model.Space; +import io.meeds.analytics.api.service.AnalyticsService; +import io.meeds.analytics.api.websocket.AnalyticsWebSocketService; +import io.meeds.analytics.api.websocket.listener.AnalyticsWebSocketListener; +import io.meeds.analytics.model.AnalyticsWebSocketMessage; +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.model.StatisticWatcher; + +import jakarta.annotation.PostConstruct; + @Asynchronous +@Component public class WebSocketUIStatisticListener extends Listener { - private static final Log LOG = ExoLogger.getLogger(WebSocketUIStatisticListener.class); + private static final Log LOG = ExoLogger.getLogger(WebSocketUIStatisticListener.class); + + private static final List EVENT_NAMES = Arrays.asList(AnalyticsWebSocketListener.EXO_ANALYTICS_MESSAGE_EVENT); + + @Autowired + private AnalyticsService analyticsService; - private AnalyticsService analyticsService; + @Autowired + private ListenerService listenerService; - public WebSocketUIStatisticListener(AnalyticsService analyticsService) { - this.analyticsService = analyticsService; + @PostConstruct + public void init() { + EVENT_NAMES.forEach(name -> listenerService.addListener(name, this)); } @Override - public void onEvent(Event event) throws Exception { + public void onEvent(Event event) throws Exception { // NOSONAR AnalyticsWebSocketMessage message = event.getData(); long userId = getUserIdentityId(message.getUserName()); if (userId <= 0) { diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/job/SpacesStatisticsCountJob.java b/analytics-listeners/src/main/java/org/exoplatform/analytics/job/SpacesStatisticsCountJob.java deleted file mode 100644 index aca87136a..000000000 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/job/SpacesStatisticsCountJob.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.exoplatform.analytics.job; - -import org.quartz.*; - -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.analytics.utils.AnalyticsUtils; -import org.exoplatform.commons.utils.ListAccess; -import org.exoplatform.container.*; -import org.exoplatform.container.component.RequestLifeCycle; -import org.exoplatform.services.log.ExoLogger; -import org.exoplatform.services.log.Log; -import org.exoplatform.social.core.space.model.Space; -import org.exoplatform.social.core.space.spi.SpaceService; - -/** - * A job to collect statistics of users count - */ -@DisallowConcurrentExecution -public class SpacesStatisticsCountJob implements Job { - - private static final Log LOG = ExoLogger.getLogger(SpacesStatisticsCountJob.class); - - private ExoContainer container; - - private SpaceService spaceService; - - public SpacesStatisticsCountJob() { - this.container = PortalContainer.getInstance(); - } - - @Override - public void execute(JobExecutionContext context) throws JobExecutionException { - long startTime = System.currentTimeMillis(); - - ExoContainer currentContainer = ExoContainerContext.getCurrentContainer(); - ExoContainerContext.setCurrentContainer(container); - RequestLifeCycle.begin(this.container); - try { - ListAccess allSpaces = getSpaceService().getAllSpacesWithListAccess(); - int allSpacesCount = allSpaces.getSize(); - - StatisticData statisticData = new StatisticData(); - statisticData.setModule("social"); - statisticData.setSubModule("space"); - statisticData.setOperation("spacesCount"); - statisticData.setDuration(System.currentTimeMillis() - startTime); - statisticData.addParameter("countType", "allSpaces"); - statisticData.addParameter("count", allSpacesCount); - AnalyticsUtils.addStatisticData(statisticData); - } catch (Exception e) { - LOG.error("Error while computing spaces statistics", e); - } finally { - RequestLifeCycle.end(); - ExoContainerContext.setCurrentContainer(currentContainer); - } - } - - private SpaceService getSpaceService() { - if (spaceService == null) { - spaceService = this.container.getComponentInstanceOfType(SpaceService.class); - } - return spaceService; - } - -} diff --git a/analytics-listeners/src/main/java/org/exoplatform/analytics/job/UsersStatisticsCountJob.java b/analytics-listeners/src/main/java/org/exoplatform/analytics/job/UsersStatisticsCountJob.java deleted file mode 100644 index 8b8f457d3..000000000 --- a/analytics-listeners/src/main/java/org/exoplatform/analytics/job/UsersStatisticsCountJob.java +++ /dev/null @@ -1,116 +0,0 @@ -/** - * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.exoplatform.analytics.job; - -import org.quartz.*; - -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.analytics.utils.AnalyticsUtils; -import org.exoplatform.commons.utils.ListAccess; -import org.exoplatform.container.*; -import org.exoplatform.container.component.RequestLifeCycle; -import org.exoplatform.services.log.ExoLogger; -import org.exoplatform.services.log.Log; -import org.exoplatform.services.organization.*; -import org.exoplatform.social.core.identity.model.Identity; -import org.exoplatform.social.core.identity.provider.OrganizationIdentityProvider; -import org.exoplatform.social.core.manager.IdentityManager; - -/** - * A job to collect statistics of users count - */ -@DisallowConcurrentExecution -public class UsersStatisticsCountJob implements Job { - - private static final Log LOG = ExoLogger.getLogger(UsersStatisticsCountJob.class); - - private ExoContainer container; - - private OrganizationService organizationService; - - private IdentityManager identityManager; - - public UsersStatisticsCountJob() { - this.container = PortalContainer.getInstance(); - } - - @Override - public void execute(JobExecutionContext context) throws JobExecutionException { - long startTime = System.currentTimeMillis(); - - ExoContainer currentContainer = ExoContainerContext.getCurrentContainer(); - ExoContainerContext.setCurrentContainer(container); - RequestLifeCycle.begin(this.container); - try { - ListAccess allUsers = getOrganizationService().getUserHandler().findAllUsers(UserStatus.ANY); - int allUsersCount = allUsers.getSize(); - - ListAccess enabledIdentities = - getIdentityManager().getIdentitiesByProfileFilter(OrganizationIdentityProvider.NAME, - null, - false); - int enabledUsersCount = enabledIdentities.getSize(); - int disabledUsersCount = allUsersCount - enabledUsersCount; - - addUsersCountStatistic("allUsers", allUsersCount, startTime); - addUsersCountStatistic("enabledUsers", enabledUsersCount, startTime); - addUsersCountStatistic("disabledUsers", disabledUsersCount, startTime); - - startTime = System.currentTimeMillis(); - Group externalsGroup = getOrganizationService().getGroupHandler().findGroupById("/platform/externals"); - int enabledExternalUsersCount = 0; - if (externalsGroup != null) { - ListAccess externalMemberships = getOrganizationService().getMembershipHandler() - .findAllMembershipsByGroup(externalsGroup); - enabledExternalUsersCount = externalMemberships.getSize(); - } - addUsersCountStatistic("enabledExternalUsers", enabledExternalUsersCount, startTime); - addUsersCountStatistic("enabledInternalUsers", (enabledUsersCount - enabledExternalUsersCount), startTime); - } catch (Exception e) { - LOG.error("Error while computing users statistics", e); - } finally { - RequestLifeCycle.end(); - ExoContainerContext.setCurrentContainer(currentContainer); - } - } - - private void addUsersCountStatistic(String countType, int count, long startTime) { - StatisticData statisticData = new StatisticData(); - statisticData.setModule("portal"); - statisticData.setSubModule("account"); - statisticData.setOperation("usersCount"); - statisticData.setDuration(System.currentTimeMillis() - startTime); - statisticData.addParameter("countType", countType); - statisticData.addParameter("count", count); - AnalyticsUtils.addStatisticData(statisticData); - } - - private OrganizationService getOrganizationService() { - if (organizationService == null) { - organizationService = this.container.getComponentInstanceOfType(OrganizationService.class); - } - return organizationService; - } - - private IdentityManager getIdentityManager() { - if (identityManager == null) { - identityManager = this.container.getComponentInstanceOfType(IdentityManager.class); - } - return identityManager; - } - -} diff --git a/analytics-packaging/pom.xml b/analytics-packaging/pom.xml index 79a5c7089..077258d83 100644 --- a/analytics-packaging/pom.xml +++ b/analytics-packaging/pom.xml @@ -2,14 +2,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - org.exoplatform.addons.analytics + io.meeds.analytics analytics-parent 7.0.x-whitepaper-SNAPSHOT analytics-packaging pom - eXo Add-on:: eXo Analytics - Packaging - eXo Add-on:: eXo Analytics - Packaging + Meeds:: Analytics - Packaging ${project.groupId} diff --git a/analytics-services/pom.xml b/analytics-services/pom.xml index d1a4917d6..a41f8c728 100644 --- a/analytics-services/pom.xml +++ b/analytics-services/pom.xml @@ -1,16 +1,19 @@ diff --git a/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/ElasticsearchConnector.java b/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/ElasticsearchConnector.java new file mode 100644 index 000000000..02f18440d --- /dev/null +++ b/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/ElasticsearchConnector.java @@ -0,0 +1,366 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.analytics.elasticsearch; + +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; +import java.util.List; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.json.JSONException; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Component; + +import org.exoplatform.services.log.ExoLogger; +import org.exoplatform.services.log.Log; + +import io.meeds.analytics.elasticsearch.model.ElasticResponse; +import io.meeds.analytics.model.StatisticDataQueueEntry; + +import jakarta.annotation.PostConstruct; +import lombok.SneakyThrows; + +@Component +public class ElasticsearchConnector { + + private static final Log LOG = + ExoLogger.getExoLogger(ElasticsearchConnector.class); + + public static final int DEFAULT_MAX_HTTP_POOL_CONNECTIONS = 100; + + private static final long DAY_IN_MS = 86400000L; + + private static final String DAY_DATE_FORMAT = "yyyy-MM-dd"; + + public static final DateTimeFormatter DAY_DATE_FORMATTER = DateTimeFormatter.ofPattern(DAY_DATE_FORMAT) + .withResolverStyle(ResolverStyle.LENIENT); + + @Autowired + private ElasticsearchSettingService elasticsearchSettingService; + + private HttpClient httpClient; + + @PostConstruct + public void init() { + checkIndexTemplateExistence(); + LOG.info("Analytics getHttpClient() initialized and is ready to proceed analytics data"); + } + + public boolean sendCreateIndexRequest() { + String index = getIndex(); + if (sendIsIndexExistsRequest(index)) { + LOG.debug("Index {} already exists. Index creation requests will not be sent.", index); + return false; + } else { + sendTurnOffWriteOnAllAnalyticsIndexes(); + + ElasticResponse createIndexResponse = sendCreateIndex(index); + if (sendIsIndexExistsRequest(index)) { + LOG.info("New analytics index {} created.", index); + return true; + } else { + throw new IllegalStateException("Error creating index " + index + " on elasticsearch, response code = " + + createIndexResponse.getStatusCode() + " , response content : " + createIndexResponse.getMessage()); + } + } + } + + public void sendTurnOffWriteOnAllAnalyticsIndexes() { + if (sendIsIndexExistsRequest(elasticsearchSettingService.getIndexAlias())) { + String esQuery = elasticsearchSettingService.getTurnOffWriteOnAllAnalyticsIndexes(); + try { + sendPostRequest("_aliases", esQuery); + LOG.info("All analytics indexes switched to RO mode to prepare creation of a new index"); + } catch (Exception e) { + LOG.warn("Analytics old indexes seems to not be turned off on write access"); + } + } + } + + @Cacheable("analytics.indexExists") + public boolean sendIsIndexExistsRequest(String esIndex) { + ElasticResponse responseExists = sendGetRequest(esIndex, false); + return responseExists.getStatusCode() == HttpStatus.SC_OK; + } + + @CacheEvict("analytics.indexExists") + private ElasticResponse sendCreateIndex(String index) { + String esIndexSettings = elasticsearchSettingService.getCreateIndexRequestContent(); + return sendPutRequest(index, esIndexSettings); + } + + public boolean sendIsIndexTemplateExistsRequest() { + ElasticResponse responseExists = sendGetRequest("_index_template/" + elasticsearchSettingService.getIndexTemplate(), false); + return responseExists.getStatusCode() == HttpStatus.SC_OK; + } + + public void sendCreateBulkDocumentsRequest(List dataQueueEntries) { + if (dataQueueEntries == null || dataQueueEntries.isEmpty()) { + return; + } + + LOG.info("Indexing in bulk {} documents", dataQueueEntries.size()); + sendCreateIndexRequest(); + + StringBuilder request = new StringBuilder(); + for (StatisticDataQueueEntry statisticDataQueueEntry : dataQueueEntries) { + String documentId = String.valueOf(statisticDataQueueEntry.getId()); + String singleDocumentQuery = elasticsearchSettingService.getCreateDocumentRequestContent(documentId); + request.append(singleDocumentQuery); + } + + LOG.debug("Create documents request to ES: {}", request); + sendPutRequest("_bulk", request.toString()); + + refreshIndex(); + } + + public String sendRequest(String esQuery) { + ElasticResponse elasticResponse = sendPostRequest(elasticsearchSettingService.getIndexAlias() + "/_search", esQuery); + String response = elasticResponse.getMessage(); + int statusCode = elasticResponse.getStatusCode(); + if (StringUtils.isBlank(response)) { + response = "Empty response was sent by ES"; + } else if (!isError(elasticResponse)) { + org.json.JSONObject json = null; + try { + json = new JSONObject(response); + if (json.has("status") && isError(json.getInt("status"))) { + throw new IllegalStateException("Error occured while requesting ES HTTP error code: '" + statusCode + + "', HTTP response: '" + response + "'"); + } + } catch (JSONException e) { + throw new IllegalStateException("Error occured while requesting ES HTTP code: '" + statusCode + + "', Error parsing response to JSON format, content = '" + response + "'", e); + } + } + return response; + } + + public String retrieveAllAnalyticsIndexesMapping() { + ElasticResponse response = sendGetRequest(elasticsearchSettingService.getIndexAlias() + "/_mapping", false); + if (isError(response)) { + LOG.warn("Error getting mapping of analytics : - \t\tcode : {} - \t\tmessage: {}", + response.getStatusCode(), + response.getMessage()); + return null; + } else { + return response.getMessage(); + } + } + + public void refreshIndex() { + refreshIndex(elasticsearchSettingService.getIndexAlias()); + } + + public void refreshIndex(String index) { + sendPostRequest(index + "/_refresh", null); + } + + public ElasticResponse sendGetRequest(String uri) { + return sendGetRequest(uri, true); + } + + public ElasticResponse sendGetRequest(String uri, boolean handleResponse) { + ElasticResponse response = sendHttpGetRequest(elasticsearchSettingService.getUrlClient() + "/" + uri); + if (handleResponse) { + return handleESResponse(response, uri, null); + } else { + return response; + } + } + + public ElasticResponse sendHeadRequest(String uri) { + ElasticResponse response = sendHttpHeadRequest(elasticsearchSettingService.getUrlClient() + "/" + uri); + return handleESResponse(response, uri, null); + } + + public ElasticResponse sendPutRequest(String uri, String content) { + ElasticResponse response = sendHttpPutRequest(elasticsearchSettingService.getUrlClient() + "/" + uri, content); + return handleESResponse(response, uri, content); + } + + public ElasticResponse sendDeleteRequest(String uri) { + ElasticResponse response = sendHttpDeleteRequest(elasticsearchSettingService.getUrlClient() + "/" + uri); + return handleESResponse(response, uri, null); + } + + public ElasticResponse sendPostRequest(String uri, String content) { + ElasticResponse response = sendHttpPostRequest(elasticsearchSettingService.getUrlClient() + "/" + uri, content); + return handleESResponse(response, uri, content); + } + + @SneakyThrows + protected ElasticResponse sendHttpPostRequest(String url, String content) { + HttpPost httpTypeRequest = new HttpPost(url); + if (StringUtils.isNotBlank(content)) { + httpTypeRequest.setEntity(new StringEntity(content, ContentType.APPLICATION_JSON)); + } + return getHttpClient().execute(httpTypeRequest, this::handleHttpResponse); + } + + @SneakyThrows + protected ElasticResponse sendHttpPutRequest(String url, String content) { + HttpPut httpTypeRequest = new HttpPut(url); + if (StringUtils.isNotBlank(content)) { + httpTypeRequest.setEntity(new StringEntity(content, ContentType.APPLICATION_JSON)); + } + return getHttpClient().execute(httpTypeRequest, this::handleHttpResponse); + } + + @SneakyThrows + protected ElasticResponse sendHttpDeleteRequest(String url) { + HttpDelete httpDeleteRequest = new HttpDelete(url); + return getHttpClient().execute(httpDeleteRequest, this::handleHttpResponse); + } + + @SneakyThrows + protected ElasticResponse sendHttpGetRequest(String url) { + HttpGet httpGetRequest = new HttpGet(url); + return getHttpClient().execute(httpGetRequest, this::handleHttpResponse); + } + + @SneakyThrows + protected ElasticResponse sendHttpHeadRequest(String url) { + HttpHead httpHeadRequest = new HttpHead(url); + return getHttpClient().execute(httpHeadRequest, this::handleHttpResponse); + } + + /** + * Handle Http response receive from ES Log an INFO if the return status code + * is 2xx Log an ERROR if the return code is different from 2xx + * + * @param httpResponse The Http Response to handle + */ + @SneakyThrows + private ElasticResponse handleHttpResponse(ClassicHttpResponse httpResponse) throws IOException { + final HttpEntity entity = httpResponse.getEntity(); + int statusCode = httpResponse.getCode(); + return new ElasticResponse(EntityUtils.toString(entity), statusCode); + } + + private boolean isError(ElasticResponse response) { + return isError(response.getStatusCode()); + } + + private boolean isError(int status) { + return status / 100 != 2; + } + + private ElasticResponse handleESResponse(ElasticResponse response, String uri, String content) { + if (isError(response) || StringUtils.contains(response.getMessage(), "\"errors\":true")) { + throw new IllegalStateException(String.format("Error message returned from ES: %s. URI: %s. Content: %s", + response.getMessage(), + uri, + content)); + } else if (StringUtils.contains(response.getMessage(), "\"type\":\"version_conflict_engine_exception\"")) { + LOG.warn("ID conflict in some content: {}", response.getMessage()); + } + return response; + } + + private void checkIndexTemplateExistence() { + if (!sendIsIndexTemplateExistsRequest()) { + String indexTemplate = elasticsearchSettingService.getIndexTemplate(); + sendPostRequest("_index_template/" + indexTemplate, elasticsearchSettingService.getEsIndexTemplateQuery()); + if (sendIsIndexTemplateExistsRequest()) { + elasticsearchSettingService.storeCreatedIndexTemplate(); + LOG.info("Index Template {} created.", indexTemplate); + } else { + throw new IllegalStateException("Error while creating Index Template " + indexTemplate); + } + } + } + + private final String getIndex() { + return getIndex(System.currentTimeMillis() / getIndexPerDaysMs()); + } + + @Cacheable("analytics.indexName") + private final String getIndex(long indexPeriodIndex) { + long periodEpochMs = indexPeriodIndex * getIndexPerDaysMs(); + String indexSuffix = DAY_DATE_FORMATTER.format(Instant.ofEpochMilli(periodEpochMs) + .atZone(ZoneOffset.UTC)); + return elasticsearchSettingService.getIndexPrefix() + "_" + indexSuffix; + } + + private long getIndexPerDaysMs() { + return DAY_IN_MS * Math.max(elasticsearchSettingService.getIndexPerDays(), 1); + } + + private HttpClient getHttpClient() { + if (httpClient == null) { + + // Check if Basic Authentication need to be used + HttpClientConnectionManager clientConnectionManager = getClientConnectionManager(); + HttpClientBuilder httpClientBuilder = HttpClientBuilder + .create() + .disableAutomaticRetries() + .setConnectionManager(clientConnectionManager) + .setConnectionReuseStrategy(new DefaultConnectionReuseStrategy()); + if (StringUtils.isNotBlank(elasticsearchSettingService.getUsername())) { + BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials( + new AuthScope(null, -1), + new UsernamePasswordCredentials(elasticsearchSettingService.getUsername(), + elasticsearchSettingService.getPassword().toCharArray())); + this.httpClient = httpClientBuilder.setDefaultCredentialsProvider(credsProvider) + .build(); + } else { + this.httpClient = httpClientBuilder.build(); + } + } + return httpClient; + } + + protected HttpClientConnectionManager getClientConnectionManager() { + return new PoolingHttpClientConnectionManager(); + } + + protected int getMaxConnections() { + return DEFAULT_MAX_HTTP_POOL_CONNECTIONS; + } + +} diff --git a/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/ElasticsearchSettingService.java b/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/ElasticsearchSettingService.java new file mode 100644 index 000000000..39e3bfc06 --- /dev/null +++ b/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/ElasticsearchSettingService.java @@ -0,0 +1,261 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.analytics.elasticsearch; + +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_DURATION; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_ERROR_CODE; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_ERROR_MESSAGE; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_IS_ANALYTICS; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_MODULE; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_OPERATION; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_SPACE_ID; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_STATUS; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_SUB_MODULE; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_TIMESTAMP; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_USER_ID; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import org.exoplatform.commons.api.settings.SettingService; +import org.exoplatform.commons.api.settings.SettingValue; +import org.exoplatform.commons.api.settings.data.Context; +import org.exoplatform.commons.api.settings.data.Scope; +import org.exoplatform.commons.search.domain.Document; +import org.exoplatform.commons.utils.IOUtil; +import org.exoplatform.container.configuration.ConfigurationManager; +import org.exoplatform.services.log.ExoLogger; +import org.exoplatform.services.log.Log; + +import io.meeds.analytics.api.service.StatisticDataQueueService; +import io.meeds.analytics.model.StatisticData; + +import jakarta.annotation.PostConstruct; +import lombok.Getter; +import lombok.SneakyThrows; + +@Component +public class ElasticsearchSettingService { + + private static final Log LOG = + ExoLogger.getLogger(ElasticsearchSettingService.class); + + private static final String DEFAULT_ES_CLIENT_SERVER_URL = "http://127.0.0.1:9200"; + + private static final String ES_CLIENT_SERVER_URL = "exo.es.index.server.url"; + + private static final String ES_CLIENT_USERNAME = "exo.es.index.server.username"; + + private static final String ES_CLIENT_PWD = "exo.es.index.server.password"; // NOSONAR + + public static final String DEFAULT_ES_INDEX_TEMPLATE = "analytics_template"; + + public static final String ES_ANALYTICS_INDEX_PREFIX = "exo.es.analytics.index.prefix"; + + public static final String ES_ANALYTICS_INDEX_TEMPLATE = "exo.es.analytics.index.template"; + + public static final Context ES_ANALYTICS_CONTEXT = Context.GLOBAL.id("analytics"); + + public static final Scope ES_ANALYTICS_SCOPE = Scope.APPLICATION.id("analytics"); + + @Autowired + private SettingService settingService; + + @Autowired + private ConfigurationManager configurationManager; + + @Autowired + private StatisticDataQueueService analyticsQueueService; + + @Getter + @Value("${analytics.es.index.prefix:analytics}") + private String indexPrefix; + + @Getter + @Value("${analytics.es.index.template:analytics_template}") + private String indexTemplate; + + @Getter + @Value("${analytics.es.index.alias:analytics_alias}") + protected String indexAlias; + + @Getter + @Value("${analytics.es.replicas:0}") + protected int replicas; + + @Getter + @Value("${analytics.es.shards:1}") + protected int shards; + + @Value("${analytics.es.index.template.path:jar:/analytics-es-template.json}") + private String mappingFilePath; + + @Getter + @Value("${analytics.es.index.server.username:}") + private String username; + + @Getter + @Value("${analytics.es.index.server.password:}") + private String password; + + @Getter + @Value("${analytics.es.index.per.days:7}") + private int indexPerDays; + + @Getter + private String urlClient; + + private String esIndexTemplateQuery; + + @PostConstruct + public void init() { + if (StringUtils.isBlank(this.urlClient)) { + this.urlClient = System.getProperty(ES_CLIENT_SERVER_URL); + this.username = System.getProperty(ES_CLIENT_USERNAME); + this.password = System.getProperty(ES_CLIENT_PWD); + } + if (StringUtils.isBlank(this.urlClient)) { + this.urlClient = DEFAULT_ES_CLIENT_SERVER_URL; + } + + SettingValue indexTemplateValue = this.settingService.get(ES_ANALYTICS_CONTEXT, + ES_ANALYTICS_SCOPE, + ES_ANALYTICS_INDEX_TEMPLATE); + if (indexTemplateValue != null && indexTemplateValue.getValue() != null) { + String storedIndexTemplate = indexTemplateValue.getValue().toString(); + if (!StringUtils.equals(storedIndexTemplate, indexTemplate)) { + LOG.warn("Can't change index template from {} to {}. New index will be ignored.", storedIndexTemplate, indexTemplate); + indexTemplate = storedIndexTemplate; + } + } + } + + @SneakyThrows + public String getEsIndexTemplateQuery() { + if (StringUtils.isBlank(esIndexTemplateQuery)) { + InputStream mappingFileIS = configurationManager.getInputStream(mappingFilePath); + esIndexTemplateQuery = IOUtil.getStreamContentAsString(mappingFileIS); + esIndexTemplateQuery = esIndexTemplateQuery.replace(ElasticsearchSettingService.DEFAULT_ES_INDEX_TEMPLATE, + getIndexAlias()) + .replace("replica.number", + String.valueOf(getReplicas())) + .replace("shard.number", + String.valueOf(getShards())); + + } + return esIndexTemplateQuery; + } + + public String getCreateIndexRequestContent() { + return " {" + + "\"aliases\": {" + + " \"" + indexAlias + "\": {" + + " \"is_write_index\" : true" + + " }" + + "}" + + "}"; + } + + public String getTurnOffWriteOnAllAnalyticsIndexes() { + return "{" + + "\"actions\": [" + + " {" + + " \"add\": {" + + " \"index\": \"" + indexPrefix + "*\"," + + " \"alias\": \"" + indexAlias + "\"," + + " \"is_write_index\": false" + + " }" + + " }" + + "]" + + "}"; + } + + public String getCreateDocumentRequestContent(String id) { + JSONObject jsonObject = createCUDHeaderRequestContent(id); + Document document = create(id); + if (document == null) { + return null; + } + JSONObject createRequest = new JSONObject(); + createRequest.put("create", jsonObject); + return createRequest.toString(2) + "\n" + document.toJSON() + "\n"; + } + + private JSONObject createCUDHeaderRequestContent(String id) { + JSONObject cudHeader = new JSONObject(); + cudHeader.put("_index", indexAlias); + cudHeader.put("_id", id); + return cudHeader; + } + + public Document create(String idString) { + if (StringUtils.isBlank(idString)) { + throw new IllegalArgumentException("id is mandatory"); + } + long id = Long.parseLong(idString); + StatisticData data = this.analyticsQueueService.get(id); + if (data == null) { + LOG.warn("Can't find document with id {}", id); + return null; + } + String timestampString = String.valueOf(data.getTimestamp()); + + Map fields = new HashMap<>(); + fields.put("id", idString); + fields.put(FIELD_TIMESTAMP, timestampString); + fields.put(FIELD_USER_ID, String.valueOf(data.getUserId())); + fields.put(FIELD_SPACE_ID, String.valueOf(data.getSpaceId())); + fields.put(FIELD_MODULE, data.getModule()); + fields.put(FIELD_SUB_MODULE, data.getSubModule()); + fields.put(FIELD_OPERATION, data.getOperation()); + fields.put(FIELD_STATUS, String.valueOf(data.getStatus().ordinal())); + fields.put(FIELD_ERROR_CODE, String.valueOf(data.getErrorCode())); + fields.put(FIELD_ERROR_MESSAGE, data.getErrorMessage()); + fields.put(FIELD_DURATION, String.valueOf(data.getDuration())); + fields.put(FIELD_IS_ANALYTICS, "true"); + if (data.getParameters() != null && !data.getParameters().isEmpty()) { + fields.putAll(data.getParameters()); + } + Document esDocument = new Document(String.valueOf(id), + null, + null, + (Set) null, + fields); + if (data.getListParameters() != null && !data.getListParameters().isEmpty()) { + esDocument.setListFields(data.getListParameters()); + } + return esDocument; + } + + public void storeCreatedIndexTemplate() { + this.settingService.set(ES_ANALYTICS_CONTEXT, + ES_ANALYTICS_SCOPE, + ES_ANALYTICS_INDEX_TEMPLATE, + SettingValue.create(indexTemplate)); + } +} diff --git a/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/model/ElasticResponse.java b/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/model/ElasticResponse.java new file mode 100644 index 000000000..b3f649d0b --- /dev/null +++ b/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/model/ElasticResponse.java @@ -0,0 +1,34 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.analytics.elasticsearch.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ElasticResponse { + + private String message; + + private int statusCode; + +} diff --git a/analytics-services/src/main/java/org/exoplatform/analytics/es/processor/ElasticSearchStatisticDataProcessor.java b/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/processor/ElasticSearchStatisticDataProcessor.java similarity index 58% rename from analytics-services/src/main/java/org/exoplatform/analytics/es/processor/ElasticSearchStatisticDataProcessor.java rename to analytics-services/src/main/java/io/meeds/analytics/elasticsearch/processor/ElasticSearchStatisticDataProcessor.java index cad76297a..6775e4ce8 100644 --- a/analytics-services/src/main/java/org/exoplatform/analytics/es/processor/ElasticSearchStatisticDataProcessor.java +++ b/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/processor/ElasticSearchStatisticDataProcessor.java @@ -1,36 +1,40 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.es.processor; +package io.meeds.analytics.elasticsearch.processor; -import static org.exoplatform.analytics.utils.AnalyticsUtils.ES_ANALYTICS_PROCESSOR_ID; +import static io.meeds.analytics.utils.AnalyticsUtils.ES_ANALYTICS_PROCESSOR_ID; import java.util.List; -import org.exoplatform.analytics.api.processor.StatisticDataProcessorPlugin; -import org.exoplatform.analytics.es.AnalyticsESClient; -import org.exoplatform.analytics.model.StatisticDataQueueEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -public class ElasticSearchStatisticDataProcessor extends StatisticDataProcessorPlugin { +import io.meeds.analytics.elasticsearch.ElasticsearchConnector; +import io.meeds.analytics.model.StatisticDataQueueEntry; +import io.meeds.analytics.plugin.StatisticDataProcessorPlugin; - private AnalyticsESClient analyticsIndexingClient; +@Component +public class ElasticSearchStatisticDataProcessor extends StatisticDataProcessorPlugin { - public ElasticSearchStatisticDataProcessor(AnalyticsESClient analyticsIndexingClient) { - this.analyticsIndexingClient = analyticsIndexingClient; - } + @Autowired + private ElasticsearchConnector esClient; @Override public String getId() { @@ -39,13 +43,7 @@ public String getId() { @Override public void process(List processorQueueEntries) { - analyticsIndexingClient.sendCreateBulkDocumentsRequest(processorQueueEntries); - } - - @Override - public void init() { - analyticsIndexingClient.init(); - setInitialized(true); + esClient.sendCreateBulkDocumentsRequest(processorQueueEntries); } } diff --git a/analytics-services/src/main/java/org/exoplatform/analytics/es/service/ESAnalyticsService.java b/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/service/ElasticsearchAnalyticsService.java similarity index 78% rename from analytics-services/src/main/java/org/exoplatform/analytics/es/service/ESAnalyticsService.java rename to analytics-services/src/main/java/io/meeds/analytics/elasticsearch/service/ElasticsearchAnalyticsService.java index 8455f65c4..577799464 100644 --- a/analytics-services/src/main/java/org/exoplatform/analytics/es/service/ESAnalyticsService.java +++ b/analytics-services/src/main/java/io/meeds/analytics/elasticsearch/service/ElasticsearchAnalyticsService.java @@ -1,152 +1,179 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.es.service; +package io.meeds.analytics.elasticsearch.service; -import static org.exoplatform.analytics.utils.AnalyticsUtils.*; +import static io.meeds.analytics.utils.AnalyticsUtils.DEFAULT_FIELDS; +import static io.meeds.analytics.utils.AnalyticsUtils.FIELD_TIMESTAMP; +import static io.meeds.analytics.utils.AnalyticsUtils.collectionToJSONString; +import static io.meeds.analytics.utils.AnalyticsUtils.fixJSONStringFormat; +import static io.meeds.analytics.utils.AnalyticsUtils.fromJsonString; +import static io.meeds.analytics.utils.AnalyticsUtils.getJSONObject; +import static io.meeds.analytics.utils.AnalyticsUtils.toJsonString; import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.*; -import java.util.concurrent.*; -import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; -import org.json.*; -import org.picocontainer.Startable; - -import org.exoplatform.analytics.api.service.*; -import org.exoplatform.analytics.es.AnalyticsESClient; -import org.exoplatform.analytics.model.*; -import org.exoplatform.analytics.model.StatisticData.StatisticStatus; -import org.exoplatform.analytics.model.chart.*; -import org.exoplatform.analytics.model.filter.*; -import org.exoplatform.analytics.model.filter.AnalyticsFilter.Range; -import org.exoplatform.analytics.model.filter.aggregation.*; -import org.exoplatform.analytics.model.filter.search.AnalyticsFieldFilter; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; + import org.exoplatform.commons.api.settings.SettingService; import org.exoplatform.commons.api.settings.SettingValue; import org.exoplatform.commons.api.settings.data.Context; import org.exoplatform.commons.api.settings.data.Scope; -import org.exoplatform.container.ExoContainerContext; -import org.exoplatform.container.PortalContainer; -import org.exoplatform.container.component.RequestLifeCycle; -import org.exoplatform.container.xml.InitParams; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; -public class ESAnalyticsService implements AnalyticsService, Startable { +import io.meeds.analytics.api.service.AnalyticsService; +import io.meeds.analytics.elasticsearch.ElasticsearchConnector; +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.model.StatisticData.StatisticStatus; +import io.meeds.analytics.model.StatisticFieldMapping; +import io.meeds.analytics.model.StatisticFieldValue; +import io.meeds.analytics.model.StatisticWatcher; +import io.meeds.analytics.model.chart.ChartAggregationLabel; +import io.meeds.analytics.model.chart.ChartAggregationResult; +import io.meeds.analytics.model.chart.ChartAggregationValue; +import io.meeds.analytics.model.chart.ChartData; +import io.meeds.analytics.model.chart.ChartDataList; +import io.meeds.analytics.model.chart.PercentageChartResult; +import io.meeds.analytics.model.chart.PercentageChartValue; +import io.meeds.analytics.model.chart.TableColumnItemValue; +import io.meeds.analytics.model.chart.TableColumnResult; +import io.meeds.analytics.model.filter.AnalyticsFilter; +import io.meeds.analytics.model.filter.AnalyticsFilter.Range; +import io.meeds.analytics.model.filter.AnalyticsPercentageFilter; +import io.meeds.analytics.model.filter.AnalyticsPeriod; +import io.meeds.analytics.model.filter.AnalyticsPeriodType; +import io.meeds.analytics.model.filter.AnalyticsTableColumnFilter; +import io.meeds.analytics.model.filter.AnalyticsTableFilter; +import io.meeds.analytics.model.filter.aggregation.AnalyticsAggregation; +import io.meeds.analytics.model.filter.aggregation.AnalyticsAggregationType; +import io.meeds.analytics.model.filter.aggregation.AnalyticsPercentageLimit; +import io.meeds.analytics.model.filter.search.AnalyticsFieldFilter; +import io.meeds.common.ContainerTransactional; - private static final Log LOG = - ExoLogger.getLogger(ESAnalyticsService.class); +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.Getter; - private static final String ANALYTICS_ADMIN_PERMISSION_PARAM_NAME = "exo.analytics.admin.permissions"; +@Primary +@Service +public class ElasticsearchAnalyticsService implements AnalyticsService { - private static final String ANALYTICS_VIEW_ALL_PERMISSION_PARAM_NAME = "exo.analytics.viewall.permissions"; + private static final String VALUE_REQUEST_BODY_PARAM = "$value"; - private static final String ANALYTICS_VIEW_PERMISSION_PARAM_NAME = "exo.analytics.view.permissions"; + private static final String NAME_REQUEST_BODY_PARAM = "$name"; - private static final String RETURNED_AGGREGATION_DOCS_COUNT_PARAM_NAME = - "exo.analytics.aggregation.terms.doc_size"; + private static final String BUCKETS_RESPONSE_BODY = "buckets"; - private static final String AGGREGATION_KEYS_SEPARATOR = "-"; + private static final String AGGREGATIONS_RESPONSE_BODY = "aggregations"; - private static final String AGGREGATION_RESULT_PARAM = "aggregation_result"; + private static final String ERROR_PARSING_RESULTS_MESSAGE = + "Error parsing results with - filter: %s - query: %s - response: %s"; - private static final String AGGREGATION_RESULT_VALUE_PARAM = "aggregation_result_value"; + private static final String FILTER_AGGREGATIONS_IS_MANDATORY_MESSAGE = "Filter aggregations is mandatory"; - private static final String AGGREGATION_BUCKETS_VALUE_PARAM = "aggregation_buckets_value"; + private static final String FILTER_IS_MANDATORY_MESSAGE = "Filter is mandatory"; - private static final Context CONTEXT = Context.GLOBAL.id("ANALYTICS"); + private static final Log LOG = + ExoLogger.getLogger(ElasticsearchAnalyticsService.class); - private static final Scope ES_SCOPE = Scope.GLOBAL.id("elasticsearch"); + private static final String AGGREGATION_KEYS_SEPARATOR = "-"; - private static final String ES_AGGREGATED_MAPPING = "ES_AGGREGATED_MAPPING"; + private static final String AGGREGATION_RESULT_PARAM = "aggregation_result"; - private List uiWatcherPlugins = new ArrayList<>(); + private static final String AGGREGATION_RESULT_VALUE_PARAM = "aggregation_result_value"; - private List uiWatchers = new ArrayList<>(); + private static final String AGGREGATION_BUCKETS_VALUE_PARAM = "aggregation_buckets_value"; - private AnalyticsESClient esClient; + private static final Context CONTEXT = Context.GLOBAL.id("ANALYTICS"); - private SettingService settingService; + private static final Scope ES_SCOPE = Scope.GLOBAL.id("elasticsearch"); - private Map esMappings = new HashMap<>(); + private static final String ES_AGGREGATED_MAPPING = "ES_AGGREGATED_MAPPING"; - private ScheduledExecutorService esMappingUpdater = Executors.newScheduledThreadPool(1); + @Autowired + private ElasticsearchConnector esClient; - private List administratorsPermissions; + @Autowired + private SettingService settingService; - private List viewAllPermissions; + private List uiWatchers = new ArrayList<>(); - private List viewPermissions; + private Map esMappings = new HashMap<>(); - private int aggregationReturnedDocumentsSize = 1000; + private ScheduledExecutorService esMappingUpdater = Executors.newScheduledThreadPool(1); - public ESAnalyticsService(AnalyticsESClient esClient, - SettingService settingService, - InitParams params) { - this.esClient = esClient; - this.settingService = settingService; + @Value("${analytics.aggregation.terms.doc_size:200}") + private int aggregationReturnedDocumentsSize; - if (params != null && params.containsKey(ANALYTICS_ADMIN_PERMISSION_PARAM_NAME)) { - this.administratorsPermissions = params.getValuesParam(ANALYTICS_ADMIN_PERMISSION_PARAM_NAME).getValues(); - } else { - this.administratorsPermissions = Collections.emptyList(); - } - if (params != null && params.containsKey(ANALYTICS_VIEW_ALL_PERMISSION_PARAM_NAME)) { - this.viewAllPermissions = params.getValuesParam(ANALYTICS_VIEW_ALL_PERMISSION_PARAM_NAME).getValues(); - } else { - this.viewAllPermissions = Collections.emptyList(); - } - if (params != null && params.containsKey(ANALYTICS_VIEW_PERMISSION_PARAM_NAME)) { - this.viewPermissions = params.getValuesParam(ANALYTICS_VIEW_PERMISSION_PARAM_NAME).getValues(); - } else { - this.viewPermissions = Collections.emptyList(); - } - if (params != null && params.containsKey(RETURNED_AGGREGATION_DOCS_COUNT_PARAM_NAME)) { - this.aggregationReturnedDocumentsSize = Integer.parseInt(params.getValueParam(RETURNED_AGGREGATION_DOCS_COUNT_PARAM_NAME) - .getValue()); - } - } + @Getter + @Value("${analytics.admin.permission:*:/platform/analytics}") + List administratorsPermissions; - @Override + @Getter + @Value("${analytics.viewall.permissions:*:/platform/administrators}") + List viewAllPermissions; + + @Getter + @Value("${analytics.view.permission:*:/platform/users}") + List viewPermissions; + + @PostConstruct public void start() { // Can't be job, because the mapping retrival must be executed on each // cluster node - esMappingUpdater.scheduleAtFixedRate(() -> { - PortalContainer container = PortalContainer.getInstance(); - ExoContainerContext.setCurrentContainer(container); - RequestLifeCycle.begin(container); - try { - retrieveMapping(true); - } catch (Exception e) { - LOG.warn("Error while getting mapping from elasticsearch", e); - } finally { - RequestLifeCycle.end(); - } - }, 1, 2, TimeUnit.MINUTES); + esMappingUpdater.scheduleAtFixedRate(() -> retrieveMapping(true), + 1, + 2, + TimeUnit.MINUTES); } - @Override + @PreDestroy public void stop() { esMappingUpdater.shutdown(); } @Override + @ContainerTransactional public Set retrieveMapping(boolean forceRefresh) { if (!forceRefresh) { if (esMappings.isEmpty()) { @@ -220,7 +247,7 @@ public List retrieveFieldValues(String field, int limit) { @Override public PercentageChartResult computePercentageChartData(AnalyticsPercentageFilter percentageFilter) { if (percentageFilter == null) { - throw new IllegalArgumentException("Filter is mandatory"); + throw new IllegalArgumentException(FILTER_IS_MANDATORY_MESSAGE); } AnalyticsPeriod currentPeriod = percentageFilter.getCurrentAnalyticsPeriod(); AnalyticsPeriod previousPeriod = percentageFilter.getPreviousAnalyticsPeriod(); @@ -279,10 +306,10 @@ public TableColumnResult computeTableColumnData(TableColumnResult columnResult, int columnIndex, boolean isValue) { if (filter == null) { - throw new IllegalArgumentException("Filter is mandatory"); + throw new IllegalArgumentException(FILTER_IS_MANDATORY_MESSAGE); } if (filter.getAggregations() == null || filter.getAggregations().isEmpty()) { - throw new IllegalArgumentException("Filter aggregations is mandatory"); + throw new IllegalArgumentException(FILTER_AGGREGATIONS_IS_MANDATORY_MESSAGE); } String esQueryString = buildAnalyticsQuery(filter.getAggregations(), @@ -302,18 +329,21 @@ public TableColumnResult computeTableColumnData(TableColumnResult columnResult, jsonResponse, filter.getLimit()); } catch (JSONException e) { - throw new IllegalStateException("Error parsing results with - filter: " + filter + " - query: " + esQueryString - + " - response: " + jsonResponse, e); + throw new IllegalStateException(String.format(ERROR_PARSING_RESULTS_MESSAGE, + filter, + esQueryString, + jsonResponse), + e); } } @Override public ChartDataList computeChartData(AnalyticsFilter filter) { if (filter == null) { - throw new IllegalArgumentException("Filter is mandatory"); + throw new IllegalArgumentException(FILTER_IS_MANDATORY_MESSAGE); } if (filter.getAggregations() == null || filter.getAggregations().isEmpty()) { - throw new IllegalArgumentException("Filter aggregations is mandatory"); + throw new IllegalArgumentException(FILTER_AGGREGATIONS_IS_MANDATORY_MESSAGE); } String esQueryString = buildAnalyticsQuery(filter.getAggregations(), @@ -326,8 +356,11 @@ public ChartDataList computeChartData(AnalyticsFilter filter) { try { return buildChartDataFromESResponse(filter, jsonResponse); } catch (JSONException e) { - throw new IllegalStateException("Error parsing results with - filter: " + filter + " - query: " + esQueryString - + " - response: " + jsonResponse, e); + throw new IllegalStateException(String.format(ERROR_PARSING_RESULTS_MESSAGE, + filter, + esQueryString, + jsonResponse), + e); } } @@ -337,14 +370,14 @@ public PercentageChartValue computePercentageChartData(AnalyticsFilter filter, AnalyticsPeriod previousPeriod, boolean hasLimitAggregation) { if (filter == null) { - throw new IllegalArgumentException("Filter is mandatory"); + throw new IllegalArgumentException(FILTER_IS_MANDATORY_MESSAGE); } if (filter.getAggregations() == null || filter.getAggregations().isEmpty()) { - throw new IllegalArgumentException("Filter aggregations is mandatory"); + throw new IllegalArgumentException(FILTER_AGGREGATIONS_IS_MANDATORY_MESSAGE); } AnalyticsAggregation yAxisAggregation = filter.getYAxisAggregation(); - AnalyticsAggregationType aggregationType = yAxisAggregation == null ? AnalyticsAggregationType.COUNT - : yAxisAggregation.getType(); + AnalyticsAggregationType aggregationType = yAxisAggregation == null ? AnalyticsAggregationType.COUNT : + yAxisAggregation.getType(); String esQueryString = buildAnalyticsQuery(filter.getAggregations(), filter.getFilters(), aggregationType, @@ -357,8 +390,11 @@ public PercentageChartValue computePercentageChartData(AnalyticsFilter filter, try { return buildPercentageChartValuesFromESResponse(jsonResponse, currentPeriod, previousPeriod); } catch (JSONException e) { - throw new IllegalStateException("Error parsing results with - filter: " + filter + " - query: " + esQueryString - + " - response: " + jsonResponse, e); + throw new IllegalStateException(String.format(ERROR_PARSING_RESULTS_MESSAGE, + filter, + esQueryString, + jsonResponse), + e); } } @@ -377,21 +413,6 @@ public List retrieveData(AnalyticsFilter filter) { } } - @Override - public List getAdministratorsPermissions() { - return administratorsPermissions; - } - - @Override - public List getViewAllPermissions() { - return viewAllPermissions; - } - - @Override - public List getViewPermissions() { - return viewPermissions; - } - @Override public List getUIWatchers() { return uiWatchers; @@ -399,18 +420,20 @@ public List getUIWatchers() { @Override public StatisticWatcher getUIWatcher(String name) { - return getUIWatchers().stream().filter(watcher -> StringUtils.equals(name, watcher.getName())).findFirst().orElse(null); + return uiWatchers.stream() + .filter(watcher -> StringUtils.equals(name, watcher.getName())) + .findFirst() + .orElse(null); } @Override - public void addUIWatcherPlugin(StatisticUIWatcherPlugin uiWatcherPlugin) { - uiWatcherPlugins.add(uiWatcherPlugin); - uiWatchers.add(uiWatcherPlugin.getStatisticWatcher()); + public void addUIWatcher(StatisticWatcher uiWatcher) { + uiWatchers.add(uiWatcher); } private List buildFieldValuesResponse(String jsonResponse) throws JSONException { JSONObject json = new JSONObject(jsonResponse); - JSONObject aggregations = json.has("aggregations") ? json.getJSONObject("aggregations") : null; + JSONObject aggregations = json.has(AGGREGATIONS_RESPONSE_BODY) ? json.getJSONObject(AGGREGATIONS_RESPONSE_BODY) : null; if (aggregations == null) { return Collections.emptyList(); } @@ -418,7 +441,7 @@ private List buildFieldValuesResponse(String jsonResponse) if (result == null) { return Collections.emptyList(); } - JSONArray buckets = result.has("buckets") ? result.getJSONArray("buckets") : null; + JSONArray buckets = result.has(BUCKETS_RESPONSE_BODY) ? result.getJSONArray(BUCKETS_RESPONSE_BODY) : null; if (buckets == null) { return Collections.emptyList(); } @@ -506,11 +529,12 @@ private void appendSearchFilterConditions(List filters, St } else { filters = new ArrayList<>(filters); } - - esQuery.append(","); - esQuery.append(" \"query\": {"); - esQuery.append(" \"bool\" : {"); - esQuery.append(" \"must\" : ["); + esQuery.append(""" + , + "query": { + "bool" : { + "must" : [ + """); for (AnalyticsFieldFilter fieldFilter : filters) { String esFieldName = fieldFilter.getField(); StatisticFieldMapping fieldMapping = this.esMappings.get(esFieldName); @@ -518,83 +542,77 @@ private void appendSearchFilterConditions(List filters, St esFieldName = fieldMapping.getAggregationFieldName(); } - String esQueryValue = fieldMapping == null ? StatisticFieldMapping.computeESQueryValue(fieldFilter.getValueString()) - : fieldMapping.getESQueryValue(fieldFilter.getValueString()); + String esQueryValue = fieldMapping == null ? StatisticFieldMapping.computeESQueryValue(fieldFilter.getValueString()) : + fieldMapping.getESQueryValue(fieldFilter.getValueString()); switch (fieldFilter.getType()) { - case NOT_NULL: - esQuery.append(" {\"exists\" : {\"") - .append("field") - .append("\" : \"") - .append(esFieldName) - .append("\" }},"); - break; - case IS_NULL: - esQuery.append(" {\"bool\": {\"must_not\": {\"exists\": {\"field\": \"") - .append(esFieldName) - .append("\" }}}},"); - break; - case EQUAL: - esQuery.append(" {\"match\" : {\"") - .append(esFieldName) - .append("\" : ") - .append(esQueryValue) - .append(" }},"); - break; - case NOT_EQUAL: - esQuery.append(" {\"bool\": {\"must_not\": {\"match\" : {\"") - .append(esFieldName) - .append("\" : ") - .append(esQueryValue) - .append(" }}}},"); - break; - case GREATER: - esQuery.append(" {\"range\" : {\"") - .append(esFieldName) - .append("\" : {") - .append("\"gte\" : ") - .append(esQueryValue) - .append(" }}},"); - break; - case LESS: - esQuery.append(" {\"range\" : {\"") - .append(esFieldName) - .append("\" : {") - .append("\"lte\" : ") - .append(esQueryValue) - .append(" }}},"); - break; - case RANGE: - Range range = fieldFilter.getRange(); - esQuery.append(" {\"range\" : {\"") - .append(esFieldName) - .append("\" : {") - .append("\"gte\" : ") - .append(range.getMin()) - .append(",\"lte\" : ") - .append(range.getMax()) - .append(" }}},"); - break; - case IN_SET: - esQuery.append(" {\"terms\" : {\"") - .append(esFieldName) - .append("\" : ") - .append(collectionToJSONString(fieldFilter.getValueString())) - .append(" }},"); - break; - case NOT_IN_SET: - esQuery.append(" {\"bool\": {\"must_not\": {\"terms\" : {\"") - .append(esFieldName) - .append("\" : ") - .append(collectionToJSONString(fieldFilter.getValueString())) - .append(" }}}},"); - break; - default: - break; + case NOT_NULL: + esQuery.append(""" + {"exists" : {"field": "$name"}}, + """.replace(NAME_REQUEST_BODY_PARAM, esFieldName)); + break; + case IS_NULL: + esQuery.append(""" + {"bool" : {"must_not": {"exists": {"field": "$name"}}}}, + """.replace(NAME_REQUEST_BODY_PARAM, esFieldName)); + break; + case EQUAL: + esQuery.append(""" + {"match" : {"$name": $value}}, + """.replace(NAME_REQUEST_BODY_PARAM, esFieldName) + .replace(VALUE_REQUEST_BODY_PARAM, esQueryValue)); + break; + case NOT_EQUAL: + esQuery.append(""" + {"bool": {"must_not": {"match" : {"$name": $value}}}}, + """.replace(NAME_REQUEST_BODY_PARAM, esFieldName) + .replace(VALUE_REQUEST_BODY_PARAM, esQueryValue)); + break; + case GREATER: + esQuery.append(""" + {"range": {"$name": {"gte": $value}}}, + """.replace(NAME_REQUEST_BODY_PARAM, esFieldName) + .replace(VALUE_REQUEST_BODY_PARAM, esQueryValue)); + break; + case LESS: + esQuery.append(""" + {"range": {"$name": {"lte": $value}}}, + """.replace(NAME_REQUEST_BODY_PARAM, esFieldName) + .replace(VALUE_REQUEST_BODY_PARAM, esQueryValue)); + break; + case RANGE: + Range range = fieldFilter.getRange(); + esQuery.append(""" + {"range": { + "$name": { + "gte": $min, + "lte": $max + } + }}, + """.replace(NAME_REQUEST_BODY_PARAM, esFieldName) + .replace("$min", range.getMin()) + .replace("$max", range.getMax())); + break; + case IN_SET: + esQuery.append(""" + {"terms": {"$name": $value}}, + """.replace(NAME_REQUEST_BODY_PARAM, esFieldName) + .replace(VALUE_REQUEST_BODY_PARAM, collectionToJSONString(fieldFilter.getValueString()))); + break; + case NOT_IN_SET: + esQuery.append(""" + {"bool": {"must_not": {"terms": {"$name": $value}}}}, + """.replace(NAME_REQUEST_BODY_PARAM, esFieldName) + .replace(VALUE_REQUEST_BODY_PARAM, collectionToJSONString(fieldFilter.getValueString()))); + break; + default: + break; } } - esQuery.append(" ],"); - esQuery.append(" },"); - esQuery.append(" },"); + esQuery.append(""" + ], + }, + }, + """); } private void buildAggregationQuery(StringBuilder esQuery, @@ -610,8 +628,8 @@ private void buildAggregationQuery(StringBuilder esQuery, AnalyticsAggregationType aggregationType = aggregation.getType(); if (aggregationType.isUseInterval() && StringUtils.isBlank(aggregation.getInterval())) { - throw new IllegalStateException("Analytics aggregation type '" + aggregationType - + "' is using intervals while it has empty interval"); + throw new IllegalStateException("Analytics aggregation type '" + aggregationType + + "' is using intervals while it has empty interval"); } String fieldName = getAggregationFieldName(aggregationType); @@ -716,17 +734,17 @@ private void buildAggregationQuery(StringBuilder esQuery, if (i == (aggregationsSize - 2)) { String bucketAggregationType = null; switch (percentageAggregationType) { - case MIN: - bucketAggregationType = "min_bucket"; - break; - case MAX: - bucketAggregationType = "max_bucket"; - break; - case AVG: - bucketAggregationType = "avg_bucket"; - break; - default: - bucketAggregationType = "sum_bucket"; + case MIN: + bucketAggregationType = "min_bucket"; + break; + case MAX: + bucketAggregationType = "max_bucket"; + break; + case AVG: + bucketAggregationType = "avg_bucket"; + break; + default: + bucketAggregationType = "sum_bucket"; } String aggregationResultBucketName = AGGREGATION_RESULT_PARAM + ">" + AGGREGATION_RESULT_VALUE_PARAM + ".value"; endOfQuery.append(" },"); @@ -782,10 +800,8 @@ private void computePercentageLimits(AnalyticsPercentageFilter percentageFilter, percentageChartResult.setCurrentPeriodLimit(currentPeriodLimit); percentageChartResult.setPreviousPeriodLimit(previousPeriodLimit); - percentageFilter.setCurrentPeriodLimit(Math.round(currentPeriodLimit * percentageLimit.getPercentage() - / 100)); - percentageFilter.setPreviousPeriodLimit(Math.round(previousPeriodLimit * percentageLimit.getPercentage() - / 100)); + percentageFilter.setCurrentPeriodLimit(Math.round(currentPeriodLimit * percentageLimit.getPercentage() / 100)); + percentageFilter.setPreviousPeriodLimit(Math.round(previousPeriodLimit * percentageLimit.getPercentage() / 100)); } private void computePercentageValuesPerPeriod(PercentageChartResult percentageResult, @@ -825,7 +841,7 @@ private TableColumnResult buildTableColumnDataFromESResponse(TableColumnResult t tableColumnResult = new TableColumnResult(); } JSONObject json = new JSONObject(jsonResponse); - JSONObject aggregations = json.has("aggregations") ? json.getJSONObject("aggregations") : null; + JSONObject aggregations = json.has(AGGREGATIONS_RESPONSE_BODY) ? json.getJSONObject(AGGREGATIONS_RESPONSE_BODY) : null; if (aggregations == null) { return tableColumnResult; } @@ -833,7 +849,7 @@ private TableColumnResult buildTableColumnDataFromESResponse(TableColumnResult t if (result == null) { return tableColumnResult; } - JSONArray buckets = result.has("buckets") ? result.getJSONArray("buckets") : null; + JSONArray buckets = result.has(BUCKETS_RESPONSE_BODY) ? result.getJSONArray(BUCKETS_RESPONSE_BODY) : null; if (buckets == null) { return tableColumnResult; } @@ -850,8 +866,8 @@ private TableColumnResult buildTableColumnDataFromESResponse(TableColumnResult t if (!isCurrent && !previousPeriod.isInPeriod(timestamp)) { continue; } - if (bucket.has(AGGREGATION_RESULT_PARAM) && bucket.getJSONObject(AGGREGATION_RESULT_PARAM).has("buckets")) { - JSONArray subBuckets = bucket.getJSONObject(AGGREGATION_RESULT_PARAM).getJSONArray("buckets"); + if (bucket.has(AGGREGATION_RESULT_PARAM) && bucket.getJSONObject(AGGREGATION_RESULT_PARAM).has(BUCKETS_RESPONSE_BODY)) { + JSONArray subBuckets = bucket.getJSONObject(AGGREGATION_RESULT_PARAM).getJSONArray(BUCKETS_RESPONSE_BODY); for (int j = 0; j < subBuckets.length(); j++) { JSONObject subBucket = subBuckets.getJSONObject(j); String key = getResultKeyAsString(subBucket); @@ -881,7 +897,7 @@ private TableColumnResult buildTableColumnDataFromESResponse(TableColumnResult t } List itemsList = null; if (limit > 0) { - itemsList = itemValues.values().stream().limit(limit).collect(Collectors.toList()); + itemsList = itemValues.values().stream().limit(limit).toList(); } else { itemsList = new ArrayList<>(itemValues.values()); } @@ -899,8 +915,8 @@ private void computeColumnItemValue(TableColumnItemValue itemValue, } else if (bucket.has(AGGREGATION_RESULT_PARAM)) { JSONObject subAggregationResult = bucket.getJSONObject(AGGREGATION_RESULT_PARAM); List values = new ArrayList<>(); - if (subAggregationResult.has("buckets")) { - JSONArray subAggregationBuckets = subAggregationResult.getJSONArray("buckets"); + if (subAggregationResult.has(BUCKETS_RESPONSE_BODY)) { + JSONArray subAggregationBuckets = subAggregationResult.getJSONArray(BUCKETS_RESPONSE_BODY); for (int j = 0; j < subAggregationBuckets.length(); j++) { JSONObject subAggregationBucket = subAggregationBuckets.getJSONObject(j); values.add(getResultKeyAsString(subAggregationBucket)); @@ -932,7 +948,7 @@ private PercentageChartValue buildPercentageChartValuesFromESResponse(String jso AnalyticsPeriod previousPeriod) throws JSONException { PercentageChartValue percentageChartValue = new PercentageChartValue(); JSONObject json = new JSONObject(jsonResponse); - JSONObject aggregations = json.getJSONObject("aggregations"); + JSONObject aggregations = json.getJSONObject(AGGREGATIONS_RESPONSE_BODY); if (aggregations == null) { return percentageChartValue; } @@ -950,7 +966,7 @@ private PercentageChartValue buildPercentageChartValuesFromESResponse(String jso percentageChartValue.setPreviousPeriodValue(valueDouble); } } else if (aggregations.has(AGGREGATION_RESULT_PARAM)) { - JSONArray buckets = aggregations.getJSONObject(AGGREGATION_RESULT_PARAM).getJSONArray("buckets"); + JSONArray buckets = aggregations.getJSONObject(AGGREGATION_RESULT_PARAM).getJSONArray(BUCKETS_RESPONSE_BODY); Map values = new HashMap<>(); if (buckets.length() > 0) { for (int i = 0; i < buckets.length(); i++) { @@ -975,7 +991,7 @@ private PercentageChartValue buildPercentageChartValuesFromESResponse(String jso .filter(aggregationValue -> { long timestamp = aggregationValue.getKey(); return timestamp < currentPeriod.getToInMS() - && timestamp >= currentPeriod.getFromInMS(); + && timestamp >= currentPeriod.getFromInMS(); }) .map(Map.Entry::getValue) @@ -990,7 +1006,7 @@ private PercentageChartValue buildPercentageChartValuesFromESResponse(String jso .filter(aggregationValue -> { long timestamp = aggregationValue.getKey(); return timestamp < previousPeriod.getToInMS() - && timestamp >= previousPeriod.getFromInMS(); + && timestamp >= previousPeriod.getFromInMS(); }) .map(Map.Entry::getValue) @@ -1009,7 +1025,7 @@ private ChartDataList buildChartDataFromESResponse(AnalyticsFilter filter, Strin ChartDataList chartsData = new ChartDataList(lang); JSONObject json = new JSONObject(jsonResponse); - JSONObject aggregations = json.getJSONObject("aggregations"); + JSONObject aggregations = json.getJSONObject(AGGREGATIONS_RESPONSE_BODY); if (aggregations == null) { return chartsData; } @@ -1034,7 +1050,7 @@ private void computeAggregatedResultEntry(AnalyticsFilter filter, String lang = filter.getLang(); JSONObject aggsResult = aggregations.getJSONObject(AGGREGATION_RESULT_PARAM); - JSONArray buckets = aggsResult.getJSONArray("buckets"); + JSONArray buckets = aggsResult.getJSONArray(BUCKETS_RESPONSE_BODY); if (buckets.length() > 0) { int nextLevel = level + 1; for (int i = 0; i < buckets.length(); i++) { @@ -1059,7 +1075,7 @@ private void computeAggregatedResultEntry(AnalyticsFilter filter, List labels = childAggregationValues.stream() .map(ChartAggregationValue::getFieldLabel) - .collect(Collectors.toList()); + .toList(); String label = StringUtils.join(labels, AGGREGATION_KEYS_SEPARATOR); ChartAggregationLabel chartLabel = new ChartAggregationLabel(childAggregationValues, label, lang); diff --git a/analytics-services/src/main/java/org/exoplatform/analytics/queue/service/DummyStatisticDataQueueService.java b/analytics-services/src/main/java/io/meeds/analytics/queue/service/DummyStatisticDataQueueService.java similarity index 71% rename from analytics-services/src/main/java/org/exoplatform/analytics/queue/service/DummyStatisticDataQueueService.java rename to analytics-services/src/main/java/io/meeds/analytics/queue/service/DummyStatisticDataQueueService.java index 5913dd972..9e96888de 100644 --- a/analytics-services/src/main/java/org/exoplatform/analytics/queue/service/DummyStatisticDataQueueService.java +++ b/analytics-services/src/main/java/io/meeds/analytics/queue/service/DummyStatisticDataQueueService.java @@ -1,55 +1,70 @@ /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.exoplatform.analytics.queue.service; +package io.meeds.analytics.queue.service; import java.math.BigInteger; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; -import org.picocontainer.Startable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.exoplatform.analytics.api.service.StatisticDataProcessorService; -import org.exoplatform.analytics.api.service.StatisticDataQueueService; -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.analytics.model.StatisticDataQueueEntry; -import org.exoplatform.container.ExoContainerContext; -import org.exoplatform.container.PortalContainer; -import org.exoplatform.container.component.RequestLifeCycle; import org.exoplatform.services.cache.CacheService; import org.exoplatform.services.cache.ExoCache; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; -public class DummyStatisticDataQueueService implements StatisticDataQueueService, Startable { +import io.meeds.analytics.api.service.StatisticDataProcessorService; +import io.meeds.analytics.api.service.StatisticDataQueueService; +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.model.StatisticDataQueueEntry; +import io.meeds.common.ContainerTransactional; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + +@Primary +@Service +public class DummyStatisticDataQueueService implements StatisticDataQueueService { private static final Log LOG = ExoLogger.getLogger(DummyStatisticDataQueueService.class); private static final String ANALYTICS_QUEUE_CACHE_NAME = "analytics.queue"; - private ExoCache statisticQueueCache = null; - + @Autowired private StatisticDataProcessorService statisticDataProcessorService; - private ScheduledExecutorService queueProcessingExecutor = null; + @Autowired + private CacheService cacheService = null; - private PortalContainer container = null; + private ExoCache statisticQueueCache = null; + + private ScheduledExecutorService queueProcessingExecutor = + Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setNameFormat("Analytics-ingestor-%d") + .build()); private BigInteger totalExecutionTime = BigInteger.ZERO; @@ -57,29 +72,21 @@ public class DummyStatisticDataQueueService implements StatisticDataQueueService private long executionCount = 0; - public DummyStatisticDataQueueService(PortalContainer container, - StatisticDataProcessorService statisticDataProcessorService, - CacheService cacheService) { - this.statisticDataProcessorService = statisticDataProcessorService; - this.container = container; + @PostConstruct + public void init() { this.statisticQueueCache = cacheService.getCacheInstance(ANALYTICS_QUEUE_CACHE_NAME); + // Can't be job, because each cluster node must process its in-memory queue + this.queueProcessingExecutor.scheduleAtFixedRate(this::processQueueTransactional, 0, 10, TimeUnit.SECONDS); + } - ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("Analytics-ingestor-%d").build(); - this.queueProcessingExecutor = Executors.newSingleThreadScheduledExecutor(namedThreadFactory); + @PreDestroy + public void shutdown() { + queueProcessingExecutor.shutdown(); } - @Override - public void start() { - // Can't be job, because each cluster node must process its in-memory queue - queueProcessingExecutor.scheduleAtFixedRate(() -> { - ExoContainerContext.setCurrentContainer(this.container); - RequestLifeCycle.begin(this.container); - try { - processQueue(); - } finally { - RequestLifeCycle.end(); - } - }, 0, 10, TimeUnit.SECONDS); + @ContainerTransactional + public void processQueueTransactional() { + processQueue(); } @Override @@ -103,11 +110,6 @@ public void processQueue() { } } - @Override - public void stop() { - queueProcessingExecutor.shutdown(); - } - @Override public void put(StatisticData data) { StatisticDataQueueEntry statisticDataQueueEntry = new StatisticDataQueueEntry(data); diff --git a/analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsESClient.java b/analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsESClient.java deleted file mode 100644 index bd6bfb783..000000000 --- a/analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsESClient.java +++ /dev/null @@ -1,429 +0,0 @@ -/** - * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.exoplatform.analytics.es; - -import java.io.InputStream; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.ResolverStyle; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.lang3.StringUtils; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.client5.http.io.HttpClientConnectionManager; -import org.json.JSONException; - -import org.exoplatform.analytics.model.StatisticDataQueueEntry; -import org.exoplatform.commons.search.es.client.ElasticClient; -import org.exoplatform.commons.search.es.client.ElasticClientException; -import org.exoplatform.commons.search.es.client.ElasticIndexingAuditTrail; -import org.exoplatform.commons.search.es.client.ElasticResponse; -import org.exoplatform.commons.utils.IOUtil; -import org.exoplatform.container.configuration.ConfigurationManager; -import org.exoplatform.container.xml.InitParams; -import org.exoplatform.services.log.ExoLogger; -import org.exoplatform.services.log.Log; - -public class AnalyticsESClient extends ElasticClient { - - private static final Log LOG = - ExoLogger.getExoLogger(AnalyticsESClient.class); - - private static final String DEFAULT_ES_CLIENT_SERVER_URL = "http://127.0.0.1:9200"; - - private static final String ES_CLIENT_SERVER_URL = "exo.es.index.server.url"; - - private static final String ES_CLIENT_USERNAME = "exo.es.index.server.username"; - - private static final String ES_CLIENT_PWD = "exo.es.index.server.password"; // NOSONAR - - private static final String ES_ANALYTICS_CLIENT_SERVER_URL = "exo.es.analytics.index.server.url"; - - private static final String ES_ANALYTICS_CLIENT_USERNAME = "exo.es.analytics.index.server.username"; - - private static final String ES_ANALYTICS_CLIENT_PWD = "exo.es.analytics.index.server.password"; // NOSONAR - - private static final String INDEX_TEMPLATE_FILE_PATH_PARAM = "index.template.file.path"; - - private static final String ES_ANALYTICS_INDEX_PER_DAYS = "exo.es.analytics.index.per.days"; - - private static final long DAY_IN_MS = 86400000L; - - private static final String DAY_DATE_FORMAT = "yyyy-MM-dd"; - - public static final DateTimeFormatter DAY_DATE_FORMATTER = DateTimeFormatter.ofPattern(DAY_DATE_FORMAT) - .withResolverStyle(ResolverStyle.LENIENT); - - private AnalyticsIndexingServiceConnector analyticsIndexingConnector; - - private AnalyticsElasticContentRequestBuilder elasticContentRequestBuilder; - - private Set existingIndexes = new HashSet<>(); - - private Map indexSuffixPerDayIndice = new HashMap<>(); - - private int indexPerDays; - - private String esIndexTemplateQuery; - - private String username; - - private String password; - - public AnalyticsESClient(ConfigurationManager configurationManager, // NOSONAR - AnalyticsElasticContentRequestBuilder elasticContentRequestBuilder, - AnalyticsIndexingServiceConnector analyticsIndexingConnector, - ElasticIndexingAuditTrail auditTrail, - InitParams initParams) { - super(auditTrail); - this.analyticsIndexingConnector = analyticsIndexingConnector; - this.elasticContentRequestBuilder = elasticContentRequestBuilder; - - if (initParams != null) { - if (initParams.containsKey(ES_ANALYTICS_CLIENT_SERVER_URL)) { - this.urlClient = initParams.getValueParam(ES_ANALYTICS_CLIENT_SERVER_URL).getValue(); - } - if (initParams.containsKey(ES_ANALYTICS_CLIENT_USERNAME)) { - this.username = initParams.getValueParam(ES_ANALYTICS_CLIENT_USERNAME).getValue(); - } - if (initParams.containsKey(ES_ANALYTICS_CLIENT_PWD)) { - this.password = initParams.getValueParam(ES_ANALYTICS_CLIENT_PWD).getValue(); - } - if (initParams.containsKey(INDEX_TEMPLATE_FILE_PATH_PARAM)) { - String mappingFilePath = initParams.getValueParam(INDEX_TEMPLATE_FILE_PATH_PARAM).getValue(); - try { - this.esIndexTemplateQuery = getFileContent(configurationManager, mappingFilePath); - } catch (Exception e) { - LOG.error("Can't read elasticsearch index mapping from path {}", mappingFilePath, e); - } - } - if (initParams.containsKey(ES_ANALYTICS_INDEX_PER_DAYS)) { - this.indexPerDays = Integer.parseInt(initParams.getValueParam(ES_ANALYTICS_INDEX_PER_DAYS).getValue()); - } - } - if (StringUtils.isBlank(this.urlClient)) { - this.urlClient = System.getProperty(ES_CLIENT_SERVER_URL); - this.username = System.getProperty(ES_CLIENT_USERNAME); - this.password = System.getProperty(ES_CLIENT_PWD); - } - if (StringUtils.isBlank(this.urlClient)) { - this.urlClient = DEFAULT_ES_CLIENT_SERVER_URL; - } - if (StringUtils.isBlank(this.esIndexTemplateQuery)) { - LOG.error("Empty elasticsearch index mapping file path parameter"); - } - initHttpClient(); - } - - public void init() { - checkIndexTemplateExistence(); - LOG.info("Analytics client initialized and is ready to proceed analytics data"); - } - - public boolean sendCreateIndexRequest(String index) { - if (sendIsIndexExistsRequest(index)) { - LOG.debug("Index {} already exists. Index creation requests will not be sent.", index); - return false; - } else { - sendTurnOffWriteOnAllAnalyticsIndexes(); - - String esIndexSettings = elasticContentRequestBuilder.getCreateIndexRequestContent(analyticsIndexingConnector); - ElasticResponse createIndexResponse = sendHttpPutRequest(index, esIndexSettings); - - if (sendIsIndexExistsRequest(index)) { - LOG.info("New analytics index {} created.", index); - return true; - } else { - throw new IllegalStateException("Error creating index " + index + " on elasticsearch, response code = " - + createIndexResponse.getStatusCode() + " , response content : " - + createIndexResponse.getMessage()); - } - } - } - - public void sendTurnOffWriteOnAllAnalyticsIndexes() { - if (sendIsIndexExistsRequest(analyticsIndexingConnector.getIndexAlias())) { - String esQuery = elasticContentRequestBuilder.getTurnOffWriteOnAllAnalyticsIndexes(analyticsIndexingConnector); - try { - sendHttpPostRequest("_aliases", esQuery); - LOG.info("All analytics indexes switched to RO mode to prepare creation of a new index"); - } catch (ElasticClientException e) { - LOG.warn("Analytics old indexes seems to not be turned off on write access"); - } - } - } - - public boolean sendIsIndexExistsRequest(String esIndex) { - if (existingIndexes.contains(esIndex)) { - return true; - } - String url = urlClient + "/" + esIndex; - ElasticResponse responseExists = super.sendHttpGetRequest(url); - boolean indexExists = responseExists.getStatusCode() == HttpStatus.SC_OK; - if (indexExists) { - existingIndexes.add(esIndex); - } - return indexExists; - } - - public boolean sendIsIndexTemplateExistsRequest() { - String url = urlClient + "/_index_template/" + analyticsIndexingConnector.getIndexTemplate(); - ElasticResponse responseExists = super.sendHttpGetRequest(url); - return responseExists.getStatusCode() == HttpStatus.SC_OK; - } - - public void sendCreateBulkDocumentsRequest(List dataQueueEntries) { - if (dataQueueEntries == null || dataQueueEntries.isEmpty()) { - return; - } - - LOG.info("Indexing in bulk {} documents", dataQueueEntries.size()); - - checkIndexExistence(dataQueueEntries); - - StringBuilder request = new StringBuilder(); - for (StatisticDataQueueEntry statisticDataQueueEntry : dataQueueEntries) { - String documentId = String.valueOf(statisticDataQueueEntry.getId()); - String singleDocumentQuery = elasticContentRequestBuilder.getCreateDocumentRequestContent(analyticsIndexingConnector, - documentId); - request.append(singleDocumentQuery); - } - - LOG.debug("Create documents request to ES: {}", request); - sendHttpPutRequest("_bulk", request.toString()); - - refreshIndex(); - } - - public String sendRequest(String esQuery) { - ElasticResponse elasticResponse = sendHttpPostRequest(analyticsIndexingConnector.getIndexAlias() + "/_search", esQuery); - String response = elasticResponse.getMessage(); - int statusCode = elasticResponse.getStatusCode(); - if (ElasticIndexingAuditTrail.isError(statusCode) || StringUtils.isBlank(response)) { - if (StringUtils.isBlank(response)) { - response = "Empty response was sent by ES"; - } - } else { - org.json.JSONObject json = null; - try { - json = new org.json.JSONObject(response); - if (json.has("status") && ElasticIndexingAuditTrail.isError(json.getInt("status"))) { - throw new IllegalStateException("Error occured while requesting ES HTTP error code: '" + statusCode - + "', HTTP response: '" - + response + "'"); - } - } catch (JSONException e) { - throw new IllegalStateException("Error occured while requesting ES HTTP code: '" + statusCode - + "', Error parsing response to JSON format, content = '" + response + "'", e); - } - } - return response; - } - - public String retrieveAllAnalyticsIndexesMapping() { - String url = urlClient + "/" + analyticsIndexingConnector.getIndexAlias() + "/_mapping"; - ElasticResponse response = super.sendHttpGetRequest(url); - if (ElasticIndexingAuditTrail.isError(response.getStatusCode())) { - LOG.warn("Error getting mapping of analytics : - \t\tcode : {} - \t\tmessage: {}", - response.getStatusCode(), - response.getMessage()); - return null; - } else { - return response.getMessage(); - } - } - - @Override - protected ElasticResponse sendHttpHeadRequest(String uri) { - String url = urlClient + "/" + uri; - ElasticResponse response = super.sendHttpHeadRequest(url); - try { - handleESResponse(response); - } catch (Exception e) { - throw new ElasticClientException("Error sending HEAD request '" + uri + "'", e); - } - return response; - } - - @Override - public ElasticResponse sendHttpGetRequest(String uri) { - return sendHttpGetRequest(null, uri); - } - - public ElasticResponse sendHttpGetRequest(String urlClient, String uri) { - if (StringUtils.isBlank(urlClient)) { - urlClient = this.urlClient; - } - String url = urlClient + "/" + uri; - ElasticResponse response = super.sendHttpGetRequest(url); - try { - handleESResponse(response); - } catch (Exception e) { - throw new ElasticClientException("Error sending GET request '" + url + "'", e); - } - return response; - } - - @Override - public ElasticResponse sendHttpPutRequest(String uri, String content) { - String url = urlClient + "/" + uri; - ElasticResponse response = super.sendHttpPutRequest(url, content); - try { - handleESResponse(response); - } catch (Exception e) { - throw new ElasticClientException("Error sending PUT request '" + url + "' with content = '" + content + "'", e); - } - return response; - } - - @Override - public ElasticResponse sendHttpDeleteRequest(String uri) { - String url = urlClient + "/" + uri; - ElasticResponse response = super.sendHttpDeleteRequest(url); - try { - handleESResponse(response); - } catch (Exception e) { - throw new ElasticClientException("Error sending 'DELETE' request '" + url + "'", e); - } - return response; - } - - @Override - public ElasticResponse sendHttpPostRequest(String uri, String content) { - String url = urlClient + "/" + uri; - ElasticResponse response = super.sendHttpPostRequest(url, content); - try { - handleESResponse(response); - } catch (Exception e) { - throw new ElasticClientException("Error sending POST request '" + url + "' with content = '" + content + "'", e); - } - return response; - } - - public String getIndexSuffix(long timestamp) { - long indexSuffixLong = timestamp / (DAY_IN_MS * indexPerDays); - String indexSuffix = indexSuffixPerDayIndice.get(indexSuffixLong); - if (indexSuffix != null) { - return indexSuffix; - } - indexSuffix = DAY_DATE_FORMATTER.format(Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.UTC)); - indexSuffixPerDayIndice.put(indexSuffixLong, indexSuffix); - return indexSuffix; - } - - public void refreshIndex() { - refreshIndex(analyticsIndexingConnector.getIndexAlias()); - } - - public void refreshIndex(String index) { - sendHttpPostRequest(index + "/_refresh", null); - } - - public int getIndexPerDays() { - return indexPerDays; - } - - @Override - protected String getEsUsernameProperty() { - return username; - } - - @Override - protected String getEsPasswordProperty() { - return password; - } - - @Override - protected HttpClientConnectionManager getClientConnectionManager() { - return new PoolingHttpClientConnectionManager(); - } - - private void handleESResponse(ElasticResponse response) { - if (response.getStatusCode() != 200) { - throw new ElasticClientException(response.getMessage()); - } - if (StringUtils.contains(response.getMessage(), "\"type\":\"version_conflict_engine_exception\"")) { - LOG.warn("ID conflict in some content", response.getMessage()); - return; - } - if (response.getStatusCode() != 200 || StringUtils.contains(response.getMessage(), "\"errors\":true")) { - throw new ElasticClientException(response.getMessage()); - } - } - - private void checkIndexExistence(List dataQueueEntries) { - Set indexes = new HashSet<>(); - for (StatisticDataQueueEntry statisticDataQueueEntry : dataQueueEntries) { - long timestamp = statisticDataQueueEntry.getStatisticData().getTimestamp(); - indexes.add(getIndex(timestamp)); - } - for (String index : indexes) { - sendCreateIndexRequest(index); - } - } - - private void checkIndexTemplateExistence() { - String indexTemplate = analyticsIndexingConnector.getIndexTemplate(); - if (sendIsIndexTemplateExistsRequest()) { - LOG.debug("Index Template {} already exists. Index creation requests will not be sent.", - indexTemplate); - } else { - long startTime = System.currentTimeMillis(); - esIndexTemplateQuery = esIndexTemplateQuery.replace(AnalyticsIndexingServiceConnector.DEFAULT_ES_INDEX_TEMPLATE, - analyticsIndexingConnector.getIndexAlias()) - .replace("replica.number", - String.valueOf(analyticsIndexingConnector.getReplicas())) - .replace("shard.number", String.valueOf(analyticsIndexingConnector.getShards())); - ElasticResponse responseCreate = sendHttpPostRequest("_index_template/" + indexTemplate, esIndexTemplateQuery); - auditTrail.audit("create_index_template", - null, - indexTemplate, - responseCreate.getStatusCode(), - responseCreate.getMessage(), - (System.currentTimeMillis() - startTime)); - - if (sendIsIndexTemplateExistsRequest()) { - LOG.info("Index Template {} created.", indexTemplate); - analyticsIndexingConnector.storeCreatedIndexTemplate(); - } else { - throw new IllegalStateException("Index Template " + indexTemplate - + " isn't created successfully"); - } - } - } - - private final String getIndex(long timestamp) { - if (indexPerDays > 0) { - String indexSuffix = getIndexSuffix(timestamp); - return analyticsIndexingConnector.getIndexPrefix() + "_" + indexSuffix; - } else { - return null; - } - } - - private String getFileContent(ConfigurationManager configurationManager, String filePath) throws Exception { - InputStream mappingFileIS = configurationManager.getInputStream(filePath); - return IOUtil.getStreamContentAsString(mappingFileIS); - } - -} diff --git a/analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsElasticContentRequestBuilder.java b/analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsElasticContentRequestBuilder.java deleted file mode 100644 index 9a92ccf19..000000000 --- a/analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsElasticContentRequestBuilder.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.exoplatform.analytics.es; - -import org.exoplatform.commons.search.es.client.ElasticContentRequestBuilder; -import org.exoplatform.commons.search.index.impl.ElasticIndexingServiceConnector; - -public class AnalyticsElasticContentRequestBuilder extends ElasticContentRequestBuilder { - - @Override - public String getCreateIndexRequestContent(ElasticIndexingServiceConnector connector) { - return " {" + - "\"aliases\": {" + - " \"" + connector.getIndexAlias() + "\": {" + - " \"is_write_index\" : true" + - " }" + - "}" + - "}"; - } - - public String getTurnOffWriteOnAllAnalyticsIndexes(AnalyticsIndexingServiceConnector connector) { - return "{" + - "\"actions\": [" + - " {" + - " \"add\": {" + - " \"index\": \"" + connector.getIndexPrefix() + "*\"," + - " \"alias\": \"" + connector.getIndexAlias() + "\"," + - " \"is_write_index\": false" + - " }" + - " }" + - "]" + - "}"; - } -} diff --git a/analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsIndexingServiceConnector.java b/analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsIndexingServiceConnector.java deleted file mode 100644 index 632fea0ee..000000000 --- a/analytics-services/src/main/java/org/exoplatform/analytics/es/AnalyticsIndexingServiceConnector.java +++ /dev/null @@ -1,178 +0,0 @@ -/** - * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.exoplatform.analytics.es; - -import static org.exoplatform.analytics.utils.AnalyticsUtils.*; - -import java.util.*; - -import org.apache.commons.lang3.StringUtils; -import org.picocontainer.Startable; - -import org.exoplatform.analytics.api.service.StatisticDataQueueService; -import org.exoplatform.analytics.model.StatisticData; -import org.exoplatform.commons.api.settings.SettingService; -import org.exoplatform.commons.api.settings.SettingValue; -import org.exoplatform.commons.api.settings.data.Context; -import org.exoplatform.commons.api.settings.data.Scope; -import org.exoplatform.commons.search.domain.Document; -import org.exoplatform.commons.search.index.impl.ElasticIndexingServiceConnector; -import org.exoplatform.container.xml.InitParams; -import org.exoplatform.services.log.ExoLogger; -import org.exoplatform.services.log.Log; - -public class AnalyticsIndexingServiceConnector extends ElasticIndexingServiceConnector implements Startable { - - private static final Log LOG = - ExoLogger.getLogger(AnalyticsIndexingServiceConnector.class); - - public static final String DEFAULT_ES_INDEX_TEMPLATE = "analytics_template"; - - public static final String DEFAULT_ES_ANALYTICS_INDEX_NAME = "analytics"; - - public static final String ES_ANALYTICS_INDEX_PREFIX = "exo.es.analytics.index.prefix"; - - public static final String ES_ANALYTICS_INDEX_TEMPLATE = "exo.es.analytics.index.template"; - - public static final Context ES_ANALYTICS_CONTEXT = Context.GLOBAL.id("analytics"); - - public static final Scope ES_ANALYTICS_SCOPE = Scope.APPLICATION.id("analytics"); - - private SettingService settingService; - - private StatisticDataQueueService analyticsQueueService; - - private String indexPrefix; - - private String indexTemplate; - - public AnalyticsIndexingServiceConnector(StatisticDataQueueService analyticsQueueService, - SettingService settingService, - InitParams initParams) { - super(initParams); - this.settingService = settingService; - this.analyticsQueueService = analyticsQueueService; - if (initParams != null) { - if (initParams.containsKey(ES_ANALYTICS_INDEX_PREFIX)) { - this.indexPrefix = initParams.getValueParam(ES_ANALYTICS_INDEX_PREFIX).getValue(); - } - if (initParams.containsKey(ES_ANALYTICS_INDEX_TEMPLATE)) { - this.indexTemplate = initParams.getValueParam(ES_ANALYTICS_INDEX_TEMPLATE).getValue(); - } - } - if (StringUtils.isBlank(this.indexPrefix)) { - this.indexPrefix = DEFAULT_ES_ANALYTICS_INDEX_NAME; - } - if (StringUtils.isBlank(this.indexTemplate)) { - this.indexTemplate = DEFAULT_ES_INDEX_TEMPLATE; - } - } - - @Override - public void start() { - SettingValue indexTemplateValue = this.settingService.get(ES_ANALYTICS_CONTEXT, - ES_ANALYTICS_SCOPE, - ES_ANALYTICS_INDEX_TEMPLATE); - if (indexTemplateValue != null && indexTemplateValue.getValue() != null) { - String storedIndexTemplate = indexTemplateValue.getValue().toString(); - if (!StringUtils.equals(storedIndexTemplate, indexTemplate)) { - LOG.warn("Can't change index template from {} to {}. New index will be ignored.", storedIndexTemplate, indexTemplate); - indexTemplate = storedIndexTemplate; - } - } - } - - @Override - public void stop() { - // Nothing to stop - } - - @Override - public String getConnectorName() { - throw new UnsupportedOperationException(); - } - - @Override - public String getMapping() { - throw new UnsupportedOperationException(); - } - - @Override - public Document create(String idString) { - if (StringUtils.isBlank(idString)) { - throw new IllegalArgumentException("id is mandatory"); - } - long id = Long.parseLong(idString); - StatisticData data = this.analyticsQueueService.get(id); - if (data == null) { - LOG.warn("Can't find document with id {}", id); - return null; - } - String timestampString = String.valueOf(data.getTimestamp()); - - Map fields = new HashMap<>(); - fields.put("id", idString); - fields.put(FIELD_TIMESTAMP, timestampString); - fields.put(FIELD_USER_ID, String.valueOf(data.getUserId())); - fields.put(FIELD_SPACE_ID, String.valueOf(data.getSpaceId())); - fields.put(FIELD_MODULE, data.getModule()); - fields.put(FIELD_SUB_MODULE, data.getSubModule()); - fields.put(FIELD_OPERATION, data.getOperation()); - fields.put(FIELD_STATUS, String.valueOf(data.getStatus().ordinal())); - fields.put(FIELD_ERROR_CODE, String.valueOf(data.getErrorCode())); - fields.put(FIELD_ERROR_MESSAGE, data.getErrorMessage()); - fields.put(FIELD_DURATION, String.valueOf(data.getDuration())); - fields.put(FIELD_IS_ANALYTICS, "true"); - if (data.getParameters() != null && !data.getParameters().isEmpty()) { - fields.putAll(data.getParameters()); - } - Document esDocument = new Document(String.valueOf(id), - null, - null, - (Set) null, - fields); - if (data.getListParameters() != null && !data.getListParameters().isEmpty()) { - esDocument.setListFields(data.getListParameters()); - } - return esDocument; - } - - @Override - public Document update(String id) { - throw new UnsupportedOperationException(); - } - - @Override - public List getAllIds(int offset, int limit) { - throw new UnsupportedOperationException(); - } - - public String getIndexPrefix() { - return indexPrefix; - } - - public String getIndexTemplate() { - return indexTemplate; - } - - public void storeCreatedIndexTemplate() { - this.settingService.set(ES_ANALYTICS_CONTEXT, - ES_ANALYTICS_SCOPE, - ES_ANALYTICS_INDEX_TEMPLATE, - SettingValue.create(indexTemplate)); - } -} diff --git a/analytics-services/src/main/resources/analytics-ui-watchers.json b/analytics-services/src/main/resources/analytics-ui-watchers.json new file mode 100644 index 000000000..b9ea3417d --- /dev/null +++ b/analytics-services/src/main/resources/analytics-ui-watchers.json @@ -0,0 +1,14 @@ +{ + "descriptors":[ + { + "name":"Unified search click", + "operation":"click", + "parameters":{ + "ui":"toolbar", + "application":"SearchPortlet" + }, + "domSelector":"#SearchApplication", + "domEvent":"mousedown" + } + ] +} \ No newline at end of file diff --git a/analytics-services/src/main/resources/conf/portal/configuration.xml b/analytics-services/src/main/resources/conf/portal/configuration.xml deleted file mode 100644 index c2efd1e57..000000000 --- a/analytics-services/src/main/resources/conf/portal/configuration.xml +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - org.exoplatform.analytics.es.AnalyticsElasticContentRequestBuilder - org.exoplatform.analytics.es.AnalyticsElasticContentRequestBuilder - - - - org.exoplatform.analytics.es.AnalyticsESClient - - - exo.es.analytics.index.server.url - ${exo.es.analytics.index.server.url:} - - - exo.es.analytics.index.server.username - ${exo.es.analytics.index.server.username:} - - - exo.es.analytics.index.server.password - ${exo.es.analytics.index.server.password:} - - - exo.es.analytics.index.per.days - ${exo.es.analytics.index.per.days:7} - - - index.template.file.path - ${exo.es.analytics.index.template.path:jar:/analytics-es-template.json} - - - - - - org.exoplatform.analytics.api.service.AnalyticsService - org.exoplatform.analytics.es.service.ESAnalyticsService - - - exo.analytics.aggregation.terms.doc_size - ${exo.analytics.aggregation.terms.doc_size:200} - - - exo.analytics.admin.permissions - ${exo.analytics.admin.permission:*:/platform/analytics} - - - exo.analytics.viewall.permissions - ${exo.analytics.viewall.permission:*:/platform/administrators} - - - exo.analytics.view.permissions - ${exo.analytics.view.permission:*:/platform/users} - - - - - - org.exoplatform.analytics.api.websocket.AnalyticsWebSocketService - - - - org.exoplatform.analytics.es.AnalyticsIndexingServiceConnector - - - exo.es.analytics.index.prefix - ${exo.es.analytics.index.prefix:analytics} - - - exo.es.analytics.index.template - ${exo.es.analytics.index.template:analytics_template} - - - constructor.params - - - - - - - - - - org.exoplatform.analytics.api.service.StatisticDataQueueService - org.exoplatform.analytics.queue.service.DummyStatisticDataQueueService - - - - org.exoplatform.analytics.api.service.StatisticDataProcessorService - - - - org.exoplatform.analytics.api.service.ManagedStatisticDataQueueService - - - - org.exoplatform.analytics.api.service.StatisticDataProcessorService - - elasticsearch - addProcessor - org.exoplatform.analytics.es.processor.ElasticSearchStatisticDataProcessor - ElasticSearch analytics data persister - - - - diff --git a/analytics-webapps/pom.xml b/analytics-webapps/pom.xml index 4d6a6f917..e1ee0af88 100644 --- a/analytics-webapps/pom.xml +++ b/analytics-webapps/pom.xml @@ -1,16 +1,19 @@ - - - - org.exoplatform.analytics.api.service.AnalyticsService - - Home page link - addUIWatcherPlugin - org.exoplatform.analytics.api.service.StatisticUIWatcherPlugin - - - watcher - - - - Home page link - - - - click - - - - - - - ui - - - toolbar - - - - - application - - - NavigationToolbarPortlet - - - - - - - #brandingTopBar a - - - - mousedown - - - - - - type - - - pageX - - - pageY - - - - - - - - href - - - class - - - id - - - - - - - - - Hamburger menu site navigation link - addUIWatcherPlugin - org.exoplatform.analytics.api.service.StatisticUIWatcherPlugin - - - watcher - - - - Hamburger menu site navigation link - - - - click - - - - - - - ui - - - HamburgerMenu - - - - - application - - - SiteHamburgerNavigation - - - - - - - #HamburgerMenuNavigation #SiteHamburgerNavigation a[role="listitem"] - - - - mousedown - - - - - - type - - - pageX - - - pageY - - - - - - - - href - - - aria-selected - - - - - - - - - Hamburger menu spaces navigation link - addUIWatcherPlugin - org.exoplatform.analytics.api.service.StatisticUIWatcherPlugin - - - watcher - - - - Hamburger menu spaces navigation link - - - - click - - - - - - - ui - - - HamburgerMenu - - - - - application - - - SpacesHamburgerNavigation - - - - - - - #HamburgerMenuNavigation .spacesNavigationContent a[role="listitem"] - - - - mousedown - - - - - - type - - - pageX - - - pageY - - - - - - - - href - - - aria-selected - - - - - - - - - Hamburger menu icon - addUIWatcherPlugin - org.exoplatform.analytics.api.service.StatisticUIWatcherPlugin - - - watcher - - - - Hamburger menu icon - - - - click - - - - - - - ui - - - toolbar - - - - - application - - - HamburgerNavigationMenuLink - - - - - - - .HamburgerNavigationMenuLink - - - - mousedown - - - - - - type - - - pageX - - - pageY - - - - - - - - - Activity Link click - addUIWatcherPlugin - org.exoplatform.analytics.api.service.StatisticUIWatcherPlugin - - - watcher - - - - Activity Link click - - - - click - - - - - - - ui - - - activityStream - - - - - application - - - activityHeader - - - - - - - .activity-stream .activity-head-time a - - - - mousedown - - - - - - - Activity Poster click - addUIWatcherPlugin - org.exoplatform.analytics.api.service.StatisticUIWatcherPlugin - - - watcher - - - - Activity Poster click - - - - click - - - - - - - ui - - - activityStream - - - - - application - - - activityHeader - - - - - - - .activity-stream .activity-head-user-link - - - - mousedown - - - - - - - Activity Space click - addUIWatcherPlugin - org.exoplatform.analytics.api.service.StatisticUIWatcherPlugin - - - watcher - - - - Activity Space click - - - - click - - - - - - - ui - - - activityStream - - - - - application - - - activityHeader - - - - - - - .activity-stream .activity-head-space-link - - - - mousedown - - - - - - - - Unified search click - addUIWatcherPlugin - org.exoplatform.analytics.api.service.StatisticUIWatcherPlugin - - - watcher - - - - Unified search click - - - - click - - - - - - - ui - - - toolbar - - - - - application - - - SearchPortlet - - - - - - - #SearchApplication - - - - mousedown - - - - - - - - - - org.exoplatform.groovyscript.text.TemplateService - - UIPortalApplication-head - addTemplateExtension - org.exoplatform.groovyscript.text.TemplateExtensionPlugin - - - templates - war:/groovy/UIPageDisplayStatisticCollection.gtmpl - - - - - - diff --git a/analytics-webapps/src/main/webapp/WEB-INF/conf/analytics/cache-configuration.xml b/analytics-webapps/src/main/webapp/WEB-INF/conf/analytics/cache-configuration.xml index c581be626..39e3faec9 100644 --- a/analytics-webapps/src/main/webapp/WEB-INF/conf/analytics/cache-configuration.xml +++ b/analytics-webapps/src/main/webapp/WEB-INF/conf/analytics/cache-configuration.xml @@ -1,16 +1,19 @@ - + - - org.exoplatform.social.core.space.spi.SpaceService - - AnalyticsSpaceListener - addSpaceListener - org.exoplatform.analytics.listener.social.AnalyticsSpaceListener - - - - - org.exoplatform.services.listener.ListenerService - - metadata.tag.added - addListener - org.exoplatform.analytics.listener.social.AnalyticsActivityTagsListener - - - - - org.exoplatform.services.listener.ListenerService - - attachment.created - addListener - org.exoplatform.analytics.listener.social.ActivityAttachmentAnalyticsListener - - - supported-type - activity - - - - - attachment.deleted - addListener - org.exoplatform.analytics.listener.social.ActivityAttachmentAnalyticsListener - - - supported-type - activity - - - - - - - org.exoplatform.services.organization.OrganizationService - - new.user.event.listener - addListenerPlugin - org.exoplatform.analytics.listener.portal.UserAnalyticsEventListener - This listener will compute again users count each time a new user is added/deleted - - - - - org.exoplatform.services.listener.ListenerService - - exo.core.security.ConversationRegistry.register - addListener - org.exoplatform.analytics.listener.portal.LoginAnalyticsListener - Listener for user login event statistics - - - exo.core.security.ConversationRegistry.unregister - addListener - org.exoplatform.analytics.listener.portal.LoginAnalyticsListener - Listener for user logout event statistics - - - - - org.exoplatform.services.listener.ListenerService - - login.failed - addListener - org.exoplatform.analytics.listener.portal.LoginFailedAnalyticsListener - Listener for user login failed event statistics - - - - - - org.exoplatform.social.core.manager.ActivityManager - - AnalyticsActivityListener - addActivityEventListener - org.exoplatform.analytics.listener.social.AnalyticsActivityListener - - - - - org.exoplatform.social.core.manager.IdentityManager - - AnalyticsProfileListener - registerProfileListener - org.exoplatform.analytics.listener.social.AnalyticsProfileListener - - - - - org.exoplatform.social.core.manager.RelationshipManager - - AnalyticsRelationshipListener - addListenerPlugin - org.exoplatform.analytics.listener.social.AnalyticsRelationshipListener - - - - - org.exoplatform.web.application.ApplicationLifecycleExtension - - PageAccessListener - addPortalApplicationLifecycle - org.exoplatform.analytics.listener.portal.PageAccessListener - - - collectAjaxQueries - Whether collect ajax queries or not - ${exo.analytics.collectAjaxQueries:false} - - - - - - - org.exoplatform.services.listener.ListenerService - - exo.analytics.websocket.messageReceived - addListener - org.exoplatform.analytics.listener.websocket.WebSocketUIStatisticListener - - - - - org.exoplatform.services.listener.ListenerService - - notification.read.item - addListener - org.exoplatform.analytics.listener.social.AnalyticsSpaceWebNotificationListener - - - notification.unread.item - addListener - org.exoplatform.analytics.listener.social.AnalyticsSpaceWebNotificationListener - - - notification.read.allItems - addListener - org.exoplatform.analytics.listener.social.AnalyticsSpaceWebNotificationListener - - - - - org.exoplatform.services.scheduler.JobSchedulerService - - UsersStatisticsCountJob - addPeriodJob - org.exoplatform.services.scheduler.PeriodJob - A cron job to daily collect users count statistics - - - job.info - - - - - - - - - - - - - - - - SpacesStatisticsCountJob - addPeriodJob - org.exoplatform.services.scheduler.PeriodJob - A cron job to daily collect spaces count statistics - - - job.info - - - - - - - - - - - - - - - - - diff --git a/analytics-webapps/src/main/webapp/WEB-INF/conf/analytics/organization-configuration.xml b/analytics-webapps/src/main/webapp/WEB-INF/conf/analytics/organization-configuration.xml index 7a60e10d7..bef3513ca 100644 --- a/analytics-webapps/src/main/webapp/WEB-INF/conf/analytics/organization-configuration.xml +++ b/analytics-webapps/src/main/webapp/WEB-INF/conf/analytics/organization-configuration.xml @@ -1,16 +1,19 @@ - + diff --git a/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics-rate.jsp b/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics-rate.jsp index ffb0c9972..ef59193f0 100644 --- a/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics-rate.jsp +++ b/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics-rate.jsp @@ -1,16 +1,19 @@ <% /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics-table.jsp b/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics-table.jsp index 73f7ca57b..70caababc 100644 --- a/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics-table.jsp +++ b/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics-table.jsp @@ -1,16 +1,19 @@ <% /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics.jsp b/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics.jsp index d4880bc0d..e8da3faa1 100644 --- a/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics.jsp +++ b/analytics-webapps/src/main/webapp/WEB-INF/jsp/analytics.jsp @@ -1,16 +1,19 @@ <% /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/analytics-webapps/src/main/webapp/WEB-INF/jsp/breadcrumb.jsp b/analytics-webapps/src/main/webapp/WEB-INF/jsp/breadcrumb.jsp index a3c54ddad..e44ffff3f 100644 --- a/analytics-webapps/src/main/webapp/WEB-INF/jsp/breadcrumb.jsp +++ b/analytics-webapps/src/main/webapp/WEB-INF/jsp/breadcrumb.jsp @@ -1,16 +1,19 @@ <% /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/analytics-webapps/src/main/webapp/WEB-INF/jsp/statistics-collection.jsp b/analytics-webapps/src/main/webapp/WEB-INF/jsp/statistics-collection.jsp index 86d51889d..abcf32ccf 100644 --- a/analytics-webapps/src/main/webapp/WEB-INF/jsp/statistics-collection.jsp +++ b/analytics-webapps/src/main/webapp/WEB-INF/jsp/statistics-collection.jsp @@ -1,16 +1,19 @@ <% /** * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2022 Meeds Association - * contact@meeds.io + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/analytics-webapps/src/main/webapp/WEB-INF/portlet.xml b/analytics-webapps/src/main/webapp/WEB-INF/portlet.xml index df87452dc..a9378bd97 100644 --- a/analytics-webapps/src/main/webapp/WEB-INF/portlet.xml +++ b/analytics-webapps/src/main/webapp/WEB-INF/portlet.xml @@ -1,16 +1,19 @@