Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
009d96f
OAK-12287: Update to Apache Parent POM to version 39
reschke Jun 29, 2026
b73a399
OAK-12287: Update to Apache Parent POM to version 39 - revert removal…
reschke Jun 30, 2026
f6cfb14
OAK-12289 : disabled blob id tracking for document node store (#2987)
rishabhdaim Jul 1, 2026
3ce3b66
Merge pull request #2984 from apache/OAK-12287
reschke Jul 1, 2026
386223e
OAK-12259: oak-http: OakServlet mis-parses HTTP Basic credentials
reschke Jul 1, 2026
ebf0597
OAK-12293 : bump commons-io to 2.21.0 (#2997)
rishabhdaim Jul 4, 2026
ac0fa02
OAK-12294 : bump commons-codec to 1.20.0 (#2998)
rishabhdaim Jul 4, 2026
480eb14
OAK-12295 : bump mongo-driver-sync to 5.3.1 (#3001)
rishabhdaim Jul 5, 2026
a71cfa3
OAK-12296 : bump testcontainers version to 2.0.3 (#3002)
rishabhdaim Jul 6, 2026
6cfac19
OAK-12244: index nodes that gain a mixin rule, delete stale docs when…
thomasmueller Jul 7, 2026
045951c
OAK-12282 : defining a fixed bound for the AbstractDiskCache (#2978)
pat-lego Jul 7, 2026
bead8ea
OAK-12303: Update mina-core dependency version to 2.1.15 (#3008) (#3010)
reschke Jul 8, 2026
69a6c68
Revert "OAK-12295 : bump mongo-driver-sync to 5.3.1 (#3001)" (#3012)
rishabhdaim Jul 8, 2026
92ca9d9
OAK-12219: Upgrade Azure SDK V8 to V12 for oak-blob-azure - rework
seropian Jun 25, 2026
4368215
OAK-12219: Upgrade Azure SDK V8 to V12 for oak-blob-azure - rework
seropian Jun 30, 2026
ceb4994
OAK-12219: Upgrade Azure SDK V8 to V12 for oak-blob-azure - rework
seropian Jun 30, 2026
a97d2cd
OAK-12219: Upgrade Azure SDK V8 to V12 for oak-blob-azure - rework
seropian Jun 30, 2026
c22cfe2
OAK-12219: fix Sonar annotations from PR #2989 CI run
seropian Jun 30, 2026
921565e
OAK-12219: raise unit-test coverage on new V12 code to clear Sonar gate
seropian Jun 30, 2026
4f740b3
Oak 12219 upgrade azure sdk v8 to v12 for oak blob azure rework (#…
seropian Jul 1, 2026
781018c
Oak 12219 upgrade azure sdk v8 to v12 for oak blob azure rework (#…
seropian Jul 3, 2026
6bd5f56
OAK-12219: Upgrade Azure SDK V8 to V12 for oak-blob-azure - rework
seropian Jul 1, 2026
6cd7e75
OAK-12219: Upgrade Azure SDK V8 to V12 for oak-blob-azure - rework
seropian Jul 1, 2026
455dee2
Remove internal service reference from comment
seropian Jul 3, 2026
82916d0
OAK-12219: cache HttpClient in AzureBlobContainerProviderV12
seropian Jul 3, 2026
8831888
OAK-12219: replace wildcard imports with explicit imports
seropian Jul 3, 2026
9d8c574
OAK-12219: move retryOptions into provider, drop getBlobContainerFrom…
seropian Jul 3, 2026
20ba36c
OAK-12219: address remaining PR #2982 review comments
seropian Jul 3, 2026
41cc158
OAK-12219: Upgrade Azure SDK V8 to V12 for oak-blob-azure - rework
seropian Jul 8, 2026
143b20a
OAK-12219: Upgrade Azure SDK V8 to V12 for oak-blob-azure - rework
seropian Jul 8, 2026
46f5406
Merge branch 'OAK-12219-test2' into OAK-12219-Upgrade-Azure-SDK-V8-to…
seropian Jul 8, 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
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,4 @@ atlassian-ide-plugin.xml
derby.log
.java-version
oak-shaded-guava/dependency-reduced-pom.xml
.DS_Store
.claude/settings.local.json
pom.xml.versionsBackup
2 changes: 1 addition & 1 deletion oak-auth-ldap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.1.12</version>
<version>2.1.15</version>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
import org.osgi.service.component.ComponentContext;

/**
* @deprecated Use {@link AzureDataStoreWrapper} instead.
*/
@Deprecated(since = "2.3", forRemoval = true)
* Kept for binary compatibility with existing callers. Use {@link AzureDataStoreWrapper} instead.
*
* @deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ public class AzureDataStoreWrapper extends AbstractDataStoreService {

private static final Logger log = LoggerFactory.getLogger(AzureDataStoreWrapper.class);

// Intentionally set to the legacy v8 PID rather than this class's own FQN.
// Existing OSGi configurations reference the v8 PID, so reusing it here means
// no config migration is required when switching to this wrapper.
public static final String NAME = "org.apache.jackrabbit.oak.plugins.blob.datastore.AzureDataStore";

// Same name for now; kept as separate constants so they can diverge if the sources need different keys later.
Expand Down Expand Up @@ -189,7 +192,8 @@ protected void setStatisticsProvider(StatisticsProvider statisticsProvider) {

@Override
protected String[] getDescription() {
return new String[]{"type=AzureBlob"};
String sdkVersion = (activeImpl instanceof AzureDataStoreV12) ? "v12" : "v8";
return new String[]{"type=AzureBlob", "sdkVersion=" + sdkVersion};
}

// -- Inner DelegatingDataStore (returned from createDataStore) -------
Expand Down
7 changes: 4 additions & 3 deletions oak-doc/src/site/markdown/osgi_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,9 @@ blobTrackSnapshotIntervalInSecs (long) - 0
deletions will be visible to other cluster nodes or repositories connected to the shared DatStore after this. This
should be less than the blobGcMaxAgeInSecs parameter above and the frequency of blob gc. See [Blob
tracker][blobtracker].
A value of `0` (the default since Oak 2.4.0) disables blob ID tracking entirely for the Segment Node Store. To enable tracking,
set this to a positive value (e.g. `43200` for 12 hours). Note that DocumentNodeStore defaults to `43200`.
A value of `0` disables blob ID tracking entirely. This has been the default for both SegmentNodeStore and
DocumentNodeStore since Oak 2.4.0.
To enable tracking, set this to a positive value (e.g. `43200` for 12 hours).

<a name="document-node-store"></a>
#### DocumentNodeStore
Expand Down Expand Up @@ -262,7 +263,7 @@ docChildrenCachePercentage | 0 (was 3 until 1.5.6) | Percentage of `cache` alloc
cacheSegmentCount | 16 | The number of segments in the LIRS cache | 1.0.15, 1.2.3, 1.3.0
cacheStackMoveDistance | 16 | The delay to move entries to the head of the queue in the LIRS cache | 1.0.15, 1.2.3, 1.3.0
sharedDSRepoId | "" | Custom SharedDataStore repositoryId. Used when custom blobstore configured. Should be unique among the repositories sharing the datastore. | 1.2.11
blobTrackSnapshotIntervalInSecs | 43200 (12 hrs) | The blob ids cached/tracked locally are synchronized with the DataStore at this interval. Any additions and deletions will be visible to other cluster nodes or repositories connected to the shared DatStore after this. This should be less than the blobGcMaxAgeInSecs parameter above and the frequency of blob gc. See [Blob tracker][blobtracker]. | 1.5.6
blobTrackSnapshotIntervalInSecs | 0 | The blob ids cached/tracked locally are synchronized with the DataStore at this interval. Any additions and deletions will be visible to other cluster nodes or repositories connected to the shared DatStore after this. This should be less than the blobGcMaxAgeInSecs parameter above and the frequency of blob gc. A value of `0` disables blob ID tracking (default since Oak 2.4.0). See [Blob tracker][blobtracker]. | 1.5.6
updateLimit | 100000 | The number of updates kept in memory until changes are written to a branch in the DocumentStore | 1.7.0
leaseCheckMode | STRICT | The lease check mode. `STRICT` is the default and will stop the DocumentNodeStore as soon as the lease expires. `LENIENT` will give the background lease update a chance to renew the lease even when the lease expired. This mode is only recommended for development, e.g. when debugging an application and the lease may expire when the JVM is stopped at a breakpoint. | 1.9.6
documentStoreType | MONGO | Set to "RDB" for `RDBDocumentStore`; will require a configured Sling DataSource called `oak`. | 1.0
Expand Down
8 changes: 5 additions & 3 deletions oak-doc/src/site/markdown/plugins/blobstore.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,11 @@ configured NodeStore services.
**Default behavior by NodeStore:**

* **SegmentNodeStore** (`SegmentNodeStoreService` / `SegmentNodeStoreFactory`): blob ID tracking is **disabled by
default** (`blobTrackSnapshotIntervalInSecs = 0`). To enable it, set a positive value such as `43200` (12 hours).
* **DocumentNodeStore**: blob ID tracking is **enabled by default** with a 12-hour sync interval
(`blobTrackSnapshotIntervalInSecs = 43200`).
default** (`blobTrackSnapshotIntervalInSecs = 0`, default since Oak 2.4.0). To enable it, set a positive value such as
`43200` (12 hours).
* **DocumentNodeStore**: blob ID tracking is **disabled by default**
(`blobTrackSnapshotIntervalInSecs = 0`, default since Oak 2.4.0). To enable it, set a positive value such as `43200`
(12 hours).

When tracking is disabled (interval = 0), blob IDs are not cached locally and the warnings described below do
not apply. The full blob ID list is always retrieved directly from the DataStore during GC.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
*/
package org.apache.jackrabbit.oak.http;

import org.apache.jackrabbit.util.Base64;

import javax.jcr.SimpleCredentials;
import javax.security.auth.login.LoginException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Enumeration;
import java.util.Locale;
import java.util.NoSuchElementException;

public class AuthorizationField {
Expand All @@ -44,11 +45,30 @@ public static SimpleCredentials valueOf(Enumeration<String> values) throws Login
return parseCredentials(field);
}

private static SimpleCredentials parseCredentials(String fieldValue) throws LoginException {
if (fieldValue.startsWith("Basic ")) {
String[] basic =
Base64.decode(fieldValue.substring("Basic ".length())).split(":");
return new SimpleCredentials(basic[0], basic[1].toCharArray());
private static SimpleCredentials parseCredentials(String rawFieldValue) throws LoginException {
boolean hasControls = rawFieldValue.chars().anyMatch(c -> c < ' ');
if (hasControls) {
throw new LoginException("Control characters are not allowed");
}

String fieldValue = rawFieldValue.trim().replaceAll(" +", " ");

if (fieldValue.toLowerCase(Locale.ENGLISH).startsWith("basic ")) {
String token68 = fieldValue.substring("basic ".length());
try {
String decoded = new String(Base64.getDecoder().decode(token68), StandardCharsets.UTF_8);
int colon = decoded.indexOf(':');
if (colon < 0) {
throw new LoginException(
"Malformed Basic credentials: missing ':' separator");
}
String userId = decoded.substring(0, colon);
String password = decoded.substring(colon + 1);

return new SimpleCredentials(userId, password.toCharArray());
} catch (IllegalArgumentException ex) {
throw new LoginException(ex.getMessage());
}
} else {
throw new LoginException("Only Basic Authentication supported");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,10 @@ public void testValid() throws LoginException {
assertEquals("bar", new String(credentials.getPassword()));
}

@Test(expected = ArrayIndexOutOfBoundsException.class) // BUG: OAK-12259
@Test(expected = LoginException.class)
public void testInvalidBase64() throws LoginException {
String b64 = Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
SimpleCredentials credentials = AuthorizationField.valueOf(Collections.enumeration(List.of("Basic dksjdkj" + b64)));
assertEquals("foo", credentials.getUserID());
assertEquals("bar", new String(credentials.getPassword()));
AuthorizationField.valueOf(Collections.enumeration(List.of("Basic dksjdkj" + b64)));
}

@Test(expected = LoginException.class)
Expand All @@ -64,9 +62,67 @@ public void testNoScheme() throws LoginException {
@Test
public void testColonInPassword() throws LoginException {
String b64 = "Basic " + Base64.getEncoder().encodeToString("foo:bar:qux".getBytes(StandardCharsets.UTF_8));
SimpleCredentials credentials =AuthorizationField.valueOf(Collections.enumeration(List.of(b64)));
SimpleCredentials credentials = AuthorizationField.valueOf(Collections.enumeration(List.of(b64)));
assertEquals("foo", credentials.getUserID());
assertEquals("bar:qux", new String(credentials.getPassword()));
}

@Test(expected = LoginException.class)
public void testMissingColon() throws LoginException {
String b64 = "Basic " + Base64.getEncoder().encodeToString("foobarqux".getBytes(StandardCharsets.UTF_8));
AuthorizationField.valueOf(Collections.enumeration(List.of(b64)));
}

@Test
public void testSchemeCase() throws LoginException {
String b64 = Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
SimpleCredentials credentials = AuthorizationField.valueOf(Collections.enumeration(List.of("BaSiC " + b64)));
assertEquals("foo", credentials.getUserID());
assertEquals("bar", new String(credentials.getPassword()));
}

@Test
public void testMoreWhitespace() throws LoginException {
String b64 = Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
SimpleCredentials credentials = AuthorizationField.valueOf(Collections.enumeration(List.of("Basic " + b64)));
assertEquals("foo", credentials.getUserID());
assertEquals("bar", new String(credentials.getPassword()));
}

@Test(expected = LoginException.class)
public void testNonSpWhitespace() throws LoginException {
String b64 = Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
AuthorizationField.valueOf(Collections.enumeration(List.of("Basic \t " + b64)));
}

@Test(expected = LoginException.class)
public void testBrokenBase64() throws LoginException {
String b64 = Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
// insert a single SP into the base64 sequence
b64 = b64.substring(0,5) + " " + b64.substring(5);
AuthorizationField.valueOf(Collections.enumeration(List.of("Basic " + b64)));
}

@Test(expected = LoginException.class)
public void testMoreBrokenBase64() throws LoginException {
String b64 = Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
b64 = b64.substring(0,5) + "=" + b64.substring(5);
AuthorizationField.valueOf(Collections.enumeration(List.of("Basic " + b64)));
}

@Test
public void testMoreNonAscii() throws LoginException {
String b64 = Base64.getEncoder().encodeToString("test:123\u00a3".getBytes(StandardCharsets.UTF_8));
SimpleCredentials credentials = AuthorizationField.valueOf(Collections.enumeration(List.of("Basic " + b64)));
assertEquals("test", credentials.getUserID());
assertEquals("123\u00a3", new String(credentials.getPassword()));
}

@Test
public void testBasic64NoPadding() throws LoginException {
String b64 = Base64.getEncoder().withoutPadding().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
SimpleCredentials credentials = AuthorizationField.valueOf(Collections.enumeration(List.of("Basic " + b64)));
assertEquals("foo", credentials.getUserID());
// BUG: OAK-12259
assertEquals("bar", new String(credentials.getPassword()));
}
}
2 changes: 1 addition & 1 deletion oak-it-osgi/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>elasticsearch</artifactId>
<artifactId>testcontainers-elasticsearch</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ protected void registerNodeStoreService() {
properties.put("repository.home", repoHome);
properties.put("mongouri", MongoUtils.URL);
properties.put("db", MongoUtils.DB);
// Explicitly enable blob ID tracking (default is 0/disabled since Oak 2.4.0).
// This test verifies tracker reinitialization which requires an active tracker.
properties.put("blobTrackSnapshotIntervalInSecs", 3600L);
MockOsgi.setConfigForPid(context.bundleContext(),
DocumentNodeStoreService.class.getName(), properties);
service = context.registerInjectActivateService(new DocumentNodeStoreService());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ private void activate(BundleContext bundleContext, Configuration config) throws
oakRegs.add(whiteboard.register(FeatureToggle.class,
new FeatureToggle(FulltextIndexEditor.FT_OAK_12193, FulltextIndexEditor.FT_OAK_12193_DISABLE),
emptyMap()));
oakRegs.add(whiteboard.register(FeatureToggle.class,
new FeatureToggle(FulltextIndexEditor.FT_OAK_12244, FulltextIndexEditor.FT_OAK_12244_DISABLE),
emptyMap()));
initializeIndexDir(bundleContext, config);
initializeExtractedTextCache(bundleContext, config, statisticsProvider);
tracker = createTracker(bundleContext, config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ private void processDocs(String indexPath, Iterable<LuceneDoc> docs, boolean doc
doc.markProcessed();
}
if (doc.delete) {
writer.deleteDocuments(doc.docPath);
writer.deleteDocumentTree(doc.docPath);
} else {
writer.updateDocument(doc.docPath, doc.doc);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,18 @@ public void updateDocument(String path, Iterable<? extends IndexableField> doc)
}

@Override
public void deleteDocuments(String path) throws IOException {
public void deleteDocumentTree(String path) throws IOException {
//Hybrid index logic drops the deletes. So no use to
//add them to the list
//addLuceneDoc(LuceneDoc.forDelete(definition.getIndexPathFromConfig(), path));
}

@Override
public void deleteDocument(String path) throws IOException {
//Hybrid index logic drops the deletes. So no use to
//add them to the list
}

@Override
public boolean close(long timestamp) throws IOException {
documentHolder.done(indexPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,12 @@ public void updateDocument(String path, Iterable<? extends IndexableField> doc)
}

@Override
public void deleteDocuments(String path) throws IOException {
public void deleteDocumentTree(String path) throws IOException {
//Do not delete documents. Query side would handle it
}

@Override
public void deleteDocument(String path) throws IOException {
//Do not delete documents. Query side would handle it
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,16 @@ public void updateDocument(String path, Iterable<? extends IndexableField> doc)
}

@Override
public void deleteDocuments(String path) throws IOException {
public void deleteDocumentTree(String path) throws IOException {
getWriter().deleteDocuments(newPathTerm(path));
getWriter().deleteDocuments(new PrefixQuery(newPathTerm(path + "/")));
}

@Override
public void deleteDocument(String path) throws IOException {
getWriter().deleteDocuments(newPathTerm(path));
}

void deleteAll() throws IOException {
getWriter().deleteAll();
indexUpdated = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,31 @@ public void execute() throws IOException {
}
}

private static class DeleteOperation extends Operation {
private static class DeleteTreeOperation extends Operation {
private final String path;

DeleteOperation(LuceneIndexWriter delegate, String path) {
DeleteTreeOperation(LuceneIndexWriter delegate, String path) {
super(delegate);
this.path = path;
}

@Override
public void execute() throws IOException {
delegate.deleteDocuments(path);
delegate.deleteDocumentTree(path);
}
}

private static class DeleteDocumentOperation extends Operation {
private final String path;

DeleteDocumentOperation(LuceneIndexWriter delegate, String path) {
super(delegate);
this.path = path;
}

@Override
public void execute() throws IOException {
delegate.deleteDocument(path);
}
}

Expand Down Expand Up @@ -280,10 +294,16 @@ public void updateDocument(LuceneIndexWriter writer, String path, Iterable<? ext
enqueueOperation(new UpdateOperation(writer, path, doc));
}

public void deleteDocuments(LuceneIndexWriter writer, String path) throws IOException {
public void deleteDocumentTree(LuceneIndexWriter writer, String path) throws IOException {
checkOpen();
this.deleteCount++;
enqueueOperation(new DeleteTreeOperation(writer, path));
}

public void deleteDocument(LuceneIndexWriter writer, String path) throws IOException {
checkOpen();
this.deleteCount++;
enqueueOperation(new DeleteOperation(writer, path));
enqueueOperation(new DeleteDocumentOperation(writer, path));
}

public boolean closeWriter(LuceneIndexWriter writer, long timestamp) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ public void updateDocument(String path, Iterable<? extends IndexableField> doc)
}

@Override
public void deleteDocuments(String path) throws IOException {
public void deleteDocumentTree(String path) throws IOException {
Mount mount = mountInfoProvider.getMountByPath(path);
getWriter(mount).deleteDocuments(path);
getWriter(mount).deleteDocumentTree(path);

//In case of default mount look for other mounts with roots under this path
//Note that one mount cannot be part of another mount
Expand All @@ -76,6 +76,13 @@ public void deleteDocuments(String path) throws IOException {
}
}

@Override
public void deleteDocument(String path) throws IOException {
// Single-document delete: no mount-under-path cleanup needed
Mount mount = mountInfoProvider.getMountByPath(path);
getWriter(mount).deleteDocument(path);
}

@Override
public boolean close(long timestamp) throws IOException {
// explicitly get writers for mounts which haven't got writers even at close.
Expand Down
Loading
Loading