001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.persistence.ocfl.impl; 019 020import edu.wisc.library.ocfl.api.OcflRepository; 021 022import org.fcrepo.common.db.DbTransactionExecutor; 023import org.fcrepo.config.FedoraPropsConfig; 024import org.fcrepo.config.OcflPropsConfig; 025import org.fcrepo.kernel.api.ContainmentIndex; 026import org.fcrepo.kernel.api.ReadOnlyTransaction; 027import org.fcrepo.kernel.api.TransactionManager; 028import org.fcrepo.kernel.api.identifiers.FedoraId; 029import org.fcrepo.persistence.ocfl.api.FedoraOcflMappingNotFoundException; 030import org.fcrepo.persistence.ocfl.api.FedoraToOcflObjectIndex; 031import org.fcrepo.persistence.ocfl.api.IndexBuilder; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034import org.springframework.beans.factory.annotation.Autowired; 035import org.springframework.beans.factory.annotation.Qualifier; 036import org.springframework.stereotype.Component; 037 038import javax.inject.Inject; 039import java.time.Duration; 040import java.time.Instant; 041 042/** 043 * An implementation of {@link IndexBuilder}. This implementation rebuilds the following indexable state derived 044 * from the underlying OCFL directory: 045 * 1) the link between a {@link org.fcrepo.kernel.api.identifiers.FedoraId} and an OCFL object identifier 046 * 2) the containment relationships between {@link org.fcrepo.kernel.api.identifiers.FedoraId}s 047 * 3) the reference relationships between {@link org.fcrepo.kernel.api.identifiers.FedoraId}s 048 * 4) the search index 049 * 5) the membership relationships for Direct and Indirect containers. 050 * 051 * @author dbernstein 052 * @author whikloj 053 * @since 6.0.0 054 */ 055@Component 056public class IndexBuilderImpl implements IndexBuilder { 057 058 private static final Logger LOGGER = LoggerFactory.getLogger(IndexBuilderImpl.class); 059 060 @Autowired 061 @Qualifier("ocflIndex") 062 private FedoraToOcflObjectIndex ocflIndex; 063 064 @Autowired 065 @Qualifier("containmentIndex") 066 private ContainmentIndex containmentIndex; 067 068 @Inject 069 private OcflRepository ocflRepository; 070 071 @Inject 072 private ReindexService reindexService; 073 074 @Inject 075 private OcflPropsConfig ocflPropsConfig; 076 077 @Inject 078 private FedoraPropsConfig fedoraPropsConfig; 079 080 @Inject 081 private TransactionManager txManager; 082 083 @Inject 084 private DbTransactionExecutor dbTransactionExecutor; 085 086 @Override 087 public void rebuildIfNecessary() { 088 if (shouldRebuild()) { 089 rebuild(); 090 } else { 091 LOGGER.debug("No index rebuild necessary"); 092 } 093 } 094 095 private void rebuild() { 096 LOGGER.info("Initiating index rebuild. This may take a while. Progress will be logged periodically."); 097 098 reindexService.reset(); 099 100 try (var objectIds = ocflRepository.listObjectIds()) { 101 final ReindexManager reindexManager = new ReindexManager(objectIds, 102 reindexService, ocflPropsConfig, txManager, dbTransactionExecutor); 103 104 LOGGER.debug("Reading object ids..."); 105 final var startTime = Instant.now(); 106 try { 107 reindexManager.start(); 108 } catch (final InterruptedException e) { 109 throw new RuntimeException(e); 110 } finally { 111 reindexManager.shutdown(); 112 } 113 final var endTime = Instant.now(); 114 final var count = reindexManager.getCompletedCount(); 115 final var errors = reindexManager.getErrorCount(); 116 LOGGER.info("Index rebuild completed {} objects successfully and {} objects had errors in {} ", 117 count, errors, getDurationMessage(Duration.between(startTime, endTime))); 118 } 119 } 120 121 private boolean shouldRebuild() { 122 final var repoRoot = getRepoRootMapping(); 123 if (fedoraPropsConfig.isRebuildOnStart()) { 124 return true; 125 } else if (repoRoot == null) { 126 return true; 127 } else { 128 return !repoContainsRootObject(repoRoot); 129 } 130 } 131 132 private String getRepoRootMapping() { 133 try { 134 return ocflIndex.getMapping(ReadOnlyTransaction.INSTANCE, FedoraId.getRepositoryRootId()).getOcflObjectId(); 135 } catch (final FedoraOcflMappingNotFoundException e) { 136 return null; 137 } 138 } 139 140 private boolean repoContainsRootObject(final String id) { 141 return ocflRepository.containsObject(id); 142 } 143 144 private String getDurationMessage(final Duration duration) { 145 String message = String.format("%d seconds", duration.toSecondsPart()); 146 if (duration.getSeconds() > 60) { 147 message = String.format("%d mins, ", duration.toMinutesPart()) + message; 148 } 149 if (duration.getSeconds() > 3600) { 150 message = String.format("%d hours, ", duration.getSeconds() / 3600) + message; 151 } 152 return message; 153 } 154}