001package org.fcrepo.migration.pidlist;
002
003import static org.junit.Assert.assertEquals;
004
005import java.io.File;
006import java.util.Map;
007import java.util.Set;
008import java.util.stream.Collectors;
009import javax.xml.stream.XMLStreamException;
010
011import edu.wisc.library.ocfl.api.OcflRepository;
012import edu.wisc.library.ocfl.api.model.DigestAlgorithm;
013import edu.wisc.library.ocfl.api.model.FileDetails;
014import edu.wisc.library.ocfl.api.model.ObjectDetails;
015import edu.wisc.library.ocfl.core.OcflRepositoryBuilder;
016import edu.wisc.library.ocfl.core.extension.storage.layout.config.HashedNTupleLayoutConfig;
017import edu.wisc.library.ocfl.core.path.mapper.LogicalPathMappers;
018import edu.wisc.library.ocfl.core.storage.OcflStorageBuilder;
019import org.apache.commons.io.FileUtils;
020import org.apache.commons.lang3.SystemUtils;
021import org.fcrepo.migration.MigrationType;
022import org.fcrepo.migration.Migrator;
023import org.fcrepo.migration.OcflSessionFactoryFactoryBean;
024import org.fcrepo.migration.ResourceMigrationType;
025import org.fcrepo.migration.foxml.LegacyFSIDResolver;
026import org.fcrepo.migration.foxml.NativeFoxmlDirectoryObjectSource;
027import org.fcrepo.migration.handlers.ObjectAbstractionStreamingFedoraObjectHandler;
028import org.fcrepo.migration.handlers.ocfl.ArchiveGroupHandler;
029import org.fcrepo.storage.ocfl.OcflObjectSessionFactory;
030import org.junit.Before;
031import org.junit.Test;
032
033/**
034 * Integration tests to test migration of head only datastream cases
035 *
036 * @author mikejritter
037 */
038public class HeadOnlyIT {
039
040    private final String user = "fedoraAdmin";
041    private final String idPrefix = "info:fedora/";
042    private final String testPid = idPrefix + "example:1";
043    private final boolean disableChecksum = false;
044    private final boolean disableDc = false;
045
046    private final DigestAlgorithm digestAlgorithm = DigestAlgorithm.sha512;
047    private final MigrationType migrationType = MigrationType.FEDORA_OCFL;
048
049    private LegacyFSIDResolver idResolver;
050    private NativeFoxmlDirectoryObjectSource objectSource;
051    private OcflObjectSessionFactory ocflObjectSessionFactory;
052
053    private File storage;
054    private File staging;
055    private File workingDir;
056
057    @Before
058    public void setup() throws Exception {
059        // Create directories expected in this test
060        storage = new File("target/test/ocfl/head-it/storage");
061        staging = new File("target/test/ocfl/head-it/staging");
062        workingDir = new File("target/test/ocfl/head-it/work");
063
064        if (storage.exists()) {
065            FileUtils.forceDelete(storage);
066        }
067        if (staging.exists()) {
068            FileUtils.forceDelete(staging);
069        }
070        if (workingDir.exists()) {
071            FileUtils.forceDelete(workingDir);
072        }
073
074        storage.mkdirs();
075        staging.mkdirs();
076        workingDir.mkdirs();
077
078        // Init Fedora3 classes
079        final var f3Hostname = "fedora.info";
080
081        final var f3ObjectDir = new File("src/test/resources/legacyFS-multiple-versions/objects/2015/0430/16/01");
082        final var f3DatastreamDir =
083            new File("src/test/resources/legacyFS-multiple-versions/datastreams/2015/0430/16/01");
084
085        idResolver = new LegacyFSIDResolver(f3DatastreamDir);
086        objectSource = new NativeFoxmlDirectoryObjectSource(f3ObjectDir, idResolver, f3Hostname);
087
088        // Init OCFL classes
089        final var userUri = idPrefix + user;
090        ocflObjectSessionFactory =
091            new OcflSessionFactoryFactoryBean(storage.toPath(), staging.toPath(), migrationType, user, userUri,
092                                              digestAlgorithm, disableChecksum).getObject();
093    }
094
095    @Test
096    public void testMigrateHeadOnly() throws XMLStreamException {
097        final var agh = archiveGroupHandler(true);
098        final var handler = new ObjectAbstractionStreamingFedoraObjectHandler(agh);
099        final var migrator = new Migrator();
100        migrator.setSource(objectSource);
101        migrator.setHandler(handler);
102
103        migrator.run();
104
105        // check that there's only single version for each datastream
106        final var ocflRepository = repository();
107        final var objectDetails = ocflRepository.describeObject(testPid);
108        final var fileVersions = collectDatastreamVersions(objectDetails);
109
110        fileVersions.values().forEach(versions -> assertEquals(1, versions.size()));
111    }
112
113    @Test
114    public void testMigrateHeadOnlyDisabled() throws XMLStreamException {
115        final var agh = archiveGroupHandler(false);
116        final var handler = new ObjectAbstractionStreamingFedoraObjectHandler(agh);
117        final var migrator = new Migrator();
118        migrator.setSource(objectSource);
119        migrator.setHandler(handler);
120
121        migrator.run();
122        final var ocflRepository = repository();
123        final var objectDetails = ocflRepository.describeObject(testPid);
124        final var fileVersions = collectDatastreamVersions(objectDetails);
125
126        // from the foxml, two datastreams have versions: DS1 and DC
127        final var dcVersions = fileVersions.get("/DC");
128        final var ds1Versions = fileVersions.get("/DS1");
129
130        assertEquals(2, dcVersions.size());
131        assertEquals(2, ds1Versions.size());
132    }
133
134    private ArchiveGroupHandler archiveGroupHandler(final boolean headOnly) {
135        final boolean foxmlFile = false;
136        final boolean deleteInactive = false;
137        final boolean datastreamExtensions = false;
138        final ResourceMigrationType resourceMigrationType = ResourceMigrationType.ARCHIVAL;
139        return new ArchiveGroupHandler(ocflObjectSessionFactory, migrationType, resourceMigrationType,
140                                       datastreamExtensions, deleteInactive, foxmlFile, user, idPrefix,
141                                       headOnly, disableChecksum, disableDc);
142    }
143
144    private OcflRepository repository() {
145        final var ocflStorage = OcflStorageBuilder.builder()
146            .fileSystem(storage.toPath())
147            .build();
148
149        final var logicalPathMapper = SystemUtils.IS_OS_WINDOWS ? LogicalPathMappers.percentEncodingWindowsMapper()
150                                                                : LogicalPathMappers.percentEncodingLinuxMapper();
151
152        return new OcflRepositoryBuilder().storage(ocflStorage)
153                                          .defaultLayoutConfig(new HashedNTupleLayoutConfig())
154                                          .logicalPathMapper(logicalPathMapper)
155                                          .workDir(staging.toPath())
156                                          .build();
157    }
158
159    private Map<String, Set<String>> collectDatastreamVersions(final ObjectDetails objectDetails) {
160        return objectDetails.getVersionMap()
161                            .values().stream()
162                            .flatMap(details -> details.getFiles().stream())
163                            .map(FileDetails::getStorageRelativePath)
164                            .filter(string -> !string.contains(".fcrepo") && !string.contains(".nt"))
165                            .collect(Collectors.groupingBy(s -> s.substring(s.lastIndexOf("/")), Collectors.toSet()));
166    }
167}