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.time.Instant; 022import java.time.LocalDateTime; 023import java.time.ZoneOffset; 024import java.util.Map; 025 026import javax.annotation.PostConstruct; 027import javax.sql.DataSource; 028 029import org.flywaydb.core.Flyway; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032import org.springframework.beans.factory.annotation.Value; 033import org.springframework.context.annotation.Bean; 034import org.springframework.context.annotation.Configuration; 035import org.springframework.core.convert.converter.Converter; 036import org.springframework.core.convert.converter.ConverterRegistry; 037import org.springframework.core.convert.support.DefaultConversionService; 038import org.springframework.jdbc.datasource.DataSourceTransactionManager; 039import org.springframework.transaction.PlatformTransactionManager; 040import org.springframework.transaction.TransactionDefinition; 041import org.springframework.transaction.annotation.EnableTransactionManagement; 042import org.springframework.transaction.support.DefaultTransactionDefinition; 043import org.springframework.transaction.support.TransactionTemplate; 044 045import com.zaxxer.hikari.HikariDataSource; 046 047/** 048 * @author pwinckles 049 */ 050@EnableTransactionManagement 051@Configuration 052public class DatabaseConfig extends BasePropsConfig { 053 054 private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseConfig.class); 055 056 private static final String H2_FILE = "fcrepo-h2"; 057 058 @Value("${fcrepo.db.url:#{'jdbc:h2:'" + 059 " + fedoraPropsConfig.fedoraData.resolve('" + H2_FILE + "').toAbsolutePath().toString()" + 060 " + ';FILE_LOCK=SOCKET'}}") 061 private String dbUrl; 062 063 @Value("${fcrepo.db.user:}") 064 private String dbUser; 065 066 @Value("${fcrepo.db.password:}") 067 private String dbPassword; 068 069 @Value("${fcrepo.db.max.pool.size:10}") 070 private Integer maxPoolSize; 071 072 @Value("${fcrepo.db.connection.checkout.timeout:30000}") 073 private Integer checkoutTimeout; 074 075 private static final Map<String, String> DB_DRIVER_MAP = Map.of( 076 "h2", "org.h2.Driver", 077 "postgresql", "org.postgresql.Driver", 078 "mariadb", "org.mariadb.jdbc.Driver", 079 "mysql", "com.mysql.cj.jdbc.Driver" 080 ); 081 082 @PostConstruct 083 public void setup() { 084 ((ConverterRegistry) DefaultConversionService.getSharedInstance()) 085 // Adds a converter for mapping local datetimes to instants. This is dubious and not supported 086 // by default because you must make an assumption about the timezone 087 .addConverter(new Converter<LocalDateTime, Instant>() { 088 @Override 089 public Instant convert(final LocalDateTime source) { 090 return source.toInstant(ZoneOffset.UTC); 091 } 092 }); 093 } 094 095 @Bean 096 public DataSource dataSource() throws Exception { 097 final var driver = identifyDbDriver(); 098 099 LOGGER.info("JDBC URL: {}", dbUrl); 100 LOGGER.info("JDBC User: {}", dbUser); 101 LOGGER.info("JDBC Password length: {}", dbPassword == null ? 0 : dbPassword.length()); 102 LOGGER.info("Using database driver: {}", driver); 103 104 final var dataSource = new HikariDataSource(); 105 dataSource.setDriverClassName(driver); 106 dataSource.setJdbcUrl(dbUrl); 107 dataSource.setUsername(dbUser); 108 dataSource.setPassword(dbPassword); 109 dataSource.setConnectionTimeout(checkoutTimeout); 110 dataSource.setMaximumPoolSize(maxPoolSize); 111 112 flyway(dataSource); 113 114 return dataSource; 115 } 116 117 /** 118 * Get the database type in use 119 * @return database type from the connect url. 120 */ 121 private String getDbType() { 122 final var parts = dbUrl.split(":"); 123 124 if (parts.length < 2) { 125 throw new IllegalArgumentException("Invalid DB url: " + dbUrl); 126 } 127 return parts[1].toLowerCase(); 128 } 129 130 private String identifyDbDriver() { 131 final var driver = DB_DRIVER_MAP.get(getDbType()); 132 133 if (driver == null) { 134 throw new IllegalStateException("No database driver found for: " + dbUrl); 135 } 136 137 return driver; 138 } 139 140 @Bean 141 public DataSourceTransactionManager txManager(final DataSource dataSource) { 142 final var txManager = new DataSourceTransactionManager(); 143 txManager.setDataSource(dataSource); 144 return txManager; 145 } 146 147 @Bean 148 public TransactionTemplate txTemplate(final PlatformTransactionManager txManager) { 149 final var txDefinition = new DefaultTransactionDefinition(); 150 txDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); 151 return new TransactionTemplate(txManager, txDefinition); 152 } 153 154 @Bean 155 public Flyway flyway(final DataSource source) throws Exception { 156 LOGGER.debug("Instantiating a new flyway bean"); 157 return FlywayFactory.create().setDataSource(source).setDatabaseType(getDbType()).getObject(); 158 } 159 160}