Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@

## [Unreleased]

### Added ✔️
- The `TemporalFileManager` has been created to manage temporary files per request.

* **OSdmsService:** Added `getTemporalFiles` method has been added to `OSdmsService`.


### Fixed 🐛
- Closed `S3ObjectInputStream` instances properly to avoid `CLOSE_WAIT` socket issues and potential memory/resource leaks during file download operations.

Expand Down
5 changes: 5 additions & 0 deletions ontimize-jee-sdms-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
<artifactId>ontimize-jee-common</artifactId>
</dependency>

<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>

<!-- REFLECTIONS -->
<dependency>
<groupId>org.reflections</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public interface IOSdmsAction {
*/
EntityResult download( OSdmsRestDataDto data );

EntityResult getTemporalFiles( OSdmsRestDataDto data );

// ------------------------------------------------------------------------------------------------------------------ \\
// -------| DMS - UPLOAD |------------------------------------------------------------------------------------------- \\
// ------------------------------------------------------------------------------------------------------------------ \\
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.ontimize.jee.sdms.common.file;

import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;

import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Component
@RequestScope
public class DefaultTemporalFileManager implements TemporalFileManager{

@Value( "${ontimize.sdms.file.temporal.directory}" )
private String temporalDirectory;

private final List<File> files = new ArrayList<>();

@Override
public File create( final String name, final InputStream inputStream ) throws IOException {
final File directory = new File( this.temporalDirectory );
final File file;
if( directory.exists() && directory.isDirectory() ) file = File.createTempFile( name, ".tmp", directory );
else file = File.createTempFile( name, ".tmp" );
try( FileOutputStream fos = new FileOutputStream( file)) {
inputStream.transferTo(fos);
}
this.files.add( file );
return file;
}

@Override
public File create( final InputStream inputStream ) throws IOException {
return this.create( UUID.randomUUID().toString(), inputStream );
}

@Override
public void delete( final File file ) {
this.files.stream().filter( target -> target.getAbsolutePath().equals( file.getAbsolutePath() ) )
.findFirst()
.ifPresent( target -> {
this.files.remove( target );
if( target.exists() ) target.delete();
});
}

@PreDestroy
@Override
public void cleanUp() {
this.files.forEach( file -> { if( file.exists() ) file.delete(); });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.ontimize.jee.sdms.common.file;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

public interface TemporalFileManager {

File create( InputStream inputStream ) throws IOException;
File create( String name, InputStream inputStream ) throws IOException;
void delete( File file );
void cleanUp();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.util.List;
import java.util.Objects;
import java.util.Set;
Expand Down Expand Up @@ -36,6 +33,7 @@ public class OSdmsZipCompressor implements IOSdmsZipCompressor {
@Override
public <T extends IOSdmsZippeable> OSdmsZipDto compress( final String zipName, final List<T> dataToZip ) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();

try (ZipOutputStream zos = new ZipOutputStream(baos)) {
final Set<OSdmsZipData> data = dataToZip.stream()
.map(IOSdmsZippeable::getDataToZip)
Expand All @@ -44,22 +42,30 @@ public <T extends IOSdmsZippeable> OSdmsZipDto compress( final String zipName, f

for (final OSdmsZipData zipData : data) {
final ZipEntry entry = new ZipEntry(zipData.getFileName());
try ( InputStream inputStream = new ByteArrayInputStream( zipData.getFileContent() )) {
final File file = zipData.getFile();

if (file == null || !file.exists()) {
LOGGER.warn("File {} does not exist or is null, skipping", zipData.getFileName());
continue;
}

try (InputStream inputStream = new FileInputStream(file)) {
zos.putNextEntry(entry);
byte[] buffer = new byte[1024];
byte[] buffer = new byte[4096];
int length;
while ((length = inputStream.read(buffer)) >= 0) {
zos.write(buffer, 0, length);
}
zos.closeEntry();
} catch (IOException e) {
LOGGER.error("Error compressing entry {}: {}", zipData.getFileName(), e.getMessage());
LOGGER.error("Error compressing file {}: {}", zipData.getFileName(), e.getMessage());
}
}
} catch (IOException e) {
LOGGER.error("Error creating ZIP output stream: {}", e.getMessage());
}

// Construir el DTO con el contenido del zip en memoria
final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
final OSdmsZipDto zipDto = new OSdmsZipDto();
zipDto.setFile(bais);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ontimize.jee.sdms.common.zip;

import java.io.File;
import java.util.Objects;


Expand All @@ -14,18 +15,18 @@ public class OSdmsZipData {
private String fileName;

/**
* The inputStream field represents the input stream to be zipped.
* The File field represents the file to be zipped.
*/
private byte[] fileContent;
private File file;

// ------------------------------------------------------------------------------------------------------------------ \\

public OSdmsZipData() {
}

public OSdmsZipData( final String fileName, final byte[] fileContent ) {
public OSdmsZipData( final String fileName, final File file ) {
this.setFileName( fileName );
this.setFileContent( fileContent );
this.setFile( file );
}

// ------------------------------------------------------------------------------------------------------------------ \\
Expand All @@ -40,12 +41,12 @@ public void setFileName( final String fileName ) {
this.fileName = fileName;
}

public byte[] getFileContent() {
return this.fileContent;
public File getFile() {
return this.file;
}

public void setFileContent( final byte[] content ) {
this.fileContent = content;
public void setFile( final File file ) {
this.file = file;
}

// ------------------------------------------------------------------------------------------------------------------ \\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ public EntityResult download( final OSdmsRestDataDto data ) {
return this.oSdmsCommandHandler.run( new OSdmsS3DownloadCommand( requestFilter ) );
}

@Override
public EntityResult getTemporalFiles( final OSdmsRestDataDto data ) {
final OSdmsS3InputFilter requestFilter = this.oSdmsS3InputFilterMapper.map( data );
return this.oSdmsCommandHandler.run( new OSdmsS3GetTemporalFilesCommand( requestFilter ) );
}

// ------------------------------------------------------------------------------------------------------------------ \\
// -------| DMS - UPLOAD |------------------------------------------------------------------------------------------- \\
// ------------------------------------------------------------------------------------------------------------------ \\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
/**
* Command to download files from S3
*/
public class OSdmsS3DownloadCommand implements IOSdmsCommand {
public class OSdmsS3DownloadCommand implements IOSdmsCommand {

//Constants
private static final String ZIP_NAME = "data.zip";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package com.ontimize.jee.sdms.engine.s3.command;

import com.amazonaws.services.s3.model.ListObjectsRequest;
import com.ontimize.jee.common.dto.EntityResult;
import com.ontimize.jee.sdms.common.command.IOSdmsCommand;
import com.ontimize.jee.sdms.common.inyector.IOSdmsInyector;
import com.ontimize.jee.sdms.common.response.builder.IOSdmsResponseBuilder;
import com.ontimize.jee.sdms.common.workspace.manager.IOSdmsWorkspaceManager;
import com.ontimize.jee.sdms.common.zip.IOSdmsZipCompressor;
import com.ontimize.jee.sdms.common.zip.OSdmsZipDto;
import com.ontimize.jee.sdms.engine.s3.repository.IOSdmsS3Repository;
import com.ontimize.jee.sdms.engine.s3.repository.OSdmsS3RepositoryProxy;
import com.ontimize.jee.sdms.engine.s3.repository.dto.OSdmsS3RepositoryDto;
import com.ontimize.jee.sdms.engine.s3.repository.response.OSdmsS3RepositoryResponse;
import com.ontimize.jee.sdms.engine.s3.repository.response.codes.OSdmsS3RepositoryResponseCodes;
import com.ontimize.jee.sdms.engine.s3.util.config.IOSdmsS3EngineConfig;
import com.ontimize.jee.sdms.engine.s3.util.input.filter.OSdmsS3InputFilter;
import com.ontimize.jee.sdms.engine.s3.util.input.filter.reader.IOSdmsS3FilterReader;
import com.ontimize.jee.sdms.engine.s3.util.normalize.IOSdmsS3KeyNormalize;
import com.ontimize.jee.sdms.engine.s3.util.response.mapper.IOSdmsS3ResponseMapper;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;


/**
* Command to download files from S3
*/
public class OSdmsS3GetTemporalFilesCommand implements IOSdmsCommand {


//Messages
private static final String MESSAGE_ERROR_NO_ACTIVE_WORKSPACE = "No active workspace found";
private static final String MESSAGE_ERROR_NO_BUCKET = "No S3 bucket has been configured";
private static final String MESSAGE_NO_CONTENT = "No file has been obtained from the query";


// Dependencies
private IOSdmsS3Repository repository;
private IOSdmsResponseBuilder responseBuilder;
private IOSdmsS3ResponseMapper responseMapper;
private IOSdmsWorkspaceManager workspaceManager;


//Data
private String bucket;
private OSdmsS3InputFilter filter;
private List<String> queries = new ArrayList<>();


//Respone
private OSdmsS3RepositoryResponse<OSdmsS3RepositoryDto> response;

// ------------------------------------------------------------------------------------------------------------------ \\
// ------| ENTRYPOINT |---------------------------------------------------------------------------------------------- \\
// ------------------------------------------------------------------------------------------------------------------ \\

public OSdmsS3GetTemporalFilesCommand( final OSdmsS3InputFilter filter ) {
this.filter = filter;
}

// ------------------------------------------------------------------------------------------------------------------ \\
// ------| INIT |--------------------------------------------------------------------------------------------------- \\
// ------------------------------------------------------------------------------------------------------------------ \\

@Override
public void init( final IOSdmsInyector inyector ) {
//Inyect dependencies
this.repository = inyector.get( OSdmsS3RepositoryProxy.class );
this.responseBuilder = inyector.get( IOSdmsResponseBuilder.class );
this.responseMapper = inyector.get( IOSdmsS3ResponseMapper.class );
this.workspaceManager = inyector.get( IOSdmsWorkspaceManager.class );
final IOSdmsS3FilterReader filterParamReader = inyector.get( IOSdmsS3FilterReader.class );
final IOSdmsS3EngineConfig s3EngineConfig = inyector.get( IOSdmsS3EngineConfig.class );
final IOSdmsS3KeyNormalize keyNormalize = inyector.get( IOSdmsS3KeyNormalize.class );

//Get Data
this.workspaceManager.active( this.filter.getWorkspace(), this.filter.getData() );
this.bucket = s3EngineConfig.getBucket();
this.queries = filterParamReader.readAllKeys( this.filter );
this.queries = this.queries.stream().map( keyNormalize::normalize ).collect( Collectors.toList() );
}

// ------------------------------------------------------------------------------------------------------------------ \\
// ------| VALIDATE |------------------------------------------------------------------------------------------------ \\
// ------------------------------------------------------------------------------------------------------------------ \\

@Override
public EntityResult validate() {
if( this.workspaceManager.getActive() == null ) {
return this.responseBuilder
.code( EntityResult.OPERATION_WRONG )
.message( MESSAGE_ERROR_NO_ACTIVE_WORKSPACE )
.build();
}

if( this.bucket == null ) {
return this.responseBuilder
.code( EntityResult.OPERATION_WRONG )
.message( MESSAGE_ERROR_NO_BUCKET )
.build();
}

return null;
}

// ------------------------------------------------------------------------------------------------------------------ \\
// ------| RUN |----------------------------------------------------------------------------------------------------- \\
// ------------------------------------------------------------------------------------------------------------------ \\

@Override
public void run() {
final List<ListObjectsRequest> requests = new ArrayList<>();

this.queries.forEach( prefix -> {
final ListObjectsRequest request = new ListObjectsRequest()
.withBucketName( this.bucket )
.withPrefix( prefix );

if( this.filter.hasMaxKeys() ) request.withMaxKeys( this.filter.getMaxKeys() );
if( this.filter.hasDelimiter() ) request.withDelimiter( this.filter.getDelimiter() );
if( this.filter.hasMarker() ) request.withMarker( this.filter.getMarker() );

requests.add( request );
} );

this.response = this.repository.download( requests );
}

// ------------------------------------------------------------------------------------------------------------------ \\
// ------| RESPONSE |------------------------------------------------------------------------------------------------ \\
// ------------------------------------------------------------------------------------------------------------------ \\

@Override
public EntityResult response() {
EntityResult result = null;

if( this.response != null ) {
final OSdmsS3RepositoryResponseCodes code = this.response.getCode();
if( code == OSdmsS3RepositoryResponseCodes.OK ) {
result = this.responseBuilder
.code( EntityResult.OPERATION_SUCCESSFUL )
.message( MESSAGE_NO_CONTENT )
.build();

List<OSdmsS3RepositoryDto> data = this.response.getData().stream()
.filter( target -> ! target.getName().equals( OSdmsS3RepositoryDto.FILE_NAME_MARK_FOLDER ) )
.collect( Collectors.toList() );
this.response.setData( data );

if( data != null && !data.isEmpty() ) result = this.responseMapper.map( this.response );
}
}

if( result == null ) result = this.responseMapper.map( this.response );

return result;
}

// ------------------------------------------------------------------------------------------------------------------ \\

}
Loading