feat(opensearch): extract vendor-neutral SearchAPI and phase-aware router#35609
feat(opensearch): extract vendor-neutral SearchAPI and phase-aware router#35609fabrizzio-dotCMS wants to merge 18 commits into
Conversation
…uter (#34609) Decouples ESSearchAPIImpl from Elasticsearch vendor types behind a neutral SearchAPI interface, completing the Search Layer migration task. New classes: - SearchAPI: vendor-neutral interface (search, searchRaw, searchRelated overloads) - SearchAPIImpl: phase-aware router delegating to ES (phases 0-1) or OS (phases 2-3) - ContentletSearchAPIES: ES implementation returning neutral DTOs - OSSearchAPIImpl: OS implementation using SearchRequest._DESERIALIZER + VersionedIndicesAPI - AggregationBucket, ContentSearchResponse, ContentSearchResults: neutral domain DTOs - OSSearchAPIImplIntegrationTest: live OS integration test suite Updated callers to use APILocator.getSearchAPI() instead of getEsSearchAPI(): - ContentTypeAPIImpl (findRecentContent, getEntriesByContentTypes) - ESContentletAPIImpl (getRelatedChildren, getRelatedParents) - BrowserAPIImpl (processSingleESQuery) - ESContentTool (search, raw) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @fabrizzio-dotCMS's task in 6m 39s —— View job PR review — vendor-neutral SearchAPIFunctional / behavioral risks1. OS path silently drops 2. No timeout on OS search — legacy ES path sets 3. Internal OS client symbol — 4. API design5. Overload collision on default ContentSearchResults<Contentlet> search(String query, boolean live, User user, boolean respectFrontendRoles)now sits next to the existing Lucene overloads: List<Contentlet> search(String luceneQuery, int limit, int offset, String sortBy, User user, boolean respectFrontendRoles)Different query language (JSON vs Lucene), different return type, same method name. The earlier 6. Default methods bypass the interceptor — 7. Smaller items8. 9. 10. OS 11. 12. TestsOS integration tests ( · branch: |
…adeSuite Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… ESSearchAPIImpl adapter Replace the reimplemented ContentletSearchAPIES with a proper adapter (ESSearchAPIImpl) that delegates to the legacy com.dotcms.enterprise.priv.ESSearchAPIImpl and converts ES-specific types to vendor-neutral DTOs via ContentSearchResponse.from(). The new class lives in com.dotcms.content.index.elasticsearch to mirror the symmetric OSSearchAPIImpl in com.dotcms.content.index.opensearch, is annotated @ApplicationScoped for CDI, and is resolved via CDIUtils in SearchAPIImpl. Also fix OSSearchAPIImplIntegrationTest.setUp() to register cluster-prefixed index names (via getNameWithClusterIDPrefix) in VersionedIndicesAPI so resolveIndex() targets the correct OpenSearch index name and does not throw index_not_found_exception. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… migrate callers to neutral API Add vendor-neutral default methods search() and searchRaw() to ContentletAPI, both delegating to APILocator.getSearchAPI() (the phase-aware router). Mark esSearch() and esSearchRaw() as @deprecated pointing to the new methods. Migrate all call sites except ESContentResourcePortlet line 262 (deferred — that caller relies on SearchResponse.toString() producing ES wire-format JSON; marked with FIXME(OS-cutover)): - PageResource: esSearch → search, ESSearchResults → ContentSearchResults - WorkflowHelper: replace ParsedStringTerms aggregations loop with AggregationBucket map iteration - ESMappingAPITest: esSearch → search (3 callers); esSearchRaw → searchRaw with direct aggregations map access instead of raw.toString() JSON parsing - ES6UpgradeTest: esSearch → search; getAggregations().asList() → getAggregations().isEmpty() - ContentletAPITest: esSearchRaw → searchRaw; getHits().getHits()[i].getId() → hits().iterator().next().id() - PageResourceTest: update mocks from ESSearchResults to ContentSearchResults ESContentletAPIImpl lines 354/360 (getEsSearchAPI() calls) remain unchanged — they serve the deprecated interface declarations and will be removed together with those declarations once ESContentResourcePortlet is also migrated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace ESSearchResults with ContentSearchResults and esSearchAPI().esSearch() with searchAPI().search() to align the test with the vendor-neutral search layer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PRs linked to this issue |
…mark ES methods for removal Add search() and searchRaw() overrides to ContentletAPIInterceptor so callers going through the ContentletAPI interface (e.g. WorkflowHelper) still trigger pre/post hooks and the community-license guard on searchRaw — addressing PR review point #1. Add matching default hook methods to ContentletAPIPreHook and ContentletAPIPostHook so existing hook implementations compile without changes. Upgrade @deprecated → @deprecated(forRemoval = true) on esSearch/esSearchRaw in ContentletAPI and ESContentletAPIImpl, and mark the corresponding hook methods in ContentletAPIPreHook/PostHook as @deprecated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…val=true) with replacement refs Add @deprecated Javadoc pointing to search()/searchRaw() replacements and upgrade @deprecated → @deprecated(forRemoval = true) on esSearch and esSearchRaw in both ContentletAPIPreHook and ContentletAPIPostHook. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n OSSearchAPIImplIntegrationTest Index a real document (identifier + inode) to verify that OSSearchAPIImpl always rewrites _source to [identifier, inode] regardless of what the caller provides, and that both fields are present in hit sourceAsMap after the search. Remove unused imports (assertFalse, Optional). Remaining gaps (phase routing, ES fallback in Phase 2, permission filtering with non-admin user) are tracked in issue #35669. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… and SearchHits ES error responses can return null getHits() (e.g. shard failures). The OS branch already guarded against this; apply the same pattern to the ES factory methods. Fixes NPE regression in ESContentletAPIImpl.getRelatedChildren/getRelatedParents when ES returns a response with null hits. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on ContentletAPI Eliminates overload ambiguity with the existing Lucene-based search(String, int, int, String, User, boolean) methods. The Json suffix makes the query contract explicit at every call-site: these methods accept an ES/OS JSON query body, not a Lucene string. Renames propagated to ContentletAPIInterceptor, ContentletAPIPreHook, ContentletAPIPostHook, WorkflowHelper, and PageResource. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nchecked casts ContentSearchResults now carries its element type as a type parameter. SearchAPI, ESSearchAPIImpl, OSSearchAPIImpl, ContentletAPI, and ContentletAPIInterceptor all declare ContentSearchResults<Contentlet>. ESContentTool declares ContentSearchResults<ContentMap> since it maps hits to view objects. Removes the (Collection<Contentlet>)(Collection<?>) double-cast in PageResource and the cast-per-element loop in ESContentTool, replacing both with direct typed iteration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… ContentSearchResults<T> Follows the rename of ContentletAPI.search/searchRaw → searchJson/searchRawJson and the introduction of the ContentSearchResults<T> generic parameter. Also removes stale (Contentlet) casts in loops that are now type-safe. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on ContentletAPI The Json suffix was semantically misleading (methods return typed Java DTOs, not JSON) and unnecessary — the 4-param signature (String, boolean, User, boolean) has no overload conflict with the existing Lucene-based search methods (6-7 params). Renamed across ContentletAPI, ContentletAPIInterceptor, Pre/PostHook interfaces, call-sites, tests, and migration guide. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…entTool ESContentTool.search() and raw() were silently replaced with neutral return types in the SearchAPI extraction commit, with no deprecation path for Velocity templates that access ES-specific properties (hits, aggregations, response, suggestions). Templates that only iterate results are unaffected, but those accessing metadata break silently at render time. Re-introduces esSearch() → ESSearchResults and esRaw() → SearchResponse as @deprecated(forRemoval=true) bridges delegating to ContentletAPI's existing deprecated methods. The neutral search() and raw() methods are untouched. These bridges will be removed in v26.08.04 alongside ContentletAPI.esSearch* and the other deprecated methods in the coordinated removal PR. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Extracts a vendor-neutral
SearchAPIinterface to decouple the search layer from Elasticsearch-specific types (SearchResponse,ESSearchResults). A phase-aware router (SearchAPIImpl) delegates reads to ES (phases 0–1) or OS (phases 2–3) using the existingPhaseRouter<T>pattern. Addresses PR review point #1 by wiringsearch/searchRawintoContentletAPIInterceptorand marking the legacy ES methods for removal.Changes
New search layer (
com.dotcms.content.index)SearchAPI— vendor-neutral interface replacingESSeachAPIforsearch,searchRaw,searchRelatedSearchAPIImpl— phase-aware router viaPhaseRouter<SearchAPI>ESSearchAPIImpl— ES adapter: delegates to legacyESSeachAPI, convertsSearchResponse→ neutral DTOsOSSearchAPIImpl— native OS implementation usingSearchRequest._DESERIALIZER+VersionedIndicesAPIContentSearchResponse,ContentSearchResults,SearchHits,SearchHit,TotalHits,AggregationBucketInterceptor & hook wiring (addresses review point #1)
ContentletAPIInterceptor— addedsearch()andsearchRaw()overrides with full pre/post hook dispatch and community-license guard onsearchRawContentletAPIPreHook/ContentletAPIPostHook— addedsearch()/searchRaw()default no-op methods; markedesSearch/esSearchRawhooks as@DeprecatedDeprecations
ContentletAPI.esSearch/esSearchRaw— upgraded to@Deprecated(forRemoval = true), replaced bysearch()/searchRaw()default methods delegating toAPILocator.getSearchAPI()ESContentletAPIImpl.esSearch/esSearchRaw— marked@Deprecated(forRemoval = true)Caller migrations
ContentTypeAPIImpl,BrowserAPIImpl,ESContentTool,WorkflowHelper,PageResource— migrated fromgetEsSearchAPI()togetSearchAPI()ESContentletAPIImpl—getRelatedChildren/getRelatedParentsmigrated to neutralSearchHitAPITests
OSSearchAPIImplIntegrationTest(registered inOpenSearchUpgradeSuite)ESMappingAPITest,ES6UpgradeTest,ContentletAPITest,PageResourceTest,ContentTypeDestroyAPIImplTestto neutral APIDeferred (separate PRs)
ESContentResourcePortlet— relies onSearchResponse.toString()for ES wire-format JSON; marked withFIXME(OS-cutover)Testing
Manual:
findRecentContent/getEntriesByContentTypesreturn correct results against ESBrowserAPIImplsearch still filters inodes correctly$ESContent.search(...)and$ESContent.raw(...)work in templatesWorkflowHelperstep aggregations display correctly in the workflow UIBreaking Changes
None. All changes are backward-compatible:
esSearch/esSearchRaware deprecated but still functional untilESContentResourcePortletis migrated and they are removed.🤖 Generated with Claude Code