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.annotation.EnableTransactionManagement; 040 041import com.mchange.v2.c3p0.ComboPooledDataSource; 042 043/** 044 * @author pwinckles 045 */ 046@EnableTransactionManagement 047@Configuration 048public class DatabaseConfig extends BasePropsConfig { 049 050 private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseConfig.class); 051 052 private static final String H2_FILE = "fcrepo-h2"; 053 054 @Value("${fcrepo.db.url:#{'jdbc:h2:'" + 055 " + fedoraPropsConfig.fedoraData.resolve('" + H2_FILE + "').toAbsolutePath().toString()" + 056 " + ';FILE_LOCK=SOCKET'}}") 057 private String dbUrl; 058 059 @Value("${fcrepo.db.user:}") 060 private String dbUser; 061 062 @Value("${fcrepo.db.password:}") 063 private String dbPassword; 064 065 @Value("${fcrepo.db.max.pool.size:15}") 066 private Integer maxPoolSize; 067 068 @Value("${fcrepo.db.connection.checkout.timeout:10000}") 069 private Integer checkoutTimeout; 070 071 @Value("${fcrepo.db.connection.idle.test.period:300}") 072 private Integer idleConnectionTestPeriod; 073 074 @Value("${fcrepo.db.connection.test.on.checkout:true}") 075 private boolean testConnectionOnCheckout; 076 077 private static final Map<String, String> DB_DRIVER_MAP = Map.of( 078 "h2", "org.h2.Driver", 079 "postgresql", "org.postgresql.Driver", 080 "mariadb", "org.mariadb.jdbc.Driver", 081 "mysql", "com.mysql.cj.jdbc.Driver" 082 ); 083 084 @PostConstruct 085 public void setup() { 086 ((ConverterRegistry) DefaultConversionService.getSharedInstance()) 087 // Adds a converter for mapping local datetimes to instants. This is dubious and not supported 088 // by default because you must make an assumption about the timezone 089 .addConverter(new Converter<LocalDateTime, Instant>() { 090 @Override 091 public Instant convert(final LocalDateTime source) { 092 return source.toInstant(ZoneOffset.UTC); 093 } 094 }); 095 } 096 097 @Bean 098 public DataSource dataSource() throws Exception { 099 final var driver = identifyDbDriver(); 100 101 LOGGER.debug("JDBC URL: {}", dbUrl); 102 LOGGER.debug("Using database driver: {}", driver); 103 104 final var dataSource = new ComboPooledDataSource(); 105 dataSource.setDriverClass(driver); 106 dataSource.setJdbcUrl(dbUrl); 107 dataSource.setUser(dbUser); 108 dataSource.setPassword(dbPassword); 109 dataSource.setCheckoutTimeout(checkoutTimeout); 110 dataSource.setMaxPoolSize(maxPoolSize); 111 dataSource.setIdleConnectionTestPeriod(idleConnectionTestPeriod); 112 dataSource.setTestConnectionOnCheckout(testConnectionOnCheckout); 113 114 flyway(dataSource); 115 116 return dataSource; 117 } 118 119 /** 120 * Get the database type in use 121 * @return database type from the connect url. 122 */ 123 private String getDbType() { 124 final var parts = dbUrl.split(":"); 125 126 if (parts.length < 2) { 127 throw new IllegalArgumentException("Invalid DB url: " + dbUrl); 128 } 129 return parts[1].toLowerCase(); 130 } 131 132 private String identifyDbDriver() { 133 final var driver = DB_DRIVER_MAP.get(getDbType()); 134 135 if (driver == null) { 136 throw new IllegalStateException("No database driver found for: " + dbUrl); 137 } 138 139 return driver; 140 } 141 142 @Bean 143 public DataSourceTransactionManager txManager(final DataSource dataSource) { 144 final var txManager = new DataSourceTransactionManager(); 145 txManager.setDataSource(dataSource); 146 return txManager; 147 } 148 149 @Bean 150 public Flyway flyway(final DataSource source) throws Exception { 151 LOGGER.debug("Instantiating a new flyway bean"); 152 return FlywayFactory.create().setDataSource(source).setDatabaseType(getDbType()).getObject(); 153 } 154 155}