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}