001package com.avaje.ebean.dbmigration;
002
003import com.avaje.ebean.Transaction;
004import com.avaje.ebean.config.ServerConfig;
005import com.avaje.ebean.dbmigration.model.CurrentModel;
006import com.avaje.ebeaninternal.api.SpiEbeanServer;
007import com.avaje.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
008import org.avaje.dbmigration.ddl.DdlRunner;
009
010import javax.persistence.PersistenceException;
011import java.io.File;
012import java.io.FileReader;
013import java.io.FileWriter;
014import java.io.IOException;
015import java.io.InputStream;
016import java.io.InputStreamReader;
017import java.io.LineNumberReader;
018import java.io.Reader;
019import java.sql.Connection;
020import java.sql.SQLException;
021
022/**
023 * Controls the generation and execution of "Create All" and "Drop All" DDL scripts.
024 *
025 * Typically the "Create All" DDL is executed for running tests etc and has nothing to do
026 * with DB Migration (diff based) DDL.
027 */
028public class DdlGenerator {
029
030  private final SpiEbeanServer server;
031
032  private final boolean generateDdl;
033  private final boolean runDdl;
034  private final boolean createOnly;
035
036  private CurrentModel currentModel;
037  private String dropAllContent;
038  private String createAllContent;
039
040  public DdlGenerator(SpiEbeanServer server, ServerConfig serverConfig) {
041    this.server = server;
042    this.generateDdl = serverConfig.isDdlGenerate();
043    this.runDdl = serverConfig.isDdlRun();
044    this.createOnly = serverConfig.isDdlCreateOnly();
045  }
046
047  /**
048   * Generate the DDL and then run the DDL based on property settings
049   * (ebean.ddl.generate and ebean.ddl.run etc).
050   */
051  public void execute(boolean online) {
052    generateDdl();
053    if (online) {
054      runDdl();
055    }
056  }
057
058  /**
059   * Generate the DDL drop and create scripts if the properties have been set.
060   */
061  protected void generateDdl() {
062    if (generateDdl) {
063      if (!createOnly) {
064        writeDrop(getDropFileName());
065      }
066      writeCreate(getCreateFileName());
067    }
068  }
069
070  /**
071   * Run the DDL drop and DDL create scripts if properties have been set.
072   */
073  protected void runDdl() {
074
075    if (runDdl) {
076      try {
077        runInitSql();
078        runDropSql();
079        runCreateSql();
080        runSeedSql();
081
082      } catch (IOException e) {
083        String msg = "Error reading drop/create script from file system";
084        throw new RuntimeException(msg, e);
085      }
086    }
087  }
088
089  /**
090   * Execute all the DDL statements in the script.
091   */
092  public int runScript(boolean expectErrors, String content, String scriptName) {
093
094    DdlRunner runner = new DdlRunner(expectErrors, scriptName);
095
096    Transaction transaction = server.createTransaction();
097    Connection connection = transaction.getConnection();
098    try {
099      if (expectErrors) {
100        connection.setAutoCommit(true);
101      }
102      int count = runner.runAll(content, connection);
103      if (expectErrors) {
104        connection.setAutoCommit(false);
105      }
106      transaction.commit();
107      return count;
108
109    } catch (SQLException e) {
110      throw new PersistenceException("Failed to run script", e);
111
112    } finally {
113      transaction.end();
114    }
115  }
116
117  protected void runDropSql() throws IOException {
118    if (!createOnly) {
119      if (dropAllContent == null) {
120        dropAllContent = readFile(getDropFileName());
121      }
122      runScript(true, dropAllContent, getDropFileName());
123    }
124  }
125
126  protected void runCreateSql() throws IOException {
127    if (createAllContent == null) {
128      createAllContent = readFile(getCreateFileName());
129    }
130    runScript(false, createAllContent, getCreateFileName());
131
132    String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
133    if (!"true".equalsIgnoreCase(ignoreExtraDdl)) {
134      String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName());
135      if (extraApply != null) {
136        runScript(false, extraApply, "extra-dll");
137      }
138    }
139  }
140
141  protected void runInitSql() throws IOException {
142    runResourceScript(server.getServerConfig().getDdlInitSql());
143  }
144
145  protected void runSeedSql() throws IOException {
146    runResourceScript(server.getServerConfig().getDdlSeedSql());
147  }
148
149  protected void runResourceScript(String sqlScript) throws IOException {
150
151    if (sqlScript != null) {
152      InputStream is = getClassLoader().getResourceAsStream(sqlScript);
153      if (is != null) {
154        String content = readContent(new InputStreamReader(is));
155        runScript(false, content, sqlScript);
156      }
157    }
158  }
159
160  /**
161   * Return the classLoader to use to read sql scripts as resources.
162   */
163  protected ClassLoader getClassLoader() {
164    ClassLoader cl = Thread.currentThread().getContextClassLoader();
165    if (cl == null) {
166      cl = this.getClassLoader();
167    }
168    return cl;
169  }
170
171  protected void writeDrop(String dropFile) {
172
173    try {
174      writeFile(dropFile, generateDropAllDdl());
175    } catch (IOException e) {
176      throw new PersistenceException("Error generating Drop DDL", e);
177    }
178  }
179
180  protected void writeCreate(String createFile) {
181
182    try {
183      writeFile(createFile, generateCreateAllDdl());
184    } catch (IOException e) {
185      throw new PersistenceException("Error generating Create DDL", e);
186    }
187  }
188
189  protected String generateDropAllDdl() {
190
191    try {
192      dropAllContent = currentModel().getDropAllDdl();
193      return dropAllContent;
194    } catch (IOException e) {
195      throw new RuntimeException(e);
196    }
197  }
198
199  protected String generateCreateAllDdl() {
200
201    try {
202      createAllContent = currentModel().getCreateDdl();
203      return createAllContent;
204    } catch (IOException e) {
205      throw new RuntimeException(e);
206    }
207  }
208
209  protected String getDropFileName() {
210    return server.getName() + "-drop-all.sql";
211  }
212
213  protected String getCreateFileName() {
214    return server.getName() + "-create-all.sql";
215  }
216
217  protected CurrentModel currentModel() {
218    if (currentModel == null) {
219      currentModel = new CurrentModel(server);
220    }
221    return currentModel;
222  }
223
224  protected void writeFile(String fileName, String fileContent) throws IOException {
225
226    File f = new File(fileName);
227
228    FileWriter fw = new FileWriter(f);
229    try {
230      fw.write(fileContent);
231      fw.flush();
232    } finally {
233      fw.close();
234    }
235  }
236
237  protected String readFile(String fileName) throws IOException {
238
239    File f = new File(fileName);
240    if (!f.exists()) {
241      return null;
242    }
243
244    return readContent(new FileReader(f));
245  }
246
247  protected String readContent(Reader reader) throws IOException {
248
249    StringBuilder buf = new StringBuilder();
250
251    LineNumberReader lineReader = new LineNumberReader(reader);
252    try {
253      String s;
254      while ((s = lineReader.readLine()) != null) {
255        buf.append(s).append("\n");
256      }
257      return buf.toString();
258
259    } finally {
260      lineReader.close();
261    }
262  }
263
264}