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