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 | |
} |