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 */ 018 019package org.fcrepo.config; 020 021import java.io.IOException; 022import java.nio.file.Files; 023import java.nio.file.Path; 024import java.util.List; 025import java.util.Objects; 026import java.util.stream.Collectors; 027 028import javax.annotation.PostConstruct; 029 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032import org.springframework.beans.factory.annotation.Value; 033import org.springframework.context.annotation.Configuration; 034 035/** 036 * Fedora's OCFL related configuration properties 037 * 038 * @author pwinckles 039 * @since 6.0.0 040 */ 041@Configuration 042public class OcflPropsConfig extends BasePropsConfig { 043 044 private static final Logger LOGGER = LoggerFactory.getLogger(OcflPropsConfig.class); 045 046 public static final String FCREPO_OCFL_STAGING = "fcrepo.ocfl.staging"; 047 public static final String FCREPO_OCFL_ROOT = "fcrepo.ocfl.root"; 048 public static final String FCREPO_OCFL_TEMP = "fcrepo.ocfl.temp"; 049 private static final String FCREPO_OCFL_S3_BUCKET = "fcrepo.ocfl.s3.bucket"; 050 051 private static final String OCFL_STAGING = "staging"; 052 private static final String OCFL_ROOT = "ocfl-root"; 053 private static final String OCFL_TEMP = "ocfl-temp"; 054 055 private static final String FCREPO_PERSISTENCE_ALGORITHM = "fcrepo.persistence.defaultDigestAlgorithm"; 056 057 @Value("${" + FCREPO_OCFL_STAGING + ":#{fedoraPropsConfig.fedoraData.resolve('" + OCFL_STAGING + "')}}") 058 private Path fedoraOcflStaging; 059 060 @Value("${" + FCREPO_OCFL_ROOT + ":#{fedoraPropsConfig.fedoraData.resolve('" + OCFL_ROOT + "')}}") 061 private Path ocflRepoRoot; 062 063 @Value("${" + FCREPO_OCFL_TEMP + ":#{fedoraPropsConfig.fedoraData.resolve('" + OCFL_TEMP + "')}}") 064 private Path ocflTemp; 065 066 /** 067 * Controls whether changes are committed to new OCFL versions or to a mutable HEAD 068 */ 069 @Value("${fcrepo.autoversioning.enabled:true}") 070 private boolean autoVersioningEnabled; 071 072 @Value("${fcrepo.storage:ocfl-fs}") 073 private String storageStr; 074 private Storage storage; 075 076 @Value("${fcrepo.aws.access-key:}") 077 private String awsAccessKey; 078 079 @Value("${fcrepo.aws.secret-key:}") 080 private String awsSecretKey; 081 082 @Value("${fcrepo.aws.region:}") 083 private String awsRegion; 084 085 @Value("${" + FCREPO_OCFL_S3_BUCKET + ":}") 086 private String ocflS3Bucket; 087 088 @Value("${fcrepo.ocfl.s3.prefix:}") 089 private String ocflS3Prefix; 090 091 @Value("${fcrepo.resource-header-cache.enable:true}") 092 private boolean resourceHeadersCacheEnabled; 093 094 @Value("${fcrepo.resource-header-cache.max-size:512}") 095 private long resourceHeadersCacheMaxSize; 096 097 @Value("${fcrepo.resource-header-cache.expire-after-seconds:600}") 098 private long resourceHeadersCacheExpireAfterSeconds; 099 100 @Value("${fcrepo.ocfl.reindex.threads:-1}") 101 private long reindexThreads; 102 103 @Value("${fcrepo.ocfl.reindex.batchSize:100}") 104 private long reindexBatchSize; 105 106 @Value("${fcrepo.ocfl.reindex.failOnError:true}") 107 private boolean reindexFailOnError; 108 109 @Value("${" + FCREPO_PERSISTENCE_ALGORITHM + ":sha512}") 110 private String FCREPO_DIGEST_ALGORITHM_VALUE; 111 112 private DigestAlgorithm FCREPO_DIGEST_ALGORITHM; 113 114 /** 115 * List of valid choices for fcrepo.persistence.defaultDigestAlgorithm 116 */ 117 private static final List<DigestAlgorithm> FCREPO_VALID_DIGEST_ALGORITHMS = List.of( 118 DigestAlgorithm.SHA256, 119 DigestAlgorithm.SHA512 120 ); 121 122 private static final long availableThreads = Runtime.getRuntime().availableProcessors(); 123 124 @PostConstruct 125 private void postConstruct() throws IOException { 126 if (reindexThreads < 0L) { 127 reindexThreads = computeDefaultReindexThreads(); 128 } else { 129 reindexThreads = checkReindexThreadLimit(reindexThreads); 130 } 131 storage = Storage.fromString(storageStr); 132 LOGGER.info("Fedora storage type: {}", storage); 133 LOGGER.info("Fedora staging: {}", fedoraOcflStaging); 134 LOGGER.info("Fedora OCFL temp: {}", ocflTemp); 135 LOGGER.info("Fedora OCFL reindexing threads: {}", reindexThreads); 136 LOGGER.info("Fedora OCFL reindexing batch size: {}", reindexBatchSize); 137 LOGGER.info("Fedora OCFL reindexing fail on error: {}", reindexFailOnError); 138 Files.createDirectories(fedoraOcflStaging); 139 Files.createDirectories(ocflTemp); 140 141 if (storage == Storage.OCFL_FILESYSTEM) { 142 LOGGER.info("Fedora OCFL root: {}", ocflRepoRoot); 143 Files.createDirectories(ocflRepoRoot); 144 } else if (storage == Storage.OCFL_S3) { 145 Objects.requireNonNull(ocflS3Bucket, 146 String.format("The property %s must be set when OCFL S3 storage is used", FCREPO_OCFL_S3_BUCKET)); 147 148 LOGGER.info("Fedora AWS access key: {}", awsAccessKey); 149 LOGGER.info("Fedora AWS secret key set: {}", Objects.isNull(awsSecretKey)); 150 LOGGER.info("Fedora AWS region: {}", awsRegion); 151 LOGGER.info("Fedora OCFL S3 bucket: {}", ocflS3Bucket); 152 LOGGER.info("Fedora OCFL S3 prefix: {}", ocflS3Prefix); 153 } 154 FCREPO_DIGEST_ALGORITHM = DigestAlgorithm.fromAlgorithm(FCREPO_DIGEST_ALGORITHM_VALUE); 155 // Throw error if the configured default digest is not known to fedora or is not a valid option 156 if (DigestAlgorithm.MISSING.equals(FCREPO_DIGEST_ALGORITHM) || 157 !FCREPO_VALID_DIGEST_ALGORITHMS.contains(FCREPO_DIGEST_ALGORITHM)) { 158 throw new IllegalArgumentException(String.format("Invalid %s property configured: %s, must be one of %s", 159 FCREPO_PERSISTENCE_ALGORITHM, FCREPO_DIGEST_ALGORITHM_VALUE, 160 FCREPO_VALID_DIGEST_ALGORITHMS.stream().map(DigestAlgorithm::getAlgorithm) 161 .collect(Collectors.joining(", ")))); 162 } 163 LOGGER.info("Fedora OCFL digest algorithm: {}", FCREPO_DIGEST_ALGORITHM.getAlgorithm()); 164 } 165 166 /** 167 * @return Path to directory Fedora stages resources before moving them into OCFL 168 */ 169 public Path getFedoraOcflStaging() { 170 return fedoraOcflStaging; 171 } 172 173 /** 174 * Sets the path to the Fedora staging directory -- should only be used for testing purposes. 175 * 176 * @param fedoraOcflStaging Path to Fedora staging directory 177 */ 178 public void setFedoraOcflStaging(final Path fedoraOcflStaging) { 179 this.fedoraOcflStaging = fedoraOcflStaging; 180 } 181 182 /** 183 * @return Path to OCFL root directory 184 */ 185 public Path getOcflRepoRoot() { 186 return ocflRepoRoot; 187 } 188 189 /** 190 * Sets the path to the Fedora OCFL root directory -- should only be used for testing purposes. 191 * 192 * @param ocflRepoRoot Path to Fedora OCFL root directory 193 */ 194 public void setOcflRepoRoot(final Path ocflRepoRoot) { 195 this.ocflRepoRoot = ocflRepoRoot; 196 } 197 198 /** 199 * @return Path to the temp directory used by the OCFL client 200 */ 201 public Path getOcflTemp() { 202 return ocflTemp; 203 } 204 205 /** 206 * Sets the path to the OCFL temp directory -- should only be used for testing purposes. 207 * 208 * @param ocflTemp Path to OCFL temp directory 209 */ 210 public void setOcflTemp(final Path ocflTemp) { 211 this.ocflTemp = ocflTemp; 212 } 213 214 /** 215 * @return true if every update should create a new OCFL version; false if the mutable HEAD should be used 216 */ 217 public boolean isAutoVersioningEnabled() { 218 return autoVersioningEnabled; 219 } 220 221 /** 222 * Determines whether or not new OCFL versions are created on every update. 223 * 224 * @param autoVersioningEnabled true to create new versions on every update 225 */ 226 public void setAutoVersioningEnabled(final boolean autoVersioningEnabled) { 227 this.autoVersioningEnabled = autoVersioningEnabled; 228 } 229 230 /** 231 * @return Indicates the storage type. ocfl-fs is the default 232 */ 233 public Storage getStorage() { 234 return storage; 235 } 236 237 /** 238 * @param storage storage to use 239 */ 240 public void setStorage(final Storage storage) { 241 this.storage = storage; 242 } 243 244 /** 245 * @return the aws access key to use, may be null 246 */ 247 public String getAwsAccessKey() { 248 return awsAccessKey; 249 } 250 251 /** 252 * @param awsAccessKey the aws access key to use 253 */ 254 public void setAwsAccessKey(final String awsAccessKey) { 255 this.awsAccessKey = awsAccessKey; 256 } 257 258 /** 259 * @return the aws secret key to use, may be null 260 */ 261 public String getAwsSecretKey() { 262 return awsSecretKey; 263 } 264 265 /** 266 * @param awsSecretKey the aws secret key to use 267 */ 268 public void setAwsSecretKey(final String awsSecretKey) { 269 this.awsSecretKey = awsSecretKey; 270 } 271 272 /** 273 * @return the aws region to use, may be null 274 */ 275 public String getAwsRegion() { 276 return awsRegion; 277 } 278 279 /** 280 * @param awsRegion the aws region to use 281 */ 282 public void setAwsRegion(final String awsRegion) { 283 this.awsRegion = awsRegion; 284 } 285 286 /** 287 * @return the s3 bucket to store objects in 288 */ 289 public String getOcflS3Bucket() { 290 return ocflS3Bucket; 291 } 292 293 /** 294 * @param ocflS3Bucket sets the s3 bucket to store objects in 295 */ 296 public void setOcflS3Bucket(final String ocflS3Bucket) { 297 this.ocflS3Bucket = ocflS3Bucket; 298 } 299 300 /** 301 * @return the s3 prefix to store objects under, may be null 302 */ 303 public String getOcflS3Prefix() { 304 return ocflS3Prefix; 305 } 306 307 /** 308 * @param ocflS3Prefix the prefix to store objects under 309 */ 310 public void setOcflS3Prefix(final String ocflS3Prefix) { 311 this.ocflS3Prefix = ocflS3Prefix; 312 } 313 314 /** 315 * @return whether or not to enable the resource headers cache 316 */ 317 public boolean isResourceHeadersCacheEnabled() { 318 return resourceHeadersCacheEnabled; 319 } 320 321 /** 322 * @param resourceHeadersCacheEnabled whether or not to enable the resource headers cache 323 */ 324 public void setResourceHeadersCacheEnabled(final boolean resourceHeadersCacheEnabled) { 325 this.resourceHeadersCacheEnabled = resourceHeadersCacheEnabled; 326 } 327 328 /** 329 * @return maximum number or resource headers in cache 330 */ 331 public long getResourceHeadersCacheMaxSize() { 332 return resourceHeadersCacheMaxSize; 333 } 334 335 /** 336 * @param resourceHeadersCacheMaxSize maximum number of resource headers in cache 337 */ 338 public void setResourceHeadersCacheMaxSize(final long resourceHeadersCacheMaxSize) { 339 this.resourceHeadersCacheMaxSize = resourceHeadersCacheMaxSize; 340 } 341 342 /** 343 * @return number of seconds to wait before expiring a resource header from the cache 344 */ 345 public long getResourceHeadersCacheExpireAfterSeconds() { 346 return resourceHeadersCacheExpireAfterSeconds; 347 } 348 349 /** 350 * @param resourceHeadersCacheExpireAfterSeconds 351 * number of seconds to wait before expiring a resource header from the cache 352 */ 353 public void setResourceHeadersCacheExpireAfterSeconds(final long resourceHeadersCacheExpireAfterSeconds) { 354 this.resourceHeadersCacheExpireAfterSeconds = resourceHeadersCacheExpireAfterSeconds; 355 } 356 357 /** 358 * @param threads 359 * number of threads to use when rebuilding from Fedora OCFL on disk. 360 */ 361 public void setReindexingThreads(final long threads) { 362 this.reindexThreads = checkReindexThreadLimit(threads); 363 } 364 365 /** 366 * @return number of threads to use when rebuilding from Fedora OCFL on disk. 367 */ 368 public long getReindexingThreads() { 369 return this.reindexThreads; 370 } 371 372 /** 373 * @return number of OCFL ids for a the reindexing manager to hand out at once. 374 */ 375 public long getReindexBatchSize() { 376 return reindexBatchSize; 377 } 378 379 /** 380 * @param reindexBatchSize 381 * number of OCFL ids for a the reindexing manager to hand out at once. 382 */ 383 public void setReindexBatchSize(final long reindexBatchSize) { 384 this.reindexBatchSize = reindexBatchSize; 385 } 386 387 /** 388 * @return whether to stop the entire reindexing process if a single object fails. 389 */ 390 public boolean isReindexFailOnError() { 391 return reindexFailOnError; 392 } 393 394 /** 395 * @param reindexFailOnError 396 * whether to stop the entire reindexing process if a single object fails. 397 */ 398 public void setReindexFailOnError(final boolean reindexFailOnError) { 399 this.reindexFailOnError = reindexFailOnError; 400 } 401 402 /** 403 * Check we don't create too few reindexing threads. 404 * @param threads the number of threads requested. 405 * @return higher of the requested amount or 1 406 */ 407 private long checkReindexThreadLimit(final long threads) { 408 if (threads <= 0) { 409 LOGGER.warn("Can't have fewer than 1 reindexing thread, setting to 1."); 410 return 1; 411 } else { 412 return threads; 413 } 414 } 415 416 /** 417 * @return number of available processors minus 1. 418 */ 419 private static long computeDefaultReindexThreads() { 420 return availableThreads - 1; 421 } 422 423 /** 424 * @return the configured OCFL digest algorithm 425 */ 426 public DigestAlgorithm getDefaultDigestAlgorithm() { 427 return FCREPO_DIGEST_ALGORITHM; 428 } 429}