AipBuilderImpl.java
package esa.bscs.pds4.packager.aip;
import static esa.bscs.pds4.reader.model.Dictionary.*;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.stream.Collectors;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import esa.bscs.pds4.packager.exception.ClassificationException;
import esa.bscs.pds4.packager.exception.PackagerException;
import esa.bscs.pds4.packager.exception.PackagerMessage;
import esa.bscs.pds4.packager.groovy.ProductClassifier;
import esa.bscs.pds4.packager.resolver.DeliveryResolver;
import esa.bscs.pds4.packager.resolver.ProductResolver;
import esa.bscs.pds4.packager.resolver.TimeResolver;
import esa.bscs.pds4.reader.model.Pds4Label;
import esa.bscs.pds4.reader.model.extension.Pds4File;
public class AipBuilderImpl implements AipBuilder {
@Autowired(required=true)
private BeanFactory factory;
@Autowired(required=true)
private DeliveryResolver deliveryResolver;
@Autowired(required=true)
private MessageSource messageSource;
@Autowired(required=true)
private TimeResolver timeResolver;
@Autowired(required=true)
private VelocityEngine velocityEngine;
public PackageInfo buildArchiveInformationProduct(List<Pds4Label> pds4Labels) {
if(pds4Labels == null || pds4Labels.isEmpty()) {
throw new IllegalArgumentException();
}
// Get bundle id
final Function<Pds4Label, String> takeBundleId = value -> value.getRootClass().searchValue(PDS.uri(), "logical_identifier", String.class).split(":")[3];
final List<String> bundleIds = pds4Labels.stream().map(takeBundleId).distinct().collect(Collectors.toList());
if(bundleIds.size() != 1) {
final PackagerMessage message = new PackagerMessage("error.packager.multibundle" , messageSource.getMessage("error.packager.multibundle", new String[] {bundleIds.toString()}, Locale.getDefault()));
throw new PackagerException(Arrays.asList(message));
}
final String bundleId = bundleIds.get(0);
// Resolve product paths
final Map<Pds4Label, String> productPaths = resolveProductPaths(bundleId, pds4Labels);
final Map<File, String> packageEntries = resolveEntries(productPaths);
// Generate checksum and manifest info
final ManifestInfo checksumInfo = buildChecksumManifest(packageEntries);
final ManifestInfo transferInfo = buildTransferManifest(productPaths);
// Get base name to fill the manifest filenames
final String basename = deliveryResolver.getBaseName(bundleId);
checksumInfo.setFilename(basename + "-checksum_manifest.tab");
transferInfo.setFilename(basename + "-transfer_manifest.tab");
// Write AIP label file
try (StringWriter writer = new StringWriter()) {
// Fill velocity context
final VelocityContext context = new VelocityContext();
context.put("bundleId", pds4Labels.get(0).getRootClass().searchValue(PDS.uri(), "logical_identifier", String.class).split(":")[3]);
context.put("productId", basename.toLowerCase());
context.put("checksumInfo", checksumInfo);
context.put("transferInfo", transferInfo);
// Merge data with velocity template
velocityEngine.getTemplate("velocity/deliveryLabel.vm").merge(context, writer);
writer.flush();
// Fill package info
final PackageInfo packageInfo = new PackageInfo();
packageInfo.setBasename(basename);
packageInfo.setFilename(basename + ".xml");
packageInfo.setBundleId(bundleId);
packageInfo.setProductId(basename.toLowerCase());
packageInfo.setContent(writer.toString());
packageInfo.setChecksumInfo(checksumInfo);
packageInfo.setTransferInfo(transferInfo);
packageInfo.getPackageEntries().putAll(packageEntries);
// Return
return packageInfo;
} catch(IOException e) {
throw new UncheckedIOException(e);
}
}
@SuppressWarnings("java:S1166")
private Map<Pds4Label, String> resolveProductPaths(String bundleId, List<Pds4Label> pds4Labels) {
final Map<Pds4Label, String> result = new LinkedHashMap<>();
final List<String> classificationErrors = new ArrayList<>();
// Get product resolver and classifier
final ProductResolver resolver = factory.getBean(ProductResolver.class, pds4Labels);
final ProductClassifier classifier = factory.getBean(ProductClassifier.class, bundleId, resolver);
// Resolve path for each product
for(Pds4Label label : pds4Labels) {
try {
result.put(label, classifier.resolvePath(label));
} catch(Exception e) {
final String name = label.getLabelLocation().getFile().contains("/") ? label.getLabelLocation().getFile().substring(label.getLabelLocation().getFile().lastIndexOf('/') + 1) : label.getLabelLocation().getFile();
classificationErrors.add(messageSource.getMessage("error.packager.classification", new String[] {name, e.getMessage()}, Locale.getDefault()));
}
}
// Check if there has been errors while classifying
if(!classificationErrors.isEmpty()) {
throw new ClassificationException(classificationErrors);
}
// Return
return result;
}
private Map<File, String> resolveEntries(Map<Pds4Label, String> productPaths) {
try {
final Map<File, String> result = new LinkedHashMap<>();
for(Map.Entry<Pds4Label, String> entry : productPaths.entrySet()) {
final Pds4Label label = entry.getKey();
final String path = entry.getValue();
final File labelFile = new File(label.getLabelLocation().toURI());
// Add label file
result.put(labelFile, path + "/" + labelFile.getName());
// Add pointed files
for(Pds4File pds4File : label.getRootClass().searchEntries(Pds4File.class)) {
final File pointedFile = new File(pds4File.resolveLocation().toURI());
result.put(pointedFile, path + "/" + pointedFile.getName());
}
}
// Return entry map
return result;
} catch(URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
private ManifestInfo buildChecksumManifest(Map<File, String> packageEntries) {
try (StringWriter writer = new StringWriter()) {
// Initialize MD5 checksum generator
final DigestUtils utils = new DigestUtils(DigestUtils.getMd5Digest());
// Generate checksum manifest content
for(Map.Entry<File, String> entry : packageEntries.entrySet()) {
writer.write(utils.digestAsHex(entry.getKey()));
writer.write("\t");
writer.write(entry.getValue());
writer.write("\r\n");
writer.flush();
}
// Fill manifest info
final Instant now = timeResolver.currentInstant();
final ManifestInfo manifestInfo = new ManifestInfo();
manifestInfo.setChecksum(utils.digestAsHex(writer.toString()));
manifestInfo.setContent(writer.getBuffer().toString());
manifestInfo.setCreationTime(DateTimeFormatter.ISO_INSTANT.format(now));
manifestInfo.setRecords(packageEntries.size());
// Return result
return manifestInfo;
} catch(IOException e) {
throw new UncheckedIOException(e);
}
}
private ManifestInfo buildTransferManifest(Map<Pds4Label, String> productPaths) {
final ToIntFunction<Pds4Label> logicalIdentifierLength = label -> label.getRootClass().searchEntry(PDS.uri(), "Identification_Area").searchValue(PDS.uri(), "logical_identifier", String.class).length();
final ToIntFunction<Map.Entry<Pds4Label, String>> fullPathLength = entry -> entry.getValue().length() + 1 + filenameFromUrl(entry.getKey().getLabelLocation()).length();
final int lidMaxLength = productPaths.keySet().stream().mapToInt(logicalIdentifierLength).max().getAsInt();
final int pathMaxLength = productPaths.entrySet().stream().mapToInt(fullPathLength).max().getAsInt();
try (StringWriter writer = new StringWriter()) {
// Generate transfer manifest content
for(Map.Entry<Pds4Label, String> entry : productPaths.entrySet()) {
final Pds4Label label = entry.getKey();
final File labelFile = new File(label.getLabelLocation().toURI());
final String logicalId = StringUtils.rightPad(label.getRootClass().searchEntry(PDS.uri(), "Identification_Area").searchValue(PDS.uri(), "logical_identifier", String.class), lidMaxLength);
final String path = StringUtils.rightPad(entry.getValue() + "/" + labelFile.getName(), pathMaxLength);
writer.write(logicalId);
writer.write("\t");
writer.write(path);
writer.write("\r\n");
writer.flush();
}
// Fill manifest info
final Instant now = timeResolver.currentInstant();
final DigestUtils utils = new DigestUtils(DigestUtils.getMd5Digest());
final ManifestInfo manifestInfo = new ManifestInfo();
manifestInfo.setChecksum(utils.digestAsHex(writer.toString()));
manifestInfo.setContent(writer.getBuffer().toString());
manifestInfo.setCreationTime(DateTimeFormatter.ISO_INSTANT.format(now));
manifestInfo.setRecords(productPaths.size());
// Return
return manifestInfo;
} catch(IOException e) {
throw new UncheckedIOException(e);
} catch(URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
private String filenameFromUrl(URL url) {
try {
return new File(url.toURI()).getName();
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
}