diff --git a/dotCMS/src/main/java/com/dotcms/content/index/domain/SearchHits.java b/dotCMS/src/main/java/com/dotcms/content/index/domain/SearchHits.java index 87f06c5e6648..2a4d25cff5e7 100644 --- a/dotCMS/src/main/java/com/dotcms/content/index/domain/SearchHits.java +++ b/dotCMS/src/main/java/com/dotcms/content/index/domain/SearchHits.java @@ -6,7 +6,6 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; -import org.elasticsearch.common.Nullable; import org.immutables.value.Value; import org.immutables.value.Value.Default; import org.jetbrains.annotations.NotNull; diff --git a/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/ContentsWebAPI.java b/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/ContentsWebAPI.java index 6970c052a3a1..5aa5a6413f52 100644 --- a/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/ContentsWebAPI.java +++ b/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/ContentsWebAPI.java @@ -40,7 +40,6 @@ import org.apache.commons.beanutils.PropertyUtils; import org.apache.velocity.tools.view.context.ViewContext; import org.apache.velocity.tools.view.tools.ViewTool; -import org.elasticsearch.search.SearchHits; public class ContentsWebAPI implements ViewTool { @@ -870,7 +869,6 @@ public HashMap pageContent(String query, String sortBy, String perPage, String c int limit = 0; List l = new ArrayList<>(); - SearchHits hits = null; List c = new ArrayList<>(); try { diff --git a/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/ESContentTool.java b/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/ESContentTool.java index 5c6bcd969228..1f9c3f9d28d6 100644 --- a/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/ESContentTool.java +++ b/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/ESContentTool.java @@ -28,6 +28,8 @@ import com.liferay.portal.model.User; +// ES-DECOMMISSION: Velocity ViewTool exposes SearchResponse and ESSearchResults in deprecated +// bridge methods. Already delegates to neutral SearchAPI — remove esSearch, esSearchRaw at R7 cutover. public class ESContentTool implements ViewTool { private HttpServletRequest req; diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/authentication/CreateJsonWebTokenResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/authentication/CreateJsonWebTokenResource.java index ba703f044e4c..184adfa16294 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/authentication/CreateJsonWebTokenResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/authentication/CreateJsonWebTokenResource.java @@ -44,7 +44,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.ExternalDocumentation; -import org.elasticsearch.common.collect.Map; +import java.util.Map; import org.glassfish.jersey.server.JSONP; import javax.servlet.http.HttpServletRequest; diff --git a/dotCMS/src/main/java/com/dotcms/rest/exception/mapper/ElasticsearchStatusExceptionMapper.java b/dotCMS/src/main/java/com/dotcms/rest/exception/mapper/ElasticsearchStatusExceptionMapper.java index 85193ab752a6..bac1f6e812fb 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/exception/mapper/ElasticsearchStatusExceptionMapper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/exception/mapper/ElasticsearchStatusExceptionMapper.java @@ -7,10 +7,12 @@ import org.elasticsearch.ElasticsearchStatusException; import javax.ws.rs.ext.ExceptionMapper; +// ES-DECOMMISSION: JAX-RS mapper typed to ElasticsearchStatusException. +// Decommission entire class — OpenSearchExceptionMapper is the OS replacement. +@Deprecated(forRemoval = true) @Provider public class ElasticsearchStatusExceptionMapper implements ExceptionMapper { - @Override public Response toResponse(final ElasticsearchStatusException exception) { diff --git a/dotCMS/src/main/java/com/dotcms/rest/exception/mapper/OpenSearchExceptionMapper.java b/dotCMS/src/main/java/com/dotcms/rest/exception/mapper/OpenSearchExceptionMapper.java new file mode 100644 index 000000000000..78b42d80a8f7 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/rest/exception/mapper/OpenSearchExceptionMapper.java @@ -0,0 +1,29 @@ +package com.dotcms.rest.exception.mapper; + +import com.dotmarketing.util.Logger; +import java.util.Optional; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import org.opensearch.client.opensearch._types.ErrorCause; +import org.opensearch.client.opensearch._types.OpenSearchException; + +@Provider +public class OpenSearchExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(final OpenSearchException exception) { + + Logger.warn(this.getClass(), exception.getMessage(), exception); + + final String message = Optional.ofNullable(exception.error()) + .map(ErrorCause::reason) + .orElse(exception.getMessage()); + final String entity = ExceptionMapperUtil.getJsonErrorAsString(message); + + final Status status = Status.fromStatusCode(exception.status()); + return ExceptionMapperUtil.createResponse(entity, message, + status != null ? status : Status.INTERNAL_SERVER_ERROR); + } +} diff --git a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/sitesearch/TotalSizeSiteSearchIndicesMetricType.java b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/sitesearch/TotalSizeSiteSearchIndicesMetricType.java index 8fe1532cc752..3cb37b4c282e 100644 --- a/dotCMS/src/main/java/com/dotcms/telemetry/collectors/sitesearch/TotalSizeSiteSearchIndicesMetricType.java +++ b/dotCMS/src/main/java/com/dotcms/telemetry/collectors/sitesearch/TotalSizeSiteSearchIndicesMetricType.java @@ -2,23 +2,25 @@ import com.dotcms.content.index.domain.IndexStats; import com.dotmarketing.exception.DotDataException; -import org.elasticsearch.common.unit.ByteSizeValue; import java.util.Collection; import java.util.Optional; -import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; import com.dotcms.telemetry.MetricsProfile; import com.dotcms.telemetry.ProfileType; /** - * Collect the total size in Mb of all the Site Search indices. + * Telemetry metric that reports the combined storage size of all Site Search indices. + * + *

The value is returned as a human-readable string with the largest applicable unit + * (b / kb / mb / gb / tb) and at most one decimal place — for example {@code "1.5gb"} or + * {@code "128mb"}. Trailing {@code .0} decimals are dropped so exact multiples render + * without noise (e.g. {@code "1gb"}, not {@code "1.0gb"}). */ @MetricsProfile(ProfileType.FULL) @ApplicationScoped public class TotalSizeSiteSearchIndicesMetricType extends IndicesSiteSearchMetricType { - @Override public String getName() { return "TOTAL_INDICES_SIZE"; @@ -30,9 +32,27 @@ public String getDescription() { } @Override - public Optional getValue(Collection indices) throws DotDataException { - return Optional.of(new ByteSizeValue( - indices.stream().collect(Collectors.summingLong(IndexStats::sizeRaw))).toString() - ); + public Optional getValue(final Collection indices) throws DotDataException { + final long total = indices.stream().mapToLong(IndexStats::sizeRaw).sum(); + return Optional.of(formatBytes(total)); + } + + /** + * Formats a byte count as a human-readable string using binary units (1 kb = 1024 b). + * At most one decimal place is shown; trailing {@code .0} is omitted. + */ + static String formatBytes(final long bytes) { + if (bytes >= 1L << 40) return format1Decimal(bytes / (double) (1L << 40)) + "tb"; + if (bytes >= 1L << 30) return format1Decimal(bytes / (double) (1L << 30)) + "gb"; + if (bytes >= 1L << 20) return format1Decimal(bytes / (double) (1L << 20)) + "mb"; + if (bytes >= 1L << 10) return format1Decimal(bytes / (double) (1L << 10)) + "kb"; + return bytes + "b"; + } + + private static String format1Decimal(final double value) { + final String s = String.valueOf(value); + final int dot = s.indexOf('.'); + if (dot == -1) return s; + return s.substring(dot + 1).equals("0") ? s.substring(0, dot) : s.substring(0, dot + 2); } } diff --git a/dotCMS/src/main/java/com/dotcms/util/HttpRequestDataUtil.java b/dotCMS/src/main/java/com/dotcms/util/HttpRequestDataUtil.java index d8a57e0160d9..3b0c2ddc603c 100644 --- a/dotCMS/src/main/java/com/dotcms/util/HttpRequestDataUtil.java +++ b/dotCMS/src/main/java/com/dotcms/util/HttpRequestDataUtil.java @@ -6,9 +6,7 @@ import com.dotmarketing.util.Logger; import com.dotmarketing.util.UtilMethods; import io.netty.util.NetUtil; -import io.vavr.control.Try; import org.apache.commons.lang.StringUtils; -import org.elasticsearch.common.network.InetAddresses; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; @@ -79,9 +77,9 @@ public static String getRemoteAddress (final HttpServletRequest request) { */ public static InetAddress getIpAddress(HttpServletRequest request) throws UnknownHostException { - byte[] remoteAddr = Try.of(()->InetAddresses.isInetAddress(request.getRemoteAddr())).getOrElse(false) - ? NetUtil.createByteArrayFromIpAddressString(request.getRemoteAddr()) - : new byte[]{127, 0, 0, 1}; + final String addr = request.getRemoteAddr(); + final byte[] parsed = addr != null ? NetUtil.createByteArrayFromIpAddressString(addr) : null; + final byte[] remoteAddr = parsed != null ? parsed : new byte[]{127, 0, 0, 1}; return InetAddress.getByAddress(remoteAddr); } diff --git a/dotCMS/src/main/java/com/dotmarketing/common/reindex/BulkActionListener.java b/dotCMS/src/main/java/com/dotmarketing/common/reindex/BulkActionListener.java index a8ed77981a61..a3ebc4471766 100644 --- a/dotCMS/src/main/java/com/dotmarketing/common/reindex/BulkActionListener.java +++ b/dotCMS/src/main/java/com/dotmarketing/common/reindex/BulkActionListener.java @@ -20,6 +20,9 @@ import com.dotmarketing.util.Logger; import com.liferay.util.StringPool; +// ES-DECOMMISSION: Implements ActionListener and references ES bulk action types directly. +// Decommission entire class — migrate callers to the vendor-neutral IndexBulkListener. +@Deprecated(forRemoval = true) class BulkActionListener implements ActionListener { BulkActionListener(final Map workingRecords) { diff --git a/dotCMS/src/main/java/com/dotmarketing/db/DotRunnableThread.java b/dotCMS/src/main/java/com/dotmarketing/db/DotRunnableThread.java index 03a3979f716c..b1f9298c412b 100644 --- a/dotCMS/src/main/java/com/dotmarketing/db/DotRunnableThread.java +++ b/dotCMS/src/main/java/com/dotmarketing/db/DotRunnableThread.java @@ -7,9 +7,6 @@ import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; import com.dotmarketing.util.UtilMethods; -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.bulk.BulkResponse; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -153,26 +150,6 @@ private void indexContentList(final List> reindexList, } } - private static class ReindexActionListeners implements ActionListener { - - private final List listeners; - - public ReindexActionListeners(final List listeners) { - this.listeners = listeners; - } - - @Override - public void onResponse(BulkResponse bulkItemResponses) { - listeners.stream().forEach(Runnable::run); - } - - @Override - public void onFailure(final Exception e) { - Logger.error(this, e.getMessage(), e); - } - } - - private boolean isOrdered(final Runnable runner) { return this.getOrder(runner) > 0; diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPI.java b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPI.java index b455b3b86581..648336ce3493 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPI.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPI.java @@ -52,6 +52,8 @@ * @since Mar 22, 2012 * */ +// ES-DECOMMISSION: Public interface exposes ESSearchResults and SearchCriteria in deprecated method +// signatures. Remove esSearch, esSearchRaw after R7 dotEvergreen cutover (~Aug 18). public interface ContentletAPI { /** diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIInterceptor.java b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIInterceptor.java index f9598632a049..88b8f574abdc 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIInterceptor.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIInterceptor.java @@ -64,6 +64,8 @@ * @since 1.6.5c * */ +// ES-DECOMMISSION: Exposes org.elasticsearch.action.search.SearchResponse, ESSearchResults, and SearchCriteria. +// Remove esSearch, esSearchRaw — migrate to ContentSearchResults. public class ContentletAPIInterceptor implements ContentletAPI, Interceptor { private List preHooks = new ArrayList<>(); diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIPostHook.java b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIPostHook.java index 83ddbe967a07..1f80dfe1960e 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIPostHook.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIPostHook.java @@ -36,9 +36,11 @@ /** * @author Jason Tesser * @since 1.6.5c - * This interface should be used as a post hook for the contentletAPI. The parameters are the same as the contentletAPI + * This interface should be used as a post hook for the contentletAPI. The parameters are the same as the contentletAPI * methods except now they also take the return type as the first parameter. */ +// ES-DECOMMISSION: Hook interface exposes SearchCriteria (ES-layer internal type) in method signatures. +// Remove esSearch, esSearchRaw hook methods when deprecated ContentletAPI signatures are removed at R7. public interface ContentletAPIPostHook { /** diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIPreHook.java b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIPreHook.java index 6959542bff87..b6febb9c08ba 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIPreHook.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/ContentletAPIPreHook.java @@ -31,10 +31,12 @@ /** * @author Jason Tesser * @since 1.6.5c - * This interface should be used as a pre hook for the contentletAPI. If the hooks + * This interface should be used as a pre hook for the contentletAPI. If the hooks * return false then the method will throw an exception up the stack. Stopping the progress. * When possible you should always return true and let the methods go about their business. */ +// ES-DECOMMISSION: Hook interface exposes SearchCriteria (ES-layer internal type) in method signatures. +// Remove esSearch, esSearchRaw hook methods when deprecated ContentletAPI signatures are removed at R7. public interface ContentletAPIPreHook { /** diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java b/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java index bbd8876e840b..24d179703377 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java @@ -176,7 +176,6 @@ import org.apache.commons.lang.time.StopWatch; import org.apache.commons.lang3.concurrent.ConcurrentUtils; import org.apache.felix.framework.OSGIUtil; -import org.elasticsearch.search.query.QueryPhaseExecutionException; import org.osgi.framework.BundleContext; /** @@ -2767,13 +2766,16 @@ private List findContentletsToProcess(final String luceneQuery, contentletAPI.search(luceneQueryWithSteps, limit, offset, null, user, !RESPECT_FRONTEND_ROLES) ).build(); }catch (Exception e){ - final Throwable rootCause = ExceptionUtil.getRootCause(e); - if(rootCause instanceof QueryPhaseExecutionException){ - final QueryPhaseExecutionException qpe = QueryPhaseExecutionException.class.cast(rootCause); - Logger.debug(getClass(),()->String.format("Unable to fetch contentlets beyond an offset of %d. %s ", offset, qpe.getMessage())); - } else { - Logger.error(getClass(),"Unexpected Error fetching contentlets from ES", e); - } + // A single generic message covers both the window-limit case (offset > max_result_window) + // and any other unexpected search failure. The ES-specific QueryPhaseExecutionException + // branch was removed because: (a) it never fires via the REST client — the client wraps + // all server errors as ElasticsearchStatusException — and (b) no typed OS equivalent exists + // in OpenSearch Java client 3.x. Detection at this call-site would require fragile message + // parsing. Full vendor-neutral handling belongs at the factory layer (Phase 3). + Logger.warnAndDebug(getClass(), + String.format("Unexpected error fetching contentlets at offset=%d — " + + "possibly an index window-limit exceeded if offset surpasses max_result_window. %s", + offset, e.getMessage()), e); } return Collections.emptyList(); diff --git a/dotCMS/src/test/java/com/dotcms/telemetry/collectors/sitesearch/TotalSizeSiteSearchIndicesMetricTypeTest.java b/dotCMS/src/test/java/com/dotcms/telemetry/collectors/sitesearch/TotalSizeSiteSearchIndicesMetricTypeTest.java new file mode 100644 index 000000000000..00baaf4cb173 --- /dev/null +++ b/dotCMS/src/test/java/com/dotcms/telemetry/collectors/sitesearch/TotalSizeSiteSearchIndicesMetricTypeTest.java @@ -0,0 +1,100 @@ +package com.dotcms.telemetry.collectors.sitesearch; + +import com.dotcms.content.index.domain.ImmutableIndexStats; +import com.dotcms.content.index.domain.IndexStats; +import com.dotmarketing.exception.DotDataException; +import org.junit.Test; + +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TotalSizeSiteSearchIndicesMetricTypeTest { + + private final TotalSizeSiteSearchIndicesMetricType metric = new TotalSizeSiteSearchIndicesMetricType(); + + // -- metadata -- + + @Test + public void testGetName() { + assertEquals("TOTAL_INDICES_SIZE", metric.getName()); + } + + // -- formatBytes boundary cases -- + + @Test + public void testEmpty_returnsZeroBytes() throws DotDataException { + assertEquals("0b", getValue(List.of())); + } + + @Test + public void testBelowKb_returnsRawBytes() throws DotDataException { + assertEquals("512b", getValue(index(512))); + assertEquals("1023b", getValue(index(1023))); + } + + @Test + public void testExactKb_dropsDecimal() throws DotDataException { + assertEquals("1kb", getValue(index(1024))); + } + + @Test + public void testFractionalKb_keepsOneDecimal() throws DotDataException { + assertEquals("1.5kb", getValue(index(1536))); + } + + @Test + public void testExactMb_dropsDecimal() throws DotDataException { + assertEquals("1mb", getValue(index(1024L * 1024))); + } + + @Test + public void testFractionalGb_keepsOneDecimal() throws DotDataException { + assertEquals("1.5gb", getValue(index((long) (1.5 * 1024 * 1024 * 1024)))); + } + + @Test + public void testLargeMb_noDecimal() throws DotDataException { + assertEquals("128mb", getValue(index(128L * 1024 * 1024))); + } + + @Test + public void testTruncationNotRounding() throws DotDataException { + // format1Decimal truncates (string slice), it does not round. + // 1.99 GB truncates to "1.9gb", not "2.0gb". + final long GB = 1L << 30; + assertEquals("1.9gb", getValue(index((long) (1.99 * GB)))); + } + + // -- aggregation across multiple indices -- + + @Test + public void testMultipleIndices_sumsCorrectly() throws DotDataException { + final IndexStats a = index(512L * 1024 * 1024); // 512 MB + final IndexStats b = index(512L * 1024 * 1024); // 512 MB → total 1 GB + assertEquals("1gb", getValue(a, b)); + } + + // -- helpers -- + + private String getValue(final IndexStats... indices) throws DotDataException { + return getValue(List.of(indices)); + } + + private String getValue(final List indices) throws DotDataException { + final Optional result = metric.getValue(indices); + assertTrue(result.isPresent()); + return (String) result.get(); + } + + private static IndexStats index(final long sizeRaw) { + return ImmutableIndexStats.builder() + .indexName("test") + .documentCount(0) + .sizeRaw(sizeRaw) + .size("") + .build(); + } +}