Importar arquivos do OpenStack Swift em Java
Baixe um objeto do OpenStack Swift para um arquivo local usando o OpenStack4j. O programa Java completo abaixo aceita objetos vazios, verifica a resposta HTTP e a contagem de bytes baixados e mantém o destino inalterado até que o download seja concluído com sucesso.
Configurar seu ambiente Java
Este exemplo usa um shell POSIX no Linux, uma configuração de localidade UTF-8 e um sistema de arquivos local com suporte à movimentação atômica de arquivos. A localidade determina como a JVM decodifica os nomes de arquivos da linha de comando. Instale separadamente um JDK 21 com manutenção ativa e o Apache Maven 3.10. O procedimento abaixo foi testado com o OpenJDK 21.0.12.1 e o Maven 3.10.0; o Maven 3.9.11 também passou nos testes.
Você também precisa de uma conta Keystone v3 com acesso no escopo do projeto a um contêiner e a um objeto existentes no Swift. Disponibilize estas variáveis de ambiente por meio da configuração de credenciais do seu provedor ou do gerenciador de segredos:
| Variável | Valor |
|---|---|
OS_AUTH_URL | URL do Keystone terminada em /v3 |
OS_USERNAME, OS_PASSWORD | Seu nome de usuário e sua senha |
OS_USER_DOMAIN_NAME | Domínio que contém o usuário |
OS_PROJECT_NAME, OS_PROJECT_DOMAIN_NAME | Projeto e seu domínio |
OS_REGION_NAME | Região que contém o endpoint público de armazenamento de objetos |
O usuário e o projeto podem pertencer a domínios diferentes. Use HTTPS tanto para o Keystone quanto para o endpoint do Swift no catálogo de serviços do Keystone, com certificados considerados confiáveis pela JVM. Não inclua senhas em commits nem as coloque em comandos de shell copiados. Este guia lê um objeto existente; não cria contêineres nem altera políticas de acesso.
Verifique cada executável antes de criar o projeto. Cole este bloco no shell que você já está usando;
o subshell mantém inalterados o diretório de trabalho e as opções desse shell. Se o diretório
swift-import já existir, a configuração falhará sem alterar seu conteúdo.
(
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
)
Salve o arquivo completo a seguir como swift-import/pom.xml. O OpenStack4j 3.12 é a versão
publicada no Maven Central.
Este artefato inclui o 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>
A verificação inicial rejeita configurações .mvn em diretórios ancestrais
antes de criar arquivos. O Maven pode herdar opções de inicialização de um projeto em diretórios
superiores; inspecione essas opções antes de incorporar este exemplo a uma aplicação existente.
Se a resolução de dependências falhar, corrija o acesso à rede ou ao repositório e repita o comando
de compilação abaixo. Mantenha o código-fonte salvo e pom.xml.
Conectar ao OpenStack Swift
Salve este programa como swift-import/src/main/java/SwiftImport.java. Ele se autentica no Keystone,
restringe o token ao projeto e ao domínio do projeto informados e seleciona a região solicitada.
Essas são escolhas separadas no modelo de identidade do 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);
}
}
}
Importar arquivos do Swift
No diretório que contém swift-import, cole o bloco a seguir. Substitua
exports e reports/report.bin pelo contêiner existente e pelo nome
exato do objeto. O destino local é relativo a swift-import, e seu diretório pai
já deve existir.
Passe os nomes de contêiner e objeto em UTF-8 na forma original, incluindo espaços ou sinais de
porcentagem literais. O programa codifica cada segmento do caminho da URL antes de chamar o
OpenStack4j. Nomes de contêiner não podem conter /;
os caminhos de objetos neste exemplo não podem ter segmentos vazios, .
ou ... Os caminhos de destino local também devem 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'
)
Em caso de sucesso, o programa imprime a contagem de bytes importados e o caminho absoluto do destino.
Um objeto vazio produz um arquivo de zero bytes e informa zero bytes importados. Uma falha na
compilação impede a execução do comando Java, inclusive quando uma classe compilada mais antiga
permanece em target/.
O programa obtém o tamanho esperado da resposta HEAD do Swift, exige um GET completo bem-sucedido e compara a contagem de bytes copiados mesmo quando o tamanho esperado é zero. O Swift documenta essas operações na referência da API de objetos. Mantenha o objeto remoto inalterado durante a importação. Uma comparação de tamanho não detecta alterações de conteúdo que mantenham o mesmo tamanho; compare separadamente um checksum confiável fornecido pelo publicador quando precisar dessa verificação.
Um destino existente só é substituído por um download concluído e com o tamanho verificado. O arquivo temporário fica no mesmo diretório do destino, permitindo que a publicação use uma movimentação atômica. Se o sistema de arquivos não conseguir realizar essa movimentação, a importação falhará sem recorrer a uma cópia não atômica. No Java, o comportamento da substituição atômica depende do sistema de arquivos; o fluxo de substituição usado aqui foi testado no Linux. Use um importador por destino e mantenha o diretório pai desse destino sob seu controle.
Lidar com arquivos grandes de forma eficiente
O conector RESTEasy padrão do OpenStack4j 3.12 armazena a resposta completa em um buffer antes de disponibilizar um fluxo de entrada. Escolha objetos que caibam com folga na memória disponível da JVM. Aumentar o buffer de cópia ou imprimir o progresso no laço de cópia não elimina essa alocação.
Implementar tratamento de erros e novas tentativas
O comando retorna um código de saída diferente de zero para objetos ausentes, leituras rejeitadas,
transferências incompletas e falhas de gravação local. Ele faz uma tentativa no nível da aplicação
e solicita um tempo limite de conexão de 10 segundos. Não há um prazo máximo para a transferência
completa. Nesta versão do RESTEasy,
withReadTimeout configura a obtenção de conexões do pool
em vez de um tempo limite de leitura do socket, por isso fica de fora deste exemplo. Se uma resposta
parar de avançar, a espera pode durar até o fechamento da conexão. Use um processo supervisionado
se sua aplicação precisar de um limite de tempo decorrido.
Após uma falha tratada, corrija sua causa e repita o mesmo bloco de compilação e execução. O arquivo
parcial é removido e um destino anterior permanece intacto quando um download falha. Um encerramento
forçado do processo ou uma falha da máquina pode deixar um arquivo .swift-import-*.part;
remova apenas o arquivo abandonado que pertence à sua importação interrompida. Não faça uma limpeza
de arquivos temporários enquanto outro importador estiver ativo.
Considerações sobre o tratamento de erros
- Em caso de falha de autenticação, verifique a URL do Keystone v3, o domínio do usuário, o domínio do projeto e as credenciais.
- Se o objeto não existir ou o acesso a ele for negado, verifique seu nome exato e o acesso de leitura do seu projeto. Um login bem-sucedido não significa que há permissão para baixá-lo.
- Em caso de falha de E/S local, verifique o diretório pai existente do destino, o acesso de gravação, o espaço em disco e o suporte à movimentação atômica de arquivos. Evite excluir um destino anterior para contornar uma falha de download.
Dicas de otimização de desempenho
Mantenha a autenticação e os downloads na mesma thread. O OpenStack4j armazena a sessão ativa em um valor local à thread; seu guia de uso de threads explica como obter, em outra thread, um cliente a partir de um token. Este comando para um único objeto usa uma thread e não implementa um pool de executores nem novas tentativas automáticas.
Para integrar à aplicação, mantenha o download, a verificação de bytes e a publicação juntos.
Retorne o caminho local ao código que consome o arquivo somente depois que
download tiver êxito.
