ExportBuilder.java

package de.dlr.shepard.neo4Core.export;

import com.fasterxml.jackson.databind.ObjectMapper;
import de.dlr.shepard.neo4Core.entities.Collection;
import de.dlr.shepard.neo4Core.entities.DataObject;
import de.dlr.shepard.neo4Core.entities.User;
import de.dlr.shepard.neo4Core.io.AbstractDataObjectIO;
import de.dlr.shepard.neo4Core.io.BasicEntityIO;
import de.dlr.shepard.neo4Core.io.BasicReferenceIO;
import de.dlr.shepard.neo4Core.io.CollectionIO;
import de.dlr.shepard.neo4Core.io.DataObjectIO;
import edu.kit.datamanager.ro_crate.RoCrate.RoCrateBuilder;
import edu.kit.datamanager.ro_crate.entities.contextual.PersonEntity;
import edu.kit.datamanager.ro_crate.entities.data.FileEntity;
import edu.kit.datamanager.ro_crate.entities.data.FileEntity.FileEntityBuilder;
import io.quarkus.logging.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import lombok.AccessLevel;
import lombok.Getter;

public class ExportBuilder {

  private ObjectMapper objectMapper = new ObjectMapper();

  @Getter(value = AccessLevel.PROTECTED)
  private RoCrateBuilder builder;

  private ByteArrayOutputStream baos;
  private ZipOutputStream zos;
  private List<String> entries;

  public ExportBuilder(Collection collection) throws IOException {
    baos = new ByteArrayOutputStream();
    zos = new ZipOutputStream(baos);
    entries = new ArrayList<>();

    var roCrateName = collection.getName() + " Research Object Crate";
    var roCrateDescription = "Research Object Crate representing the shepard Collection " + collection.getName();

    builder = new RoCrateBuilder().addName(roCrateName).addDescription(roCrateDescription);

    var collectionEntity = createFileEntity(new CollectionIO(collection));
    builder.addDataEntity(collectionEntity);
    addPersonEntity(collection.getCreatedBy());
  }

  public ExportBuilder addDataObject(DataObject dataObject) throws IOException {
    var dataObjectEntity = createFileEntity(new DataObjectIO(dataObject));
    builder.addDataEntity(dataObjectEntity);
    addPersonEntity(dataObject.getCreatedBy());
    return this;
  }

  public ExportBuilder addReference(BasicReferenceIO reference, User author) throws IOException {
    var referenceEntity = createFileEntity(reference);
    builder.addDataEntity(referenceEntity);
    addPersonEntity(author);
    return this;
  }

  public ExportBuilder addPayload(byte[] payload, String filename, String name) throws IOException {
    addToZip(filename, payload);
    builder.addDataEntity(createFileEntityBuilder(filename, name).build());
    return this;
  }

  public ExportBuilder addPayload(byte[] payload, String filename, String name, String encodingFormat)
    throws IOException {
    addToZip(filename, payload);
    builder.addDataEntity(createFileEntityBuilder(filename, name, encodingFormat).build());
    return this;
  }

  private void addPersonEntity(User user) {
    if (user == null) return;

    var entity = new PersonEntity.PersonEntityBuilder()
      .setId(user.getUsername())
      .setEmail(user.getEmail())
      .setGivenName(user.getFirstName())
      .setFamilyName(user.getLastName())
      .build();

    builder.addContextualEntity(entity);
  }

  public InputStream build() throws IOException {
    var roCrate = builder.build();
    Object jsonObject = objectMapper.readValue(roCrate.getJsonMetadata(), Object.class);
    byte[] bytes = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(jsonObject);
    addToZip(ExportConstants.ROCRATE_METADATA, bytes);
    zos.close();

    return new ByteArrayInputStream(baos.toByteArray());
  }

  private FileEntity createFileEntity(AbstractDataObjectIO entity) throws IOException {
    var filename = entity.getId() + ExportConstants.JSON_FILE_EXTENSION;
    writeEntityToZip(filename, entity);

    var fileEntityBuilder = createFileEntityBuilder(entity, filename);
    fileEntityBuilder.addProperty(ExportConstants.DESCRIPTION_PROP, entity.getDescription());
    return fileEntityBuilder.build();
  }

  private FileEntity createFileEntity(BasicReferenceIO entity) throws IOException {
    var filename = entity.getId() + ExportConstants.JSON_FILE_EXTENSION;
    writeEntityToZip(filename, entity);

    var fileEntityBuilder = createFileEntityBuilder(entity, filename);
    fileEntityBuilder.addProperty(ExportConstants.TYPE_PROP, entity.getType());
    return fileEntityBuilder.build();
  }

  private void writeEntityToZip(String filename, Object entity) throws IOException {
    var bytes = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(entity);
    addToZip(filename, bytes);
  }

  private static FileEntityBuilder createFileEntityBuilder(String filename, String name) {
    return new FileEntity.FileEntityBuilder().setId(filename).addProperty(ExportConstants.NAME_PROP, name);
  }

  private static FileEntityBuilder createFileEntityBuilder(String filename, String name, String encodingFormat) {
    var fileEntityBuilder = createFileEntityBuilder(filename, name);
    fileEntityBuilder.addProperty("encodingFormat", encodingFormat);
    return fileEntityBuilder;
  }

  private static FileEntityBuilder createFileEntityBuilder(BasicEntityIO entity, String filename) {
    var createdAt = convertToIsoDate(entity.getCreatedAt());
    var type = entity.getClass().getSimpleName().replace("IO", "");

    var fileEntityBuilder = createFileEntityBuilder(filename, entity.getName(), "application/json");
    fileEntityBuilder
      .addAuthor(entity.getCreatedBy())
      .addProperty(ExportConstants.CREATED_PROP, createdAt)
      .addProperty(ExportConstants.TYPE_PROP, type);

    if (entity.getUpdatedAt() != null) fileEntityBuilder.addProperty(
      ExportConstants.UPDATED_PROP,
      convertToIsoDate(entity.getUpdatedAt())
    );

    return fileEntityBuilder;
  }

  private static String convertToIsoDate(Date dateToConvert) {
    var localDateTime = LocalDateTime.ofInstant(dateToConvert.toInstant(), ZoneId.systemDefault());
    return DateTimeFormatter.ISO_DATE_TIME.format(localDateTime);
  }

  private void addToZip(String filename, byte[] bytes) throws IOException {
    if (entries.contains(filename)) {
      Log.warnf("%s already in zip file, skipping", filename);
      return;
    }

    zos.putNextEntry(new ZipEntry(filename));
    zos.write(bytes);
    zos.closeEntry();
    entries.add(filename);
  }
}