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