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