| 1 |  |  package liquibase.changelog.visitor; | 
  | 2 |  |   | 
  | 3 |  |  import java.io.File; | 
  | 4 |  |  import java.io.FileOutputStream; | 
  | 5 |  |  import java.io.IOException; | 
  | 6 |  |  import java.io.InputStream; | 
  | 7 |  |  import java.util.ArrayList; | 
  | 8 |  |  import java.util.HashMap; | 
  | 9 |  |  import java.util.List; | 
  | 10 |  |  import java.util.Map; | 
  | 11 |  |  import java.util.Set; | 
  | 12 |  |  import java.util.SortedSet; | 
  | 13 |  |  import java.util.TreeSet; | 
  | 14 |  |   | 
  | 15 |  |  import liquibase.change.Change; | 
  | 16 |  |  import liquibase.changelog.ChangeSet; | 
  | 17 |  |  import liquibase.changelog.DatabaseChangeLog; | 
  | 18 |  |  import liquibase.database.Database; | 
  | 19 |  |  import liquibase.database.structure.Column; | 
  | 20 |  |  import liquibase.database.structure.DatabaseObject; | 
  | 21 |  |  import liquibase.database.structure.Table; | 
  | 22 |  |  import liquibase.dbdoc.AuthorListWriter; | 
  | 23 |  |  import liquibase.dbdoc.AuthorWriter; | 
  | 24 |  |  import liquibase.dbdoc.ChangeLogListWriter; | 
  | 25 |  |  import liquibase.dbdoc.ChangeLogWriter; | 
  | 26 |  |  import liquibase.dbdoc.ColumnWriter; | 
  | 27 |  |  import liquibase.dbdoc.HTMLWriter; | 
  | 28 |  |  import liquibase.dbdoc.PendingChangesWriter; | 
  | 29 |  |  import liquibase.dbdoc.PendingSQLWriter; | 
  | 30 |  |  import liquibase.dbdoc.RecentChangesWriter; | 
  | 31 |  |  import liquibase.dbdoc.TableListWriter; | 
  | 32 |  |  import liquibase.dbdoc.TableWriter; | 
  | 33 |  |  import liquibase.exception.DatabaseException; | 
  | 34 |  |  import liquibase.exception.DatabaseHistoryException; | 
  | 35 |  |  import liquibase.exception.LiquibaseException; | 
  | 36 |  |  import liquibase.resource.ResourceAccessor; | 
  | 37 |  |  import liquibase.snapshot.DatabaseSnapshot; | 
  | 38 |  |  import liquibase.snapshot.DatabaseSnapshotGeneratorFactory; | 
  | 39 |  |  import liquibase.util.StreamUtil; | 
  | 40 |  |   | 
  | 41 |  |  public class DBDocVisitor implements ChangeSetVisitor { | 
  | 42 |  |   | 
  | 43 |  |      private Database database; | 
  | 44 |  |   | 
  | 45 |  |      private SortedSet<ChangeLogInfo> changeLogs; | 
  | 46 |  |      private Map<DatabaseObject, List<Change>> changesByObject; | 
  | 47 |  |      private Map<String, List<Change>> changesByAuthor; | 
  | 48 |  |   | 
  | 49 |  |      private Map<DatabaseObject, List<Change>> changesToRunByObject; | 
  | 50 |  |      private Map<String, List<Change>> changesToRunByAuthor; | 
  | 51 |  |      private List<Change> changesToRun; | 
  | 52 |  |      private List<Change> recentChanges; | 
  | 53 |  |   | 
  | 54 |  |      private String rootChangeLogName; | 
  | 55 |  |      private DatabaseChangeLog rootChangeLog; | 
  | 56 |  |   | 
  | 57 |  |      private static final int MAX_RECENT_CHANGE = 50; | 
  | 58 |  |   | 
  | 59 | 0 |      public DBDocVisitor(Database database) { | 
  | 60 | 0 |          this.database = database; | 
  | 61 |  |   | 
  | 62 | 0 |          changesByObject = new HashMap<DatabaseObject, List<Change>>(); | 
  | 63 | 0 |          changesByAuthor = new HashMap<String, List<Change>>(); | 
  | 64 | 0 |          changeLogs = new TreeSet<ChangeLogInfo>(); | 
  | 65 |  |   | 
  | 66 | 0 |          changesToRunByObject = new HashMap<DatabaseObject, List<Change>>(); | 
  | 67 | 0 |          changesToRunByAuthor = new HashMap<String, List<Change>>(); | 
  | 68 | 0 |          changesToRun = new ArrayList<Change>(); | 
  | 69 | 0 |          recentChanges = new ArrayList<Change>(); | 
  | 70 | 0 |      } | 
  | 71 |  |   | 
  | 72 |  |      @Override | 
  | 73 |  |      public ChangeSetVisitor.Direction getDirection() { | 
  | 74 | 0 |          return ChangeSetVisitor.Direction.FORWARD; | 
  | 75 |  |      } | 
  | 76 |  |   | 
  | 77 |  |      @Override | 
  | 78 |  |      public void visit(ChangeSet changeSet, DatabaseChangeLog databaseChangeLog, Database database) | 
  | 79 |  |              throws LiquibaseException { | 
  | 80 | 0 |          ChangeSet.RunStatus runStatus = this.database.getRunStatus(changeSet); | 
  | 81 | 0 |          if (rootChangeLogName == null) { | 
  | 82 | 0 |              rootChangeLogName = changeSet.getFilePath(); | 
  | 83 |  |          } | 
  | 84 |  |   | 
  | 85 | 0 |          if (rootChangeLog == null) { | 
  | 86 | 0 |              this.rootChangeLog = databaseChangeLog; | 
  | 87 |  |          } | 
  | 88 |  |   | 
  | 89 | 0 |          if (!changesByAuthor.containsKey(changeSet.getAuthor())) { | 
  | 90 | 0 |              changesByAuthor.put(changeSet.getAuthor(), new ArrayList<Change>()); | 
  | 91 |  |          } | 
  | 92 | 0 |          if (!changesToRunByAuthor.containsKey(changeSet.getAuthor())) { | 
  | 93 | 0 |              changesToRunByAuthor.put(changeSet.getAuthor(), new ArrayList<Change>()); | 
  | 94 |  |          } | 
  | 95 |  |   | 
  | 96 | 0 |          boolean toRun = runStatus.equals(ChangeSet.RunStatus.NOT_RAN) | 
  | 97 |  |                  || runStatus.equals(ChangeSet.RunStatus.RUN_AGAIN); | 
  | 98 | 0 |          for (Change change : changeSet.getChanges()) { | 
  | 99 | 0 |              if (toRun) { | 
  | 100 | 0 |                  changesToRunByAuthor.get(changeSet.getAuthor()).add(change); | 
  | 101 | 0 |                  changesToRun.add(change); | 
  | 102 |  |              } else { | 
  | 103 | 0 |                  changesByAuthor.get(changeSet.getAuthor()).add(change); | 
  | 104 | 0 |                  recentChanges.add(0, change); | 
  | 105 |  |              } | 
  | 106 |  |          } | 
  | 107 |  |   | 
  | 108 | 0 |          ChangeLogInfo changeLogInfo = new ChangeLogInfo(changeSet.getFilePath(), | 
  | 109 |  |                  databaseChangeLog.getPhysicalFilePath()); | 
  | 110 | 0 |          if (!changeLogs.contains(changeLogInfo)) { | 
  | 111 | 0 |              changeLogs.add(changeLogInfo); | 
  | 112 |  |          } | 
  | 113 |  |   | 
  | 114 | 0 |          for (Change change : changeSet.getChanges()) { | 
  | 115 | 0 |              Set<DatabaseObject> affectedDatabaseObjects = change.getAffectedDatabaseObjects(database); | 
  | 116 | 0 |              if (affectedDatabaseObjects != null) { | 
  | 117 | 0 |                  for (DatabaseObject dbObject : affectedDatabaseObjects) { | 
  | 118 | 0 |                      if (toRun) { | 
  | 119 | 0 |                          if (!changesToRunByObject.containsKey(dbObject)) { | 
  | 120 | 0 |                              changesToRunByObject.put(dbObject, new ArrayList<Change>()); | 
  | 121 |  |                          } | 
  | 122 | 0 |                          changesToRunByObject.get(dbObject).add(change); | 
  | 123 |  |                      } | 
  | 124 |  |   | 
  | 125 | 0 |                      if (!changesByObject.containsKey(dbObject)) { | 
  | 126 | 0 |                          changesByObject.put(dbObject, new ArrayList<Change>()); | 
  | 127 |  |                      } | 
  | 128 | 0 |                      changesByObject.get(dbObject).add(change); | 
  | 129 |  |                  } | 
  | 130 |  |              } | 
  | 131 | 0 |          } | 
  | 132 | 0 |      } | 
  | 133 |  |   | 
  | 134 |  |      public void writeHTML(File rootOutputDir, ResourceAccessor resourceAccessor) throws IOException, DatabaseException, | 
  | 135 |  |              DatabaseHistoryException { | 
  | 136 | 0 |          ChangeLogWriter changeLogWriter = new ChangeLogWriter(resourceAccessor, rootOutputDir); | 
  | 137 | 0 |          HTMLWriter authorWriter = new AuthorWriter(rootOutputDir, database); | 
  | 138 | 0 |          HTMLWriter tableWriter = new TableWriter(rootOutputDir, database); | 
  | 139 | 0 |          HTMLWriter columnWriter = new ColumnWriter(rootOutputDir, database); | 
  | 140 | 0 |          HTMLWriter pendingChangesWriter = new PendingChangesWriter(rootOutputDir, database); | 
  | 141 | 0 |          HTMLWriter recentChangesWriter = new RecentChangesWriter(rootOutputDir, database); | 
  | 142 | 0 |          HTMLWriter pendingSQLWriter = new PendingSQLWriter(rootOutputDir, database, rootChangeLog); | 
  | 143 |  |   | 
  | 144 | 0 |          copyFile("liquibase/dbdoc/stylesheet.css", rootOutputDir); | 
  | 145 | 0 |          copyFile("liquibase/dbdoc/index.html", rootOutputDir); | 
  | 146 | 0 |          copyFile("liquibase/dbdoc/globalnav.html", rootOutputDir); | 
  | 147 | 0 |          copyFile("liquibase/dbdoc/overview-summary.html", rootOutputDir); | 
  | 148 |  |   | 
  | 149 | 0 |          DatabaseSnapshot snapshot = DatabaseSnapshotGeneratorFactory.getInstance().createSnapshot(database, null, null); | 
  | 150 |  |   | 
  | 151 | 0 |          new ChangeLogListWriter(rootOutputDir).writeHTML(changeLogs); | 
  | 152 | 0 |          new TableListWriter(rootOutputDir).writeHTML(new TreeSet<Object>(snapshot.getTables())); | 
  | 153 | 0 |          new AuthorListWriter(rootOutputDir).writeHTML(new TreeSet<Object>(changesByAuthor.keySet())); | 
  | 154 |  |   | 
  | 155 | 0 |          for (String author : changesByAuthor.keySet()) { | 
  | 156 | 0 |              authorWriter.writeHTML(author, changesByAuthor.get(author), changesToRunByAuthor.get(author), | 
  | 157 |  |                      rootChangeLogName); | 
  | 158 |  |          } | 
  | 159 |  |   | 
  | 160 | 0 |          for (Table table : snapshot.getTables()) { | 
  | 161 | 0 |              tableWriter | 
  | 162 |  |                      .writeHTML(table, changesByObject.get(table), changesToRunByObject.get(table), rootChangeLogName); | 
  | 163 |  |          } | 
  | 164 |  |   | 
  | 165 | 0 |          for (Column column : snapshot.getColumns()) { | 
  | 166 | 0 |              columnWriter.writeHTML(column, changesByObject.get(column), changesToRunByObject.get(column), | 
  | 167 |  |                      rootChangeLogName); | 
  | 168 |  |          } | 
  | 169 |  |   | 
  | 170 | 0 |          for (ChangeLogInfo changeLog : changeLogs) { | 
  | 171 | 0 |              changeLogWriter.writeChangeLog(changeLog.logicalPath, changeLog.physicalPath); | 
  | 172 |  |          } | 
  | 173 |  |   | 
  | 174 | 0 |          pendingChangesWriter.writeHTML("index", null, changesToRun, rootChangeLogName); | 
  | 175 | 0 |          pendingSQLWriter.writeHTML("sql", null, changesToRun, rootChangeLogName); | 
  | 176 |  |   | 
  | 177 | 0 |          if (recentChanges.size() > MAX_RECENT_CHANGE) { | 
  | 178 | 0 |              recentChanges = recentChanges.subList(0, MAX_RECENT_CHANGE); | 
  | 179 |  |          } | 
  | 180 | 0 |          recentChangesWriter.writeHTML("index", recentChanges, null, rootChangeLogName); | 
  | 181 |  |   | 
  | 182 | 0 |      } | 
  | 183 |  |   | 
  | 184 |  |      private void copyFile(String fileToCopy, File rootOutputDir) throws IOException { | 
  | 185 | 0 |          InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileToCopy); | 
  | 186 | 0 |          FileOutputStream outputStream = null; | 
  | 187 |  |          try { | 
  | 188 | 0 |              if (inputStream == null) { | 
  | 189 | 0 |                  throw new IOException("Can not find " + fileToCopy); | 
  | 190 |  |              } | 
  | 191 | 0 |              outputStream = new FileOutputStream(new File(rootOutputDir, fileToCopy.replaceFirst(".*\\/", "")), false); | 
  | 192 | 0 |              StreamUtil.copy(inputStream, outputStream); | 
  | 193 |  |          } finally { | 
  | 194 | 0 |              if (outputStream != null) { | 
  | 195 | 0 |                  outputStream.close(); | 
  | 196 |  |              } | 
  | 197 |  |          } | 
  | 198 | 0 |      } | 
  | 199 |  |   | 
  | 200 | 0 |      private static class ChangeLogInfo implements Comparable<ChangeLogInfo> { | 
  | 201 |  |          public String logicalPath; | 
  | 202 |  |          public String physicalPath; | 
  | 203 |  |   | 
  | 204 | 0 |          private ChangeLogInfo(String logicalPath, String physicalPath) { | 
  | 205 | 0 |              this.logicalPath = logicalPath; | 
  | 206 | 0 |              this.physicalPath = physicalPath; | 
  | 207 | 0 |          } | 
  | 208 |  |   | 
  | 209 |  |          @Override | 
  | 210 |  |          public boolean equals(Object o) { | 
  | 211 | 0 |              if (this == o) | 
  | 212 | 0 |                  return true; | 
  | 213 | 0 |              if (o == null || getClass() != o.getClass()) | 
  | 214 | 0 |                  return false; | 
  | 215 |  |   | 
  | 216 | 0 |              ChangeLogInfo that = (ChangeLogInfo) o; | 
  | 217 |  |   | 
  | 218 | 0 |              return logicalPath.equals(that.logicalPath); | 
  | 219 |  |   | 
  | 220 |  |          } | 
  | 221 |  |   | 
  | 222 |  |          @Override | 
  | 223 |  |          public int hashCode() { | 
  | 224 | 0 |              return logicalPath.hashCode(); | 
  | 225 |  |          } | 
  | 226 |  |   | 
  | 227 |  |          @Override | 
  | 228 |  |          public int compareTo(ChangeLogInfo o) { | 
  | 229 | 0 |              return this.logicalPath.compareTo(o.logicalPath); | 
  | 230 |  |          } | 
  | 231 |  |   | 
  | 232 |  |          @Override | 
  | 233 |  |          public String toString() { | 
  | 234 | 0 |              return logicalPath; | 
  | 235 |  |          } | 
  | 236 |  |      } | 
  | 237 |  |  } |