001package io.ebeaninternal.dbmigration.model;
002
003import io.ebean.config.DbConstraintNaming;
004import io.ebean.config.dbplatform.DatabasePlatform;
005import io.ebeaninternal.api.SpiEbeanServer;
006import io.ebeaninternal.dbmigration.Detect;
007import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
008import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions;
009import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
010import io.ebeaninternal.dbmigration.migration.ChangeSet;
011import io.ebeaninternal.dbmigration.model.build.ModelBuildBeanVisitor;
012import io.ebeaninternal.dbmigration.model.build.ModelBuildContext;
013import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing;
014import io.ebeaninternal.extraddl.model.DdlScript;
015import io.ebeaninternal.extraddl.model.ExtraDdl;
016import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
017import io.ebeaninternal.dbmigration.ddlgeneration.PlatformDdlBuilder;
018
019import java.io.IOException;
020import java.util.List;
021
022import static io.ebeaninternal.api.PlatformMatch.matchPlatform;
023
024/**
025 * Reads EbeanServer bean descriptors to build the current model.
026 */
027public class CurrentModel {
028
029  private final SpiEbeanServer server;
030  private final DatabasePlatform databasePlatform;
031  private final DbConstraintNaming constraintNaming;
032  private final boolean platformTypes;
033  private final boolean jaxbPresent;
034  private final String ddlHeader;
035  private final DdlOptions ddlOptions = new DdlOptions();
036
037  private ModelContainer model;
038  private ChangeSet changeSet;
039  private DdlWrite writer;
040
041  /**
042   * Construct with a given EbeanServer instance for DDL create all generation, not migration.
043   */
044  public CurrentModel(SpiEbeanServer server) {
045    this(server, server.config().getConstraintNaming(), true);
046  }
047
048  /**
049   * Construct with a given EbeanServer, platformDdl and constraintNaming convention.
050   * <p>
051   * Note the EbeanServer is just used to read the BeanDescriptors and platformDdl supplies
052   * the platform specific handling on
053   * </p>
054   */
055  public CurrentModel(SpiEbeanServer server, DbConstraintNaming constraintNaming) {
056    this(server, constraintNaming, false);
057  }
058
059  private CurrentModel(SpiEbeanServer server, DbConstraintNaming constraintNaming, boolean platformTypes) {
060    this.server = server;
061    this.databasePlatform = server.databasePlatform();
062    this.constraintNaming = constraintNaming;
063    this.platformTypes = platformTypes;
064    this.ddlHeader = server.config().getDdlHeader();
065    this.jaxbPresent = Detect.isJAXBPresent(server.config());
066  }
067
068  public DdlOptions getDdlOptions() {
069    return ddlOptions;
070  }
071
072  /**
073   * Return true if the model contains tables that are partitioned.
074   */
075  public boolean isTablePartitioning() {
076    return read().isTablePartitioning();
077  }
078
079  /**
080   * Return the tables that have partitioning.
081   */
082  public List<MTable> getPartitionedTables() {
083    return read().getPartitionedTables();
084  }
085
086  /**
087   * Return the current model by reading all the bean descriptors and properties.
088   */
089  public ModelContainer read() {
090    if (model == null) {
091      model = new ModelContainer();
092      ModelBuildContext context = new ModelBuildContext(model, databasePlatform, constraintNaming, platformTypes);
093      ModelBuildBeanVisitor visitor = new ModelBuildBeanVisitor(context);
094      VisitAllUsing visit = new VisitAllUsing(visitor, server);
095      visit.visitAllBeans();
096      // adjust the foreign keys on the 'draft' tables
097      context.adjustDraftReferences();
098    }
099    return model;
100  }
101
102  public void setChangeSet(ChangeSet changeSet) {
103    this.changeSet = changeSet;
104  }
105
106  /**
107   * Return as a ChangeSet.
108   */
109  public ChangeSet getChangeSet() {
110    read();
111    if (changeSet == null) {
112      changeSet = asChangeSet();
113    }
114    return changeSet;
115  }
116
117  /**
118   * Return the 'Create' DDL.
119   */
120  public String getCreateDdl() {
121
122    createDdl();
123
124    StringBuilder ddl = new StringBuilder(2000);
125    if (ddlHeader != null && !ddlHeader.isEmpty()) {
126      ddl.append(ddlHeader).append('\n');
127    }
128    if (jaxbPresent) {
129      addExtraDdl(ddl, ExtraDdlXmlReader.readBuiltin(), "-- init script ");
130    }
131
132    try {
133      writer.writeApply(ddl);
134    } catch (IOException e) { // should not happen on StringBuilder
135      throw new RuntimeException(e);
136    }
137
138    return ddl.toString();
139  }
140
141  private void addExtraDdl(StringBuilder ddl, ExtraDdl extraDdl, String prefix) {
142    if (extraDdl != null) {
143      List<DdlScript> ddlScript = extraDdl.getDdlScript();
144      for (DdlScript script : ddlScript) {
145        if (script.isInit() && matchPlatform(server.platform(), script.getPlatforms())) {
146          ddl.append(prefix).append(script.getName()).append('\n');
147          ddl.append(script.getValue());
148        }
149      }
150    }
151  }
152
153  /**
154   * Return the 'Drop' DDL.
155   */
156  public String getDropAllDdl() {
157
158    createDdl();
159
160    StringBuilder ddl = new StringBuilder(2000);
161    if (ddlHeader != null && !ddlHeader.isEmpty()) {
162      ddl.append(ddlHeader).append('\n');
163    }
164    try {
165      writer.writeDropAll(ddl);
166    } catch (IOException e) { // should not happen on StringBuilder
167      throw new RuntimeException(e);
168    }
169    return ddl.toString();
170  }
171
172  /**
173   * Create all the DDL based on the changeSet.
174   */
175  private void createDdl() {
176    if (writer == null) {
177      ChangeSet createChangeSet = getChangeSet();
178      writer = new DdlWrite(new MConfiguration(), model, ddlOptions);
179      DdlHandler handler = handler();
180      handler.generateProlog(writer);
181      handler.generate(writer, createChangeSet);
182      handler.generateEpilog(writer);
183    }
184  }
185
186  /**
187   * Return the platform specific DdlHandler (to generate DDL).
188   */
189  private DdlHandler handler() {
190    return PlatformDdlBuilder.create(databasePlatform).createDdlHandler(server.config());
191  }
192
193  /**
194   * Convert the model into a ChangeSet.
195   */
196  private ChangeSet asChangeSet() {
197    // empty diff so changes will effectively all be create
198    ModelDiff diff = new ModelDiff();
199    diff.compareTo(model);
200    return diff.getApplyChangeSet();
201  }
202
203}