View Javadoc
1   package de.funfried.maven.plugin.zonky;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.sql.Connection;
6   import java.sql.SQLException;
7   import java.sql.Statement;
8   import java.util.HashMap;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.UUID;
12  import java.util.concurrent.TimeoutException;
13  
14  import javax.sql.DataSource;
15  
16  import org.apache.maven.plugin.AbstractMojo;
17  import org.apache.maven.plugin.MojoExecutionException;
18  import org.apache.maven.plugins.annotations.LifecyclePhase;
19  import org.apache.maven.plugins.annotations.Mojo;
20  import org.apache.maven.plugins.annotations.Parameter;
21  import org.apache.maven.project.MavenProject;
22  
23  import de.funfried.maven.plugin.zonky.utils.AlreadyStartedPolicy;
24  import de.funfried.maven.plugin.zonky.utils.MavenProjectUtil;
25  import de.funfried.maven.plugin.zonky.utils.ZonkyUtil;
26  import io.zonky.test.db.postgres.embedded.EmbeddedPostgres;
27  
28  /**
29   * Goal which starts an embedded postgres database.
30   */
31  @Mojo(name = "start", defaultPhase = LifecyclePhase.INITIALIZE, requiresProject = true, threadSafe = false)
32  public class StartEmbeddedPostgresMojo extends AbstractMojo {
33  	/**
34  	 * The port on which the database will be accessible. A value less than or equal to 0 means auto detect a free port. The port is available through the property ${zonky.port}.
35  	 */
36  	@Parameter(defaultValue = "0", property = "port")
37  	private int port;
38  
39  	/**
40  	 * If {@code true}, a create database statement with the given database name will be executed on startup.
41  	 */
42  	@Parameter(defaultValue = "true", property = "createDatabase")
43  	private boolean createDatabase;
44  
45  	/**
46  	 * If {@code true}, only one database instance is used for all multimodule projects, otherwise all projects get their own instance.
47  	 */
48  	@Parameter(defaultValue = "true", property = "singleInstance")
49  	private boolean singleInstance;
50  
51  	/**
52  	 * The database name to write your data. Should not be postgres!
53  	 */
54  	@Parameter(defaultValue = "data", property = "databaseName")
55  	private String databaseName;
56  
57  	/**
58  	 * Define what should be done when the database is already started and the start goal is called again. Choose between:
59  	 * <ul>
60  	 * <li>fail (lets the build fail)</li>
61  	 * <li>reinit (drops the database and if "createDatabase" is true recreates the database again)</li>
62  	 * <li>restart (stops the database and starts it again)</li>
63  	 * <li>ignore (just keeps the current database and does not start a new one)</li>
64  	 * </ul>
65  	 */
66  	@Parameter(defaultValue = "reinit", property = "onAlreadyStarted")
67  	private AlreadyStartedPolicy onAlreadyStarted;
68  
69  	/**
70  	 * The working directory for the embedded database.
71  	 */
72  	@Parameter(defaultValue = "${project.build.directory}/embedded-postgres/work", property = "workingDirectory")
73  	private String workingDirectory;
74  
75  	/**
76  	 * The data directory for the embedded database.
77  	 */
78  	@Parameter(defaultValue = "${project.build.directory}/embedded-postgres/data", property = "dataDirectory")
79  	private String dataDirectory;
80  
81  	/**
82  	 * The maven project.
83  	 */
84  	@Parameter(defaultValue = "${project}", required = true, readonly = true)
85  	private MavenProject project;
86  
87  	/**
88  	 * Contains the full list of projects in the reactor.
89  	 */
90  	@Parameter(defaultValue = "${reactorProjects}", readonly = true, required = true)
91  	private List<MavenProject> reactorProjects;
92  
93  	/**
94  	 * Starts the embedded postgres database.
95  	 *
96  	 * @throws MojoExecutionException if an error occurs
97  	 */
98  	@Override
99  	public void execute() throws MojoExecutionException {
100 		EmbeddedPostgres pg = MavenProjectUtil.getProjectProperty(project, singleInstance ? reactorProjects : null, MavenProjectUtil.PROP_DB_INSTANCE);
101 		if (pg != null) {
102 			if (AlreadyStartedPolicy.fail.equals(onAlreadyStarted)) {
103 				throw new MojoExecutionException("Embedded database already started.");
104 			}
105 
106 			if (AlreadyStartedPolicy.reinit.equals(onAlreadyStarted)) {
107 				DataSource dataSource = pg.getDatabase("postgres", "postgres");
108 				try (Connection connection = dataSource.getConnection()) {
109 					try (Statement stmt = connection.createStatement()) {
110 						stmt.execute("DROP DATABASE \"" + databaseName + "\";");
111 
112 						if (createDatabase) {
113 							stmt.execute("CREATE DATABASE \"" + databaseName + "\";");
114 						}
115 					}
116 				} catch (SQLException ex) {
117 					throw new MojoExecutionException("Failed to reset embedded database", ex);
118 				}
119 
120 				String workDir = MavenProjectUtil.getProjectProperty(project, singleInstance ? reactorProjects : null, MavenProjectUtil.PROP_WORK_DIRECTORY);
121 				String dataDir = MavenProjectUtil.getProjectProperty(project, singleInstance ? reactorProjects : null, MavenProjectUtil.PROP_DATA_DIRECTORY);
122 
123 				File workDirFile = new File(workDir);
124 				File dataDirFile = new File(dataDir);
125 
126 				started(pg, workDirFile, dataDirFile);
127 
128 				getLog().info("Embedded postgres database reinitialized");
129 			} else if (AlreadyStartedPolicy.restart.equals(onAlreadyStarted)) {
130 				try {
131 					String workDir = MavenProjectUtil.getProjectProperty(project, singleInstance ? reactorProjects : null, MavenProjectUtil.PROP_WORK_DIRECTORY);
132 					String dataDir = MavenProjectUtil.getProjectProperty(project, singleInstance ? reactorProjects : null, MavenProjectUtil.PROP_DATA_DIRECTORY);
133 
134 					File workDirFile = new File(workDir);
135 					File dataDirFile = new File(dataDir);
136 
137 					ZonkyUtil.stop(pg, workDirFile, dataDirFile);
138 
139 					start(port, workDirFile, dataDirFile);
140 				} catch (IOException | InterruptedException | TimeoutException ex) {
141 					getLog().error("Failed to stop database", ex);
142 				}
143 			}
144 		} else {
145 			String subDir = UUID.randomUUID().toString();
146 
147 			start(port, new File(workingDirectory, subDir), new File(dataDirectory, subDir));
148 		}
149 	}
150 
151 	private EmbeddedPostgres start(int port, File workingDirectory, File dataDirectory) throws MojoExecutionException {
152 		EmbeddedPostgres pg;
153 
154 		try {
155 			pg = ZonkyUtil.start(port, workingDirectory, dataDirectory);
156 
157 			if (createDatabase) {
158 				DataSource dataSource = pg.getDatabase("postgres", "postgres");
159 				try (Connection connection = dataSource.getConnection()) {
160 					try (Statement stmt = connection.createStatement()) {
161 						stmt.execute("DROP DATABASE IF EXISTS \"" + databaseName + "\";");
162 						stmt.execute("CREATE DATABASE \"" + databaseName + "\";");
163 					}
164 				} catch (SQLException ex) {
165 					throw new MojoExecutionException("Failed to create embedded database '" + databaseName + "'", ex);
166 				}
167 			}
168 
169 			started(pg, workingDirectory, dataDirectory);
170 		} catch (IOException ex) {
171 			throw new MojoExecutionException("Failed to start embedded database", ex);
172 		}
173 
174 		return pg;
175 	}
176 
177 	private void started(EmbeddedPostgres pg, File workingDirectory, File dataDirectory) {
178 		int pgPort = pg.getPort();
179 		String jdbcUrl = pg.getJdbcUrl("postgres", databaseName);
180 
181 		getLog().info("Started embedded postgres database at port " + pgPort + " (JDBC URL: " + jdbcUrl + ")");
182 
183 		Map<String, Object> properties = new HashMap<>();
184 		properties.put(MavenProjectUtil.PROP_HOST, "localhost");
185 		properties.put(MavenProjectUtil.PROP_PORT, pgPort);
186 		properties.put(MavenProjectUtil.PROP_DATABASE, databaseName);
187 		properties.put(MavenProjectUtil.PROP_USERNAME, "postgres");
188 		properties.put(MavenProjectUtil.PROP_PASSWORD, "postgres");
189 		properties.put(MavenProjectUtil.PROP_JDBC_URL, jdbcUrl);
190 		properties.put(MavenProjectUtil.PROP_WORK_DIRECTORY, workingDirectory.getAbsolutePath());
191 		properties.put(MavenProjectUtil.PROP_DATA_DIRECTORY, dataDirectory.getAbsolutePath());
192 		properties.put(MavenProjectUtil.PROP_DB_INSTANCE, pg);
193 
194 		MavenProjectUtil.putProjectProperty(project, singleInstance ? reactorProjects : null, properties);
195 	}
196 }