/** * 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.common.monitor; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.roda.core.RodaCoreFactory; import org.roda.core.common.iterables.CloseableIterable; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.exceptions.AlreadyExistsException; import org.roda.core.data.exceptions.GenericException; import org.roda.core.data.exceptions.IsStillUpdatingException; import org.roda.core.data.exceptions.NotFoundException; import org.roda.core.data.exceptions.RequestNotValidException; import org.roda.core.data.v2.LiteRODAObject; import org.roda.core.data.v2.common.OptionalWithCause; import org.roda.core.data.v2.index.filter.Filter; import org.roda.core.data.v2.index.filter.SimpleFilterParameter; import org.roda.core.data.v2.ip.TransferredResource; import org.roda.core.index.IndexService; import org.roda.core.model.LiteRODAObjectFactory; import org.roda.core.storage.ContentPayload; import org.roda.core.storage.fs.FSUtils; import org.roda.core.util.IdUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TransferredResourcesScanner { private static final Logger LOGGER = LoggerFactory.getLogger(TransferredResourcesScanner.class); private static final List<String> fieldsToReturn = Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.TRANSFERRED_RESOURCE_RELATIVEPATH); private final Path basePath; private IndexService index; public TransferredResourcesScanner(Path basePath, IndexService index) { this.basePath = basePath; this.index = index; } public void commit() throws GenericException { index.commit(TransferredResource.class); } public Path getBasePath() { return basePath; } public TransferredResource createFolder(String parentUUID, String folderName) throws GenericException, RequestNotValidException, NotFoundException { Path parentPath; if (parentUUID != null) { TransferredResource parent = index.retrieve(TransferredResource.class, parentUUID, fieldsToReturn); parentPath = basePath.resolve(parent.getRelativePath()); } else { parentPath = basePath; } try { Path createdPath = Files.createDirectories(parentPath.resolve(folderName)); BasicFileAttributes attrs = Files.readAttributes(createdPath, BasicFileAttributes.class); TransferredResource resource = createTransferredResource(createdPath, attrs, 0L, basePath, new Date()); index.create(TransferredResource.class, resource); return resource; } catch (IOException e) { LOGGER.error("Cannot create folder", e); throw new GenericException("Cannot create folder", e); } } public TransferredResource createFile(String parentUUID, String fileName, InputStream inputStream) throws GenericException, RequestNotValidException, NotFoundException, AlreadyExistsException { Path parentPath; if (StringUtils.isNotBlank(parentUUID)) { TransferredResource parent = index.retrieve(TransferredResource.class, parentUUID, fieldsToReturn); parentPath = basePath.resolve(parent.getRelativePath()); } else { parentPath = basePath; } Path file = parentPath.resolve(fileName); try { try { Files.createDirectories(parentPath); } catch (FileAlreadyExistsException e) { // do nothing and carry on } Files.copy(inputStream, file); BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class); TransferredResource resource = createTransferredResource(file, attrs, attrs.size(), basePath, new Date()); index.create(TransferredResource.class, resource); return resource; } catch (FileAlreadyExistsException e) { LOGGER.error("Cannot create file", e); throw new AlreadyExistsException(file.toString()); } catch (IOException e) { LOGGER.error("Cannot create file", e); throw new GenericException("Cannot create file", e); } } public InputStream retrieveFile(String path) throws NotFoundException, RequestNotValidException, GenericException { InputStream ret; Path p = basePath.resolve(path); if (!FSUtils.exists(p)) { throw new NotFoundException("File not found: " + path); } else if (!FSUtils.isFile(p)) { throw new RequestNotValidException("Requested file is not a regular file: " + path); } else { try { ret = Files.newInputStream(p); } catch (IOException e) { throw new GenericException("Could not create input stream: " + e.getMessage()); } } return ret; } protected static TransferredResource createTransferredResource(Path resourcePath, BasicFileAttributes attr, long size, Path basePath, Date lastScanDate) { Date d = new Date(attr.creationTime().toMillis()); TransferredResource tr = instantiateTransferredResource(resourcePath, basePath); tr.setSize(size); tr.setCreationDate(d); tr.setLastScanDate(lastScanDate); return tr; } public static TransferredResource instantiateTransferredResource(Path resourcePath, Path basePath) { Path relativeToBase = basePath.relativize(resourcePath); TransferredResource tr = new TransferredResource(); tr.setFile(!FSUtils.isDirectory(resourcePath)); tr.setFullPath(resourcePath.toString()); String id = relativeToBase.toString(); tr.setId(id); tr.setUUID(IdUtils.getTransferredResourceUUID(relativeToBase)); tr.setName(resourcePath.getFileName().toString()); tr.setRelativePath(relativeToBase.toString()); if (relativeToBase.getParent() != null) { String parentId = relativeToBase.getParent().toString(); tr.setParentId(parentId); tr.setParentUUID(IdUtils.createUUID(parentId)); } List<String> ancestors = new ArrayList<>(); StringBuilder temp = new StringBuilder(); Iterator<Path> pathIterator = relativeToBase.iterator(); while (pathIterator.hasNext()) { temp.append(pathIterator.next().toString()); ancestors.add(temp.toString()); temp.append("/"); } ancestors.remove(ancestors.size() - 1); tr.setAncestorsPaths(ancestors); return tr; } public void deleteTransferredResource(List<String> ids) throws NotFoundException, GenericException, RequestNotValidException { for (String uuid : ids) { TransferredResource tr = index.retrieve(TransferredResource.class, uuid, fieldsToReturn); Path relative = Paths.get(tr.getRelativePath()); Path fullPath = basePath.resolve(relative); if (FSUtils.exists(fullPath)) { FSUtils.deletePath(fullPath); Filter filter = new Filter( new SimpleFilterParameter(RodaConstants.TRANSFERRED_RESOURCE_ANCESTORS, relative.toString())); index.delete(TransferredResource.class, filter); } else { throw new NotFoundException("Path does not exist: " + fullPath); } } index.delete(TransferredResource.class, ids); index.commit(TransferredResource.class); } public void updateTransferredResources(Optional<String> folderRelativePath, boolean waitToFinish) throws IsStillUpdatingException, GenericException { if (!RodaCoreFactory.getTransferredResourcesScannerUpdateStatus(folderRelativePath)) { if (index != null) { ReindexTransferredResourcesRunnable reindexRunnable = new ReindexTransferredResourcesRunnable(index, basePath, folderRelativePath); if (waitToFinish) { reindexRunnable.run(); } else { Thread threadReindex = new Thread(reindexRunnable, "ReindexThread"); threadReindex.start(); } } else { throw new GenericException("Could not update transferred resources because index was not initialized"); } } else { LOGGER.warn("Could not update transferred resources because it is still updating"); throw new IsStillUpdatingException(); } } public void updateTransferredResource(Optional<String> folderRelativePath, ContentPayload payload, String name, boolean waitToFinish) throws NotFoundException, GenericException, IOException, IsStillUpdatingException { if (folderRelativePath.isPresent()) { Path path = basePath.resolve(folderRelativePath.get()); Path parent = path.getParent(); Path parentToBase = basePath.relativize(parent); FSUtils.deletePath(path); payload.writeToPath(parent.resolve(name)); updateTransferredResources(Optional.ofNullable(parentToBase.toString()), waitToFinish); } } public String renameTransferredResource(TransferredResource resource, String newName, boolean replaceExisting, boolean reindexResources) throws AlreadyExistsException, GenericException, IsStillUpdatingException, NotFoundException { if (FSUtils.exists(Paths.get(resource.getFullPath()))) { Path resourcePath = Paths.get(resource.getFullPath()); Path newPath = resourcePath.getParent().resolve(newName); FSUtils.move(resourcePath, newPath, replaceExisting); if (reindexResources) { if (resource.getParentUUID() != null) { try { TransferredResource parent = index.retrieve(TransferredResource.class, resource.getParentUUID(), fieldsToReturn); if (parent != null) { updateTransferredResources(Optional.of(parent.getRelativePath()), true); } else { updateTransferredResources(Optional.empty(), true); } } catch (GenericException | NotFoundException e) { LOGGER.error("Could not reindex transferred resources after renaming"); } } else { updateTransferredResources(Optional.empty(), true); } } Path relativeToBase = basePath.relativize(resourcePath.getParent().resolve(newName)); return IdUtils.createUUID(relativeToBase.toString()); } else { throw new NotFoundException("Transferred resource was moved or does not exist"); } } public Map<String, String> moveTransferredResource(String newRelativePath, List<String> resourcesUUIDs, boolean replaceExisting) throws AlreadyExistsException, GenericException, IsStillUpdatingException, NotFoundException { List<TransferredResource> resources = Collections.emptyList(); try { List<String> resourceFields = Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.TRANSFERRED_RESOURCE_FULLPATH, RodaConstants.TRANSFERRED_RESOURCE_RELATIVEPATH, RodaConstants.TRANSFERRED_RESOURCE_NAME); resources = index.retrieve(TransferredResource.class, resourcesUUIDs, resourceFields); } catch (NotFoundException e) { // do nothing and pass it an empty list } return moveTransferredResource(resources, newRelativePath, replaceExisting, false, true); } public Map<String, String> moveTransferredResource(List<TransferredResource> resources, String newRelativePath, boolean replaceExisting, boolean reindexResources) throws AlreadyExistsException, GenericException, IsStillUpdatingException, NotFoundException { return moveTransferredResource(resources, newRelativePath, replaceExisting, reindexResources, false); } public Map<String, String> moveTransferredResource(List<TransferredResource> resources, String newRelativePath, boolean replaceExisting, boolean reindexResources, boolean addOldRelativePathToNewRelativePath) throws AlreadyExistsException, GenericException, IsStillUpdatingException, NotFoundException { Map<String, String> oldToNewTransferredResourceIds = new HashMap<>(); List<TransferredResource> resourcesToIndex = new ArrayList<>(); boolean notFoundResources = false; String baseFolder = RodaCoreFactory.getRodaConfiguration().getString("core.ingest.processed.base_folder", "PROCESSED"); String successFolder = RodaCoreFactory.getRodaConfiguration() .getString("core.ingest.processed.successfully_ingested", "SUCCESSFULLY_INGESTED"); String unsuccessFolder = RodaCoreFactory.getRodaConfiguration() .getString("core.ingest.processed.unsuccessfully_ingested", "UNSUCCESSFULLY_INGESTED"); for (TransferredResource resource : resources) { if (FSUtils.exists(Paths.get(resource.getFullPath()))) { Path newResourcePath = basePath.resolve(newRelativePath); if (addOldRelativePathToNewRelativePath) { newResourcePath = newResourcePath.resolve(resource.getRelativePath() .replace(baseFolder + "/" + successFolder + "/", "").replace(baseFolder + "/" + unsuccessFolder + "/", "")); } else { newResourcePath = newResourcePath.resolve(resource.getName()); } FSUtils.move(Paths.get(resource.getFullPath()), newResourcePath, replaceExisting); // create & index transferred resource in the new location TransferredResource newResource = instantiateTransferredResource(newResourcePath, basePath); Date creationDate = resource.getCreationDate(); try { BasicFileAttributes attr = Files.readAttributes(newResourcePath, BasicFileAttributes.class); creationDate = new Date(attr.creationTime().toMillis()); } catch (IOException e) { creationDate = new Date(); } newResource.setCreationDate(creationDate); newResource.setSize(resource.getSize()); newResource.setLastScanDate(new Date()); try { index.create(TransferredResource.class, newResource); } catch (RequestNotValidException e) { // do nothing } oldToNewTransferredResourceIds.put(resource.getUUID(), newResource.getUUID()); resourcesToIndex.add(resource); } else { notFoundResources = true; } } if (reindexResources) { updateTransferredResources(Optional.of(newRelativePath), true); } reindexOldResourcesParentsAfterMove(resourcesToIndex); // doing the throw after the moving process to reindex the moved ones if (notFoundResources) { throw new NotFoundException("Some transferred resources were moved or do not exist"); } return oldToNewTransferredResourceIds; } public void reindexOldResourcesParentsAfterMove(List<TransferredResource> resources) throws IsStillUpdatingException, GenericException { try { List<String> resourceUUIDs = resources.stream().map(tr -> tr.getUUID()).collect(Collectors.toList()); index.delete(TransferredResource.class, resourceUUIDs); } catch (RequestNotValidException e) { LOGGER.error("Could not delete old transferred resources"); } } public CloseableIterable<OptionalWithCause<LiteRODAObject>> listTransferredResources() { CloseableIterable<OptionalWithCause<LiteRODAObject>> resources = null; try { final Stream<Path> files = Files.walk(basePath, FileVisitOption.FOLLOW_LINKS) .filter(path -> !path.equals(basePath)); final Iterator<Path> fileIterator = files.iterator(); resources = new CloseableIterable<OptionalWithCause<LiteRODAObject>>() { @Override public void close() throws IOException { files.close(); } @Override public Iterator<OptionalWithCause<LiteRODAObject>> iterator() { return new Iterator<OptionalWithCause<LiteRODAObject>>() { @Override public boolean hasNext() { return fileIterator.hasNext(); } @Override public OptionalWithCause<LiteRODAObject> next() { Path file = fileIterator.next(); Optional<LiteRODAObject> liteResource = LiteRODAObjectFactory.get(TransferredResource.class, Arrays.asList(file.toString()), false); return OptionalWithCause.of(liteResource); } }; } }; } catch (IOException e) { LOGGER.error("Errored when file walking to list transferred resources"); } return resources; } }