001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.persistence.ocfl.impl; 007 008import static org.fcrepo.persistence.ocfl.impl.OcflPersistentStorageUtils.createFilesystemRepository; 009import static org.fcrepo.persistence.ocfl.impl.OcflPersistentStorageUtils.createS3Repository; 010 011import java.io.IOException; 012import java.net.URI; 013import java.util.concurrent.TimeUnit; 014 015import javax.inject.Inject; 016import javax.sql.DataSource; 017 018import org.fcrepo.config.MetricsConfig; 019import org.fcrepo.config.OcflPropsConfig; 020import org.fcrepo.config.Storage; 021import org.fcrepo.storage.ocfl.CommitType; 022import org.fcrepo.storage.ocfl.DefaultOcflObjectSessionFactory; 023import org.fcrepo.storage.ocfl.validation.ObjectValidator; 024import org.fcrepo.storage.ocfl.OcflObjectSessionFactory; 025import org.fcrepo.storage.ocfl.ResourceHeaders; 026import org.fcrepo.storage.ocfl.cache.Cache; 027import org.fcrepo.storage.ocfl.cache.CaffeineCache; 028import org.fcrepo.storage.ocfl.cache.NoOpCache; 029 030import org.apache.commons.lang3.StringUtils; 031import org.springframework.context.annotation.Bean; 032import org.springframework.context.annotation.Configuration; 033 034import com.github.benmanes.caffeine.cache.Caffeine; 035 036import edu.wisc.library.ocfl.api.MutableOcflRepository; 037import io.micrometer.core.instrument.MeterRegistry; 038import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics; 039import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; 040import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; 041import software.amazon.awssdk.regions.Region; 042import software.amazon.awssdk.services.s3.S3Client; 043 044/** 045 * A Configuration for OCFL dependencies 046 * 047 * @author dbernstein 048 * @since 6.0.0 049 */ 050@Configuration 051public class OcflPersistenceConfig { 052 053 @Inject 054 private OcflPropsConfig ocflPropsConfig; 055 056 @Inject 057 private MetricsConfig metricsConfig; 058 059 @Inject 060 private MeterRegistry meterRegistry; 061 062 @Inject 063 private DataSource dataSource; 064 065 /** 066 * Create an OCFL Repository 067 * @return the repository 068 */ 069 @Bean 070 public MutableOcflRepository repository() throws IOException { 071 if (ocflPropsConfig.getStorage() == Storage.OCFL_S3) { 072 return createS3Repository( 073 dataSource, 074 s3Client(), 075 ocflPropsConfig.getOcflS3Bucket(), 076 ocflPropsConfig.getOcflS3Prefix(), 077 ocflPropsConfig.getOcflTemp(), 078 ocflPropsConfig.getDefaultDigestAlgorithm(), 079 ocflPropsConfig.isOcflS3DbEnabled(), 080 ocflPropsConfig.isOcflUpgradeOnWrite(), 081 ocflPropsConfig.verifyInventory()); 082 } else { 083 return createFilesystemRepository(ocflPropsConfig.getOcflRepoRoot(), ocflPropsConfig.getOcflTemp(), 084 ocflPropsConfig.getDefaultDigestAlgorithm(), ocflPropsConfig.isOcflUpgradeOnWrite(), 085 ocflPropsConfig.verifyInventory()); 086 } 087 } 088 089 @Bean 090 public OcflObjectSessionFactory ocflObjectSessionFactory() throws IOException { 091 final var objectMapper = OcflPersistentStorageUtils.objectMapper(); 092 093 final var factory = new DefaultOcflObjectSessionFactory(repository(), 094 ocflPropsConfig.getFedoraOcflStaging(), 095 objectMapper, 096 createCache("resourceHeadersCache"), 097 createCache("rootIdCache"), 098 commitType(), 099 "Authored by Fedora 6", 100 "fedoraAdmin", 101 "info:fedora/fedoraAdmin"); 102 factory.useUnsafeWrite(ocflPropsConfig.isUnsafeWriteEnabled()); 103 return factory; 104 } 105 106 @Bean 107 public ObjectValidator objectValidator() throws IOException { 108 final var objectMapper = OcflPersistentStorageUtils.objectMapper(); 109 return new ObjectValidator(repository(), objectMapper.readerFor(ResourceHeaders.class)); 110 } 111 112 private CommitType commitType() { 113 if (ocflPropsConfig.isAutoVersioningEnabled()) { 114 return CommitType.NEW_VERSION; 115 } 116 return CommitType.UNVERSIONED; 117 } 118 119 private S3Client s3Client() { 120 final var builder = S3Client.builder(); 121 122 if (StringUtils.isNotBlank(ocflPropsConfig.getAwsRegion())) { 123 builder.region(Region.of(ocflPropsConfig.getAwsRegion())); 124 } 125 126 if (StringUtils.isNotBlank(ocflPropsConfig.getS3Endpoint())) { 127 builder.endpointOverride(URI.create(ocflPropsConfig.getS3Endpoint())); 128 } 129 130 if (ocflPropsConfig.isPathStyleAccessEnabled()) { 131 builder.serviceConfiguration(config -> config.pathStyleAccessEnabled(true)); 132 } 133 134 if (StringUtils.isNoneBlank(ocflPropsConfig.getAwsAccessKey(), ocflPropsConfig.getAwsSecretKey())) { 135 builder.credentialsProvider(StaticCredentialsProvider.create( 136 AwsBasicCredentials.create(ocflPropsConfig.getAwsAccessKey(), ocflPropsConfig.getAwsSecretKey()))); 137 } 138 139 // May want to do additional HTTP client configuration, connection pool, etc 140 141 return builder.build(); 142 } 143 144 private <K, V> Cache<K, V> createCache(final String metricName) { 145 if (ocflPropsConfig.isResourceHeadersCacheEnabled()) { 146 final var builder = Caffeine.newBuilder(); 147 148 if (metricsConfig.isMetricsEnabled()) { 149 builder.recordStats(); 150 } 151 152 final var cache = builder 153 .maximumSize(ocflPropsConfig.getResourceHeadersCacheMaxSize()) 154 .expireAfterAccess(ocflPropsConfig.getResourceHeadersCacheExpireAfterSeconds(), TimeUnit.SECONDS) 155 .build(); 156 157 if (metricsConfig.isMetricsEnabled()) { 158 CaffeineCacheMetrics.monitor(meterRegistry, cache, metricName); 159 } 160 161 return new CaffeineCache<>(cache); 162 } 163 164 return new NoOpCache<>(); 165 } 166 167}