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.ExtractSchemaContext;
34  import org.kuali.common.impex.schema.service.ExtractSchemaService;
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.LoggerUtils;
43  import org.kuali.common.util.PercentCompleteInformer;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  public class DefaultExtractSchemaService implements ExtractSchemaService {
48  
49  	private static Logger logger = LoggerFactory.getLogger(DefaultExtractSchemaService.class);
50  
51  	protected static final int SINGLE_THREAD_COUNT = 1;
52  
53  	@Override
54  	public Schema getSchema(ExtractSchemaContext context) {
55  		// Connect to the db using JDBC and create a Schema model object
56  		Schema schema = extractSchema(context);
57  
58  		// Sort the schema elements by name
59  		sortSchemaElements(schema);
60  
61  		// Return our schema object
62  		return schema;
63  	}
64  
65  	protected Schema extractSchema(ExtractSchemaContext context) {
66  		try {
67  			// Decide if we are executing in single threaded or multi-threaded mode
68  			if (context.getThreadCount() <= SINGLE_THREAD_COUNT) {
69  				return extractSingleThreaded(context);
70  			} else {
71  				return extractMultiThreaded(context);
72  			}
73  		} catch (SQLException e) {
74  			throw new IllegalStateException("Unexpected SQL error", e);
75  		}
76  	}
77  
78  	protected Schema extractMultiThreaded(ExtractSchemaContext context) throws SQLException {
79  		// Preserve our start time
80  		long start = System.currentTimeMillis();
81  
82  		logger.info("[schema:extract:starting]");
83  
84  		// Use JDBC calls to acquire the list of table names for our schema
85  		// This is usually very quick. The KS schema has 574 tables at the moment, and this call takes ~250ms
86  		List<String> tableNames = getTableNames(context);
87  
88  		// The total number of schema extraction tasks is calculated as follows:
89  		// One task for each table name to get table/column data
90  		int totalTasks = tableNames.size();
91  
92  		// One task for each table name to get foreign keys
93  		totalTasks += tableNames.size();
94  
95  		// One task for sequences + one task for views
96  		totalTasks += 2;
97  
98  		// The total number of tasks to track progress on will be (2 * number of tables) + 2
99  		PercentCompleteInformer informer = new PercentCompleteInformer();
100 		informer.setTotal(totalTasks);
101 		context.setInformer(informer);
102 
103 		// One thread will handle all views and sequences, then split the table names among other threads
104 		int maxTableThreads = context.getThreadCount() - 1;
105 
106 		// Split the list of tables evenly across the threads
107 		List<List<String>> splitNames = CollectionUtils.splitEvenly(tableNames, maxTableThreads);
108 
109 		// Create buckets to hold results
110 		List<ExtractSchemaBucket> schemaBuckets = new ArrayList<ExtractSchemaBucket>();
111 
112 		// Setup a schema object shared across all of the threads
113 		Schema schema = new Schema();
114 
115 		// Add one special schema bucket for handling views and sequences
116 		ExtractSchemaBucket viewSequenceBucket = new ExtractViewsAndSequencesBucket();
117 		viewSequenceBucket.setContext(context);
118 		viewSequenceBucket.setSchema(schema);
119 		viewSequenceBucket.setInformer(informer);
120 		schemaBuckets.add(viewSequenceBucket);
121 
122 		// Create one bucket for each group of table names from the split
123 		for (List<String> names : splitNames) {
124 			ExtractSchemaBucket bucket = new ExtractSchemaBucket();
125 			bucket.setTableNames(names);
126 			bucket.setContext(context);
127 			bucket.setSchema(schema);
128 			bucket.setInformer(informer);
129 
130 			schemaBuckets.add(bucket);
131 		}
132 
133 		// Store some context for the thread handler
134 		ThreadHandlerContext<ExtractSchemaBucket> thc = new ThreadHandlerContext<ExtractSchemaBucket>();
135 		thc.setList(schemaBuckets);
136 		thc.setHandler(new ExtractSchemaBucketHandler(this));
137 		thc.setMax(schemaBuckets.size());
138 		thc.setMin(schemaBuckets.size());
139 		thc.setDivisor(1);
140 
141 		// Start threads to acquire table metadata concurrently
142 		logger.info("[schema:extract:metadata:starting]");
143 		informer.start();
144 		// Create and invoke threads to fill in the metadata
145 		ExecutionStatistics stats = new ThreadInvoker().invokeThreads(thc);
146 		informer.stop();
147 		logger.info("[schema:extract:metadata:complete] - {}", FormatUtils.getTime(stats.getExecutionTime()));
148 		logger.info("[schema:extract:complete] - {}", FormatUtils.getTime(System.currentTimeMillis() - start));
149 		return schema;
150 	}
151 
152 	@Override
153 	public List<Table> extractTables(List<String> tableNames, ExtractSchemaContext context) throws SQLException {
154 		List<Table> results = new ArrayList<Table>(tableNames.size());
155 
156 		DatabaseMetaData metaData = getMetaDataInstance(context);
157 
158 		try {
159 			for (String name : tableNames) {
160 				results.add(extractTable(name, context.getSchemaName(), metaData));
161 				context.getInformer().incrementProgress();
162 			}
163 
164 			return results;
165 		} finally {
166 			JdbcUtils.closeQuietly(context.getDataSource(), metaData.getConnection());
167 		}
168 	}
169 
170 	protected Table extractTable(String tablename, String schemaName, DatabaseMetaData metaData) throws SQLException {
171 		Table result = new Table(tablename);
172 
173 		result.setDescription(ExtractionUtils.extractTableComment(tablename, schemaName, metaData));
174 		result.setColumns(ExtractionUtils.extractTableColumns(tablename, schemaName, metaData));
175 
176 		List<Index> allTableIndices = ExtractionUtils.extractTableIndices(tablename, schemaName, metaData);
177 
178 		for (Index index : allTableIndices) {
179 			if (index.isUnique()) {
180 				UniqueConstraint u = new UniqueConstraint(index.getColumnNames(), index.getName());
181 				result.getUniqueConstraints().add(u);
182 			} else {
183 				result.getIndices().add(index);
184 			}
185 		}
186 		return result;
187 
188 	}
189 
190 	protected List<String> getTableNames(ExtractSchemaContext context) throws SQLException {
191 		long start = System.currentTimeMillis();
192 		logger.info("[schema:extract:tablenames] - {}", LoggerUtils.getLogMsg(context.getNameFilter()));
193 		List<String> tableNames = ExtractionUtils.getTableNames(context.getDataSource(), context.getSchemaName());
194 		List<String> excluded = CollectionUtils.filterAndSort(tableNames, context.getNameFilter());
195 		String time = FormatUtils.getTime(System.currentTimeMillis() - start);
196 		String original = FormatUtils.getCount(tableNames.size() + excluded.size());
197 		String filtered = FormatUtils.getCount(tableNames.size());
198 		if (!CollectionUtils.isEmpty(excluded)) {
199 			logger.info("  excluded -> [{}]", CollectionUtils.getSpaceSeparatedCSV(excluded));
200 		}
201 		Object[] args = { original, filtered, time };
202 		logger.info("[schema:extract:tablenames] - [all: {}  filtered: {}] - {}", args);
203 		return tableNames;
204 	}
205 
206 	@Override
207 	public List<View> extractViews(ExtractSchemaContext context) throws SQLException {
208 		Connection connection = context.getDataSource().getConnection();
209 		try {
210 			return context.getViewFinder().findViews(connection, context.getSchemaName(), context.getNameFilter());
211 		} finally {
212 			JdbcUtils.closeQuietly(context.getDataSource(), connection);
213 		}
214 	}
215 
216 	@Override
217 	public List<Sequence> extractSequences(ExtractSchemaContext context) throws SQLException {
218 		Connection connection = context.getDataSource().getConnection();
219 		try {
220 			return context.getSequenceFinder().findSequences(connection, context.getSchemaName(), context.getNameFilter());
221 		} finally {
222 			JdbcUtils.closeQuietly(context.getDataSource(), connection);
223 		}
224 	}
225 
226 	@Override
227 	public List<ForeignKey> extractForeignKeys(List<String> tableNames, ExtractSchemaContext context) throws SQLException {
228 		DatabaseMetaData meta = getMetaDataInstance(context);
229 		try {
230 			return ExtractionUtils.extractForeignKeys(meta, context.getSchemaName(), tableNames, context.getInformer());
231 		} finally {
232 			JdbcUtils.closeQuietly(context.getDataSource(), meta.getConnection());
233 		}
234 	}
235 
236 	/**
237 	 * Sort the varous schema elements by name
238 	 */
239 	protected void sortSchemaElements(Schema schema) {
240 		Collections.sort(schema.getTables(), NamedElementComparator.getInstance());
241 		Collections.sort(schema.getForeignKeys(), NamedElementComparator.getInstance());
242 		Collections.sort(schema.getSequences(), NamedElementComparator.getInstance());
243 		Collections.sort(schema.getViews(), NamedElementComparator.getInstance());
244 	}
245 
246 	protected DatabaseMetaData getMetaDataInstance(ExtractSchemaContext context) throws SQLException {
247 		return context.getDataSource().getConnection().getMetaData();
248 	}
249 
250 	protected Schema extractSingleThreaded(ExtractSchemaContext context) throws SQLException {
251 		long startTime = System.currentTimeMillis();
252 		logger.info("Single threaded schema extraction started");
253 
254 		Schema result = new Schema();
255 
256 		List<String> tableNames = getTableNames(context);
257 		logger.debug("Extracting {} tables...", new Object[] { tableNames.size() });
258 		result.getTables().addAll(extractTables(tableNames, context));
259 		logger.debug("Table extraction complete.");
260 
261 		result.getViews().addAll(extractViews(context));
262 		logger.debug("View extraction complete");
263 
264 		result.getSequences().addAll(extractSequences(context));
265 		logger.debug("Sequence extraction complete");
266 
267 		result.getForeignKeys().addAll(extractForeignKeys(tableNames, context));
268 		logger.debug("Foreign Key extraction complete");
269 
270 		String timeString = FormatUtils.getTime(System.currentTimeMillis() - startTime);
271 		logger.info("Single threaded schema extraction complete - Time: {}", new Object[] { timeString });
272 		return result;
273 
274 	}
275 
276 }