/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE file at the root of the source * tree and available online at * * https://github.com/keeps/roda */ package org.roda.core.model.utils; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Function; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.roda.core.RodaCoreFactory; import org.roda.core.common.iterables.CloseableIterable; import org.roda.core.common.iterables.CloseableIterables; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.exceptions.AuthorizationDeniedException; import org.roda.core.data.exceptions.GenericException; import org.roda.core.data.exceptions.NotFoundException; import org.roda.core.data.exceptions.RODAException; import org.roda.core.data.exceptions.RequestNotValidException; import org.roda.core.data.utils.JsonUtils; import org.roda.core.data.utils.URNUtils; import org.roda.core.data.v2.IsRODAObject; import org.roda.core.data.v2.LiteRODAObject; import org.roda.core.data.v2.common.OptionalWithCause; import org.roda.core.data.v2.formats.Format; import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.DIP; import org.roda.core.data.v2.ip.DIPFile; import org.roda.core.data.v2.ip.File; import org.roda.core.data.v2.ip.Representation; import org.roda.core.data.v2.ip.StoragePath; import org.roda.core.data.v2.ip.TransferredResource; import org.roda.core.data.v2.ip.metadata.DescriptiveMetadata; import org.roda.core.data.v2.ip.metadata.OtherMetadata; import org.roda.core.data.v2.ip.metadata.PreservationMetadata; import org.roda.core.data.v2.ip.metadata.PreservationMetadata.PreservationMetadataType; import org.roda.core.data.v2.jobs.Job; import org.roda.core.data.v2.jobs.Report; import org.roda.core.data.v2.notifications.Notification; import org.roda.core.data.v2.risks.Risk; import org.roda.core.data.v2.risks.RiskIncidence; import org.roda.core.model.LiteRODAObjectFactory; import org.roda.core.storage.Binary; import org.roda.core.storage.DefaultBinary; import org.roda.core.storage.DefaultDirectory; import org.roda.core.storage.DefaultStoragePath; import org.roda.core.storage.Resource; import org.roda.core.storage.StorageService; import org.roda.core.util.IdUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ResourceParseUtils { private static final Logger LOGGER = LoggerFactory.getLogger(ResourceParseUtils.class); private static final String RESOURCE_CANNOT_BE_NULL = "Resource cannot be null"; private ResourceParseUtils() { // do nothing } public static File convertResourceToFile(Resource resource) throws GenericException, NotFoundException, AuthorizationDeniedException, RequestNotValidException { File ret; if (resource == null) { throw new RequestNotValidException(RESOURCE_CANNOT_BE_NULL); } StoragePath resourcePath = resource.getStoragePath(); String id = resourcePath.getName(); String aipId = ModelUtils.extractAipId(resourcePath); String representationId = ModelUtils.extractRepresentationId(resourcePath); List<String> filePath = ModelUtils.extractFilePathFromRepresentationData(resourcePath); if (resource instanceof DefaultBinary) { boolean isDirectory = false; ret = new File(id, aipId, representationId, filePath, isDirectory); } else if (resource instanceof DefaultDirectory) { boolean isDirectory = true; ret = new File(id, aipId, representationId, filePath, isDirectory); } else { throw new GenericException( "Error while trying to convert something that it isn't a Binary into a representation file"); } return ret; } public static DIPFile convertResourceToDIPFile(Resource resource) throws GenericException, NotFoundException, AuthorizationDeniedException, RequestNotValidException { DIPFile ret; if (resource == null) { throw new RequestNotValidException(RESOURCE_CANNOT_BE_NULL); } StoragePath resourcePath = resource.getStoragePath(); String id = resourcePath.getName(); String dipId = ModelUtils.extractDipId(resourcePath); List<String> filePath = ModelUtils.extractFilePathFromDIPData(resourcePath); if (resource instanceof DefaultBinary) { boolean isDirectory = false; ret = new DIPFile(id, dipId, filePath, isDirectory); } else if (resource instanceof DefaultDirectory) { boolean isDirectory = true; ret = new DIPFile(id, dipId, filePath, isDirectory); } else { throw new GenericException( "Error while trying to convert something that is not a Binary or Directory into a DIP file"); } return ret; } private static PreservationMetadata convertResourceToPreservationMetadata(Resource resource) throws RequestNotValidException { if (resource == null) { throw new RequestNotValidException(RESOURCE_CANNOT_BE_NULL); } StoragePath resourcePath = resource.getStoragePath(); String filename = resourcePath.getName(); PreservationMetadata pm = new PreservationMetadata(); String id; String aipId = ModelUtils.extractAipId(resourcePath); String representationId = ModelUtils.extractRepresentationId(resourcePath); List<String> fileDirectoryPath = null; String fileId = null; PreservationMetadataType type; if (filename.startsWith(URNUtils.getPremisPrefix(PreservationMetadataType.AGENT))) { id = filename.substring(0, filename.length() - RodaConstants.PREMIS_SUFFIX.length()); type = PreservationMetadataType.AGENT; aipId = null; representationId = null; } else if (filename.startsWith(URNUtils.getPremisPrefix(PreservationMetadataType.EVENT))) { id = filename.substring(0, filename.length() - RodaConstants.PREMIS_SUFFIX.length()); type = PreservationMetadataType.EVENT; try { String separator = URLEncoder.encode(RodaConstants.URN_SEPARATOR, RodaConstants.DEFAULT_ENCODING); if (StringUtils.countMatches(id, separator) > 0) { fileDirectoryPath = ModelUtils.extractFilePathFromRepresentationPreservationMetadata(resourcePath); fileId = id.substring(id.lastIndexOf(separator) + 1); } } catch (UnsupportedEncodingException e) { LOGGER.error("Error encoding urn separator when converting file event preservation metadata"); } } else if (filename.startsWith(URNUtils.getPremisPrefix(PreservationMetadataType.FILE))) { type = PreservationMetadataType.FILE; fileDirectoryPath = ModelUtils.extractFilePathFromRepresentationPreservationMetadata(resourcePath); fileId = filename.substring(0, filename.length() - RodaConstants.PREMIS_SUFFIX.length()); id = fileId; } else if (filename.startsWith(URNUtils.getPremisPrefix(PreservationMetadataType.OTHER))) { type = PreservationMetadataType.OTHER; fileDirectoryPath = ModelUtils.extractFilePathFromRepresentationPreservationMetadata(resourcePath); fileId = filename.substring(0, filename.length() - RodaConstants.PREMIS_SUFFIX.length()); id = fileId; } else if (filename.startsWith(URNUtils.getPremisPrefix(PreservationMetadataType.REPRESENTATION))) { id = filename.substring(0, filename.length() - RodaConstants.PREMIS_SUFFIX.length()); type = PreservationMetadataType.REPRESENTATION; } else { throw new RequestNotValidException("Unsupported PREMIS extension type in file: " + filename); } pm.setId(id); pm.setAipId(aipId); pm.setRepresentationId(representationId); pm.setFileDirectoryPath(fileDirectoryPath); pm.setFileId(fileId); pm.setType(type); return pm; } private static OtherMetadata convertResourceToOtherMetadata(Resource resource) throws RequestNotValidException { if (resource == null) { throw new RequestNotValidException(RESOURCE_CANNOT_BE_NULL); } StoragePath resourcePath = resource.getStoragePath(); String filename = resourcePath.getName(); String aipId = ModelUtils.extractAipId(resourcePath); String representationId = ModelUtils.extractRepresentationId(resourcePath); String type = representationId != null ? ModelUtils.extractTypeFromRepresentationOtherMetadata(resourcePath) : ModelUtils.extractTypeFromAipOtherMetadata(resourcePath); List<String> fileDirectoryPath = representationId != null ? ModelUtils.extractFilePathFromRepresentationOtherMetadata(resourcePath) : ModelUtils.extractFilePathFromAipOtherMetadata(resourcePath); String fileId = filename.substring(0, filename.lastIndexOf('.')); String suffix = filename.substring(filename.lastIndexOf('.'), filename.length()); OtherMetadata om = new OtherMetadata(); om.setId(IdUtils.getOtherMetadataId(aipId, representationId, fileDirectoryPath, fileId)); om.setAipId(aipId); om.setRepresentationId(representationId); om.setFileDirectoryPath(fileDirectoryPath); om.setFileId(fileId); om.setType(type); om.setFileSuffix(suffix); return om; } public static Representation convertResourceToRepresentation(Resource resource) throws GenericException, NotFoundException, AuthorizationDeniedException, RequestNotValidException { if (resource == null) { throw new RequestNotValidException(RESOURCE_CANNOT_BE_NULL); } StoragePath resourcePath = resource.getStoragePath(); String id = resourcePath.getName(); String aipId = ModelUtils.extractAipId(resourcePath); AIP aip = RodaCoreFactory.getModelService().retrieveAIP(aipId); Optional<Representation> rep = aip.getRepresentations().stream().filter(r -> r.getId().equals(id)).findFirst(); if (rep.isPresent()) { return rep.get(); } else { throw new NotFoundException("Unable to find representation with storage path " + resourcePath); } } public static <T extends Serializable> T convertResourceToObject(Resource resource, Class<T> objectClass) throws GenericException, NotFoundException, AuthorizationDeniedException, RequestNotValidException, IOException { if (resource == null) { throw new RequestNotValidException(RESOURCE_CANNOT_BE_NULL); } Binary binary = (Binary) resource; InputStream inputStream = binary.getContent().createInputStream(); String jsonString = IOUtils.toString(inputStream, RodaConstants.DEFAULT_ENCODING); T ret = JsonUtils.getObjectFromJson(jsonString, objectClass); IOUtils.closeQuietly(inputStream); return ret; } public static <T extends IsRODAObject> OptionalWithCause<T> convertResourceTo(StorageService storage, Resource resource, Class<T> classToReturn) { OptionalWithCause<T> ret; try { if (classToReturn.equals(AIP.class)) { ret = OptionalWithCause.of(classToReturn.cast(getAIPMetadata(storage, resource.getStoragePath()))); } else if (classToReturn.equals(Representation.class)) { ret = OptionalWithCause.of(classToReturn.cast(convertResourceToRepresentation(resource))); } else if (classToReturn.equals(File.class)) { ret = OptionalWithCause.of(classToReturn.cast(convertResourceToFile(resource))); } else if (classToReturn.equals(PreservationMetadata.class)) { ret = OptionalWithCause.of(classToReturn.cast(convertResourceToPreservationMetadata(resource))); } else if (classToReturn.equals(OtherMetadata.class)) { ret = OptionalWithCause.of(classToReturn.cast(convertResourceToOtherMetadata(resource))); } else if (classToReturn.equals(DIP.class)) { ret = OptionalWithCause.of(classToReturn.cast(getDIPMetadata(storage, resource.getStoragePath()))); } else if (classToReturn.equals(DIPFile.class)) { ret = OptionalWithCause.of(classToReturn.cast(convertResourceToDIPFile(resource))); } else { ret = OptionalWithCause.of(convertResourceToObject(resource, classToReturn)); } } catch (RODAException e) { ret = OptionalWithCause.empty(e); } catch (IOException e) { ret = OptionalWithCause.empty(new RequestNotValidException(e)); } return ret; } public static <T extends IsRODAObject> OptionalWithCause<LiteRODAObject> convertResourceToLite(Resource resource, Class<T> classToReturn) { OptionalWithCause<LiteRODAObject> ret; StoragePath storagePath = resource.getStoragePath(); String fileName = resource.getStoragePath().getName(); if (classToReturn.equals(AIP.class) || classToReturn.equals(DIP.class)) { ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, fileName)); } else if (classToReturn.equals(Representation.class)) { String aipId = ModelUtils.extractAipId(storagePath); String repId = ModelUtils.extractRepresentationId(storagePath); ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, aipId, repId)); } else if (classToReturn.equals(File.class)) { List<String> ids = new ArrayList<>(); ids.add(ModelUtils.extractAipId(storagePath)); ids.add(ModelUtils.extractRepresentationId(storagePath)); ids.addAll(ModelUtils.extractFilePathFromRepresentationData(storagePath)); ids.add(fileName); ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ids)); } else if (classToReturn.equals(DIPFile.class)) { List<String> ids = new ArrayList<>(); ids.add(ModelUtils.extractDipId(storagePath)); ids.addAll(ModelUtils.extractFilePathFromDIPData(storagePath)); ids.add(fileName); ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ids)); } else if (classToReturn.equals(Risk.class)) { ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ModelUtils.getRiskId(storagePath))); } else if (classToReturn.equals(RiskIncidence.class)) { ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ModelUtils.getRiskIncidenceId(storagePath))); } else if (classToReturn.equals(Format.class)) { ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ModelUtils.getFormatId(storagePath))); } else if (classToReturn.equals(Notification.class)) { ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ModelUtils.getNotificationId(storagePath))); } else if (classToReturn.equals(Job.class)) { ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ModelUtils.getJobId(storagePath))); } else if (classToReturn.equals(Report.class)) { ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ModelUtils.getJobAndReportIds(storagePath))); } else if (classToReturn.equals(DescriptiveMetadata.class)) { List<String> ids = new ArrayList<>(); ids.add(ModelUtils.extractAipId(storagePath)); String representationId = ModelUtils.extractRepresentationId(storagePath); if (representationId != null) { ids.add(representationId); } ids.add(fileName); ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ids)); } else if (classToReturn.equals(PreservationMetadata.class)) { List<String> ids = new ArrayList<>(); String aipId = ModelUtils.extractAipId(storagePath); if (aipId != null) { ids.add(aipId); } String representationId = ModelUtils.extractRepresentationId(storagePath); if (representationId != null) { ids.add(representationId); } ids.add(fileName.replace(RodaConstants.PREMIS_SUFFIX, "")); ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ids)); } else { ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, fileName)); } return ret; } public static AIP getAIPMetadata(StorageService storage, String aipId) throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException { return getAIPMetadata(storage, aipId, ModelUtils.getAIPStoragePath(aipId)); } public static AIP getAIPMetadata(StorageService storage, StoragePath storagePath) throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException { return getAIPMetadata(storage, storagePath.getName(), storagePath); } public static AIP getAIPMetadata(StorageService storage, String aipId, StoragePath storagePath) throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException { DefaultStoragePath metadataStoragePath = DefaultStoragePath.parse(storagePath, RodaConstants.STORAGE_AIP_METADATA_FILENAME); Binary binary = storage.getBinary(metadataStoragePath); String json; AIP aip; InputStream inputStream = null; try { inputStream = binary.getContent().createInputStream(); json = IOUtils.toString(inputStream, Charset.forName(RodaConstants.DEFAULT_ENCODING)); aip = JsonUtils.getObjectFromJson(json, AIP.class); } catch (IOException | GenericException e) { throw new GenericException("Could not parse AIP metadata of " + aipId + " at " + metadataStoragePath, e); } finally { IOUtils.closeQuietly(inputStream); } // Setting information that does not come in JSON aip.setId(aipId); return aip; } public static DIP getDIPMetadata(StorageService storage, StoragePath storagePath) throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException { return getDIPMetadata(storage, storagePath.getName(), storagePath); } public static DIP getDIPMetadata(StorageService storage, String dipId, StoragePath storagePath) throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException { DefaultStoragePath metadataStoragePath = DefaultStoragePath.parse(storagePath, RodaConstants.STORAGE_DIP_METADATA_FILENAME); Binary binary = storage.getBinary(metadataStoragePath); String json; DIP dip; InputStream inputStream = null; try { inputStream = binary.getContent().createInputStream(); json = IOUtils.toString(inputStream, Charset.forName(RodaConstants.DEFAULT_ENCODING)); dip = JsonUtils.getObjectFromJson(json, DIP.class); } catch (IOException | GenericException e) { throw new GenericException("Could not parse DIP metadata of " + dipId + " at " + metadataStoragePath, e); } finally { IOUtils.closeQuietly(inputStream); } // Setting information that does not come in JSON dip.setId(dipId); return dip; } private static <T extends Serializable> boolean isDirectoryAcceptable(Class<T> classToReturn) { return classToReturn.equals(File.class) || classToReturn.equals(AIP.class) || classToReturn.equals(Representation.class) || classToReturn.equals(TransferredResource.class) || classToReturn.equals(DIPFile.class) || classToReturn.equals(DIP.class); } @FunctionalInterface interface ResourceParser<T extends IsRODAObject, R extends Serializable> { OptionalWithCause<R> parse(StorageService storage, Resource resource, Class<T> classToReturn); default <V extends IsRODAObject> ResourceParser<T, V> andThen( Function<OptionalWithCause<R>, OptionalWithCause<V>> after) { Objects.requireNonNull(after); return (StorageService storage, Resource resource, Class<T> classToReturn) -> after .apply(parse(storage, resource, classToReturn)); } } public static <T extends IsRODAObject> CloseableIterable<OptionalWithCause<T>> convert(final StorageService storage, final CloseableIterable<Resource> iterable, final Class<T> classToReturn) { return convert(storage, iterable, classToReturn, (s, r, c) -> ResourceParseUtils.convertResourceTo(s, r, c)); } public static <T extends IsRODAObject> CloseableIterable<OptionalWithCause<LiteRODAObject>> convertLite( StorageService storage, CloseableIterable<Resource> iterable, Class<T> classToReturn) { return convert(storage, iterable, classToReturn, (s, r, c) -> ResourceParseUtils.convertResourceToLite(r, c)); } private static <T extends IsRODAObject, R extends Serializable> CloseableIterable<OptionalWithCause<R>> convert( final StorageService storage, final CloseableIterable<Resource> iterable, final Class<T> classToReturn, ResourceParser<T, R> parser) { final CloseableIterable<Resource> filtered; if (isDirectoryAcceptable(classToReturn)) { filtered = iterable; } else { filtered = CloseableIterables.filter(iterable, p -> !p.isDirectory()); } CloseableIterable<OptionalWithCause<R>> it; final Iterator<Resource> iterator = filtered.iterator(); it = new CloseableIterable<OptionalWithCause<R>>() { @Override public Iterator<OptionalWithCause<R>> iterator() { return new Iterator<OptionalWithCause<R>>() { @Override public boolean hasNext() { if (iterator == null) { return true; } return iterator.hasNext(); } @Override public OptionalWithCause<R> next() { return parser.parse(storage, iterator.next(), classToReturn); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public void close() throws IOException { filtered.close(); } }; return it; } }