+ ** .withReferenceProvider(FieldReferenceProvider.keyPrefix("files", "media", "storageKey"))
+ **
+ *******************************************************************************/
+public class FieldReferenceProvider implements StorageReferenceProvider
+{
+ /*******************************************************************************
+ ** What the field holds.
+ *******************************************************************************/
+ private enum Holds
+ {
+ KEY,
+ KEY_PREFIX,
+ URL
+ }
+
+ private final String bucketAlias;
+ private final String tableName;
+ private final String fieldName;
+ private final Holds holds;
+ private String labelField;
+
+
+
+ /*******************************************************************************
+ ** Constructor.
+ *******************************************************************************/
+ private FieldReferenceProvider(String bucketAlias, String tableName, String fieldName, Holds holds)
+ {
+ this.bucketAlias = bucketAlias;
+ this.tableName = tableName;
+ this.fieldName = fieldName;
+ this.holds = holds;
+ }
+
+
+
+ /*******************************************************************************
+ ** The field holds one object key.
+ *******************************************************************************/
+ public static FieldReferenceProvider key(String bucketAlias, String tableName, String fieldName)
+ {
+ return (new FieldReferenceProvider(bucketAlias, tableName, fieldName, Holds.KEY));
+ }
+
+
+
+ /*******************************************************************************
+ ** The field holds a key prefix: every object under it is referenced.
+ *******************************************************************************/
+ public static FieldReferenceProvider keyPrefix(String bucketAlias, String tableName, String fieldName)
+ {
+ return (new FieldReferenceProvider(bucketAlias, tableName, fieldName, Holds.KEY_PREFIX));
+ }
+
+
+
+ /*******************************************************************************
+ ** The field holds a public URL.
+ *******************************************************************************/
+ public static FieldReferenceProvider url(String bucketAlias, String tableName, String fieldName)
+ {
+ return (new FieldReferenceProvider(bucketAlias, tableName, fieldName, Holds.URL));
+ }
+
+
+
+ /*******************************************************************************
+ ** Use another field as the label of the referencing record (default: the table
+ ** name and id).
+ *******************************************************************************/
+ public FieldReferenceProvider withLabelField(String labelField)
+ {
+ this.labelField = labelField;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Every reference this provider knows of in a bucket.
+ *******************************************************************************/
+ @Override
+ public Stream
+ ** StorageBucket files = ObjectStorage.bucket("files");
+ ** files.put(PutRequest.of("media/42/hero.jpg", bytes).withContentType("image/jpeg"));
+ ** URL link = files.presignGet(PresignGet.of("media/42/hero.jpg"));
+ **
+ ** // a calling qbit with its own permission check (for example backups):
+ ** ObjectStorage.asSystem("backups", runId).bucket("backups").put(...);
+ **
+ *******************************************************************************/
+public final class ObjectStorage
+{
+ /*******************************************************************************
+ ** Utility class.
+ *******************************************************************************/
+ private ObjectStorage()
+ {
+ }
+
+
+
+ /*******************************************************************************
+ ** The bucket behind an alias from config, called as the current session user.
+ *******************************************************************************/
+ public static StorageBucket bucket(String alias)
+ {
+ ConfigSync.ensureSynced();
+ return (new StorageBucket(alias, StorageCaller.user()));
+ }
+
+
+
+ /*******************************************************************************
+ ** A facade whose calls run as a system caller: tagged with the calling qbit
+ ** and its run, not subject to the user's object_storage.* permissions (the
+ ** calling qbit must have checked its own permission first), and logged as
+ ** "system: backups run 1234 (started by user X)".
+ *******************************************************************************/
+ public static SystemStorage asSystem(String callerName, String runId)
+ {
+ return (new SystemStorage(StorageCaller.system(callerName, runId)));
+ }
+
+
+
+ /*******************************************************************************
+ ** Register a change listener (the CDN qbit plans purges with one).
+ *******************************************************************************/
+ public static void addChangeListener(StorageChangeListener listener)
+ {
+ ObjectStorageRuntime.addChangeListener(listener);
+ }
+
+
+
+ /*******************************************************************************
+ ** Replace the public URL resolver (the CDN qbit returns its hostnames).
+ *******************************************************************************/
+ public static void setPublicUrlResolver(PublicUrlResolver resolver)
+ {
+ ObjectStorageRuntime.setPublicUrlResolver(resolver);
+ }
+}
diff --git a/src/main/java/com/kingsrook/qbits/objectstorage/api/ObjectStream.java b/src/main/java/com/kingsrook/qbits/objectstorage/api/ObjectStream.java
new file mode 100644
index 0000000..6da756a
--- /dev/null
+++ b/src/main/java/com/kingsrook/qbits/objectstorage/api/ObjectStream.java
@@ -0,0 +1,136 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * 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
+ *
+ * https://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 com.kingsrook.qbits.objectstorage.api;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.Instant;
+
+
+/*******************************************************************************
+ ** The content of an object plus its basic metadata. Close it when done.
+ *******************************************************************************/
+public class ObjectStream implements AutoCloseable
+{
+ private final InputStream inputStream;
+ private final Long contentLength;
+ private final String contentType;
+ private final String etag;
+ private final Instant lastModified;
+ private final ObjectHeaders headers;
+
+
+
+ /*******************************************************************************
+ ** Constructor.
+ *******************************************************************************/
+ public ObjectStream(InputStream inputStream, Long contentLength, String contentType, String etag, Instant lastModified, ObjectHeaders headers)
+ {
+ this.inputStream = inputStream;
+ this.contentLength = contentLength;
+ this.contentType = contentType;
+ this.etag = etag;
+ this.lastModified = lastModified;
+ this.headers = headers;
+ }
+
+
+
+ /*******************************************************************************
+ ** The content.
+ *******************************************************************************/
+ public InputStream getInputStream()
+ {
+ return (inputStream);
+ }
+
+
+
+ /*******************************************************************************
+ ** Read everything into memory (small objects only).
+ *******************************************************************************/
+ public byte[] readAllBytes() throws IOException
+ {
+ return (inputStream.readAllBytes());
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for contentLength.
+ *******************************************************************************/
+ public Long getContentLength()
+ {
+ return (contentLength);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for contentType.
+ *******************************************************************************/
+ public String getContentType()
+ {
+ return (contentType);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for etag.
+ *******************************************************************************/
+ public String getEtag()
+ {
+ return (etag);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for lastModified.
+ *******************************************************************************/
+ public Instant getLastModified()
+ {
+ return (lastModified);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for headers.
+ *******************************************************************************/
+ public ObjectHeaders getHeaders()
+ {
+ return (headers);
+ }
+
+
+
+ /*******************************************************************************
+ ** Close the stream.
+ *******************************************************************************/
+ @Override
+ public void close() throws IOException
+ {
+ inputStream.close();
+ }
+}
diff --git a/src/main/java/com/kingsrook/qbits/objectstorage/api/ObjectSummary.java b/src/main/java/com/kingsrook/qbits/objectstorage/api/ObjectSummary.java
new file mode 100644
index 0000000..e5bba76
--- /dev/null
+++ b/src/main/java/com/kingsrook/qbits/objectstorage/api/ObjectSummary.java
@@ -0,0 +1,202 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * 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
+ *
+ * https://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 com.kingsrook.qbits.objectstorage.api;
+
+
+import java.time.Instant;
+
+
+/********************************************************************************
+ ** One object as reported by a listing (no headers).
+ *******************************************************************************/
+public class ObjectSummary
+{
+ private String key;
+ private Long size;
+ private String etag;
+ private Instant lastModified;
+ private String storageClass;
+
+
+
+ /********************************************************************************
+ ** Default constructor.
+ *******************************************************************************/
+ public ObjectSummary()
+ {
+ }
+
+
+
+ /********************************************************************************
+ ** Getter for key.
+ *******************************************************************************/
+ public String getKey()
+ {
+ return (this.key);
+ }
+
+
+
+ /********************************************************************************
+ ** Setter for key.
+ *******************************************************************************/
+ public void setKey(String key)
+ {
+ this.key = key;
+ }
+
+
+
+ /********************************************************************************
+ ** Fluent setter for key.
+ *******************************************************************************/
+ public ObjectSummary withKey(String key)
+ {
+ this.key = key;
+ return (this);
+ }
+
+
+
+ /********************************************************************************
+ ** Getter for size.
+ *******************************************************************************/
+ public Long getSize()
+ {
+ return (this.size);
+ }
+
+
+
+ /********************************************************************************
+ ** Setter for size.
+ *******************************************************************************/
+ public void setSize(Long size)
+ {
+ this.size = size;
+ }
+
+
+
+ /********************************************************************************
+ ** Fluent setter for size.
+ *******************************************************************************/
+ public ObjectSummary withSize(Long size)
+ {
+ this.size = size;
+ return (this);
+ }
+
+
+
+ /********************************************************************************
+ ** Getter for etag.
+ *******************************************************************************/
+ public String getEtag()
+ {
+ return (this.etag);
+ }
+
+
+
+ /********************************************************************************
+ ** Setter for etag.
+ *******************************************************************************/
+ public void setEtag(String etag)
+ {
+ this.etag = etag;
+ }
+
+
+
+ /********************************************************************************
+ ** Fluent setter for etag.
+ *******************************************************************************/
+ public ObjectSummary withEtag(String etag)
+ {
+ this.etag = etag;
+ return (this);
+ }
+
+
+
+ /********************************************************************************
+ ** Getter for lastModified.
+ *******************************************************************************/
+ public Instant getLastModified()
+ {
+ return (this.lastModified);
+ }
+
+
+
+ /********************************************************************************
+ ** Setter for lastModified.
+ *******************************************************************************/
+ public void setLastModified(Instant lastModified)
+ {
+ this.lastModified = lastModified;
+ }
+
+
+
+ /********************************************************************************
+ ** Fluent setter for lastModified.
+ *******************************************************************************/
+ public ObjectSummary withLastModified(Instant lastModified)
+ {
+ this.lastModified = lastModified;
+ return (this);
+ }
+
+
+
+ /********************************************************************************
+ ** Getter for storageClass.
+ *******************************************************************************/
+ public String getStorageClass()
+ {
+ return (this.storageClass);
+ }
+
+
+
+ /********************************************************************************
+ ** Setter for storageClass.
+ *******************************************************************************/
+ public void setStorageClass(String storageClass)
+ {
+ this.storageClass = storageClass;
+ }
+
+
+
+ /********************************************************************************
+ ** Fluent setter for storageClass.
+ *******************************************************************************/
+ public ObjectSummary withStorageClass(String storageClass)
+ {
+ this.storageClass = storageClass;
+ return (this);
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qbits/objectstorage/api/Page.java b/src/main/java/com/kingsrook/qbits/objectstorage/api/Page.java
new file mode 100644
index 0000000..3c7920d
--- /dev/null
+++ b/src/main/java/com/kingsrook/qbits/objectstorage/api/Page.java
@@ -0,0 +1,139 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * 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
+ *
+ * https://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 com.kingsrook.qbits.objectstorage.api;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/********************************************************************************
+ ** One page of results plus a continuation token (null at the end) and, for delimiter listings, the common prefixes.
+ *******************************************************************************/
+public class Page