diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index 4ad72bfdc7c0..466f9761e967 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -5,7 +5,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - computed, inject, input, model, @@ -56,18 +55,7 @@ export class DotSideBarComponent { */ $fakeColumns = signal(Array.from({ length: 50 }).map((_) => this.getPercentage())); - $state = computed(() => { - const folders = this.$folders(); - - console.log(folders); - - return { - folders, - defaultFile: folders.find((folder) => folder.data.identifier === 'SYSTEM_HOST') - }; - }); - - $selectedFile = model(this.$state().defaultFile); + $selectedFile = model(null); /** * Event emitter for when a tree node is expanded. diff --git a/core-web/libs/portlets/dot-experiments/portlet/src/lib/dot-experiments-configuration/components/dot-experiments-configuration-variants/dot-experiments-configuration-variants.component.spec.ts b/core-web/libs/portlets/dot-experiments/portlet/src/lib/dot-experiments-configuration/components/dot-experiments-configuration-variants/dot-experiments-configuration-variants.component.spec.ts index 55d5e53f8f31..e3ddec80d2c1 100644 --- a/core-web/libs/portlets/dot-experiments/portlet/src/lib/dot-experiments-configuration/components/dot-experiments-configuration-variants/dot-experiments-configuration-variants.component.spec.ts +++ b/core-web/libs/portlets/dot-experiments/portlet/src/lib/dot-experiments-configuration/components/dot-experiments-configuration-variants/dot-experiments-configuration-variants.component.spec.ts @@ -18,8 +18,7 @@ import { Tooltip } from 'primeng/tooltip'; import { DotExperimentsService, DotHttpErrorManagerService, - DotMessageService, - DotSessionStorageService + DotMessageService } from '@dotcms/data-access'; import { DEFAULT_VARIANT_ID, @@ -77,7 +76,6 @@ describe('DotExperimentsConfigurationVariantsComponent', () => { let dotExperimentsService: SpyObject; let router: Router; - let dotSessionStorageService: DotSessionStorageService; let confirmationService: ConfirmationService; const createComponent = createComponentFactory({ @@ -97,7 +95,6 @@ describe('DotExperimentsConfigurationVariantsComponent', () => { mockProvider(DotExperimentsService), mockProvider(MessageService), mockProvider(DotHttpErrorManagerService), - mockProvider(DotSessionStorageService), mockProvider(Router) ] }); @@ -112,7 +109,6 @@ describe('DotExperimentsConfigurationVariantsComponent', () => { store = spectator.inject(DotExperimentsConfigurationStore); router = spectator.inject(Router); - dotSessionStorageService = spectator.inject(DotSessionStorageService); confirmationService = spectator.inject(ConfirmationService); dotExperimentsService = spectator.inject(DotExperimentsService); @@ -247,7 +243,6 @@ describe('DotExperimentsConfigurationVariantsComponent', () => { it('should goToEditPage emit a variant and mode(preview) when View button is clicked', () => { spectator.click(byTestId('variant-preview-button')); - expect(dotSessionStorageService.setVariationId).toHaveBeenCalledWith(variants[0].id); expect(router.navigate).toHaveBeenCalledWith(['edit-page/content'], { queryParams: { mode: DotPageMode.PREVIEW, @@ -261,7 +256,6 @@ describe('DotExperimentsConfigurationVariantsComponent', () => { it('should goToEditPage emit a variant and mode(edit) when edit button is clicked', () => { spectator.click(byTestId('variant-edit-button')); - expect(dotSessionStorageService.setVariationId).toHaveBeenCalledWith(variants[1].id); expect(router.navigate).toHaveBeenCalledWith(['edit-page/content'], { queryParams: { mode: DotPageMode.EDIT, diff --git a/core-web/libs/portlets/dot-experiments/portlet/src/lib/dot-experiments-configuration/components/dot-experiments-configuration-variants/dot-experiments-configuration-variants.component.ts b/core-web/libs/portlets/dot-experiments/portlet/src/lib/dot-experiments-configuration/components/dot-experiments-configuration-variants/dot-experiments-configuration-variants.component.ts index 65aa998cfe41..20e0cd6b33d8 100644 --- a/core-web/libs/portlets/dot-experiments/portlet/src/lib/dot-experiments-configuration/components/dot-experiments-configuration-variants/dot-experiments-configuration-variants.component.ts +++ b/core-web/libs/portlets/dot-experiments/portlet/src/lib/dot-experiments-configuration/components/dot-experiments-configuration-variants/dot-experiments-configuration-variants.component.ts @@ -15,7 +15,7 @@ import { TooltipModule } from 'primeng/tooltip'; import { tap } from 'rxjs/operators'; -import { DotMessageService, DotSessionStorageService } from '@dotcms/data-access'; +import { DotMessageService } from '@dotcms/data-access'; import { ComponentStatus, DEFAULT_VARIANT_NAME, @@ -85,7 +85,6 @@ export class DotExperimentsConfigurationVariantsComponent { constructor( private readonly dotExperimentsConfigurationStore: DotExperimentsConfigurationStore, private readonly confirmationService: ConfirmationService, - private readonly dotSessionStorageService: DotSessionStorageService, private readonly dotMessageService: DotMessageService, private readonly router: Router, private readonly route: ActivatedRoute @@ -157,7 +156,6 @@ export class DotExperimentsConfigurationVariantsComponent { * @memberof DotExperimentsConfigurationVariantsComponent */ goToEditPageVariant(variant: Variant, mode: DotPageMode) { - this.dotSessionStorageService.setVariationId(variant.id); this.router.navigate(['edit-page/content'], { queryParams: { variantName: variant.id, diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/dot-ema-dialog.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/dot-ema-dialog.component.spec.ts index 6cf8e165cb89..526a3d2567ff 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/dot-ema-dialog.component.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/dot-ema-dialog.component.spec.ts @@ -10,6 +10,7 @@ import { of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { signal } from '@angular/core'; import { By } from '@angular/platform-browser'; import { MessageService } from 'primeng/api'; @@ -42,6 +43,7 @@ import { DotEmaWorkflowActionsService } from '../../services/dot-ema-workflow-ac import { FormStatus, NG_CUSTOM_EVENTS } from '../../shared/enums'; import { MOCK_RESPONSE_HEADLESS, PAYLOAD_MOCK } from '../../shared/mocks'; import { DotPage } from '../../shared/models'; +import { UVEStore } from '../../store/dot-uve.store'; describe('DotEmaDialogComponent', () => { let spectator: Spectator; @@ -85,6 +87,14 @@ describe('DotEmaDialogComponent', () => { HttpClient, DotWorkflowActionsFireService, MessageService, + { + provide: UVEStore, + useValue: { + params: signal({ + variantName: 'DEFAULT' // Is the only thing we need to test the component + }) + } + }, { provide: DotcmsConfigService, useValue: new DotcmsConfigServiceMock() diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.spec.ts index 84966322f09e..033ec24ea268 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.spec.ts @@ -2,6 +2,8 @@ import { expect, it, describe } from '@jest/globals'; import { SpectatorService, createServiceFactory } from '@ngneat/spectator/jest'; import { of } from 'rxjs'; +import { signal } from '@angular/core'; + import { CLIENT_ACTIONS } from '@dotcms/client'; import { DotMessageService } from '@dotcms/data-access'; import { MockDotMessageService } from '@dotcms/utils-testing'; @@ -13,6 +15,9 @@ import { LAYOUT_URL } from '../../../shared/consts'; import { DialogStatus, FormStatus } from '../../../shared/enums'; import { PAYLOAD_MOCK } from '../../../shared/mocks'; import { DotPage } from '../../../shared/models'; +import { UVEStore } from '../../../store/dot-uve.store'; + +const TEST_VARIANT = 'my-test-variant'; describe('DotEmaDialogStoreService', () => { let spectator: SpectatorService; @@ -21,6 +26,15 @@ describe('DotEmaDialogStoreService', () => { service: DotEmaDialogStore, mocks: [DotActionUrlService], providers: [ + { + provide: UVEStore, + useValue: { + params: signal({ + variantName: TEST_VARIANT // Is the only thing we need to test the component + }) + } + }, + { provide: DotMessageService, useValue: new MockDotMessageService({ @@ -108,7 +122,8 @@ describe('DotEmaDialogStoreService', () => { p_p_mode: 'view', _content_struts_action: '/ext/contentlet/edit_contentlet', _content_cmd: 'edit', - inode: '123' + inode: '123', + variantName: TEST_VARIANT }); spectator.service.dialogState$.subscribe((state) => { @@ -141,7 +156,8 @@ describe('DotEmaDialogStoreService', () => { p_p_mode: 'view', _content_struts_action: '/ext/contentlet/edit_contentlet', _content_cmd: 'edit', - inode: '123' + inode: '123', + variantName: TEST_VARIANT }); spectator.service.dialogState$.subscribe((state) => { @@ -173,7 +189,8 @@ describe('DotEmaDialogStoreService', () => { p_p_mode: 'view', _content_struts_action: '/ext/contentlet/edit_contentlet', _content_cmd: 'edit', - inode: '123' + inode: '123', + variantName: TEST_VARIANT }); spectator.service.dialogState$.subscribe((state) => { @@ -202,7 +219,9 @@ describe('DotEmaDialogStoreService', () => { spectator.service.dialogState$.subscribe((state) => { expect(state).toEqual({ - url: '/html/ng-contentlet-selector.jsp?ng=true&container_id=1234&add=test&language_id=1', + url: + '/html/ng-contentlet-selector.jsp?ng=true&container_id=1234&add=test&language_id=1&' + + new URLSearchParams({ variantName: TEST_VARIANT }).toString(), header: 'Search Content', type: 'content', status: DialogStatus.LOADING, @@ -246,7 +265,9 @@ describe('DotEmaDialogStoreService', () => { spectator.service.dialogState$.subscribe((state) => { expect(state).toEqual({ - url: 'some/really/long/url', + url: + 'http://localhost/some/really/long/url?' + + new URLSearchParams({ variantName: TEST_VARIANT }).toString(), status: DialogStatus.LOADING, header: 'Create test', type: 'content', @@ -271,7 +292,10 @@ describe('DotEmaDialogStoreService', () => { spectator.service.dialogState$.subscribe((state) => { expect(state.header).toBe('Create Blog Posts'); expect(state.status).toBe(DialogStatus.LOADING); - expect(state.url).toBe('some/really/long/url'); + expect(state.url).toBe( + 'http://localhost/some/really/long/url?' + + new URLSearchParams({ variantName: TEST_VARIANT }).toString() + ); expect(state.type).toBe('content'); expect(state.actionPayload).toEqual(PAYLOAD_MOCK); done(); @@ -294,7 +318,10 @@ describe('DotEmaDialogStoreService', () => { expect(state.header).toBe('Create Blog'); expect(state.status).toBe(DialogStatus.LOADING); - expect(state.url).toBe('https://demo.dotcms.com/jsp.jsp'); + expect(state.url).toBe( + 'https://demo.dotcms.com/jsp.jsp?' + + new URLSearchParams({ variantName: TEST_VARIANT }).toString() + ); expect(state.type).toBe('content'); expect(state.actionPayload).toEqual(PAYLOAD_MOCK); done(); @@ -370,7 +397,8 @@ describe('DotEmaDialogStoreService', () => { inode: '', lang: '2', populateaccept: 'true', - reuseLastLang: 'true' + reuseLastLang: 'true', + variantName: TEST_VARIANT }); spectator.service.dialogState$.subscribe((state) => { @@ -415,7 +443,8 @@ describe('DotEmaDialogStoreService', () => { inode: '', lang: '2', populateaccept: 'true', - reuseLastLang: 'true' + reuseLastLang: 'true', + variantName: TEST_VARIANT }); spectator.service.dialogState$.subscribe((state) => { diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.ts index b0a2eb001a9e..5b16147c6590 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.ts @@ -20,6 +20,7 @@ import { EditContentletPayload, EditEmaDialogState } from '../../../shared/models'; +import { UVEStore } from '../../../store/dot-uve.store'; @Injectable() export class DotEmaDialogStore extends ComponentStore { @@ -41,6 +42,8 @@ export class DotEmaDialogStore extends ComponentStore { private dotMessageService = inject(DotMessageService); + private uveStore = inject(UVEStore); + readonly dialogState$ = this.select((state) => state); /** @@ -97,9 +100,13 @@ export class DotEmaDialogStore extends ComponentStore { */ readonly createContentlet = this.updater( (state, { url, contentType, actionPayload }: CreateContentletAction) => { + const completeURL = new URL(url, window.location.origin); + + completeURL.searchParams.set('variantName', this.uveStore.params().variantName); + return { ...state, - url, + url: completeURL.toString(), actionPayload, header: this.dotMessageService.get( 'contenttypes.content.create.contenttype', @@ -295,7 +302,8 @@ export class DotEmaDialogStore extends ComponentStore { p_p_mode: 'view', _content_struts_action: '/ext/contentlet/edit_contentlet', _content_cmd: 'edit', - inode: inode + inode: inode, + variantName: this.uveStore.params().variantName }); return `${LAYOUT_URL}?${queryParams.toString()}`; @@ -322,7 +330,8 @@ export class DotEmaDialogStore extends ComponentStore { ng: 'true', container_id: containerId, add: acceptTypes, - language_id + language_id, + variantName: this.uveStore.params().variantName }); return `${CONTENTLET_SELECTOR_URL}?${queryParams.toString()}`; @@ -344,7 +353,8 @@ export class DotEmaDialogStore extends ComponentStore { inode: '', lang: newLanguage.toString(), populateaccept: 'true', - reuseLastLang: 'true' + reuseLastLang: 'true', + variantName: this.uveStore.params().variantName }); return `${LAYOUT_URL}?${queryParams.toString()}`; diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts index d7542cab4a7a..c07b3966cd5d 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts @@ -322,7 +322,7 @@ describe('DotEmaShellComponent', () => { }); spectator.detectChanges(); - expect(store.init).not.toHaveBeenCalledTimes(2); // The first call is on the beforeEach + expect(store.init).toHaveBeenCalled(); }); it('should trigger a load when changing the clientHost and it is on the allowedDevURLs', () => { diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts index e361762eeffe..3c65cdaf5ed6 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts @@ -11,6 +11,7 @@ import { switchMap, shareReplay, catchError, tap, take, map } from 'rxjs/operato import { DotLanguagesService, DotLicenseService, DotExperimentsService } from '@dotcms/data-access'; import { LoginService } from '@dotcms/dotcms-js'; +import { DEFAULT_VARIANT_ID } from '@dotcms/dotcms-models'; import { DotPageApiService, DotPageApiParams } from '../../../services/dot-page-api.service'; import { UVE_STATUS } from '../../../shared/enums'; @@ -140,7 +141,11 @@ export function withLoad() { currentUser, experiment, languages, - params, + params: { + ...params, + variantName: + params.variantName || DEFAULT_VARIANT_ID + }, canEditPage, pageIsLocked, isTraditionalPage, diff --git a/dotCMS/src/main/java/com/dotcms/filters/interceptor/SimpleWebInterceptorDelegateImpl.java b/dotCMS/src/main/java/com/dotcms/filters/interceptor/SimpleWebInterceptorDelegateImpl.java index 4989e10a76fb..1f03413a8fa2 100644 --- a/dotCMS/src/main/java/com/dotcms/filters/interceptor/SimpleWebInterceptorDelegateImpl.java +++ b/dotCMS/src/main/java/com/dotcms/filters/interceptor/SimpleWebInterceptorDelegateImpl.java @@ -1,7 +1,11 @@ package com.dotcms.filters.interceptor; import com.dotcms.repackage.com.google.common.annotations.VisibleForTesting; +import com.dotcms.rest.config.DotRestApplication; +import com.dotmarketing.util.Config; +import com.dotmarketing.util.Logger; import com.dotmarketing.util.RegEX; +import io.vavr.Lazy; import org.apache.commons.collections.iterators.ReverseListIterator; import javax.servlet.http.HttpServletRequest; @@ -30,6 +34,9 @@ public class SimpleWebInterceptorDelegateImpl implements WebInterceptorDelegate private final AtomicBoolean reverseOrderForPostInvoke = new AtomicBoolean(false); + private static final Lazy ENABLE_TELEMETRY_FROM_CORE = Lazy.of(() -> + Config.getBooleanProperty("FEATURE_FLAG_TELEMETRY_CORE_ENABLED", false)); + @Override public void addBefore(final String webInterceptorName, final WebInterceptor webInterceptor) { @@ -83,7 +90,11 @@ public void add(final int order, final WebInterceptor webInterceptor) { @Override public void addFirst(final WebInterceptor webInterceptor) { - + if (Boolean.TRUE.equals(ENABLE_TELEMETRY_FROM_CORE.get()) && + webInterceptor.getClass().getName().equalsIgnoreCase("com.dotcms.experience.collectors.api.ApiMetricWebInterceptor")) { + Logger.warn(DotRestApplication.class, "Bypassing addition of API Metric Web Interceptor from OSGi"); + return; + } this.interceptors.add(0, webInterceptor); this.init(webInterceptor); } // addFirst. diff --git a/dotCMS/src/main/java/com/dotcms/rest/config/DotRestApplication.java b/dotCMS/src/main/java/com/dotcms/rest/config/DotRestApplication.java index f8163c40798b..1c155bc5823b 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/config/DotRestApplication.java +++ b/dotCMS/src/main/java/com/dotcms/rest/config/DotRestApplication.java @@ -1,20 +1,25 @@ package com.dotcms.rest.config; import com.dotcms.cdi.CDIUtils; +import com.dotcms.telemetry.rest.TelemetryResource; +import com.dotmarketing.util.Config; +import com.dotmarketing.util.Logger; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; -import io.swagger.v3.jaxrs2.integration.resources.AcceptHeaderOpenApiResource; -import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource; -import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.servers.Server; import io.swagger.v3.oas.annotations.tags.Tag; +import io.vavr.Lazy; +import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; +import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.glassfish.jersey.server.ResourceConfig; + +import javax.ws.rs.ApplicationPath; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import javax.ws.rs.ApplicationPath; -import org.glassfish.jersey.media.multipart.MultiPartFeature; -import org.glassfish.jersey.server.ResourceConfig; /** * This class provides the list of all the REST end-points in dotCMS. Every new @@ -47,18 +52,24 @@ ) public class DotRestApplication extends ResourceConfig { - public DotRestApplication() { + private static final Lazy ENABLE_TELEMETRY_FROM_CORE = Lazy.of(() -> + Config.getBooleanProperty("FEATURE_FLAG_TELEMETRY_CORE_ENABLED", false)); + public DotRestApplication() { + final List packages = new ArrayList<>(List.of( + "com.dotcms.rest", + "com.dotcms.contenttype.model.field", + "com.dotcms.rendering.js", + "com.dotcms.ai.rest", + "io.swagger.v3.jaxrs2")); + if (Boolean.TRUE.equals(ENABLE_TELEMETRY_FROM_CORE.get())) { + packages.add(TelemetryResource.class.getPackageName()); + } register(MultiPartFeature.class). register(JacksonJaxbJsonProvider.class). registerClasses(customClasses.keySet()). - packages( - "com.dotcms.rest", - "com.dotcms.contenttype.model.field", - "com.dotcms.rendering.js", - "com.dotcms.ai.rest", - "io.swagger.v3.jaxrs2" - ).register(CdiComponentProvider.class); + packages(packages.toArray(new String[0])). + register(CdiComponentProvider.class); } /** @@ -70,10 +81,15 @@ public DotRestApplication() { * adds a class and reloads * @param clazz the class ot add */ - public static synchronized void addClass(Class clazz) { + public static synchronized void addClass(final Class clazz) { if(clazz==null){ return; } + if (Boolean.TRUE.equals(ENABLE_TELEMETRY_FROM_CORE.get()) + && clazz.getName().equalsIgnoreCase("com.dotcms.experience.TelemetryResource")) { + Logger.warn(DotRestApplication.class, "Bypassing activation of Telemetry REST Endpoint from OSGi"); + return; + } if (Boolean.TRUE.equals(customClasses.computeIfAbsent(clazz,c -> true))) { final Optional reloader = CDIUtils.getBean(ContainerReloader.class); reloader.ifPresent(ContainerReloader::reload); diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricFactory.java b/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricFactory.java deleted file mode 100644 index 0c3ef97a621a..000000000000 --- a/dotCMS/src/main/java/com/dotcms/telemetry/business/MetricFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.dotcms.telemetry.business; - -import com.dotcms.business.CloseDBIfOpened; -import com.dotmarketing.common.db.DotConnect; -import com.dotmarketing.db.DbConnectionFactory; -import com.dotmarketing.exception.DotDataException; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * Utility class tha provide methods to run SQL Query into dotCMS DataBase - */ -public enum MetricFactory { - - INSTANCE; - - @CloseDBIfOpened - public Optional getValue(final String sqlQuery) throws DotDataException { - final DotConnect dotConnect = new DotConnect(); - final List> loadObjectResults = dotConnect.setSQL(sqlQuery) - .loadObjectResults(); - - if (loadObjectResults.isEmpty()) { - return Optional.empty(); - } - - return Optional.ofNullable(loadObjectResults.get(0).get("value")); - } - - /** - * Execute a query that returns a list of String objects. The query should retrieve a field - * called value, as shown in the example below: - *
-     * {@code
-     * SELECT identifier as value FROM template
-     * }
-     * 
- *

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

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

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

    + *
  • Searching Templates: Override the {@code getTemplatesIds} method to return only the + * Template IDs for the templates used on a WORKING page.This is achieved using a SQL UNION + * query. The first part of the query retrieves standard templates from the + * {@code template_version_info} table, identifying those that have only a working version. The + * second part of the query retrieves file templates from the {@code contenlet_version_info} + * table, also focusing on those with just a working version.
  • + *
  • Retrieve the Template Version: Override the getTemplate method to get the last WORKING + * version of the Template.
  • + *
*/ public abstract class TotalContainersInWorkingPageDatabaseMetricType extends TotalContainersInTemplateDatabaseMetricType { @@ -70,8 +72,10 @@ public abstract class TotalContainersInWorkingPageDatabaseMetricType extends Tot "OR page.live_inode IS NULL " + ")"; + protected MetricsAPI metricsAPI; + private Collection getWorkingUsedTemplatesInodes() { - return MetricsAPI.INSTANCE.getList(WORKING_USED_TEMPLATES_INODES_QUERY + " UNION " + + return metricsAPI.getList(WORKING_USED_TEMPLATES_INODES_QUERY + " UNION " + WORKING_USED_FILE_TEMPLATES_INODES_QUERY); } @@ -85,5 +89,6 @@ Template getTemplate(String id) throws DotDataException, DotSecurityException { return APILocator.getTemplateAPI() .findWorkingTemplate(id, APILocator.systemUser(), false); } + } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLivePageDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLivePageDatabaseMetricType.java index 2b5058cc60bf..638afcb0a2d9 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLivePageDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLivePageDatabaseMetricType.java @@ -1,11 +1,19 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.inject.Inject; + /** * Total of FILE containers used in LIVE pages */ -public class TotalFileContainersInLivePageDatabaseMetricType extends TotalContainersInLivePageDatabaseMetricType { +public class TotalFileContainersInLivePageDatabaseMetricType extends TotalContainersInLivePageDatabaseMetricType { + + @Inject + public TotalFileContainersInLivePageDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -21,5 +29,5 @@ public String getDescription() { boolean filterContainer(final String containerId) { return FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } -} +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLiveTemplatesDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLiveTemplatesDatabaseMetricType.java index dd30ec4fe6c7..c36fc056dec5 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLiveTemplatesDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInLiveTemplatesDatabaseMetricType.java @@ -1,11 +1,21 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + /** * Total of FILE containers used in LIVE templates */ -public class TotalFileContainersInLiveTemplatesDatabaseMetricType extends TotalContainersInLiveTemplatesDatabaseMetricType { +@ApplicationScoped +public class TotalFileContainersInLiveTemplatesDatabaseMetricType extends TotalContainersInLiveTemplatesDatabaseMetricType { + + @Inject + public TotalFileContainersInLiveTemplatesDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -16,9 +26,11 @@ public String getName() { public String getDescription() { return "Total of FILE containers used in LIVE templates"; } + @Override boolean filterContainer(final String containerId) { return FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } + } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInWorkingPageDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInWorkingPageDatabaseMetricType.java index 40d1cabd2095..b454fdbc6256 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInWorkingPageDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalFileContainersInWorkingPageDatabaseMetricType.java @@ -1,11 +1,19 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.inject.Inject; + /** * Total of FILE containers used in LIVE pages */ -public class TotalFileContainersInWorkingPageDatabaseMetricType extends TotalContainersInWorkingPageDatabaseMetricType { +public class TotalFileContainersInWorkingPageDatabaseMetricType extends TotalContainersInWorkingPageDatabaseMetricType { + + @Inject + public TotalFileContainersInWorkingPageDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -21,5 +29,5 @@ public String getDescription() { boolean filterContainer(final String containerId) { return FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } -} +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLivePageDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLivePageDatabaseMetricType.java index e0d09c899944..066b3621c615 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLivePageDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLivePageDatabaseMetricType.java @@ -1,11 +1,19 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.inject.Inject; + /** * Total of STANDARD containers used in LIVE pages */ -public class TotalStandardContainersInLivePageDatabaseMetricType extends TotalContainersInLivePageDatabaseMetricType { +public class TotalStandardContainersInLivePageDatabaseMetricType extends TotalContainersInLivePageDatabaseMetricType { + + @Inject + public TotalStandardContainersInLivePageDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -21,5 +29,5 @@ public String getDescription() { boolean filterContainer(final String containerId) { return !FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } -} +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLiveTemplatesDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLiveTemplatesDatabaseMetricType.java index a29555de8e2d..b1ff647c7687 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLiveTemplatesDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInLiveTemplatesDatabaseMetricType.java @@ -1,11 +1,21 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + /** * Total of STANDARD containers used in LIVE templates */ -public class TotalStandardContainersInLiveTemplatesDatabaseMetricType extends TotalContainersInLiveTemplatesDatabaseMetricType { +@ApplicationScoped +public class TotalStandardContainersInLiveTemplatesDatabaseMetricType extends TotalContainersInLiveTemplatesDatabaseMetricType { + + @Inject + public TotalStandardContainersInLiveTemplatesDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -16,9 +26,11 @@ public String getName() { public String getDescription() { return "Total of STANDARD containers used in LIVE templates"; } + @Override boolean filterContainer(final String containerId) { return !FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } + } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInWorkingPageDatabaseMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInWorkingPageDatabaseMetricType.java index 27a1aaf75e2c..2fe1bc9f4093 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInWorkingPageDatabaseMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/container/TotalStandardContainersInWorkingPageDatabaseMetricType.java @@ -1,11 +1,19 @@ package com.dotcms.telemetry.collectors.container; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil; +import javax.inject.Inject; + /** * Total of STANDARD containers used in WORKING pages */ -public class TotalStandardContainersInWorkingPageDatabaseMetricType extends TotalContainersInWorkingPageDatabaseMetricType { +public class TotalStandardContainersInWorkingPageDatabaseMetricType extends TotalContainersInWorkingPageDatabaseMetricType { + + @Inject + public TotalStandardContainersInWorkingPageDatabaseMetricType(final MetricsAPI metricsAPI) { + super.metricsAPI = metricsAPI; + } @Override public String getName() { @@ -16,9 +24,10 @@ public String getName() { public String getDescription() { return "Count of STANDARD containers used in WORKING pages"; } + @Override boolean filterContainer(final String containerId) { return !FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerId); } -} +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/theme/TotalFilesInThemeMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/theme/TotalFilesInThemeMetricType.java index 381958e7fff5..0a7c00069550 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/theme/TotalFilesInThemeMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/theme/TotalFilesInThemeMetricType.java @@ -3,12 +3,12 @@ import com.dotcms.telemetry.MetricCategory; import com.dotcms.telemetry.MetricFeature; import com.dotcms.telemetry.collectors.DBMetricType; -import com.dotmarketing.util.Logger; /** * Collects the total of Number of LIVE/WORKING files in themes */ public class TotalFilesInThemeMetricType implements DBMetricType { + @Override public String getName() { return "TOTAL_FILES_IN_THEMES"; @@ -38,4 +38,5 @@ public String getSqlQuery() { "FROM contentlet_version_info cvi INNER JOIN identifier id ON cvi.identifier = id.id " + "WHERE id.parent_path LIKE '/application/themes/%' AND id.asset_name = 'template.vtl')"; } + } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/job/MetricsStatsJob.java b/dotCMS/src/main/java/com/dotcms/telemetry/job/MetricsStatsJob.java index ee726513c68c..33361ce7605a 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/job/MetricsStatsJob.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/job/MetricsStatsJob.java @@ -1,10 +1,9 @@ package com.dotcms.telemetry.job; -import com.dotcms.concurrent.lock.ClusterLockManager; import com.dotcms.exception.ExceptionUtil; import com.dotcms.telemetry.MetricsSnapshot; -import com.dotcms.telemetry.business.MetricsAPI; import com.dotcms.telemetry.collectors.MetricStatsCollector; +import com.dotmarketing.business.APILocator; import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; import io.vavr.Lazy; @@ -22,7 +21,7 @@ public class MetricsStatsJob implements StatefulJob { public static final String JOB_GROUP = "MetricsStatsJobGroup"; public static final String ENABLED_PROP = "TELEMETRY_SAVE_SCHEDULE_JOB_ENABLED"; public static final String CRON_EXPR_PROP = "TELEMETRY_SAVE_SCHEDULE"; - private static final String CRON_EXPRESSION_DEFAULT = "0 0 22 * * ?"; + public static final String CRON_EXPRESSION_DEFAULT = "0 0 22 * * ?"; public static final Lazy ENABLED = Lazy.of(() -> Config.getBooleanProperty(ENABLED_PROP, true)); @@ -34,15 +33,11 @@ public void execute(final JobExecutionContext jobExecutionContext) throws JobExe final MetricsSnapshot metricsSnapshot; try { metricsSnapshot = MetricStatsCollector.getStatsAndCleanUp(); - MetricsAPI.INSTANCE.persistMetricsSnapshot(metricsSnapshot); + APILocator.getMetricsAPI().persistMetricsSnapshot(metricsSnapshot); } catch (final Throwable e) { - Logger.debug(this, String.format("Error occurred during job execution: %s", + Logger.error(this, String.format("An error occurred during job execution: %s", ExceptionUtil.getErrorMessage(e)), e); - } } - - public void run(ClusterLockManager lockManager) { - } } diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/osgi/Activator.java b/dotCMS/src/main/java/com/dotcms/telemetry/osgi/Activator.java deleted file mode 100644 index 397501ce346e..000000000000 --- a/dotCMS/src/main/java/com/dotcms/telemetry/osgi/Activator.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.dotcms.telemetry.osgi; - -import com.dotcms.concurrent.DotConcurrentFactory; -import com.dotcms.concurrent.lock.ClusterLockManager; -import com.dotcms.telemetry.rest.TelemetryResource; -import com.dotcms.telemetry.collectors.api.ApiMetricAPI; -import com.dotcms.telemetry.collectors.api.ApiMetricFactorySubmitter; -import com.dotcms.telemetry.collectors.api.ApiMetricWebInterceptor; -import com.dotcms.telemetry.job.MetricsStatsJob; -import com.dotcms.filters.interceptor.FilterWebInterceptorProvider; -import com.dotcms.filters.interceptor.WebInterceptor; -import com.dotcms.filters.interceptor.WebInterceptorDelegate; -import com.dotcms.rest.config.RestServiceUtil; -import com.dotmarketing.filters.InterceptorFilter; -import com.dotmarketing.osgi.GenericBundleActivator; -import com.dotmarketing.util.Config; -import com.dotmarketing.util.Logger; -import io.vavr.Lazy; -import org.apache.logging.log4j.core.util.CronExpression; -import org.osgi.framework.BundleContext; - -import java.text.ParseException; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -public class Activator extends GenericBundleActivator { - public static String version; - - private final WebInterceptorDelegate delegate = FilterWebInterceptorProvider - .getInstance(Config.CONTEXT) - .getDelegate(InterceptorFilter.class); - - private final WebInterceptor apiCallWebInterceptor = new ApiMetricWebInterceptor(); - private final MetricsStatsJob metricsStatsJob = new MetricsStatsJob(); - - private static final String METRICS_JOB_LOCK_KEY = "metrics_job_lock"; - private ScheduledFuture scheduledFuture; - private final Lazy enableTelemetry = Lazy.of(() -> - Config.getBooleanProperty("FEATURE_FLAG_TELEMETRY", false)); - - private final Lazy enableAPIMetrics = Lazy.of(() -> - Config.getBooleanProperty("TELEMETRY_API_METRICS_ENABLED", false)); - - public static final ApiMetricAPI apiStatAPI = new ApiMetricAPI(); - - @Override - public void start(final BundleContext context) { - - PluginVersionUtil.init(context); - - if(Boolean.TRUE.equals(enableTelemetry.get())) { - Logger.debug(Activator.class.getName(), "Starting the Telemetry plugin"); - - RestServiceUtil.addResource(TelemetryResource.class); - - try { - apiStatAPI.dropTemporaryTable(); - apiStatAPI.createTemporaryTable(); - - if(Boolean.TRUE.equals(enableAPIMetrics.get())) { - Logger.debug(Activator.class.getName(), "API metrics enabled"); - delegate.addFirst(apiCallWebInterceptor); - ApiMetricFactorySubmitter.INSTANCE.start(); - } - Logger.debug(Activator.class.getName(), "Scheduling Telemetry Job"); - scheduleMetricsJob(); - Logger.debug(Activator.class.getName(), "The Telemetry plugin was started"); - } catch (Throwable t) { - Logger.debug(this, "Error starting the Telemetry plugin.", t); - } - } - } - - private void scheduleMetricsJob() throws ParseException { - final ClusterLockManager lockManager = DotConcurrentFactory.getInstance() - .getClusterLockManager(METRICS_JOB_LOCK_KEY); - - CronExpression cron = new CronExpression(Config - .getStringProperty("TELEMETRY_SAVE_SCHEDULE", "0 0 22 * * ?")) ; - - final Instant now = Instant.now(); - final Instant previousRun = cron.getPrevFireTime(Date.from(now)).toInstant(); - final Instant nextRun = cron.getNextValidTimeAfter(Date.from(previousRun)).toInstant(); - final Duration delay = Duration.between(now, nextRun); - final Duration runEvery = Duration.between(previousRun, nextRun); - - scheduledFuture = DotConcurrentFactory.getScheduledThreadPoolExecutor().scheduleAtFixedRate( - () -> metricsStatsJob.run(lockManager) - , delay.get(ChronoUnit.SECONDS), - runEvery.get(ChronoUnit.SECONDS), - TimeUnit.SECONDS); - } - - @Override - public void stop(BundleContext context) throws Exception { - if(Boolean.TRUE.equals(enableTelemetry.get())) { - RestServiceUtil.removeResource(TelemetryResource.class); - scheduledFuture.cancel(false); - apiStatAPI.dropTemporaryTable(); - - if(Boolean.TRUE.equals(enableAPIMetrics.get())) { - delegate.remove(apiCallWebInterceptor.getName(), true); - ApiMetricFactorySubmitter.INSTANCE.shutdownNow(); - } - } - } -} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/osgi/PluginVersionUtil.java b/dotCMS/src/main/java/com/dotcms/telemetry/osgi/PluginVersionUtil.java deleted file mode 100644 index 15173b777101..000000000000 --- a/dotCMS/src/main/java/com/dotcms/telemetry/osgi/PluginVersionUtil.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.dotcms.telemetry.osgi; - -import org.osgi.framework.BundleContext; - -public class PluginVersionUtil { - - private static String version; - - private PluginVersionUtil(){} - - public static void init(BundleContext context) { - version = context.getBundle().getHeaders().get("Bundle-Version"); - } - - public static String getVersion(){ - return version; - } -} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/rest/TelemetryResource.java b/dotCMS/src/main/java/com/dotcms/telemetry/rest/TelemetryResource.java index cb50f24c37fa..1e4e677c2809 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/rest/TelemetryResource.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/rest/TelemetryResource.java @@ -1,8 +1,8 @@ package com.dotcms.telemetry.rest; -import com.dotcms.telemetry.collectors.MetricStatsCollector; import com.dotcms.rest.WebResource; import com.dotcms.rest.annotation.NoCache; +import com.dotcms.telemetry.collectors.MetricStatsCollector; import com.dotmarketing.business.Role; import com.dotmarketing.util.Logger; import com.fasterxml.jackson.jaxrs.json.annotation.JSONP; @@ -37,6 +37,7 @@ public class TelemetryResource { ResponseEntityMetricsSnapshotView.class)))}) public final Response getData(@Context final HttpServletRequest request, @Context final HttpServletResponse response) { + Logger.debug(this, () -> "Generating dotCMS Telemetry data"); new WebResource.InitBuilder(new WebResource()) .requestAndResponse(request, response) .requiredBackendUser(true) diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/util/MetricCaches.java b/dotCMS/src/main/java/com/dotcms/telemetry/util/MetricCaches.java index 4044048424e9..c37142fc9a72 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/util/MetricCaches.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/util/MetricCaches.java @@ -1,5 +1,6 @@ package com.dotcms.telemetry.util; +import com.dotcms.cdi.CDIUtils; import com.dotcms.telemetry.collectors.api.ApiMetricAPI; import java.util.Arrays; @@ -10,7 +11,7 @@ public enum MetricCaches { SITE_SEARCH_INDICES(new MetricCache<>(IndicesSiteSearchUtil.INSTANCE::getESIndices)), - TEMPORARY_TABLA_DATA(new MetricCache<>(ApiMetricAPI::getMetricTemporaryTableData)); + TEMPORARY_TABLA_DATA(new MetricCache<>(() -> CDIUtils.getBeanThrows(ApiMetricAPI.class).getMetricTemporaryTableData())); private final MetricCache metricCache; diff --git a/dotCMS/src/main/java/com/dotcms/variant/business/web/VariantWebAPIImpl.java b/dotCMS/src/main/java/com/dotcms/variant/business/web/VariantWebAPIImpl.java index bf386e9e9831..5a83c9088ce9 100644 --- a/dotCMS/src/main/java/com/dotcms/variant/business/web/VariantWebAPIImpl.java +++ b/dotCMS/src/main/java/com/dotcms/variant/business/web/VariantWebAPIImpl.java @@ -253,7 +253,7 @@ private static boolean isWidgetFallback(ContentType type) { } private static boolean isFileFallback(ContentType type) { - return type.baseType() == BaseContentType.FILEASSET + return (type.baseType() == BaseContentType.FILEASSET || type.baseType() == BaseContentType.DOTASSET) && APILocator.getLanguageAPI().canDefaultFileToDefaultLanguage(); } diff --git a/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java b/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java index 2acb7cb49e27..758031be4879 100644 --- a/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java +++ b/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java @@ -94,6 +94,7 @@ import com.dotcms.storage.FileStorageAPIImpl; import com.dotcms.system.event.local.business.LocalSystemEventsAPI; import com.dotcms.system.event.local.business.LocalSystemEventsAPIFactory; +import com.dotcms.telemetry.business.MetricsAPI; import com.dotcms.timemachine.business.TimeMachineAPI; import com.dotcms.timemachine.business.TimeMachineAPIImpl; import com.dotcms.util.FileWatcherAPI; @@ -1163,6 +1164,11 @@ public static AnalyticsAPI getAnalyticsAPI() { return (AnalyticsAPI) getInstance(APIIndex.ANALYTICS_API); } + /** + * Returns a single instance of the {@link ContentTypeDestroyAPI} class. + * + * @return The {@link ContentTypeDestroyAPI} instance. + */ public static ContentTypeDestroyAPI getContentTypeDestroyAPI() { return (ContentTypeDestroyAPI) getInstance(APIIndex.CONTENT_TYPE_DESTROY_API); } @@ -1193,6 +1199,15 @@ public static ContentAnalyticsAPI getContentAnalyticsAPI() { return (ContentAnalyticsAPI) getInstance(APIIndex.CONTENT_ANALYTICS_API); } + /** + * Returns a single instance of the {@link MetricsAPI} class via CDI. + * + * @return The {@link MetricsAPI} instance. + */ + public static MetricsAPI getMetricsAPI() { + return CDIUtils.getBeanThrows(MetricsAPI.class); + } + /** * Generates a unique instance of the specified dotCMS API. * diff --git a/dotCMS/src/main/java/com/dotmarketing/business/cache/provider/caffine/CaffineCache.java b/dotCMS/src/main/java/com/dotmarketing/business/cache/provider/caffine/CaffineCache.java index fffacbef9db7..f26c41573ae2 100644 --- a/dotCMS/src/main/java/com/dotmarketing/business/cache/provider/caffine/CaffineCache.java +++ b/dotCMS/src/main/java/com/dotmarketing/business/cache/provider/caffine/CaffineCache.java @@ -199,7 +199,7 @@ public CacheProviderStats getStats() { : Config.getIntProperty("cache." + DEFAULT_CACHE + ".size"); final int seconds = - isDefault ? Config.getIntProperty("cache." + DEFAULT_CACHE + ".seconds", 100) + isDefault ? Config.getIntProperty("cache." + DEFAULT_CACHE + ".seconds", -1) : Config.getIntProperty("cache." + group + ".seconds", -1); com.github.benmanes.caffeine.cache.stats.CacheStats cstats = foundCache.stats(); diff --git a/dotCMS/src/main/java/com/dotmarketing/business/cache/provider/timedcache/TimedCacheProvider.java b/dotCMS/src/main/java/com/dotmarketing/business/cache/provider/timedcache/TimedCacheProvider.java index 0a889a2c0c91..cde5eef3a339 100644 --- a/dotCMS/src/main/java/com/dotmarketing/business/cache/provider/timedcache/TimedCacheProvider.java +++ b/dotCMS/src/main/java/com/dotmarketing/business/cache/provider/timedcache/TimedCacheProvider.java @@ -31,8 +31,6 @@ public class TimedCacheProvider extends CacheProvider { private final ConcurrentHashMap> groups = new ConcurrentHashMap<>(); static final String DEFAULT_CACHE = CacheProviderAPI.DEFAULT_CACHE; - static final String LIVE_CACHE_PREFIX = CacheProviderAPI.LIVE_CACHE_PREFIX; - static final String WORKING_CACHE_PREFIX = CacheProviderAPI.WORKING_CACHE_PREFIX; private final HashSet availableCaches = new HashSet<>(); @@ -94,7 +92,7 @@ public void put(String group, String key, Object content) { } @Override - public synchronized Object get(String group, String key) { + public Object get(String group, String key) { // Get the cache for the given group Cache cache = getCache(group); return cache.getIfPresent(key); @@ -204,7 +202,7 @@ private String cacheKey(String group, String key) { return (group + ":" + key).toLowerCase(); } - private synchronized Cache getCache(String cacheName) { + private Cache getCache(String cacheName) { if (cacheName == null) { throw new DotStateException("Null cache region passed in"); } @@ -265,4 +263,4 @@ private synchronized Cache getCache(String cacheName) { -} \ No newline at end of file +} diff --git a/dotCMS/src/main/java/com/dotmarketing/filters/InterceptorFilter.java b/dotCMS/src/main/java/com/dotmarketing/filters/InterceptorFilter.java index b1d69259343a..9b3390ec46c0 100644 --- a/dotCMS/src/main/java/com/dotmarketing/filters/InterceptorFilter.java +++ b/dotCMS/src/main/java/com/dotmarketing/filters/InterceptorFilter.java @@ -10,20 +10,31 @@ import com.dotcms.jitsu.EventLogWebInterceptor; import com.dotcms.prerender.PreRenderSEOWebInterceptor; import com.dotcms.security.multipart.MultiPartRequestSecurityWebInterceptor; +import com.dotcms.telemetry.collectors.api.ApiMetricWebInterceptor; import com.dotcms.variant.business.web.CurrentVariantWebInterceptor; import com.dotmarketing.business.APILocator; import com.dotmarketing.util.Config; +import io.vavr.Lazy; import javax.servlet.FilterConfig; import javax.servlet.ServletException; /** - * This empty filter is useful to attach {@link com.dotcms.filters.interceptor.WebInterceptor}, it is the first one on the - * filter pipeline and maps everything. + * This empty filter is useful to attach {@link com.dotcms.filters.interceptor.WebInterceptor} + * objects to it. This is the first one in the filter pipeline and maps everything. This way, it's + * not necessary to modify the web.xml file to add any new interceptors, and they can even be added + * programmatically via OSGi plug-ins. + * * @author jsanca */ public class InterceptorFilter extends AbstractWebInterceptorSupportFilter { + private static final Lazy ENABLE_TELEMETRY_FROM_CORE = Lazy.of(() -> + Config.getBooleanProperty("FEATURE_FLAG_TELEMETRY_CORE_ENABLED", false)); + + private static final Lazy TELEMETRY_API_METRICS_ENABLED = Lazy.of(() -> + Config.getBooleanProperty("TELEMETRY_API_METRICS_ENABLED", false)); + @Override public void init(final FilterConfig config) throws ServletException { @@ -34,8 +45,12 @@ public void init(final FilterConfig config) throws ServletException { super.init(config); } // init. + /** + * Adds the interceptors to the delegate. You can add more to the list, as required. + * + * @param config The current instance of the {@link FilterConfig} object. + */ private void addInterceptors(final FilterConfig config) { - final WebInterceptorDelegate delegate = this.getDelegate(config.getServletContext()); @@ -48,7 +63,9 @@ private void addInterceptors(final FilterConfig config) { delegate.add(new EventLogWebInterceptor()); delegate.add(new CurrentVariantWebInterceptor()); delegate.add(analyticsTrackWebInterceptor); - + if (Boolean.TRUE.equals(ENABLE_TELEMETRY_FROM_CORE.get()) && Boolean.TRUE.equals(TELEMETRY_API_METRICS_ENABLED.get())) { + delegate.add(new ApiMetricWebInterceptor()); + } APILocator.getLocalSystemEventsAPI().subscribe(SystemTableUpdatedKeyEvent.class, analyticsTrackWebInterceptor); } // addInterceptors. diff --git a/dotCMS/src/main/java/com/dotmarketing/init/DotInitScheduler.java b/dotCMS/src/main/java/com/dotmarketing/init/DotInitScheduler.java index 5b6ca319aecd..c35632c9de27 100644 --- a/dotCMS/src/main/java/com/dotmarketing/init/DotInitScheduler.java +++ b/dotCMS/src/main/java/com/dotmarketing/init/DotInitScheduler.java @@ -5,10 +5,22 @@ import com.dotcms.job.system.event.DeleteOldSystemEventsJob; import com.dotcms.job.system.event.SystemEventsJob; import com.dotcms.publisher.business.PublisherQueueJob; +import com.dotcms.telemetry.job.MetricsStatsJob; import com.dotcms.workflow.EscalationThread; import com.dotmarketing.exception.DotDataException; import com.dotmarketing.quartz.QuartzUtils; -import com.dotmarketing.quartz.job.*; +import com.dotmarketing.quartz.job.AccessTokenRenewJob; +import com.dotmarketing.quartz.job.BinaryCleanupJob; +import com.dotmarketing.quartz.job.CleanUnDeletedUsersJob; +import com.dotmarketing.quartz.job.DeleteInactiveLiveWorkingIndicesJob; +import com.dotmarketing.quartz.job.DeleteSiteSearchIndicesJob; +import com.dotmarketing.quartz.job.DropOldContentVersionsJob; +import com.dotmarketing.quartz.job.EsReadOnlyMonitorJob; +import com.dotmarketing.quartz.job.FreeServerFromClusterJob; +import com.dotmarketing.quartz.job.PruneTimeMachineBackupJob; +import com.dotmarketing.quartz.job.ServerHeartbeatJob; +import com.dotmarketing.quartz.job.StartEndScheduledExperimentsJob; +import com.dotmarketing.quartz.job.UsersToDeleteThread; import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; import com.dotmarketing.util.UtilMethods; @@ -36,19 +48,15 @@ public class DotInitScheduler { public static final String DOTCMS_JOB_GROUP_NAME = "dotcms_jobs"; - public static final String CRON_EXPRESSION_EVERY_5_MINUTES = "0 */5 * ? * *"; - private static void deleteOldJobs() throws DotDataException { - // remove unused old jobs QuartzUtils.deleteJobDB("WebDavCleanupJob", DOTCMS_JOB_GROUP_NAME); QuartzUtils.deleteJobDB("TrashCleanupJob", DOTCMS_JOB_GROUP_NAME); QuartzUtils.deleteJobDB("DeleteOldClickstreams", DOTCMS_JOB_GROUP_NAME); QuartzUtils.deleteJobDB("linkchecker", DOTCMS_JOB_GROUP_NAME); QuartzUtils.deleteJobDB("ContentReindexerJob", DOTCMS_JOB_GROUP_NAME); - } /** @@ -66,12 +74,9 @@ public static void start() throws Exception { Calendar calendar; boolean isNew; - - // remove unused old jobs deleteOldJobs(); - if(Config.getBooleanProperty("ENABLE_USERS_TO_DELETE_THREAD", false)) { try { isNew = false; @@ -298,34 +303,26 @@ public static void start() throws Exception { } addDropOldContentVersionsJob(); - if ( !Config.getBooleanProperty(DOTCMS_DISABLE_WEBSOCKET_PROTOCOL, false) ) { // Enabling the System Events Job addSystemEventsJob(); } - // start the server heartbeat job addServerHeartbeatJob(); - // Enabling the Delete Old System Events Job addDeleteOldSystemEvents(sched); - if ( !Config.getBooleanProperty(DOTCMS_DISABLE_ELASTIC_READONLY_MONITOR, false) ) { // Enabling the Read only monitor addElasticReadyOnlyMonitor(sched); } - //Enable the delete old ES Indices Job addDeleteOldESIndicesJob(sched); - //Enable the delete old SS Indices Job addDeleteOldSiteSearchIndicesJob(sched); - AccessTokenRenewJob.AccessTokensRenewJobScheduler.schedule(); - addStartEndScheduledExperimentsJob(sched); - addPruneOldTimeMachineBackups(sched); + addTelemetryMetricsStatsJob(sched); //Starting the sequential and standard Schedulers QuartzUtils.startSchedulers(); @@ -537,6 +534,39 @@ private static void addPruneOldTimeMachineBackups (final Scheduler scheduler) { } } + /** + * Adds the {@link MetricsStatsJob} Quartz Job to the scheduler during the startup + * process. + */ + private static void addTelemetryMetricsStatsJob(final Scheduler scheduler) { + if (Config.getBooleanProperty("FEATURE_FLAG_TELEMETRY_CORE_ENABLED", false)) { + final String triggerName = "trigger36"; + final String triggerGroup = "group36"; + final JobBuilder telemetryMetricsStatsJob = new JobBuilder() + .setJobClass(MetricsStatsJob.class) + .setJobName(MetricsStatsJob.JOB_NAME) + .setJobGroup(DOTCMS_JOB_GROUP_NAME) + .setTriggerName(triggerName) + .setTriggerGroup(triggerGroup) + .setCronExpressionProp(MetricsStatsJob.CRON_EXPR_PROP) + .setCronExpressionPropDefault(MetricsStatsJob.CRON_EXPRESSION.get()) + .setCronMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING); + if (Boolean.FALSE.equals(MetricsStatsJob.ENABLED.get())) { + telemetryMetricsStatsJob.enabled(false); + } + scheduleJob(telemetryMetricsStatsJob); + } else { + try { + // If the job is not enabled, delete it from the scheduler if it exists + if (null != (scheduler.getJobDetail(MetricsStatsJob.JOB_NAME, DOTCMS_JOB_GROUP_NAME))) { + scheduler.deleteJob(MetricsStatsJob.JOB_NAME, DOTCMS_JOB_GROUP_NAME); + } + } catch (final SchedulerException e) { + Logger.warn(DotInitScheduler.class, e.toString()); + } + } + } + private static void addServerHeartbeatJob () { final int initialDelay = Config.getIntProperty("SERVER_HEARTBEAT_INITIAL_DELAY_SECONDS", 60); diff --git a/dotCMS/src/main/java/com/dotmarketing/servlets/ShortyServlet.java b/dotCMS/src/main/java/com/dotmarketing/servlets/ShortyServlet.java index f17548d4534a..568a31ded0c3 100644 --- a/dotCMS/src/main/java/com/dotmarketing/servlets/ShortyServlet.java +++ b/dotCMS/src/main/java/com/dotmarketing/servlets/ShortyServlet.java @@ -499,7 +499,20 @@ private boolean isValidRequest(final HttpServletRequest request, return true; } - + + /** + * Resolves and builds the appropriate file path for a contentlet's field. + * This method handles both regular fields and special cases for image/file fields, + * including language fallback logic when necessary. + * + * @param contentlet The contentlet whose field path needs to be resolved + * @param tryField The name of the field to try to resolve + * @param live Whether to use the live version (true) or working version (false) + * @return A string representing the path to the field's content + * @throws DotStateException If there's an issue with the contentlet's state + * @throws DotDataException If there's an error accessing the data + * @throws DotSecurityException If there's a security violation + */ protected final String inodePath(final Contentlet contentlet, final String tryField, final boolean live) @@ -508,35 +521,73 @@ protected final String inodePath(final Contentlet contentlet, final Optional fieldOpt = resolveField(contentlet, tryField); if (fieldOpt.isEmpty()) { - return "/" + contentlet.getInode() + "/" + FILE_ASSET_DEFAULT; + return buildFieldPath(contentlet, FILE_ASSET_DEFAULT); } final Field field = fieldOpt.get(); if (field instanceof ImageField || field instanceof FileField) { final String relatedImageId = contentlet.getStringProperty(field.variable()); - final Optional contentletVersionInfo = + Optional contentletVersionInfo = this.versionableAPI.getContentletVersionInfo(relatedImageId, contentlet.getLanguageId()); - if (contentletVersionInfo.isPresent()) { - final String inode = live ? contentletVersionInfo.get().getLiveInode() - : contentletVersionInfo.get().getWorkingInode(); - - final Contentlet imageContentlet = APILocator.getContentletAPI() - .find(inode, APILocator.systemUser(), false); - - validateContentlet(imageContentlet, live, inode); - - final String fieldVar = imageContentlet.isDotAsset() ? - DotAssetContentType.ASSET_FIELD_VAR : FILE_ASSET_DEFAULT; + if (contentletVersionInfo.isEmpty() && shouldFallbackToDefaultLanguage(contentlet)) { + // Try finding the contentlet version with the default language ID + Logger.info(this, "No contentlet version found for identifier " + relatedImageId + " in language " + contentlet.getLanguageId() + ", trying default language."); + contentletVersionInfo = this.versionableAPI.getContentletVersionInfo(relatedImageId,APILocator.getLanguageAPI().getDefaultLanguage().getId()); + } - return new StringBuilder(StringPool.FORWARD_SLASH).append(inode) - .append(StringPool.FORWARD_SLASH).append(fieldVar).toString(); + if (contentletVersionInfo.isPresent()) { + Logger.debug(this, "Contentlet version found for identifier: " + relatedImageId); + final Contentlet imageContentlet = getImageContentlet(contentletVersionInfo.get(), live); + validateContentlet(imageContentlet, live, imageContentlet.getInode()); + final String fieldVar = imageContentlet.isDotAsset() ? DotAssetContentType.ASSET_FIELD_VAR : FILE_ASSET_DEFAULT; + return buildFieldPath(imageContentlet, fieldVar); } + Logger.debug(this, "No contentlet version found for identifier: " + relatedImageId + ", returning path based on original contentlet inode: " + contentlet.getInode()); } + return buildFieldPath(contentlet, field.variable()); + } + + /** + * Determines whether the system should attempt to fallback to the default language + * for the given contentlet. This is used when content is not found in the + * contentlet's original language. + * + * @param contentlet The contentlet to check for language fallback eligibility + * @return true if the system should attempt to use the default language, false otherwise + */ + private boolean shouldFallbackToDefaultLanguage(final Contentlet contentlet) { + return APILocator.getLanguageAPI().canDefaultFileToDefaultLanguage() && + APILocator.getLanguageAPI().getDefaultLanguage().getId() != contentlet.getLanguageId(); + } + + /** + * Retrieves the appropriate contentlet version (live or working) for an image + * based on the provided version info. + * + * @param versionInfo The version information for the contentlet + * @param live Whether to retrieve the live version (true) or working version (false) + * @return The requested version of the contentlet + * @throws DotDataException If there's an error accessing the data + * @throws DotSecurityException If there's a security violation + */ + private Contentlet getImageContentlet(final ContentletVersionInfo versionInfo, final boolean live) + throws DotDataException, DotSecurityException { + final String inode = live ? versionInfo.getLiveInode() : versionInfo.getWorkingInode(); + return APILocator.getContentletAPI().find(inode, APILocator.systemUser(), false); + } - return new StringBuilder(StringPool.FORWARD_SLASH).append(contentlet.getInode()) - .append(StringPool.FORWARD_SLASH).append(field.variable()).toString(); + /** + * Constructs a standardized field path for a contentlet and field variable. + * The path format is: /[contentlet-inode]/[field-variable] + * + * @param contentlet The contentlet for which to build the path + * @param fieldVar The field variable to append to the path + * @return A formatted path string + */ + private String buildFieldPath(final Contentlet contentlet, final String fieldVar) { + return StringPool.FORWARD_SLASH + contentlet.getInode() + StringPool.FORWARD_SLASH + fieldVar; } private void validateContentlet(final Contentlet contentlet, final boolean live, final String inode) throws DotDataException { diff --git a/dotCMS/src/main/webapp/WEB-INF/jsp/lucene/lucene_search.jsp b/dotCMS/src/main/webapp/WEB-INF/jsp/lucene/lucene_search.jsp index 1f480a25aeb3..9d20647ae0e2 100644 --- a/dotCMS/src/main/webapp/WEB-INF/jsp/lucene/lucene_search.jsp +++ b/dotCMS/src/main/webapp/WEB-INF/jsp/lucene/lucene_search.jsp @@ -96,8 +96,9 @@ query = Xss.strip(query); } function setContentletItem(contentlet, index) { + return `${index}.<%= LanguageUtil.get(pageContext, "Title") %> - + ${contentlet.title} <%= LanguageUtil.get(pageContext, "ContentType") %>: ${contentlet.contentType} @@ -159,9 +160,9 @@ query = Xss.strip(query); luceneResultTable.innerHTML = `
<%= LanguageUtil.get(pageContext, "No-Results") %>
`; } - function openContent(event, inode) { + function openContent(event, inode, variantName) { if (! hastModifiers(event)){ - window.parent.location = `/dotAdmin/#/c/content/${inode}`; + window.parent.location = `/dotAdmin/#/c/content/${inode}&variantName=${variantName ?? 'DEFAULT'}`; return false; } return true; diff --git a/dotCMS/src/main/webapp/html/ng-contentlet-selector.jsp b/dotCMS/src/main/webapp/html/ng-contentlet-selector.jsp index cef8be1697a5..b9ed4d9a7f67 100644 --- a/dotCMS/src/main/webapp/html/ng-contentlet-selector.jsp +++ b/dotCMS/src/main/webapp/html/ng-contentlet-selector.jsp @@ -27,6 +27,8 @@ <% String containerIdentifier = request.getParameter("container_id"); String language_id = request.getParameter("language_id"); + String variantName = request.getParameter("variantName") != null ? request.getParameter("variantName") : "DEFAULT"; + User user = PortalUtil.getUser(request); Container container = null; if (FileAssetContainerUtil.getInstance().isFolderAssetContainerId(containerIdentifier)) { @@ -139,7 +141,7 @@ function addNewContentlet(iNode, contentType) { var href = "/c/portal/layout?p_l_id=<%=contentLayout.getId()%>&p_p_id=content&p_p_action=1&p_p_state=maximized&p_p_mode=view"; href += "&_content_struts_action=%2Fext%2Fcontentlet%2Fedit_contentlet&_content_cmd=new"; - href += "&selectedStructure=" + (iNode || _dotSelectedStructure) + "&lang=" + getCurrentUrlLanguageId(); + href += "&selectedStructure=" + (iNode || _dotSelectedStructure) + "&lang=" + getCurrentUrlLanguageId() + "&variantName=" + '<%=variantName%>'; createContentlet(href, contentType || _dotSelectedStructureVariable); } diff --git a/dotCMS/src/main/webapp/html/portlet/ext/contentlet/edit_contentlet_js_inc.jsp b/dotCMS/src/main/webapp/html/portlet/ext/contentlet/edit_contentlet_js_inc.jsp index 4c3d52a6ac10..c2e78fd8d3b3 100644 --- a/dotCMS/src/main/webapp/html/portlet/ext/contentlet/edit_contentlet_js_inc.jsp +++ b/dotCMS/src/main/webapp/html/portlet/ext/contentlet/edit_contentlet_js_inc.jsp @@ -9,6 +9,7 @@ String catCount = (String) request.getAttribute("counter"); String isURLMap = (String) request.getParameter("isURLMap"); + String variantNameParam = request.getParameter("variantName") != null ? request.getParameter("variantName") : "DEFAULT"; %>