diff --git a/pom.xml b/pom.xml
index 51cf9a344..0c0ca1c39 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1278,17 +1278,6 @@
oauth2-oidc-sdk
${oauth2.oidc.sdk.version}
-
- software.amazon.awssdk
- s3
- ${s3.version}
-
-
- software.amazon.awssdk
- netty-nio-client
-
-
-
org.apache.httpcomponents.client5
httpclient5
@@ -1478,6 +1467,19 @@
com.google.cloud
google-cloud-storage
+
+
+ software.amazon.awssdk
+ s3
+
diff --git a/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java b/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
index 549b46ae8..cb1f98545 100644
--- a/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
+++ b/src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
@@ -443,7 +443,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
/** The key of the configuration. e.g. http,https */
String CRAWLER_WEB_PROTOCOLS = "crawler.web.protocols";
- /** The key of the configuration. e.g. file,smb,smb1,ftp,s3 */
+ /** The key of the configuration. e.g. file,smb,smb1,ftp */
String CRAWLER_FILE_PROTOCOLS = "crawler.file.protocols";
/** The key of the configuration. e.g. ^FESS_ENV_.* */
@@ -3571,7 +3571,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
/**
* Get the value for the key 'crawler.file.protocols'.
- * The value is, e.g. file,smb,smb1,ftp,s3
+ * The value is, e.g. file,smb,smb1,ftp
* comment: Supported file protocols for crawling.
* @return The value of found property. (NotNull: if not found, exception but basically no way)
*/
@@ -14560,7 +14560,7 @@ protected java.util.Map prepareGeneratedDefaultMap() {
defaultMap.put(FessConfig.CRAWLER_DOCUMENT_FULLSTOP_CHARS, "u002eu06d4u2e3cu3002");
defaultMap.put(FessConfig.CRAWLER_CRAWLING_DATA_ENCODING, "UTF-8");
defaultMap.put(FessConfig.CRAWLER_WEB_PROTOCOLS, "http,https");
- defaultMap.put(FessConfig.CRAWLER_FILE_PROTOCOLS, "file,smb,smb1,ftp,s3");
+ defaultMap.put(FessConfig.CRAWLER_FILE_PROTOCOLS, "file,smb,smb1,ftp");
defaultMap.put(FessConfig.CRAWLER_DATA_ENV_PARAM_KEY_PATTERN, "^FESS_ENV_.*");
defaultMap.put(FessConfig.CRAWLER_IGNORE_ROBOTS_TXT, "false");
defaultMap.put(FessConfig.CRAWLER_IGNORE_ROBOTS_TAGS, "false");
diff --git a/src/main/java/org/codelibs/fess/storage/S3StorageClient.java b/src/main/java/org/codelibs/fess/storage/S3StorageClient.java
deleted file mode 100644
index b05ae6e7f..000000000
--- a/src/main/java/org/codelibs/fess/storage/S3StorageClient.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright 2012-2025 CodeLibs Project and the Others.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language
- * governing permissions and limitations under the License.
- */
-package org.codelibs.fess.storage;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.codelibs.core.lang.StringUtil;
-import org.codelibs.fess.crawler.Constants;
-import org.codelibs.fess.exception.StorageException;
-import org.codelibs.fess.mylasta.direction.FessConfig;
-import org.codelibs.fess.util.ComponentUtil;
-
-import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
-import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
-import software.amazon.awssdk.core.sync.RequestBody;
-import software.amazon.awssdk.regions.Region;
-import software.amazon.awssdk.services.s3.S3Client;
-import software.amazon.awssdk.services.s3.S3ClientBuilder;
-import software.amazon.awssdk.services.s3.model.CommonPrefix;
-import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
-import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
-import software.amazon.awssdk.services.s3.model.GetObjectRequest;
-import software.amazon.awssdk.services.s3.model.GetObjectTaggingRequest;
-import software.amazon.awssdk.services.s3.model.GetObjectTaggingResponse;
-import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
-import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
-import software.amazon.awssdk.services.s3.model.ListObjectsV2Response;
-import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
-import software.amazon.awssdk.services.s3.model.PutObjectRequest;
-import software.amazon.awssdk.services.s3.model.PutObjectTaggingRequest;
-import software.amazon.awssdk.services.s3.model.S3Object;
-import software.amazon.awssdk.services.s3.model.Tag;
-import software.amazon.awssdk.services.s3.model.Tagging;
-
-/**
- * S3-compatible storage client implementation using AWS SDK v2.
- * Supports Amazon S3, MinIO, and other S3-compatible storage systems.
- */
-public class S3StorageClient implements StorageClient {
-
- private static final Logger logger = LogManager.getLogger(S3StorageClient.class);
-
- private S3Client s3Client;
- private String bucket;
-
- /**
- * Creates an unconfigured client, which is how LastaDi builds the prototype component.
- * {@link #init()} reads the configuration and opens the connection.
- */
- public S3StorageClient() {
- // configured by init()
- }
-
- /**
- * Creates a new S3StorageClient instance.
- *
- * @param endpoint the S3 endpoint URL (null for AWS default)
- * @param accessKey the AWS access key
- * @param secretKey the AWS secret key
- * @param bucket the bucket name
- * @param region the AWS region
- */
- public S3StorageClient(final String endpoint, final String accessKey, final String secretKey, final String bucket,
- final String region) {
- configure(endpoint, accessKey, secretKey, bucket, region);
- }
-
- @Override
- public void init() {
- final FessConfig fessConfig = ComponentUtil.getFessConfig();
- configure(fessConfig.getStorageEndpoint(), fessConfig.getStorageAccessKey(), fessConfig.getStorageSecretKey(),
- fessConfig.getStorageBucket(), fessConfig.getStorageRegion());
- }
-
- private void configure(final String endpoint, final String accessKey, final String secretKey, final String bucket,
- final String region) {
- this.bucket = bucket;
-
- final AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey);
- final S3ClientBuilder builder =
- S3Client.builder().credentialsProvider(StaticCredentialsProvider.create(credentials)).region(Region.of(getRegion(region)));
-
- // For non-AWS endpoints (MinIO, etc.), set custom endpoint with path-style access
- if (StringUtil.isNotBlank(endpoint)) {
- builder.endpointOverride(URI.create(endpoint)).forcePathStyle(true);
- }
-
- this.s3Client = builder.build();
- }
-
- private String getRegion(final String region) {
- return StringUtil.isNotBlank(region) ? region : "us-east-1";
- }
-
- @Override
- public void uploadObject(final String objectName, final InputStream inputStream, final long size, final String contentType) {
- try {
- final PutObjectRequest request =
- PutObjectRequest.builder().bucket(bucket).key(objectName).contentType(contentType).contentLength(size).build();
- s3Client.putObject(request, RequestBody.fromInputStream(inputStream, size));
- } catch (final Exception e) {
- throw new StorageException("Failed to upload " + objectName, e);
- }
- }
-
- @Override
- public void downloadObject(final String objectName, final OutputStream outputStream) {
- try {
- final GetObjectRequest request = GetObjectRequest.builder().bucket(bucket).key(objectName).build();
- try (InputStream in = s3Client.getObject(request)) {
- in.transferTo(outputStream);
- }
- } catch (final Exception e) {
- throw new StorageException("Failed to download " + objectName, e);
- }
- }
-
- @Override
- public void deleteObject(final String objectName) {
- try {
- final DeleteObjectRequest request = DeleteObjectRequest.builder().bucket(bucket).key(objectName).build();
- s3Client.deleteObject(request);
- } catch (final Exception e) {
- throw new StorageException("Failed to delete " + objectName, e);
- }
- }
-
- @Override
- public List listObjects(final String prefix, final int maxItems) {
- final List items = new ArrayList<>();
-
- try {
- final ListObjectsV2Request.Builder requestBuilder =
- ListObjectsV2Request.builder().bucket(bucket).delimiter("/").maxKeys(maxItems);
-
- if (StringUtil.isNotBlank(prefix)) {
- final String normalizedPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
- requestBuilder.prefix(normalizedPrefix);
- }
-
- final ListObjectsV2Response response = s3Client.listObjectsV2(requestBuilder.build());
-
- // Process common prefixes (directories)
- for (final CommonPrefix commonPrefix : response.commonPrefixes()) {
- final String dirName = getName(commonPrefix.prefix());
- if (StringUtil.isNotBlank(dirName)) {
- items.add(new StorageItem(dirName, prefix, true, 0, null, encodeId(commonPrefix.prefix())));
- }
- }
-
- // Process objects (files)
- for (final S3Object s3Object : response.contents()) {
- final String objectKey = s3Object.key();
- // Skip directory markers (objects ending with /)
- if (!objectKey.endsWith("/")) {
- final String fileName = getName(objectKey);
- final ZonedDateTime lastModified =
- s3Object.lastModified() != null ? s3Object.lastModified().atZone(java.time.ZoneId.systemDefault()) : null;
- items.add(new StorageItem(fileName, prefix, false, s3Object.size(), lastModified, encodeId(objectKey)));
- }
- }
- } catch (final NoSuchBucketException e) {
- logger.info("Bucket does not exist: {}", bucket);
- } catch (final Exception e) {
- if (logger.isDebugEnabled()) {
- logger.debug("Failed to list objects in {}", bucket, e);
- }
- }
-
- return items;
- }
-
- @Override
- public Map getObjectTags(final String objectName) {
- try {
- final GetObjectTaggingRequest request = GetObjectTaggingRequest.builder().bucket(bucket).key(objectName).build();
- final GetObjectTaggingResponse response = s3Client.getObjectTagging(request);
- return response.tagSet().stream().collect(Collectors.toMap(Tag::key, Tag::value));
- } catch (final Exception e) {
- throw new StorageException("Failed to get tags from " + objectName, e);
- }
- }
-
- @Override
- public void setObjectTags(final String objectName, final Map tags) {
- try {
- final List tagList = tags.entrySet()
- .stream()
- .map(e -> Tag.builder().key(e.getKey()).value(e.getValue()).build())
- .collect(Collectors.toList());
-
- final PutObjectTaggingRequest request = PutObjectTaggingRequest.builder()
- .bucket(bucket)
- .key(objectName)
- .tagging(Tagging.builder().tagSet(tagList).build())
- .build();
- s3Client.putObjectTagging(request);
- } catch (final Exception e) {
- throw new StorageException("Failed to update tags for " + objectName, e);
- }
- }
-
- @Override
- public void ensureBucketExists() {
- try {
- final HeadBucketRequest request = HeadBucketRequest.builder().bucket(bucket).build();
- s3Client.headBucket(request);
- } catch (final NoSuchBucketException e) {
- try {
- final CreateBucketRequest createRequest = CreateBucketRequest.builder().bucket(bucket).build();
- s3Client.createBucket(createRequest);
- logger.info("Created storage bucket: {}", bucket);
- } catch (final Exception e1) {
- logger.warn("Failed to create storage bucket: {}", bucket, e1);
- }
- } catch (final Exception e) {
- if (logger.isDebugEnabled()) {
- logger.debug("Failed to check bucket: {}", bucket, e);
- }
- }
- }
-
- @Override
- public boolean isAvailable() {
- try {
- final HeadBucketRequest request = HeadBucketRequest.builder().bucket(bucket).build();
- s3Client.headBucket(request);
- return true;
- } catch (final Exception e) {
- return false;
- }
- }
-
- @Override
- public void close() {
- if (s3Client != null) {
- s3Client.close();
- }
- }
-
- /**
- * Extracts the file/directory name from a full object path.
- *
- * @param objectName the full object path
- * @return the name portion of the path
- */
- private String getName(final String objectName) {
- if (StringUtil.isBlank(objectName)) {
- return StringUtil.EMPTY;
- }
- // Remove trailing slash if present
- String name = objectName;
- if (name.endsWith("/")) {
- name = name.substring(0, name.length() - 1);
- }
- final String[] values = name.split("/");
- if (values.length == 0) {
- return StringUtil.EMPTY;
- }
- return values[values.length - 1];
- }
-
- /**
- * Encodes an object name to a URL-safe base64 string.
- *
- * @param objectName the object name to encode
- * @return base64 encoded string
- */
- private String encodeId(final String objectName) {
- if (objectName == null) {
- return StringUtil.EMPTY;
- }
- return new String(Base64.getUrlEncoder().encode(objectName.getBytes(Constants.UTF_8_CHARSET)), Constants.UTF_8_CHARSET);
- }
-}
diff --git a/src/main/java/org/codelibs/fess/storage/StorageClientFactory.java b/src/main/java/org/codelibs/fess/storage/StorageClientFactory.java
index 14f371178..021e3c0f5 100644
--- a/src/main/java/org/codelibs/fess/storage/StorageClientFactory.java
+++ b/src/main/java/org/codelibs/fess/storage/StorageClientFactory.java
@@ -72,8 +72,10 @@ public static StorageType detectStorageType(final String endpoint) {
public static StorageClient createClient(final FessConfig fessConfig) {
final String componentName = componentName(fessConfig.getStorageType(), fessConfig.getStorageEndpoint());
if (!ComponentUtil.hasComponent(componentName)) {
- throw new StorageException("No storage client is registered as " + componentName + " for storage.type="
- + fessConfig.getStorageType() + ". Install the plugin that provides it, such as fess-storage-gcs for gcs.");
+ throw new StorageException(
+ "No storage client is registered as " + componentName + " for storage.type=" + fessConfig.getStorageType()
+ + ". Every backend ships as a fess-storage-* plugin: install fess-storage-s3 for s3 and s3_compat, "
+ + "or fess-storage-gcs for gcs.");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating {} for endpoint: {}", componentName, fessConfig.getStorageEndpoint());
@@ -87,10 +89,10 @@ public static StorageClient createClient(final FessConfig fessConfig) {
* Returns the name of the DI component that serves a storage type.
*
* The mapping from a {@code storage.type} value to an implementation lives in the DI
- * definition rather than here, which is what lets the clients ship as plugins: core no longer
- * names GcsStorageClient or S3StorageClient, and a plugin registering
- * {@code StorageClient} is reachable by setting {@code storage.type=}. The
- * components are prototypes because every caller closes the client it was handed.
+ * definition rather than here, which is what lets the clients ship as plugins: core names no
+ * implementation at all, and a plugin registering {@code StorageClient} is reachable by
+ * setting {@code storage.type=}. The components are prototypes because every caller
+ * closes the client it was handed.
*
* @param typeStr the configured type, blank or {@code auto} to detect from the endpoint
* @param endpoint the storage endpoint, used only when detecting
diff --git a/src/main/resources/fess_config.properties b/src/main/resources/fess_config.properties
index 930757415..066b66cda 100644
--- a/src/main/resources/fess_config.properties
+++ b/src/main/resources/fess_config.properties
@@ -383,7 +383,7 @@ crawler.crawling.data.encoding=UTF-8
# Supported web protocols for crawling.
crawler.web.protocols=http,https
# Supported file protocols for crawling.
-crawler.file.protocols=file,smb,smb1,ftp,s3
+crawler.file.protocols=file,smb,smb1,ftp
# Pattern for environment variable keys in crawling data.
crawler.data.env.param.key.pattern=^FESS_ENV_.*
# Whether to ignore robots.txt during crawling.
diff --git a/src/main/resources/fess_storage.xml b/src/main/resources/fess_storage.xml
index 00846fbc8..faae617b6 100644
--- a/src/main/resources/fess_storage.xml
+++ b/src/main/resources/fess_storage.xml
@@ -2,21 +2,18 @@
-
-
-
-
-
-
diff --git a/src/test/java/org/codelibs/fess/app/web/admin/wizard/AdminWizardActionTest.java b/src/test/java/org/codelibs/fess/app/web/admin/wizard/AdminWizardActionTest.java
index 3c7568eb3..190ebf75c 100644
--- a/src/test/java/org/codelibs/fess/app/web/admin/wizard/AdminWizardActionTest.java
+++ b/src/test/java/org/codelibs/fess/app/web/admin/wizard/AdminWizardActionTest.java
@@ -42,7 +42,7 @@ public String getCrawlerWebProtocols() {
@Override
public String getCrawlerFileProtocols() {
- return "file,smb,smb1,ftp,s3";
+ return "file,smb,smb1,ftp";
}
});
final ProtocolHelper protocolHelper = new ProtocolHelper();
diff --git a/src/test/java/org/codelibs/fess/app/web/go/GoActionTest.java b/src/test/java/org/codelibs/fess/app/web/go/GoActionTest.java
index 97166ee3f..114a6205b 100644
--- a/src/test/java/org/codelibs/fess/app/web/go/GoActionTest.java
+++ b/src/test/java/org/codelibs/fess/app/web/go/GoActionTest.java
@@ -48,7 +48,7 @@ public String getCrawlerWebProtocols() {
@Override
public String getCrawlerFileProtocols() {
- return "file,smb,smb1,ftp,s3";
+ return "file,smb,smb1,ftp";
}
});
final ProtocolHelper protocolHelper = new ProtocolHelper();
diff --git a/src/test/java/org/codelibs/fess/crawler/transformer/FessFileTransformerTest.java b/src/test/java/org/codelibs/fess/crawler/transformer/FessFileTransformerTest.java
index ae37d5ab2..000eb934e 100644
--- a/src/test/java/org/codelibs/fess/crawler/transformer/FessFileTransformerTest.java
+++ b/src/test/java/org/codelibs/fess/crawler/transformer/FessFileTransformerTest.java
@@ -46,7 +46,7 @@ public String getCrawlerWebProtocols() {
@Override
public String getCrawlerFileProtocols() {
- return "file,smb,smb1,ftp,s3";
+ return "file,smb,smb1,ftp";
}
@Override
diff --git a/src/test/java/org/codelibs/fess/helper/ProtocolHelperTest.java b/src/test/java/org/codelibs/fess/helper/ProtocolHelperTest.java
index eb09cb026..08bf70936 100644
--- a/src/test/java/org/codelibs/fess/helper/ProtocolHelperTest.java
+++ b/src/test/java/org/codelibs/fess/helper/ProtocolHelperTest.java
@@ -738,24 +738,25 @@ public String getCrawlerWebProtocols() {
@Override
public String getCrawlerFileProtocols() {
- return "file,smb,smb1,ftp,s3";
+ return "file,smb,smb1,ftp";
}
});
final ProtocolHelper protocolHelper = new ProtocolHelper();
protocolHelper.init();
- assertEquals(5, protocolHelper.getFileProtocols().length);
+ assertEquals(4, protocolHelper.getFileProtocols().length);
// All file protocols should be valid
assertTrue(protocolHelper.isValidFileProtocol("file:///path/to/file"));
assertTrue(protocolHelper.isValidFileProtocol("smb://server/share"));
assertTrue(protocolHelper.isValidFileProtocol("smb1://server/share"));
assertTrue(protocolHelper.isValidFileProtocol("ftp://ftp.example.com/file"));
- assertTrue(protocolHelper.isValidFileProtocol("s3://bucket/key"));
- // gcs is not in the shipped default: fess-storage-gcs adds it with addFileProtocol when the
- // plugin is installed, which test_s3_gcs_protocols_add_dynamically covers.
+ // Neither object-storage protocol is in the shipped default: fess-storage-s3 and
+ // fess-storage-gcs add theirs with addFileProtocol when the plugin is installed, which
+ // test_s3_gcs_protocols_add_dynamically covers.
+ assertFalse(protocolHelper.isValidFileProtocol("s3://bucket/key"));
assertFalse(protocolHelper.isValidFileProtocol("gcs://bucket/object"));
// Web protocols should NOT be valid as file protocols
diff --git a/src/test/java/org/codelibs/fess/storage/StorageClientFactoryTest.java b/src/test/java/org/codelibs/fess/storage/StorageClientFactoryTest.java
index 92c7e6502..c998bac65 100644
--- a/src/test/java/org/codelibs/fess/storage/StorageClientFactoryTest.java
+++ b/src/test/java/org/codelibs/fess/storage/StorageClientFactoryTest.java
@@ -18,6 +18,12 @@
import org.codelibs.fess.unit.UnitFessTestCase;
import org.junit.jupiter.api.Test;
+/**
+ * What core still owns is the type-to-name mapping: {@link StorageClientFactory#componentName}
+ * and the endpoint detection behind it. The implementations moved to fess-storage-s3 and
+ * fess-storage-gcs together with their SDKs, so the component definitions and the prototype
+ * contract are verified in those plugins, not here.
+ */
public class StorageClientFactoryTest extends UnitFessTestCase {
@Test
@@ -49,42 +55,49 @@ public void test_componentName_detectsFromTheEndpoint() {
}
/**
- * Every name {@link #test_componentName_detectsFromTheEndpoint} and the storage.type values
- * the distribution serves can produce has to exist in fess_storage.xml, or the admin storage
- * screen fails at runtime for a configuration the UI offers.
+ * The distribution on its own serves no storage type: every backend ships as a
+ * fess-storage-* plugin that contributes its component through fess_storage++.xml, so
+ * fess_storage.xml declares nothing and storage.type is a configuration error until the
+ * matching plugin is installed. This asserts the absence, because registering any of these
+ * names in core again would pull an SDK back into the war - the AWS one for s3 and s3_compat,
+ * the Google Cloud Storage one for gcs.
+ *
+ * test_app.xml includes fess_storage.xml, so this runs against the file the distribution
+ * ships rather than against an empty container.
*/
@Test
- public void test_everyTypeTheDistributionServesHasAComponent() {
- for (final StorageType type : new StorageType[] { StorageType.S3, StorageType.S3_COMPAT }) {
+ public void test_noTypeIsServedByCore() {
+ for (final StorageType type : StorageType.values()) {
final String name = StorageClientFactory.componentName(type.name(), null);
- assertTrue(org.codelibs.fess.util.ComponentUtil.hasComponent(name), name + " is not registered in fess_storage.xml");
+ assertFalse(org.codelibs.fess.util.ComponentUtil.hasComponent(name),
+ name + " belongs to a fess-storage-* plugin and must not be declared in fess_storage.xml");
}
- assertTrue(org.codelibs.fess.util.ComponentUtil.hasComponent(StorageClientFactory.componentName("auto", null)));
+ // auto with no endpoint resolves to s3, which is a plugin as well.
+ assertFalse(org.codelibs.fess.util.ComponentUtil.hasComponent(StorageClientFactory.componentName("auto", null)));
}
/**
- * GCS is deliberately not in that list. Its client ships in fess-storage-gcs, together with the
- * Google Cloud Storage SDK the distribution no longer carries, so what core keeps is the name
- * the plugin registers under and the endpoint detection that produces it. Registering a
- * component here again would pull the SDK back into the war.
+ * With the clients gone, the mapping from an endpoint to a type name is what core contributes
+ * to reaching them, so it is fixed here as well as through {@link #componentName} above: a
+ * type that came out wrong would resolve a component name no installed plugin registers.
*/
@Test
- public void test_gcsIsServedByAPluginRatherThanCore() {
- assertEquals("gcsStorageClient", StorageClientFactory.componentName(StorageType.GCS.name(), null));
- assertFalse(org.codelibs.fess.util.ComponentUtil.hasComponent("gcsStorageClient"),
- "gcsStorageClient belongs to fess-storage-gcs, which contributes it through fess_storage++.xml");
- }
+ public void test_detectStorageType_mapsEndpointsToTypes() {
+ // No endpoint at all means AWS, which supplies its own.
+ assertEquals(StorageType.S3, StorageClientFactory.detectStorageType(null));
+ assertEquals(StorageType.S3, StorageClientFactory.detectStorageType(""));
+ assertEquals(StorageType.S3, StorageClientFactory.detectStorageType(" "));
- /**
- * A prototype, not a singleton: every caller closes the client it was handed, so a shared
- * instance would be shut down for everybody by the first caller to finish.
- */
- @Test
- public void test_theComponentsArePrototypes() {
- final Object first = org.codelibs.fess.util.ComponentUtil.getComponent("s3StorageClient");
- final Object second = org.codelibs.fess.util.ComponentUtil.getComponent("s3StorageClient");
- assertNotNull(first);
- assertNotNull(second);
- assertFalse(first == second, "s3StorageClient must be instance=\"prototype\"");
+ assertEquals(StorageType.GCS, StorageClientFactory.detectStorageType("https://storage.googleapis.com"));
+ assertEquals(StorageType.GCS, StorageClientFactory.detectStorageType("https://STORAGE.GOOGLEAPIS.COM"));
+ assertEquals(StorageType.GCS, StorageClientFactory.detectStorageType("https://bucket.storage.cloud.google.com"));
+
+ assertEquals(StorageType.S3, StorageClientFactory.detectStorageType("https://s3.us-east-1.amazonaws.com"));
+ assertEquals(StorageType.S3, StorageClientFactory.detectStorageType("https://bucket.s3.amazonaws.com"));
+ assertEquals(StorageType.S3, StorageClientFactory.detectStorageType("https://s3-accelerate.amazonaws.com"));
+
+ // No vendor host: MinIO and the like, served by the same client as s3 under another name.
+ assertEquals(StorageType.S3_COMPAT, StorageClientFactory.detectStorageType("http://minio.internal:9000"));
+ assertEquals(StorageType.S3_COMPAT, StorageClientFactory.detectStorageType("https://objects.example.com"));
}
}
diff --git a/src/test/java/org/codelibs/fess/storage/StorageItemTest.java b/src/test/java/org/codelibs/fess/storage/StorageItemTest.java
index 7f6f0fcb5..4f3d6f90b 100644
--- a/src/test/java/org/codelibs/fess/storage/StorageItemTest.java
+++ b/src/test/java/org/codelibs/fess/storage/StorageItemTest.java
@@ -19,9 +19,11 @@
import java.time.ZonedDateTime;
import org.codelibs.fess.unit.UnitFessTestCase;
+import org.junit.jupiter.api.Test;
public class StorageItemTest extends UnitFessTestCase {
+ @Test
public void test_constructorAndGetters() {
final String name = "test-file.txt";
final String path = "/documents/";
@@ -40,6 +42,7 @@ public void test_constructorAndGetters() {
assertEquals(encodedId, item.getEncodedId());
}
+ @Test
public void test_directoryItem() {
final String name = "documents";
final String path = "/";
@@ -57,6 +60,7 @@ public void test_directoryItem() {
assertEquals(encodedId, item.getEncodedId());
}
+ @Test
public void test_fileItem() {
final ZonedDateTime now = ZonedDateTime.now();
final StorageItem item = new StorageItem("report.pdf", "/reports/2025/", false, 2048576L, now, "cmVwb3J0LnBkZg==");
@@ -68,6 +72,7 @@ public void test_fileItem() {
assertEquals(now, item.getLastModified());
}
+ @Test
public void test_nullValues() {
final StorageItem item = new StorageItem(null, null, false, 0L, null, null);
@@ -79,6 +84,7 @@ public void test_nullValues() {
assertNull(item.getEncodedId());
}
+ @Test
public void test_emptyName() {
final StorageItem item = new StorageItem("", "/path/", false, 100L, null, "");
@@ -87,6 +93,7 @@ public void test_emptyName() {
assertEquals("", item.getEncodedId());
}
+ @Test
public void test_largeFileSize() {
final long largeSize = 10737418240L; // 10 GB
final StorageItem item = new StorageItem("large-file.zip", "/backups/", false, largeSize, null, "bGFyZ2UtZmlsZS56aXA=");
@@ -94,6 +101,7 @@ public void test_largeFileSize() {
assertEquals(largeSize, item.getSize());
}
+ @Test
public void test_rootPath() {
final StorageItem item = new StorageItem("root-file.txt", "", false, 512L, null, "cm9vdC1maWxlLnR4dA==");
@@ -101,6 +109,7 @@ public void test_rootPath() {
assertEquals("", item.getPath());
}
+ @Test
public void test_deeplyNestedPath() {
final String deepPath = "/level1/level2/level3/level4/level5/";
final StorageItem item = new StorageItem("nested.txt", deepPath, false, 256L, null, "bmVzdGVkLnR4dA==");
@@ -108,6 +117,7 @@ public void test_deeplyNestedPath() {
assertEquals(deepPath, item.getPath());
}
+ @Test
public void test_specialCharactersInName() {
final String specialName = "file with spaces & symbols!@#.txt";
final StorageItem item = new StorageItem(specialName, "/special/", false, 100L, null, "c3BlY2lhbA==");
@@ -115,6 +125,7 @@ public void test_specialCharactersInName() {
assertEquals(specialName, item.getName());
}
+ @Test
public void test_unicodeInName() {
final String unicodeName = "ファイル名.txt";
final StorageItem item = new StorageItem(unicodeName, "/unicode/", false, 200L, null, "dW5pY29kZQ==");
diff --git a/src/test/java/org/codelibs/fess/storage/StorageTypeTest.java b/src/test/java/org/codelibs/fess/storage/StorageTypeTest.java
index 73901c7a0..917f6729b 100644
--- a/src/test/java/org/codelibs/fess/storage/StorageTypeTest.java
+++ b/src/test/java/org/codelibs/fess/storage/StorageTypeTest.java
@@ -16,39 +16,47 @@
package org.codelibs.fess.storage;
import org.codelibs.fess.unit.UnitFessTestCase;
+import org.junit.jupiter.api.Test;
public class StorageTypeTest extends UnitFessTestCase {
+ @Test
public void test_enumValues() {
final StorageType[] types = StorageType.values();
assertNotNull(types);
assertEquals(3, types.length);
}
+ @Test
public void test_s3Type() {
assertEquals(StorageType.S3, StorageType.valueOf("S3"));
}
+ @Test
public void test_gcsType() {
assertEquals(StorageType.GCS, StorageType.valueOf("GCS"));
}
+ @Test
public void test_s3CompatType() {
assertEquals(StorageType.S3_COMPAT, StorageType.valueOf("S3_COMPAT"));
}
+ @Test
public void test_valueOf() {
for (final StorageType type : StorageType.values()) {
assertEquals(type, StorageType.valueOf(type.name()));
}
}
+ @Test
public void test_ordinal() {
assertEquals(0, StorageType.S3.ordinal());
assertEquals(1, StorageType.GCS.ordinal());
assertEquals(2, StorageType.S3_COMPAT.ordinal());
}
+ @Test
public void test_name() {
assertEquals("S3", StorageType.S3.name());
assertEquals("GCS", StorageType.GCS.name());
diff --git a/src/test/resources/test_app.xml b/src/test/resources/test_app.xml
index ec499dfe6..a2171bded 100644
--- a/src/test/resources/test_app.xml
+++ b/src/test/resources/test_app.xml
@@ -5,11 +5,12 @@
-
+