1 /***
2 * Copyright 2009 ATG DUST Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 *
7 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and limitations under the License.
13 */
14 package atg.adapter.gsa;
15
16 import java.sql.Connection;
17 import java.sql.SQLException;
18 import java.util.ArrayList;
19 import java.util.Iterator;
20 import java.util.List;
21
22
23 import org.apache.ddlutils.DatabaseOperationException;
24 import org.apache.ddlutils.Platform;
25 import org.apache.ddlutils.PlatformFactory;
26 import org.apache.ddlutils.model.Column;
27 import org.apache.ddlutils.model.Database;
28 import org.apache.ddlutils.model.ForeignKey;
29 import org.apache.ddlutils.model.IndexColumn;
30 import org.apache.ddlutils.model.Reference;
31 import org.apache.ddlutils.model.UniqueIndex;
32
33 import atg.repository.RepositoryException;
34
35 /***
36 * This class is used to generate drop and alter a database schema required for
37 * a given repository. It uses the Apache DDLUtils tools for the actual schema
38 * manipulation. To use the class first initialize it's "model" by passing a
39 * GSARepository to the constructor. Afterwards you may invoke the action
40 * methods such as:
41 * <ul>
42 * <li>createSchema - Creates schema including constraints.
43 * <li>dropSchema - Drops the schema including constraints.
44 * <li>alterSchema - Attempts to alter an existing schema into the one currently
45 * required for the given repository.
46 * </ul>
47 * These methods affect the DataSource used by the given GSARepository. If that
48 * DataSource is not accessible then these methods will fail. Schema
49 * modification may continue or fail on error. Set the <code>strict</code>
50 * property to true to always fail on error. The default is to continue on
51 * error.
52 *
53 * @version $Id: //test/UnitTests/base/main/src/Java/atg/adapter/gsa/
54 * GSARepositorySchemaGenerator.java#1 $
55 * @author adamb
56 */
57 public class GSARepositorySchemaGenerator {
58
59
60 public GSARepository mRepository = null;
61
62 public Platform mPlatform = null;
63
64 public Database mDatabase = null;
65
66 public DatabaseTypeNameToJDBC mDatabaseTypeNameToJDBC = null;
67
68
69 /***
70 * Creates a new GSARepositorySchemaGenerator and initializes it with a model
71 * based upon the given repository.
72 *
73 * @param pRepository
74 * @throws RepositoryException
75 */
76 public GSARepositorySchemaGenerator(GSARepository pRepository)
77 throws RepositoryException {
78 buildModel(pRepository);
79 }
80
81
82 /***
83 * Initialize this class with a model for the given repository. Any previous
84 * model will be discarded.
85 *
86 * @param pRepository
87 * @throws RepositoryException
88 */
89 public void buildModel(GSARepository pRepository) throws RepositoryException {
90 mDatabaseTypeNameToJDBC = new DatabaseTypeNameToJDBC(pRepository
91 .getDatabaseTableInfo());
92 mRepository = pRepository;
93 mPlatform = PlatformFactory.createNewPlatformInstance(pRepository
94 .getDataSource());
95 mDatabase = new Database();
96 mDatabase.setName(pRepository.getAbsoluteName());
97 mDatabase.setVersion("1.0");
98 String[] names = pRepository.getItemDescriptorNames();
99 for (String name : names) {
100 GSAItemDescriptor desc = (GSAItemDescriptor) pRepository
101 .getItemDescriptor(name);
102 Table[] tables = desc.getTables();
103
104 processTables(pRepository, tables, true);
105 }
106 for (String name : names) {
107 GSAItemDescriptor desc = (GSAItemDescriptor) pRepository
108 .getItemDescriptor(name);
109 Table[] tables = desc.getTables();
110
111 desc.getPrimaryTable();
112 processTables(pRepository, tables, false);
113 }
114
115 }
116
117 /***
118 * Walks the tables of this repository building up a DDLUtils model.
119 *
120 * @param pRepository
121 * @param tables
122 * @param pPrimary
123 * - if True only processes primary tables
124 * @throws RepositoryException
125 */
126 private void processTables(GSARepository pRepository, Table[] tables,
127 boolean pPrimary) throws RepositoryException {
128 for (Table table : tables) {
129 if (!table.isInherited() && (table.isPrimaryTable() == pPrimary)) {
130
131
132
133
134
135
136 List<GSARepository> repositoriesUsingTable = SchemaTracker
137 .getSchemaTracker().getTableToRepository().get(table.getName());
138 if (repositoriesUsingTable != null
139 && repositoriesUsingTable.contains(pRepository)) {
140 pRepository.logWarning("Table " + table.getName()
141 + " already defined by repository "
142 + repositoriesUsingTable.toString()
143 + " skipping schema creation for this table. multi="
144 + table.isMultiTable() + " auxiliary=" + table.isAuxiliaryTable()
145 + " primary=" + table.isPrimaryTable());
146 } else {
147
148 buildSingleTableModel(mDatabase, table, pRepository);
149 repositoriesUsingTable = new ArrayList<GSARepository>();
150 repositoriesUsingTable.add(pRepository);
151 }
152 SchemaTracker.getSchemaTracker().getTableToRepository().put(
153 table.getName(), repositoriesUsingTable);
154 }
155 }
156 }
157
158
159 /***
160 * Adds the definition of the given table to the current DDLUtils database
161 * model.
162 *
163 * @param pDb
164 * @param pTable
165 * @param pRepository
166 * @throws RepositoryException
167 */
168 void buildSingleTableModel(Database pDb, Table pTable,
169 GSARepository pRepository) throws RepositoryException {
170 TableColumns columns = new TableColumns(pTable, pRepository
171 .getDatabaseTableInfo());
172 pTable.collectColumnsForName(columns);
173 AccessibleTableColumns atable = new AccessibleTableColumns(columns);
174
175
176
177
178 org.apache.ddlutils.model.Table t = new org.apache.ddlutils.model.Table();
179 t.setName(pTable.getName());
180 pDb.addTable(t);
181
182
183
184
185
186 ColumnDefinitionNode columnDefinition = null;
187 boolean proceed = false;
188
189 for (columnDefinition = atable.getHead(), proceed = true; columnDefinition != null
190 && proceed; columnDefinition = columnDefinition.mNext) {
191
192
193 if (atable.getHead() == atable.getTail())
194 proceed = false;
195
196 Column c = new Column();
197
198
199
200
201
202 c.setName(columnDefinition.mColumnName);
203 t.addColumn(c);
204
205
206
207
208
209 setupColumnType(pRepository, columnDefinition, c);
210
211
212
213
214
215 if (atable.getPrimaryKeys().contains(c.getName())
216 || c.getName().equals(atable.getMultiColumnName())) {
217 c.setPrimaryKey(true);
218 }
219
220
221
222
223
224 if (columnDefinition.mIsRequired
225 || atable.getPrimaryKeys().contains(columnDefinition.mColumnName)) {
226 c.setRequired(true);
227 } else {
228 c.setRequired(false);
229 }
230
231
232
233
234
235
236 if (columnDefinition.mIsUnique) {
237 UniqueIndex uniqueIndex = new UniqueIndex();
238 uniqueIndex.setName("uidx_" + t.getName() + "_" + c.getName());
239 uniqueIndex.addColumn(new IndexColumn(c));
240 t.addIndex(uniqueIndex);
241 }
242
243
244
245
246
247 if (columnDefinition.mReferenced != null && !columns.mVersioned) {
248
249 ForeignKey foreignKey = new ForeignKey();
250 Reference reference = new Reference();
251 String referencedTableName = columnDefinition.mReferenced.substring(0,
252 columnDefinition.mReferenced.indexOf("("));
253 String referencedColumnName = columnDefinition.mReferenced.substring(
254 columnDefinition.mReferenced.indexOf("(") + 1,
255 columnDefinition.mReferenced.indexOf(")"));
256 org.apache.ddlutils.model.Table referencedTable = pDb
257 .findTable(referencedTableName);
258 foreignKey.setName(t.getName() + c.getName() + "FK"
259 + referencedTableName + referencedColumnName);
260 if (referencedTable != null) {
261 Column referencedColumn = referencedTable
262 .findColumn(referencedColumnName);
263 if (referencedTable.getName().equals(t.getName())
264 && referencedColumn.getName().equals(c.getName())) {
265 pRepository
266 .logWarning("Skipping foreign key constraint, table and column are the same. Table.Column="
267 + referencedTableName + "." + referencedColumnName);
268 } else {
269 reference.setForeignColumn(referencedColumn);
270 reference.setLocalColumn(c);
271 foreignKey.addReference(reference);
272 foreignKey.setForeignTable(referencedTable);
273 t.addForeignKey(foreignKey);
274 }
275 }
276
277
278
279
280
281 if (atable.getForeignKeys() != null && !columns.mVersioned) {
282
283 }
284 }
285 }
286
287 }
288
289 /***
290 * Determines the appropriate jdbc type for the given ColumnDefinitionNode and
291 * sets that in Column "c".
292 *
293 * @param pRepository
294 * @param columnDefinition
295 * @param c
296 */
297 void setupColumnType(GSARepository pRepository,
298 ColumnDefinitionNode columnDefinition, Column c) {
299 c.setDescription(columnDefinition.mDataTypeString);
300 String typeName = null;
301 String size = null;
302 if (columnDefinition.mDataTypeString.contains("(")) {
303 typeName = columnDefinition.mDataTypeString.substring(0,
304 columnDefinition.mDataTypeString.indexOf("("));
305 size = columnDefinition.mDataTypeString.substring(
306 columnDefinition.mDataTypeString.indexOf("(") + 1,
307 columnDefinition.mDataTypeString.indexOf(")"));
308 } else {
309 typeName = columnDefinition.mDataTypeString;
310 }
311
312 String precision = null;
313 String scale = null;
314 if (size != null) {
315 if (size.contains(",")) {
316 precision = size.substring(0, size.indexOf(","));
317 scale = size.substring(size.indexOf(",") + 1, size.length());
318 c.setPrecisionRadix(Integer.parseInt(precision.trim()));
319 c.setScale(Integer.parseInt(scale.trim()));
320 } else {
321 c.setSize(size);
322 }
323 }
324
325 c.setTypeCode(mDatabaseTypeNameToJDBC.databaseTypeNametoJDBCType(typeName));
326 }
327
328
329 /***
330 * Creates the schema based on the current model. If no model has been
331 * created, this method throws a NoModelException.
332 *
333 * @param pContinueOnError
334 * - If true, continue on error, else fail.
335 * @param pDrop
336 * - If true, drops schema first before attempting to create it.
337 * @throws SQLException
338 * @throws DatabaseOperationException
339 */
340 public void createSchema(final boolean pContinueOnError, final boolean pDrop)
341 throws DatabaseOperationException, SQLException {
342 boolean success = new DoInAutoCommit(this, mRepository)
343 .doInAutoCommit(new AutoCommitable() {
344 @Override
345 public void doInAutoCommit(Connection pConnection) {
346 mPlatform.createTables(pConnection, mDatabase, pDrop,
347 pContinueOnError);
348 }
349 });
350 if (!success)
351 throw new DatabaseOperationException("Failed to create tables.");
352 }
353
354
355 /***
356 * Drops the schema based on the current model. If no model has been created,
357 * this method throws a NoModelException.
358 *
359 * @param pContinueOnError
360 * - If true, continue on error, else fail.
361 * @throws SQLException
362 * @throws DatabaseOperationException
363 */
364 public void dropSchema(final boolean pContinueOnError)
365 throws DatabaseOperationException, SQLException {
366 boolean success = new DoInAutoCommit(this, mRepository)
367 .doInAutoCommit(new AutoCommitable() {
368 @Override
369 public void doInAutoCommit(Connection pConnection) {
370 mPlatform.dropTables(pConnection, mDatabase, pContinueOnError);
371 }
372 });
373 if (!success)
374 throw new DatabaseOperationException("Failed to drop tables.");
375 }
376
377
378 /***
379 * Alters the schema based on the current model. If no model has been created,
380 * this method throws a NoModelException. This method attempts to preserve the
381 * data in the target database.
382 *
383 * @param pContinueOnError
384 * - If true, fail on error, else continue on error.
385 * @throws SQLException
386 * @throws DatabaseOperationException
387 */
388 public void alterSchema(final boolean pContinueOnError)
389 throws DatabaseOperationException, SQLException {
390 mPlatform.alterTables(mRepository.getConnection(), mDatabase,
391 pContinueOnError);
392 boolean success = new DoInAutoCommit(this, mRepository)
393 .doInAutoCommit(new AutoCommitable() {
394 @Override
395 public void doInAutoCommit(Connection pConnection) {
396 mPlatform.alterTables(pConnection, mDatabase, pContinueOnError);
397 }
398 });
399 if (!success)
400 throw new DatabaseOperationException("Failed to alter tables.");
401 }
402 }