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 051@Configuration 052public class OcflPersistenceConfig { 053 054 @Inject 055 private OcflPropsConfig ocflPropsConfig; 056 057 @Inject 058 private MetricsConfig metricsConfig; 059 060 @Inject 061 private MeterRegistry meterRegistry; 062 063 @Inject 064 private DataSource dataSource; 065 066 /** 067 * Create an OCFL Repository 068 * @return the repository 069 */ 070 @Bean 071 public MutableOcflRepository repository() throws IOException { 072 if (ocflPropsConfig.getStorage() == Storage.OCFL_S3) { 073 return createS3Repository( 074 dataSource, 075 s3Client(), 076 ocflPropsConfig.getOcflS3Bucket(), 077 ocflPropsConfig.getOcflS3Prefix(), 078 ocflPropsConfig.getOcflTemp(), 079 ocflPropsConfig.getDefaultDigestAlgorithm(), 080 ocflPropsConfig.isOcflS3DbEnabled()); 081 } else { 082 return createFilesystemRepository(ocflPropsConfig.getOcflRepoRoot(), ocflPropsConfig.getOcflTemp(), 083 ocflPropsConfig.getDefaultDigestAlgorithm()); 084 } 085 } 086 087 @Bean 088 public OcflObjectSessionFactory ocflObjectSessionFactory() throws IOException { 089 final var objectMapper = OcflPersistentStorageUtils.objectMapper(); 090 091 final var factory = new DefaultOcflObjectSessionFactory(repository(), 092 ocflPropsConfig.getFedoraOcflStaging(), 093 objectMapper, 094 createCache("resourceHeadersCache"), 095 createCache("rootIdCache"), 096 commitType(), 097 "Authored by Fedora 6", 098 "fedoraAdmin", 099 "info:fedora/fedoraAdmin"); 100 factory.useUnsafeWrite(ocflPropsConfig.isUnsafeWriteEnabled()); 101 return factory; 102 } 103 104 @Bean 105 public ObjectValidator objectValidator() throws IOException { 106 final var objectMapper = OcflPersistentStorageUtils.objectMapper(); 107 return new ObjectValidator(repository(), objectMapper.readerFor(ResourceHeaders.class)); 108 } 109 110 private CommitType commitType() { 111 if (ocflPropsConfig.isAutoVersioningEnabled()) { 112 return CommitType.NEW_VERSION; 113 } 114 return CommitType.UNVERSIONED; 115 } 116 117 private S3Client s3Client() { 118 final var builder = S3Client.builder(); 119 120 if (StringUtils.isNotBlank(ocflPropsConfig.getAwsRegion())) { 121 builder.region(Region.of(ocflPropsConfig.getAwsRegion())); 122 } 123 124 if (StringUtils.isNotBlank(ocflPropsConfig.getS3Endpoint())) { 125 builder.endpointOverride(URI.create(ocflPropsConfig.getS3Endpoint())); 126 } 127 128 if (ocflPropsConfig.isPathStyleAccessEnabled()) { 129 builder.serviceConfiguration(config -> config.pathStyleAccessEnabled(true)); 130 } 131 132 if (StringUtils.isNoneBlank(ocflPropsConfig.getAwsAccessKey(), ocflPropsConfig.getAwsSecretKey())) { 133 builder.credentialsProvider(StaticCredentialsProvider.create( 134 AwsBasicCredentials.create(ocflPropsConfig.getAwsAccessKey(), ocflPropsConfig.getAwsSecretKey()))); 135 } 136 137 // May want to do additional HTTP client configuration, connection pool, etc 138 139 return builder.build(); 140 } 141 142 private <K, V> Cache<K, V> createCache(final String metricName) { 143 if (ocflPropsConfig.isResourceHeadersCacheEnabled()) { 144 final var builder = Caffeine.newBuilder(); 145 146 if (metricsConfig.isMetricsEnabled()) { 147 builder.recordStats(); 148 } 149 150 final var cache = builder 151 .maximumSize(ocflPropsConfig.getResourceHeadersCacheMaxSize()) 152 .expireAfterAccess(ocflPropsConfig.getResourceHeadersCacheExpireAfterSeconds(), TimeUnit.SECONDS) 153 .build(); 154 155 if (metricsConfig.isMetricsEnabled()) { 156 CaffeineCacheMetrics.monitor(meterRegistry, cache, metricName); 157 } 158 159 return new CaffeineCache<>(cache); 160 } 161 162 return new NoOpCache<>(); 163 } 164 165}