Importa archivos de OpenStack Swift en Java
Descarga un objeto de OpenStack Swift a un archivo local con OpenStack4j. El programa Java completo que aparece a continuación admite objetos vacíos, verifica la respuesta HTTP y la cantidad de bytes descargados, y mantiene el destino sin modificar hasta que la descarga se completa correctamente.
Configura tu entorno Java
Este ejemplo usa un shell POSIX en Linux, una configuración regional UTF-8 y un sistema de archivos local que admite movimientos atómicos. La configuración regional determina cómo la JVM decodifica los nombres de archivo de la línea de comandos. Instala por separado una versión de JDK 21 que siga recibiendo mantenimiento y Apache Maven 3.10. El procedimiento que sigue se probó con OpenJDK 21.0.12.1 y Maven 3.10.0; también funcionó con Maven 3.9.11.
También necesitas una cuenta de Keystone v3 con acceso limitado al ámbito de un proyecto a un contenedor y un objeto existentes de Swift. Ten disponibles estas variables de entorno mediante la configuración de credenciales de tu proveedor o un gestor de secretos:
| Variable | Valor |
|---|---|
OS_AUTH_URL | URL de Keystone que termina en /v3 |
OS_USERNAME, OS_PASSWORD | Tu nombre de usuario y contraseña |
OS_USER_DOMAIN_NAME | Dominio al que pertenece el usuario |
OS_PROJECT_NAME, OS_PROJECT_DOMAIN_NAME | Proyecto y su dominio |
OS_REGION_NAME | Región que contiene el endpoint público de almacenamiento de objetos |
El usuario y el proyecto pueden pertenecer a dominios diferentes. Usa HTTPS tanto para Keystone como para el endpoint de Swift en su catálogo de servicios, con certificados en los que confíe la JVM. No incluyas contraseñas en los commits ni en comandos de shell que vayas a copiar. Este tutorial lee un objeto existente; no crea contenedores ni cambia las políticas de acceso.
Verifica cada ejecutable antes de crear el proyecto. Pega este bloque en tu shell actual;
el subshell mantiene sin cambios el directorio de trabajo y las opciones de ese shell.
Si ya existe un directorio swift-import, la preparación falla sin modificar su contenido.
(
cd -P . &&
swift_parent=$PWD &&
while :; do
if [ -e "$swift_parent/.mvn" ] || [ -L "$swift_parent/.mvn" ]; then
printf 'Run setup outside an existing Maven project.\n' >&2
exit 1
fi
[ "$swift_parent" = / ] && break
swift_parent=${swift_parent%/*}
[ -n "$swift_parent" ] || swift_parent=/
done &&
command -v java >/dev/null &&
command -v javac >/dev/null &&
command -v mvn >/dev/null &&
command -v locale >/dev/null &&
[ "$(locale charmap)" = UTF-8 ] &&
mkdir swift-import &&
cd swift-import &&
mkdir -p src/main/java
)
Guarda el siguiente archivo completo como swift-import/pom.xml. OpenStack4j 3.12 es la versión
publicada en Maven Central.
Este artefacto incluye el conector RESTEasy.
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>swift-import</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.github.openstack4j.core</groupId>
<artifactId>openstack4j</artifactId>
<version>3.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins>
</build>
</project>
La comprobación de preparación rechaza la configuración .mvn de directorios
superiores antes de crear archivos. Maven puede heredar opciones de lanzamiento de un proyecto
que lo contiene; revísalas antes de incorporar este ejemplo a una aplicación existente. Si falla
la resolución de dependencias, corrige el acceso a la red o al repositorio y repite el comando de
compilación que aparece a continuación. Conserva el código fuente guardado y pom.xml.
Conéctate a OpenStack Swift
Guarda este programa como swift-import/src/main/java/SwiftImport.java. Se autentica en Keystone,
limita el ámbito del token al proyecto y al dominio del proyecto indicados, y selecciona la región
solicitada. Estas son elecciones independientes en el
modelo de identidad de Keystone.
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.api.exceptions.ResponseException;
import org.openstack4j.core.transport.Config;
import org.openstack4j.core.transport.HttpResponse;
import org.openstack4j.model.common.Identifier;
import org.openstack4j.model.storage.object.SwiftObject;
import org.openstack4j.openstack.OSFactory;
public class SwiftImport {
private static String encodeName(String value, boolean object) {
String[] segments = value.split("/", -1);
if (!object && segments.length != 1) {
throw new IllegalArgumentException("A container name cannot contain a slash");
}
for (int index = 0; index < segments.length; index++) {
String segment = segments[index];
if (segment.isEmpty() || segment.equals(".") || segment.equals("..")) {
throw new IllegalArgumentException("Empty and dot path segments are unsupported");
}
segments[index] = URLEncoder.encode(segment, StandardCharsets.UTF_8).replace("+", "%20");
}
return String.join("/", segments);
}
private static String required(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Missing environment variable: " + name);
}
return value;
}
private static long download(OSClientV3 os, String container, String object,
Path destination) throws IOException {
SwiftObject remote = os.objectStorage().objects().get(container, object);
if (remote == null) {
throw new IOException("Object not found");
}
long expected = remote.getSizeInBytes();
Path partial = Files.createTempFile(destination.getParent(), ".swift-import-", ".part");
try {
long written;
// The download API exposes its response without mapping HTTP errors to exceptions.
try (HttpResponse response = remote.download().getHttpResponse()) {
if (response.getStatus() != 200) {
throw new IOException("Download rejected (HTTP " + response.getStatus() + ")");
}
try (InputStream input = response.getInputStream()) {
if (input == null) {
throw new IOException("Missing download stream");
}
written = Files.copy(input, partial, StandardCopyOption.REPLACE_EXISTING);
}
}
if (written != expected) {
throw new IOException("Downloaded byte count does not match object metadata");
}
Files.move(partial, destination,
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
return written;
} finally {
Files.deleteIfExists(partial);
}
}
public static void main(String[] args) {
if (args.length != 3) {
System.err.println("Usage: SwiftImport CONTAINER OBJECT DESTINATION");
System.exit(2);
}
try {
String container = encodeName(args[0], false);
String object = encodeName(args[1], true);
Config config = Config.newConfig().withConnectionTimeout(10_000);
OSClientV3 os = OSFactory.builderV3()
.endpoint(required("OS_AUTH_URL"))
.credentials(required("OS_USERNAME"), required("OS_PASSWORD"),
Identifier.byName(required("OS_USER_DOMAIN_NAME")))
.scopeToProject(Identifier.byName(required("OS_PROJECT_NAME")),
Identifier.byName(required("OS_PROJECT_DOMAIN_NAME")))
.withConfig(config)
.authenticate()
.useRegion(required("OS_REGION_NAME"));
Path destination = Path.of(args[2]).toAbsolutePath();
long written = download(os, container, object, destination);
System.out.println("Imported " + written + " bytes to " + destination);
} catch (ResponseException error) {
System.err.println("OpenStack request failed (HTTP " + error.getStatus() + ").");
System.exit(1);
} catch (Exception error) {
System.err.println("Import failed (" + error.getClass().getSimpleName()
+ "). Check configuration, object access, and local storage before retrying.");
System.exit(1);
}
}
}
Importa archivos de Swift
Desde el directorio que contiene swift-import, pega el siguiente bloque. Sustituye
exports y reports/report.bin por tu contenedor existente y el nombre exacto del objeto.
El destino local es relativo a swift-import, y su directorio padre debe existir previamente.
Pasa los nombres del contenedor y del objeto en UTF-8 sin codificar para URL, incluidos los espacios
o signos de porcentaje literales. El programa codifica cada segmento de la ruta de la URL antes de
llamar a OpenStack4j. Los nombres de contenedor no pueden contener /;
las rutas de objeto de este ejemplo no deben tener segmentos vacíos, . ni
... Las rutas de destino local también deben usar UTF-8 válido.
(
cd swift-import &&
mvn -B package dependency:copy-dependencies &&
java -cp 'target/classes:target/dependency/*' SwiftImport \
'exports' 'reports/report.bin' './download.bin'
)
Si la operación se completa correctamente, imprime la cantidad de bytes importados y la ruta
absoluta de destino. Un objeto vacío produce un archivo de cero bytes e informa que se importaron
cero bytes. Una compilación fallida impide que se ejecute el comando Java, incluso si queda una
clase compilada anterior en target/.
El programa obtiene el tamaño esperado de la respuesta HEAD de Swift, exige que la solicitud GET completa se ejecute correctamente y compara la cantidad de bytes copiados incluso cuando el tamaño esperado es cero. Swift documenta estas operaciones en su referencia de la API de objetos. Mantén el objeto remoto sin cambios durante la importación. Una comparación de tamaño no puede detectar cambios de contenido que mantengan el mismo tamaño; compara por separado una suma de verificación confiable proporcionada por quien publica el objeto cuando necesites esa comprobación.
Un destino existente solo se reemplaza con una descarga completa cuyo tamaño se haya verificado. El archivo temporal comparte su directorio para que la publicación pueda usar un movimiento atómico. Si el sistema de archivos no puede realizar ese movimiento, la importación falla sin recurrir a una copia no atómica. En Java, el comportamiento del reemplazo atómico depende del sistema de archivos; el procedimiento de reemplazo usado aquí se probó en Linux. Usa un solo importador por destino y mantén su directorio padre bajo tu control.
Gestiona archivos grandes de forma eficiente
El conector RESTEasy predeterminado de OpenStack4j 3.12 almacena la respuesta completa en un búfer antes de exponer un flujo de entrada. Elige objetos que quepan con suficiente margen en la memoria disponible de la JVM. Aumentar el búfer de copia o imprimir el progreso desde el bucle de copia no elimina esa asignación de memoria.
Implementa la gestión de errores y los reintentos
El comando devuelve un código de salida distinto de cero si faltan objetos, se rechazan lecturas,
las transferencias quedan incompletas o fallan las escrituras locales. Realiza un intento a nivel
de aplicación y solicita un tiempo de espera de conexión de 10 segundos. No hay un límite de tiempo
para la transferencia completa. En esta versión de RESTEasy,
withReadTimeout configura la adquisición de conexiones del pool
en lugar de un tiempo de espera de lectura del socket, por lo que se omite aquí. Una respuesta
atascada puede quedarse esperando hasta que se cierre la conexión. Usa un proceso supervisado si
tu aplicación necesita un límite de tiempo transcurrido.
Tras un fallo gestionado, corrige su causa y repite el mismo bloque de compilación y ejecución.
Cuando falla una descarga, se elimina el archivo parcial y el destino anterior permanece intacto.
La terminación forzada del proceso o un fallo de la máquina pueden dejar un archivo
.swift-import-*.part; elimina únicamente el archivo abandonado que pertenezca a tu importación detenida.
No elimines archivos temporales de forma general mientras haya otro importador activo.
Consideraciones para la gestión de errores
- Si falla la autenticación, verifica la URL de Keystone v3, el dominio del usuario, el dominio del proyecto y las credenciales.
- Si el objeto no existe o se deniega el acceso, verifica su nombre exacto y el acceso de lectura de tu proyecto. Iniciar sesión correctamente no implica tener permiso para descargarlo.
- Si ocurre un fallo de E/S local, verifica el directorio padre existente del destino, el acceso de escritura, el espacio en disco y la compatibilidad con movimientos atómicos. Evita eliminar un destino anterior para sortear una descarga fallida.
Consejos para optimizar el rendimiento
Mantén la autenticación y las descargas en el mismo hilo. OpenStack4j almacena la sesión activa en un valor local al hilo; su guía sobre hilos explica cómo crear un cliente a partir de un token en otro hilo. Este comando para un solo objeto usa un hilo y no implementa un pool de trabajadores ni reintentos automáticos.
Para integrarlo en una aplicación, mantén juntas la descarga, la comprobación de bytes y la
publicación. Devuelve la ruta local al código que la consume solo después de que
download se complete correctamente.
