Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b9dcda1
feat(opensearch) #34610: add @ESCoupled annotation and OpenSearch exc…
fabrizzio-dotCMS May 25, 2026
80a5c7f
feat(opensearch) #34610: remove ES quick-win imports from vendor-neut…
fabrizzio-dotCMS May 25, 2026
025c227
refactor(opensearch) #34610: simplify @ESCoupled — drop trackedIn/pha…
fabrizzio-dotCMS May 25, 2026
fc6e2ff
feat(opensearch) #34610: remove QueryPhaseExecutionException from Wor…
fabrizzio-dotCMS May 25, 2026
6afce85
fix(opensearch) #34610: guard HttpRequestDataUtil.getIpAddress agains…
fabrizzio-dotCMS May 25, 2026
3f5a25a
Merge branch 'main' into feat/opensearch-es-coupling-annotation-utili…
fabrizzio-dotCMS May 25, 2026
3bc0345
refactor(telemetry): replace ByteSizeValue (ES vendor) with neutral f…
fabrizzio-dotCMS May 25, 2026
48ef818
docs(telemetry): add Javadoc to TotalSizeSiteSearchIndicesMetricType …
fabrizzio-dotCMS May 25, 2026
9727e03
revert(opensearch) #34610: restore WorkflowAPIImpl to pre-branch state
fabrizzio-dotCMS May 25, 2026
f416061
revert(opensearch): restore WorkflowAPIImpl to main state
fabrizzio-dotCMS May 25, 2026
51ff06e
Merge branch 'main' into feat/opensearch-es-coupling-annotation-utili…
fabrizzio-dotCMS May 26, 2026
43438be
fix(opensearch) #34610: address PR #35824 review feedback
fabrizzio-dotCMS May 26, 2026
e24e62c
Merge branch 'main' into feat/opensearch-es-coupling-annotation-utili…
fabrizzio-dotCMS May 26, 2026
4c0200f
Merge branch 'main' into feat/opensearch-es-coupling-annotation-utili…
fabrizzio-dotCMS May 26, 2026
aa6cc7b
refactor(opensearch) #34610: replace @ESCoupled annotation with ES-DE…
fabrizzio-dotCMS May 27, 2026
eec5e59
docs(opensearch) #34610: document deferred ES migration in WorkflowAP…
fabrizzio-dotCMS May 27, 2026
43251f1
refactor(opensearch) #34610: simplify WorkflowAPIImpl catch — remove …
fabrizzio-dotCMS May 27, 2026
634f72a
Merge branch 'main' into feat/opensearch-es-coupling-annotation-utili…
fabrizzio-dotCMS May 27, 2026
6ab90a3
Merge branch 'main' into feat/opensearch-es-coupling-annotation-utili…
fabrizzio-dotCMS May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -870,7 +869,6 @@ public HashMap pageContent(String query, String sortBy, String perPage, String c
int limit = 0;

List<Map> l = new ArrayList<>();
SearchHits hits = null;
List<Contentlet> c = new ArrayList<>();

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ElasticsearchStatusException> {


@Override
public Response toResponse(final ElasticsearchStatusException exception) {

Expand Down
Original file line number Diff line number Diff line change
@@ -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<OpenSearchException> {

@Override
public Response toResponse(final OpenSearchException exception) {
Comment thread
fabrizzio-dotCMS marked this conversation as resolved.

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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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";
Expand All @@ -30,9 +32,27 @@ public String getDescription() {
}

@Override
public Optional<Object> getValue(Collection<IndexStats> indices) throws DotDataException {
return Optional.of(new ByteSizeValue(
indices.stream().collect(Collectors.summingLong(IndexStats::sizeRaw))).toString()
);
public Optional<Object> getValue(final Collection<IndexStats> 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);
}
}
8 changes: 3 additions & 5 deletions dotCMS/src/main/java/com/dotcms/util/HttpRequestDataUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import com.dotmarketing.util.Logger;
import com.liferay.util.StringPool;

// ES-DECOMMISSION: Implements ActionListener<BulkResponse> and references ES bulk action types directly.
// Decommission entire class — migrate callers to the vendor-neutral IndexBulkListener.
@Deprecated(forRemoval = true)
class BulkActionListener implements ActionListener<BulkResponse> {

BulkActionListener(final Map<String, ReindexEntry> workingRecords) {
Expand Down
23 changes: 0 additions & 23 deletions dotCMS/src/main/java/com/dotmarketing/db/DotRunnableThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -153,26 +150,6 @@ private void indexContentList(final List<List<Contentlet>> reindexList,
}
}

private static class ReindexActionListeners implements ActionListener<BulkResponse> {

private final List<Runnable> listeners;

public ReindexActionListeners(final List<Runnable> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>.
public class ContentletAPIInterceptor implements ContentletAPI, Interceptor {

private List<ContentletAPIPreHook> preHooks = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -2767,13 +2766,16 @@ private List<Contentlet> 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IndexStats> indices) throws DotDataException {
final Optional<Object> 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();
}
}
Loading