View Javadoc

1   /**
2    * Copyright 2011 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   */
15  
16  package org.kuali.common.impex.schema.service.impl;
17  
18  import java.sql.Connection;
19  import java.sql.DatabaseMetaData;
20  import java.sql.SQLException;
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.List;
24  
25  import org.kuali.common.impex.model.ForeignKey;
26  import org.kuali.common.impex.model.Index;
27  import org.kuali.common.impex.model.Schema;
28  import org.kuali.common.impex.model.Sequence;
29  import org.kuali.common.impex.model.Table;
30  import org.kuali.common.impex.model.UniqueConstraint;
31  import org.kuali.common.impex.model.View;
32  import org.kuali.common.impex.model.util.NamedElementComparator;
33  import org.kuali.common.impex.schema.service.SchemaExtractionContext;
34  import org.kuali.common.impex.schema.service.SchemaExtractionService;
35  import org.kuali.common.impex.util.ExtractionUtils;
36  import org.kuali.common.jdbc.JdbcUtils;
37  import org.kuali.common.threads.ExecutionStatistics;
38  import org.kuali.common.threads.ThreadHandlerContext;
39  import org.kuali.common.threads.ThreadInvoker;
40  import org.kuali.common.util.CollectionUtils;
41  import org.kuali.common.util.FormatUtils;
42  import org.kuali.common.util.PercentCompleteInformer;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  public class DefaultSchemaExtractionService implements SchemaExtractionService {
47  
48  	private static Logger log = LoggerFactory.getLogger(DefaultSchemaExtractionService.class);
49  
50  	protected static final int SINGLE_THREAD_COUNT = 1;
51  
52  	@Override
53  	public Schema getSchema(SchemaExtractionContext context) {
54  
55  		Schema result;
56  
57  		try {
58  			if (context.getThreadCount() <= SINGLE_THREAD_COUNT) {
59  				result = extractSingleThreaded(context);
60  			} else {
61  				result = extractMultiThreaded(context);
62  			}
63  		} catch (SQLException e) {
64  			throw new IllegalStateException("Unexpected SQL error", e);
65  		}
66  
67  		sortSchemaElements(result);
68  
69  		return result;
70  	}
71  
72  	protected void sortSchemaElements(Schema result) {
73  		Collections.sort(result.getTables(), NamedElementComparator.getInstance());
74  		Collections.sort(result.getForeignKeys(), NamedElementComparator.getInstance());
75  		Collections.sort(result.getSequences(), NamedElementComparator.getInstance());
76  		Collections.sort(result.getViews(), NamedElementComparator.getInstance());
77  	}
78  
79  	protected Schema extractSingleThreaded(SchemaExtractionContext context) throws SQLException {
80  		long startTime = System.currentTimeMillis();
81  		log.info("Single threaded schema extraction started");
82  
83  		Schema result = new Schema();
84  
85  		List<String> tableNames = getTableNames(context);
86  		log.debug("Extracting {} tables...", new Object[] { tableNames.size() });
87  		result.getTables().addAll(extractTables(tableNames, context));
88  		log.debug("Table extraction complete.");
89  
90  		result.getViews().addAll(extractViews(context));
91  		log.debug("View extraction complete");
92  
93  		result.getSequences().addAll(extractSequences(context));
94  		log.debug("Sequence extraction complete");
95  
96  		result.getForeignKeys().addAll(extractForeignKeys(tableNames, context));
97  		log.debug("Foreign Key extraction complete");
98  
99  		String timeString = FormatUtils.getTime(System.currentTimeMillis() - startTime);
100 		log.info("Single threaded schema extraction complete - Time: {}", new Object[] { timeString });
101 		return result;
102 
103 	}
104 
105 	protected Schema extractMultiThreaded(SchemaExtractionContext context) throws SQLException {
106 		log.info("Multi threaded schema extraction started");
107 
108 		List<String> tableNames = getTableNames(context);
109 
110 		// the total number of schema extraction tasks is calculated as follows:
111 		// - One task for each table name to get table/column data
112 		int totalTasks = tableNames.size();
113 
114 		// - One task for each table name to get foreign keys
115 		totalTasks += tableNames.size();
116 
117 		// - One task to get all sequences and all views
118 		totalTasks++;
119 
120 		// so the total number of tasks to track progress on will be (2 * number of tables) + 1
121 		PercentCompleteInformer progressTracker = new PercentCompleteInformer();
122 		progressTracker.setTotal(totalTasks);
123 
124 		Schema schema = new Schema();
125 
126 		// one thread will handle all views and sequences, then split the table names among other threads
127 		int maxTableThreads = context.getThreadCount() - 1;
128 
129 		List<List<String>> splitNames = CollectionUtils.splitEvenly(tableNames, maxTableThreads);
130 
131 		List<ExtractSchemaBucket> schemaBuckets = new ArrayList<ExtractSchemaBucket>(splitNames.size() + 1);
132 
133 		// add one special schema bucket for handling views and sequences
134 		ExtractSchemaBucket viewSequenceBucket = new ExtractViewsAndSequencesBucket();
135 		viewSequenceBucket.setContext(context);
136 		viewSequenceBucket.setSchema(schema);
137 		schemaBuckets.add(viewSequenceBucket);
138 
139 		// create one bucket for each group of table names from the split
140 		for (List<String> names : splitNames) {
141 			ExtractSchemaBucket bucket = new ExtractSchemaBucket();
142 			bucket.setTableNames(names);
143 			bucket.setContext(context);
144 			bucket.setSchema(schema);
145 
146 			schemaBuckets.add(bucket);
147 		}
148 
149 		// Create and invoke threads to fill in the metadata
150 		// Store some context for the thread handler
151 		ThreadHandlerContext<ExtractSchemaBucket> thc = new ThreadHandlerContext<ExtractSchemaBucket>();
152 		thc.setList(schemaBuckets);
153 		thc.setHandler(new ExtractSchemaBucketHandler(this));
154 		thc.setMax(schemaBuckets.size());
155 		thc.setMin(schemaBuckets.size());
156 		thc.setDivisor(1);
157 
158 		// Start threads to acquire table metadata concurrently
159 		ExecutionStatistics stats = new ThreadInvoker().invokeThreads(thc);
160 
161 		String time = FormatUtils.getTime(stats.getExecutionTime());
162 		log.info("Schema extraction completed.  Time: {}", time);
163 
164 		return schema;
165 	}
166 
167 	@Override
168 	public List<Table> extractTables(List<String> tableNames, SchemaExtractionContext context) throws SQLException {
169 		List<Table> results = new ArrayList<Table>(tableNames.size());
170 
171 		DatabaseMetaData metaData = getMetaDataInstance(context);
172 
173 		try {
174 			for (String name : tableNames) {
175 				results.add(extractTable(name, context.getSchemaName(), metaData));
176 			}
177 
178 			return results;
179 		} finally {
180 			JdbcUtils.closeQuietly(context.getDataSource(), metaData.getConnection());
181 		}
182 	}
183 
184 	protected Table extractTable(String tablename, String schemaName, DatabaseMetaData metaData) throws SQLException {
185 		Table result = new Table(tablename);
186 
187 		result.setDescription(ExtractionUtils.extractTableComment(tablename, schemaName, metaData));
188 		result.setColumns(ExtractionUtils.extractTableColumns(tablename, schemaName, metaData));
189 
190 		List<Index> allTableIndices = ExtractionUtils.extractTableIndices(tablename, schemaName, metaData);
191 
192 		for (Index index : allTableIndices) {
193 			if (index.isUnique()) {
194 				UniqueConstraint u = new UniqueConstraint(index.getColumnNames(), index.getName());
195 				result.getUniqueConstraints().add(u);
196 			} else {
197 				result.getIndices().add(index);
198 			}
199 		}
200 		return result;
201 
202 	}
203 
204 	protected List<String> getTableNames(SchemaExtractionContext context) throws SQLException {
205 		DatabaseMetaData metaData = getMetaDataInstance(context);
206 
207 		List<String> allTables;
208 		try {
209 			allTables = ExtractionUtils.getTableNamesFromMetaData(context.getSchemaName(), metaData);
210 		} finally {
211 			JdbcUtils.closeQuietly(context.getDataSource(), metaData.getConnection());
212 		}
213 
214 		List<String> filteredNames = new ArrayList<String>();
215 		for (String name : allTables) {
216 			if (context.getNameFilter().include(name)) {
217 				filteredNames.add(name);
218 			}
219 		}
220 		return filteredNames;
221 	}
222 
223 	@Override
224 	public List<View> extractViews(SchemaExtractionContext context) throws SQLException {
225 		Connection connection = context.getDataSource().getConnection();
226 		try {
227 			return context.getViewFinder().findViews(connection, context.getSchemaName(), context.getNameFilter());
228 		} finally {
229 			JdbcUtils.closeQuietly(context.getDataSource(), connection);
230 		}
231 	}
232 
233 	@Override
234 	public List<Sequence> extractSequences(SchemaExtractionContext context) throws SQLException {
235 		Connection connection = context.getDataSource().getConnection();
236 		try {
237 			return context.getSequenceFinder().findSequences(connection, context.getSchemaName(), context.getNameFilter());
238 		} finally {
239 			JdbcUtils.closeQuietly(context.getDataSource(), connection);
240 		}
241 	}
242 
243 	@Override
244 	public List<ForeignKey> extractForeignKeys(List<String> tableNames, SchemaExtractionContext context) throws SQLException {
245 		DatabaseMetaData metaData = getMetaDataInstance(context);
246 		try {
247 			return ExtractionUtils.extractForeignKeys(tableNames, context.getSchemaName(), metaData);
248 		} finally {
249 			JdbcUtils.closeQuietly(context.getDataSource(), metaData.getConnection());
250 		}
251 	}
252 
253 	protected DatabaseMetaData getMetaDataInstance(SchemaExtractionContext context) throws SQLException {
254 		return context.getDataSource().getConnection().getMetaData();
255 	}
256 }