001package ball.spring.mysqld; 002/*- 003 * ########################################################################## 004 * Reusable Spring Components 005 * %% 006 * Copyright (C) 2018 - 2022 Allen D. Ball 007 * %% 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 * ########################################################################## 020 */ 021import java.io.File; 022import java.io.IOException; 023import java.lang.ProcessBuilder.Redirect; 024import java.nio.file.Files; 025import javax.annotation.PostConstruct; 026import javax.annotation.PreDestroy; 027import lombok.NoArgsConstructor; 028import lombok.ToString; 029import lombok.extern.log4j.Log4j2; 030import org.springframework.beans.factory.annotation.Value; 031import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 032import org.springframework.context.annotation.Bean; 033import org.springframework.context.annotation.Configuration; 034import org.springframework.scheduling.annotation.EnableScheduling; 035 036import static java.util.concurrent.TimeUnit.SECONDS; 037import static org.apache.commons.lang3.StringUtils.EMPTY; 038 039/** 040 * {@code mysqld} {@link Configuration}. A {@code mysqld} process is 041 * started if the {@code mysqld.home} application property is set. In 042 * addition, a port must be specified with the {@code mysqld.port} property. 043 * 044 * {@injected.fields} 045 * 046 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 047 */ 048@Configuration 049@EnableScheduling 050@ConditionalOnProperty(name = "mysqld.home", havingValue = EMPTY) 051@NoArgsConstructor @ToString @Log4j2 052public class MysqldConfiguration { 053 @Value("${mysqld.home}") 054 private File home; 055 056 @Value("${mysqld.defaults.file:${mysqld.home}/my.cnf}") 057 private File defaults; 058 059 @Value("${mysqld.datadir:${mysqld.home}/data}") 060 private File datadir; 061 062 @Value("${mysqld.port}") 063 private Integer port; 064 065 @Value("${mysqld.socket:${mysqld.home}/socket}") 066 private File socket; 067 068 @Value("${logging.file.path}/mysqld.log") 069 private File console; 070 071 private volatile Process mysqld = null; 072 073 @PostConstruct 074 public void init() { } 075 076 @Bean 077 public Process mysqld() throws IOException { 078 if (mysqld == null) { 079 synchronized (this) { 080 if (mysqld == null) { 081 Files.createDirectories(home.toPath()); 082 Files.createDirectories(datadir.toPath().getParent()); 083 Files.createDirectories(console.toPath().getParent()); 084 085 String defaultsArg = "--no-defaults"; 086 087 if (defaults.exists()) { 088 defaultsArg = "--defaults-file=" + defaults.getAbsolutePath(); 089 } 090 091 String datadirArg = "--datadir=" + datadir.getAbsolutePath(); 092 String socketArg = "--socket=" + socket.getAbsolutePath(); 093 String portArg = "--port=" + port; 094 095 if (! datadir.exists()) { 096 try { 097 new ProcessBuilder("mysqld", defaultsArg, datadirArg, "--initialize-insecure") 098 .directory(home) 099 .inheritIO() 100 .redirectOutput(Redirect.to(console)) 101 .redirectErrorStream(true) 102 .start() 103 .waitFor(); 104 } catch (InterruptedException exception) { 105 } 106 } 107 108 if (datadir.exists()) { 109 socket.delete(); 110 111 mysqld = 112 new ProcessBuilder("mysqld", defaultsArg, datadirArg, socketArg, portArg) 113 .directory(home) 114 .inheritIO() 115 .redirectOutput(Redirect.appendTo(console)) 116 .redirectErrorStream(true) 117 .start(); 118 119 while (! socket.exists()) { 120 try { 121 mysqld.waitFor(15, SECONDS); 122 } catch (InterruptedException exception) { 123 } 124 125 if (mysqld.isAlive()) { 126 continue; 127 } else { 128 throw new IllegalStateException("mysqld not started"); 129 } 130 } 131 } else { 132 throw new IllegalStateException("mysqld datadir does not exist"); 133 } 134 } 135 } 136 } 137 138 return mysqld; 139 } 140 141 @PreDestroy 142 public void destroy() { 143 if (mysqld != null) { 144 try { 145 for (int i = 0; i < 8; i+= 1) { 146 if (mysqld.isAlive()) { 147 mysqld.destroy(); 148 mysqld.waitFor(15, SECONDS); 149 } else { 150 break; 151 } 152 } 153 } catch (InterruptedException exception) { 154 } 155 156 try { 157 if (mysqld.isAlive()) { 158 mysqld.destroyForcibly().waitFor(60, SECONDS); 159 } 160 } catch (InterruptedException exception) { 161 } 162 } 163 } 164}