refactor!: move the S3 storage backend to the fess-storage-s3 plugin - #3428
Merged
Merged
Conversation
The AWS SDK for S3 is 30 jars the distribution carried for every installation, on its own release cadence, to serve a backend most installations never configure. It ships in the fess-storage-s3 plugin now, alongside the S3 crawler client registration, the way fess-storage-gcs already ships the Google Cloud Storage SDK. S3StorageClient moves to org.codelibs.fess.storage.s3 in that plugin, so fess_storage.xml declares no component of its own. The file and the fess.xml include stay: fess_storage++.xml merges into it, and without a base file the plugins would have nowhere to contribute to. The plugin registers both s3StorageClient and s3_compatStorageClient, which is what keeps storage.type=auto working for MinIO and other S3-compatible endpoints once it is installed. Keeping the AWS jars out of the war is the exclusion on fess-crawler-lasta, not the removal of the direct declaration: fess-crawler still compiles S3Client and the s3: URL handler against the SDK. The netty-nio-client exclusion the direct declaration carried is not repeated because it had nothing to remove -- awssdk:s3 2.54.2 uses apache5-client for its synchronous transport and does not depend on netty-nio-client at any scope. Measured with mvn dependency:list -DincludeScope=runtime before and after: 286 runtime artifacts to 256. Exactly 30 removals, no additions, and no version changes, so nothing that stays behind fell back to a different version. The 30 jars are 8,799,602 bytes. crawler.file.protocols loses s3 from its shipped default in all three places that spell it out: fess_config.properties, the two FessConfig javadoc comments, and the compiled defaultMap. Missing that last one is what #3410 had to fix for the removed storage protocol. The plugin adds the protocol back through ProtocolHelper.addFileProtocol when it is installed. The s3: literals in ProtocolHelper itself stay: they are string comparisons with no class reference behind them, and dropping them would break /go/ links, the crawling wizard and file name decoding. StorageClientFactory keeps the storage.type-to-component-name mapping and the endpoint detection, which is now all core contributes toward reaching a plugin. Its exception names both plugins instead of only fess-storage-gcs. StorageClientFactoryTest asserted that every type the distribution serves has a component, which inverts: test_noTypeIsServedByCore now walks StorageType.values() and auto, and requires hasComponent to be false for each. test_app.xml includes the shipped fess_storage.xml, so re-adding a client to core -- and with it an SDK to the war -- fails a test rather than only showing up in the artifact. test_detectStorageType_mapsEndpointsToTypes is new and fixes the detection table directly. The prototype assertion is dropped here because core has no component left to assert it against; that contract belongs to the plugins now. Pairs with the fess-crawler change that stops registering s3Client, which has to be merged and published first: crawler/client.xml still naming the component while the SDK is gone makes every CrawlerClientFactory fail to build, and the unit suite does not catch it because test_app.xml does not include crawler/client.xml.
StorageTypeTest and StorageItemTest are written in the JUnit 3 style the suite used before: public void test_* on a UnitFessTestCase subclass, with no annotation. UnitFessTestCase runs on JUnit 5 through utflute, which discovers tests by annotation, so neither class contributed a single test -- they are absent from all 462 surefire reports of a full mvn test run. Annotating them makes 17 tests run, and they pass as written. This matters here because StorageType is one of the two things core still contributes toward reaching a storage plugin, now that the clients themselves ship as fess-storage-*: StorageTypeTest is what pins its constants, and it was pinning nothing. 27 test classes in this repository have the same shape, roughly 65 test methods; only the two in the storage package are annotated here.
This was referenced Sep 10, 2026
marevol
added a commit
to codelibs/fess-parent
that referenced
this pull request
Sep 10, 2026
…lished (#80) These two repositories are how one repository in this family sees another's change, and Maven's default updatePolicy for a snapshot is daily. A build whose local repository already resolved a -SNAPSHOT earlier the same day never asks again, so a CI job that caches ~/.m2 cannot see a sibling's snapshot published minutes ago. updatePolicy always removes that window. Measured, not assumed. codelibs/fess-crawler#204 merged at 02:48:54 and its snapshot deployed at 02:49:31. codelibs/fess#3428, which depends on it, was re-run at 02:59:29 -- ten minutes after the fix was published -- and failed with DiXmlParseFailureException: crawler/client.xml included by app.xml Caused by: NoClassDefFoundError: software/amazon/awssdk/auth/credentials/AwsCredentials because it built against the previous fess-crawler-lasta. The job log contains no request to maven.codelibs.org at all: not one "Downloading from codelibs.org.snapshot" line, while "Cache restored from key: Linux-maven-..." sits at the top. The published jar was correct the whole time. Reproduced and fixed locally on the same shape of cache. Before, the resolved fess-crawler-lasta-15.9.0-SNAPSHOT.jar declared one s3Client component; after installing this pom and resolving once, it declares none and names the plugin that took over, and the timestamped build the fix landed in appears in ~/.m2. The Fess branch's DI container then built clientFactory and mapped s3://bucket/key, using nothing but published artifacts. Every consumer inherits this, which is the point: the plugin repositories' CI installs this pom from source and then resolves org.codelibs.fess:fess from these repositories, so they are in the same window whenever core publishes. The cost is one metadata request per snapshot dependency per build. Offline builds skip it, and a release build has no snapshot dependencies to check.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves the S3 storage backend out of the distribution, the way #3424 moved Google Cloud Storage. The AWS SDK for S3 is 30 jars, 8,799,602 bytes that every installation carried, on its own release cadence, for a backend most installations never configure.
What moves and what stays
S3StorageClientbecomesorg.codelibs.fess.storage.s3.S3StorageClientin the plugin, sofess_storage.xmldeclares no component of its own. The file and thefess.xmlinclude stay:fess_storage++.xmlmerges into it, and without a base file the plugins would have nowhere to contribute to. The plugin registers boths3StorageClientands3_compatStorageClient, which is what keepsstorage.type=autoworking for MinIO and other S3-compatible endpoints once installed.Staying in core:
StorageType,StorageClientFactory'sstorage.type-to-component-name mapping and its endpoint detection, and the fours3:literals inProtocolHelper— those are string comparisons with no class behind them, and dropping them would break/go/links, the crawling wizard and file name decoding.Keeping the jars out of the war is the exclusion on
fess-crawler-lasta, not the removal of the direct declaration: fess-crawler still compilesS3Clientand thes3:URL handler against the SDK.The
netty-nio-clientexclusion the direct declaration carried is not repeated, because it had nothing to remove —awssdk:s32.54.2 usesapache5-clientfor its synchronous transport and does not depend onnetty-nio-clientat any scope.Dependency measurement
mvn dependency:list -DincludeScope=runtimebefore and after: 286 runtime artifacts to 256. Exactly 30 removals, no additions, and no version changes, so nothing that stays behind fell back to a different version.crawler.file.protocolsLoses
s3in all three places that spell it out —fess_config.properties, the twoFessConfigjavadoc comments, and the compileddefaultMap. Missing that last one is what #3410 had to fix for the removedstorageprotocol. The plugin adds the protocol back throughProtocolHelper.addFileProtocolwhen installed.Tests
StorageClientFactoryTestasserted that every type the distribution serves has a component, which inverts here.test_noTypeIsServedByCorewalksStorageType.values()plusautoand requireshasComponentto be false for each;test_app.xmlincludes the shippedfess_storage.xml, so re-adding a client to core — and with it an SDK to the war — fails a test rather than only showing up in the artifact.test_detectStorageType_mapsEndpointsToTypesis new and pins the detection table. The prototype assertion is dropped because core has no component left to assert it against; that contract belongs to the plugins.The second commit annotates
StorageTypeTestandStorageItemTest, which are written in the JUnit 3 style —public void test_*with no annotation — and so contributed no tests at all: neither appears in any of the 462 surefire reports of a full run. Annotating them runs 17 tests, which pass as written. It matters here becauseStorageTypeis one of the two things core still contributes toward reaching a plugin, and the test pinning it was pinning nothing. 27 test classes in this repository have the same shape, roughly 65 methods; only the two in the storage package are touched.mvn test— 7696 tests, 0 failures.Runtime verification
A LastaDi container was booted on a plain JVM with
fess_storage.xml,crawler/client.xmlandcrawler/mimetype.xml, on a class path built the way Fess builds one:WEB-INF/classes, then the war's runtime jars from this branch, then the plugin jar.With the plugin (and fess-crawler#204):
s3StorageClientands3_compatStorageClientboth resolve to the plugin's class, both are prototypes, andclientFactorymapss3://bucket/keytoorg.codelibs.fess.crawler.client.s3.S3Client— the class from fess-crawler, registered by the plugin.file:unaffected.Without the plugin — the state of an installation that upgrades and does not install it — the container still initializes, so the webapp starts.
hasComponentis false fors3StorageClient,s3_compatStorageClientandgcsStorageClient;clientFactorybuilds;getClient("s3://bucket/k")returnsnull;file:still resolves. That is the designed degradation, and #3427 is what makes it visible rather than silent.Storage operations were then exercised for real against MinIO on that same class path — the war's jars with no AWS SDK, plus the plugin jar.
ensureBucketExists,isAvailable,uploadObject,listObjectsat two prefix levels,downloadObject,setObjectTags/getObjectTagsanddeleteObject: 10 checks, all passing. Worth noting what that resolves: the SDK's HTTP service implementation loads from the plugin jar while httpclient5 loads from the war at 5.6.1, which is older than the 5.6.4apache5-clientasks for. That pairing is not new — the war ships the same combination today — and it works.Why CI is red
master'sfess-crawler-lastastill declaress3Clientincrawler/client.xml, and LastaDi resolves aclassattribute when it parses the XML, not when the component is first used. With the SDK excluded, booting Fess therefore fails at container init:Reproduced on a plain JVM, and it is the CI's "Run Fess" step that will hit it. The unit suite does not, because
test_app.xmldoes not includecrawler/client.xml— which is exactly why the merge order is a requirement.