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}