All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avaje.ebean.dbmigration.DdlGenerator Maven / Gradle / Ivy

package com.avaje.ebean.dbmigration;

import com.avaje.ebean.Transaction;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.model.CurrentModel;
import com.avaje.ebeaninternal.api.SpiEbeanPlugin;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.PersistenceException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * Controls the generation of DDL and potentially runs the resulting scripts.
 */
public class DdlGenerator implements SpiEbeanPlugin {

  private static final Logger logger = LoggerFactory.getLogger(DdlGenerator.class);

  private SpiEbeanServer server;

  private boolean generateDdl;
  private boolean runDdl;

  private CurrentModel currentModel;
  private String dropContent;
  private String createContent;

  public void setup(SpiEbeanServer server, ServerConfig serverConfig) {
    this.server = server;
    this.generateDdl = serverConfig.isDdlGenerate();
    this.runDdl = serverConfig.isDdlRun();
  }

  /**
   * Generate the DDL and then run the DDL based on property settings
   * (ebean.ddl.generate and ebean.ddl.run etc).
   */
  public void execute(boolean online) {
    generateDdl();
    if (online) {
      runDdl();
    }
  }

  /**
   * Generate the DDL drop and create scripts if the properties have been set.
   */
  public void generateDdl() {
    if (generateDdl) {
      writeDrop(getDropFileName());
      writeCreate(getCreateFileName());
    }
  }

  /**
   * Run the DDL drop and DDL create scripts if properties have been set.
   */
  public void runDdl() {

    if (runDdl) {
      try {
        if (dropContent == null) {
          dropContent = readFile(getDropFileName());
        }
        if (createContent == null) {
          createContent = readFile(getCreateFileName());
        }
        runScript(true, dropContent);
        runScript(false, createContent);

      } catch (IOException e) {
        String msg = "Error reading drop/create script from file system";
        throw new RuntimeException(msg, e);
      }
    }
  }

  protected void writeDrop(String dropFile) {

    try {
      String c = generateDropDdl();
      writeFile(dropFile, c);
    } catch (IOException e) {
      throw new PersistenceException("Error generating Drop DDL", e);
    }
  }

  protected void writeCreate(String createFile) {

    try {
      String c = generateCreateDdl();
      writeFile(createFile, c);
    } catch (IOException e) {
      throw new PersistenceException("Error generating Create DDL", e);
    }
  }

  public String generateDropDdl() {

    try {
      dropContent = currentModel().getDropDdl();
      return dropContent;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public String generateCreateDdl() {

    try {
      createContent = currentModel().getCreateDdl();
      return createContent;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  protected String getDropFileName() {
    return server.getName() + "-drop-all.sql";
  }

  protected String getCreateFileName() {
    return server.getName() + "-create-all.sql";
  }

  protected CurrentModel currentModel() {
    if (currentModel == null) {
      currentModel = new CurrentModel(server);
    }
    return currentModel;
  }

  protected void writeFile(String fileName, String fileContent) throws IOException {

    File f = new File(fileName);

    FileWriter fw = new FileWriter(f);
    try {
      fw.write(fileContent);
      fw.flush();
    } finally {
      fw.close();
    }
  }

  protected String readFile(String fileName) throws IOException {

    File f = new File(fileName);
    if (!f.exists()) {
      return null;
    }

    StringBuilder buf = new StringBuilder();

    FileReader fr = new FileReader(f);
    LineNumberReader lr = new LineNumberReader(fr);
    try {
      String s;
      while ((s = lr.readLine()) != null) {
        buf.append(s).append("\n");
      }
    } finally {
      lr.close();
    }

    return buf.toString();
  }

  /**
   * Execute all the DDL statements in the script.
   */
  public void runScript(boolean expectErrors, String content) {

    StringReader sr = new StringReader(content);
    List statements = parseStatements(sr);

    Transaction t = server.createTransaction();
    try {
      Connection connection = t.getConnection();

      logger.info("Running DDL");

      runStatements(expectErrors, statements, connection);

      logger.info("Running DDL Complete");

      t.commit();

    } catch (Exception e) {
      throw new PersistenceException("Error: " + e.getMessage(), e);
    } finally {
      t.end();
    }
  }

  /**
   * Execute the list of statements.
   */
  private void runStatements(boolean expectErrors, List statements, Connection c) {
    List noDuplicates = new ArrayList();

    for (String statement : statements) {
      if (!noDuplicates.contains(statement)) {
        noDuplicates.add(statement);
      }
    }

    for (int i = 0; i < noDuplicates.size(); i++) {
      String xOfy = (i + 1) + " of " + noDuplicates.size();
      runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
    }
  }

  /**
   * Execute the statement.
   */
  private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {

    PreparedStatement pstmt = null;
    try {

      // trim and remove trailing ; or /
      stmt = stmt.trim();
      if (stmt.endsWith(";")) {
        stmt = stmt.substring(0, stmt.length() - 1);
      } else if (stmt.endsWith("/")) {
        stmt = stmt.substring(0, stmt.length() - 1);
      }

      logger.info("executing " + oneOf + " " + getSummary(stmt));

      pstmt = c.prepareStatement(stmt);
      pstmt.execute();

    } catch (Exception e) {
      if (expectErrors) {
        logger.info(" ... ignoring error executing " + getSummary(stmt) + "  error: " + e.getMessage());
      } else {
        String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
        throw new RuntimeException(msg, e);
      }
    } finally {
      if (pstmt != null) {
        try {
          pstmt.close();
        } catch (SQLException e) {
          logger.error("Error closing pstmt", e);
        }
      }
    }
  }

  /**
   * Local utility used to detect the end of statements / separate statements.
   * This is often just the semicolon character but for trigger/procedures this
   * detects the $$ demarcation used in the history DDL generation for MySql and
   * Postgres.
   */
  static class StatementsSeparator {

    ArrayList statements = new ArrayList();

    boolean trimDelimiter;

    boolean inDbProcedure;

    StringBuilder sb = new StringBuilder();

    void lineContainsDollars(String line) {
      if (inDbProcedure) {
        if (trimDelimiter) {
          line = line.replace("$$","");
        }
        endOfStatement(line);
      } else {
        // MySql style delimiter needs to be trimmed/removed
        trimDelimiter = line.equals("delimiter $$");
        if (!trimDelimiter) {
          sb.append(line).append(" ");
        }
      }
      inDbProcedure = !inDbProcedure;
    }

    void endOfStatement(String line) {
      // end of Db procedure
      sb.append(line);
      statements.add(sb.toString().trim());
      sb = new StringBuilder();
    }

    void nextLine(String line) {

      if (line.contains("$$")) {
        lineContainsDollars(line);
        return;
      }

      if (inDbProcedure) {
        sb.append(line).append(" ");
        return;
      }

      int semiPos = line.indexOf(';');
      if (semiPos == -1) {
        sb.append(line).append(" ");

      } else if (semiPos == line.length() - 1) {
        // semicolon at end of line
        endOfStatement(line);

      } else {
        // semicolon in middle of line
        String preSemi = line.substring(0, semiPos);
        endOfStatement(preSemi);
        sb.append(line.substring(semiPos + 1));
      }
    }
  }

  /**
   * Break up the sql in reader into a list of statements using the semi-colon
   * character;
   */
  protected List parseStatements(StringReader reader) {

    try {
      BufferedReader br = new BufferedReader(reader);
      StatementsSeparator statements = new StatementsSeparator();

      String s;
      while ((s = br.readLine()) != null) {
        s = s.trim();
        statements.nextLine(s);
      }

      return statements.statements;

    } catch (IOException e) {
      throw new PersistenceException(e);
    }
  }

  private String getSummary(String s) {
    if (s.length() > 80) {
      return s.substring(0, 80).trim() + "...";
    }
    return s;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy