View Javadoc

1   /**
2    * Copyright 2010-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.common.jdbc;
17  
18  import java.util.ArrayList;
19  import java.util.Arrays;
20  import java.util.Date;
21  import java.util.List;
22  import java.util.Properties;
23  
24  import org.apache.commons.lang3.StringUtils;
25  import org.kuali.common.jdbc.context.DatabaseProcessContext;
26  import org.kuali.common.jdbc.context.DatabaseResetContext;
27  import org.kuali.common.jdbc.context.ExecutionContext;
28  import org.kuali.common.jdbc.listener.BucketListener;
29  import org.kuali.common.jdbc.listener.LogSqlListener;
30  import org.kuali.common.jdbc.listener.NotifyingListener;
31  import org.kuali.common.jdbc.listener.ProgressListener;
32  import org.kuali.common.jdbc.listener.SqlListener;
33  import org.kuali.common.jdbc.listener.SummaryListener;
34  import org.kuali.common.util.CollectionUtils;
35  import org.kuali.common.util.FormatUtils;
36  import org.kuali.common.util.LocationUtils;
37  import org.kuali.common.util.LoggerLevel;
38  import org.kuali.common.util.LoggerUtils;
39  import org.kuali.common.util.nullify.NullUtils;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  
43  public class DefaultDatabaseService implements DatabaseService {
44  	private static final Logger logger = LoggerFactory.getLogger(DefaultDatabaseService.class);
45  	private static final String CONCURRENT = "concurrent";
46  	private static final String SEQUENTIAL = "sequential";
47  	private static final String MESSAGE = "message";
48  	private static final String LIST_SUFFIX = ".list";
49  	private static final String ORDER = "order";
50  
51  	@Override
52  	public void reset(DatabaseResetContext context) {
53  		DatabaseProcessContext dpc = context.getDatabaseProcessContext();
54  		logger.info("------------------------------------------------------------------------");
55  		logger.info("Reset Database - {}", context.getDatabaseProcessContext().getUrl());
56  		logger.info("------------------------------------------------------------------------");
57  		logger.info("Vendor - {}", context.getDatabaseProcessContext().getVendor());
58  		logger.info("URL - {}", context.getDatabaseProcessContext().getUrl());
59  		logger.info("User - {}", LoggerUtils.getUsername(dpc.getUsername()));
60  		logger.info("Password - {}", LoggerUtils.getPassword(dpc.getUsername(), dpc.getPassword()));
61  		logger.info("DBA URL - {}", context.getDatabaseProcessContext().getDbaUrl());
62  		logger.info("DBA User - {}", LoggerUtils.getUsername(dpc.getDbaUsername()));
63  		logger.info("DBA Password - {}", LoggerUtils.getPassword(dpc.getDbaUsername(), dpc.getDbaPassword()));
64  		JdbcMetaData metadata = context.getService().getJdbcMetaData(context.getDbaJdbcContext().getDataSource());
65  		logger.info("Product Name - {}", metadata.getDatabaseProductName());
66  		logger.info("Product Version - {}", metadata.getDatabaseProductVersion());
67  		logger.info("Driver - {}", context.getDatabaseProcessContext().getDriver());
68  		logger.info("Driver Name - {}", metadata.getDriverName());
69  		logger.info("Driver Version - {}", metadata.getDriverVersion());
70  		logger.info("SQL Encoding - {}", context.getEncoding());
71  		logger.info("------------------------------------------------------------------------");
72  
73  		int threads = context.getThreads();
74  		List<ExecutionContext> schemas = getExecutionContexts(context.getSchemaPropertyPrefix(), threads, context.getProperties());
75  		List<ExecutionContext> data = getExecutionContexts(context.getDataPropertyPrefix(), threads, context.getProperties());
76  		List<ExecutionContext> constraints = getExecutionContexts(context.getConstraintPropertyPrefix(), threads, context.getProperties());
77  		List<ExecutionContext> other = getExecutionContexts(context.getOtherPropertyPrefix(), threads, context.getProperties());
78  
79  		List<ExecutionContext> contexts = new ArrayList<ExecutionContext>();
80  		contexts.addAll(schemas);
81  		for (ExecutionContext schema : schemas) {
82  			schema.setListener(getDDLListener());
83  		}
84  		contexts.addAll(data);
85  		for (ExecutionContext ec : data) {
86  			ec.setListener(getDMLListener());
87  		}
88  		contexts.addAll(constraints);
89  		for (ExecutionContext ec : constraints) {
90  			ec.setListener(getDDLListener());
91  		}
92  
93  		contexts.addAll(other);
94  		for (ExecutionContext ec : other) {
95  			ec.setListener(getDDLListener());
96  		}
97  
98  		JdbcService service = new DefaultJdbcService();
99  		ExecutionContext dba = getDbaContext(context);
100 		dba.setExecute(context.isExecuteSql());
101 		dba.setReader(context.getDbaReader());
102 
103 		long start = System.currentTimeMillis();
104 		service.executeSql(dba);
105 		for (ExecutionContext ec : contexts) {
106 			ec.setEncoding(context.getEncoding());
107 			ec.setReader(context.getReader());
108 			ec.setJdbcContext(context.getNormalJdbcContext());
109 			ec.setExecute(context.isExecuteSql());
110 			service.executeSql(ec);
111 		}
112 		logger.info("------------------------------------------------------------------------");
113 		logger.info("Database Reset Completed");
114 		logger.info("------------------------------------------------------------------------");
115 		logger.info("Total time: {}", FormatUtils.getTime(System.currentTimeMillis() - start));
116 		logger.info("Finished at: {}", new Date());
117 		logger.info("------------------------------------------------------------------------");
118 	}
119 
120 	protected ExecutionContext getDbaContext(DatabaseResetContext context) {
121 		ExecutionContext ec = new ExecutionContext();
122 		ec.setMessage("Executing DBA SQL");
123 		ec.setJdbcContext(context.getDbaJdbcContext());
124 		ec.setReader(context.getReader());
125 		ec.setSql(Arrays.asList(context.getDbaSql()));
126 		ec.setListener(getDbaListener());
127 		return ec;
128 	}
129 
130 	protected NotifyingListener getDefaultListener(boolean showRate) {
131 		List<SqlListener> listeners = new ArrayList<SqlListener>();
132 		listeners.add(new ProgressListener());
133 		listeners.add(new SummaryListener(showRate));
134 		return new NotifyingListener(listeners);
135 	}
136 
137 	protected NotifyingListener getDDLListener() {
138 		return getDefaultListener(false);
139 	}
140 
141 	protected NotifyingListener getDMLListener() {
142 		NotifyingListener listener = getDefaultListener(true);
143 		listener.getListeners().add(new BucketListener());
144 		return listener;
145 	}
146 
147 	protected List<String> getLocationsFromCSV(String csv, String listSuffixPattern, Properties properties) {
148 		// Parse the CSV into a list
149 		List<String> keys = CollectionUtils.getTrimmedListFromCSV(csv);
150 
151 		// Allocate some storage for the locations we find
152 		List<String> locations = new ArrayList<String>();
153 
154 		// Iterate through the keys
155 		for (String key : keys) {
156 
157 			// Extract the value associated with the key
158 			String value = properties.getProperty(key);
159 
160 			// The properties file is not configured correctly
161 			if (value == null) {
162 				throw new IllegalArgumentException("Could not locate a value for [" + key + "]");
163 			}
164 
165 			// This key has a value but has been explicitly configured to NONE
166 			if (NullUtils.isNullOrNone(value)) {
167 				continue;
168 			}
169 
170 			// Are we dealing with a SQL file or a list of SQL files
171 			if (StringUtils.endsWith(key, listSuffixPattern)) {
172 				// If the key we used to look up the value ends with ".list", the value is a resource containing a list of SQL locations
173 				locations.addAll(LocationUtils.getLocations(value));
174 			} else {
175 				// Otherwise, it is a SQL location itself
176 				locations.add(value);
177 			}
178 		}
179 
180 		// Return the locations we found
181 		return locations;
182 	}
183 
184 	protected List<ExecutionContext> getExecutionContexts(String prefix, int threads, Properties properties) {
185 
186 		String concurrent = properties.getProperty(prefix + "." + CONCURRENT);
187 		String sequential = properties.getProperty(prefix + "." + SEQUENTIAL);
188 
189 		String concurrentMsg = properties.getProperty(prefix + "." + CONCURRENT + "." + MESSAGE);
190 		String sequentialMsg = properties.getProperty(prefix + "." + SEQUENTIAL + "." + MESSAGE);
191 
192 		List<String> concurrentLocations = getLocationsFromCSV(concurrent, LIST_SUFFIX, properties);
193 		List<String> sequentialLocations = getLocationsFromCSV(sequential, LIST_SUFFIX, properties);
194 
195 		validateExists(concurrentLocations);
196 		validateExists(sequentialLocations);
197 
198 		String order = properties.getProperty(prefix + "." + ORDER);
199 		if (order == null) {
200 			order = CONCURRENT + "," + SEQUENTIAL;
201 		}
202 		List<String> orderings = CollectionUtils.getTrimmedListFromCSV(order);
203 		if (orderings.size() != ExecutionMode.values().length) {
204 			throw new IllegalArgumentException("Only valid values for ordering are " + ExecutionMode.CONCURRENT + " and " + ExecutionMode.SEQUENTIAL);
205 		}
206 
207 		ExecutionMode one = ExecutionMode.valueOf(orderings.get(0).toUpperCase());
208 		ExecutionMode two = ExecutionMode.valueOf(orderings.get(1).toUpperCase());
209 
210 		// They can't be the same
211 		if (one.equals(two)) {
212 			throw new IllegalArgumentException(getInvalidOrderingMessage(order));
213 		}
214 
215 		List<ExecutionContext> contexts = new ArrayList<ExecutionContext>();
216 		ExecutionContext context1 = new ExecutionContext();
217 		ExecutionContext context2 = new ExecutionContext();
218 
219 		if (one.equals(ExecutionMode.CONCURRENT)) {
220 			// Concurrent first, then sequential
221 			context1.setLocations(concurrentLocations);
222 			context1.setThreads(threads);
223 			context1.setMessage(concurrentMsg);
224 			context2.setLocations(sequentialLocations);
225 			context2.setMessage(sequentialMsg);
226 		} else {
227 			// Sequential first, then concurrent
228 			context1.setLocations(sequentialLocations);
229 			context1.setMessage(sequentialMsg);
230 			context2.setLocations(concurrentLocations);
231 			context2.setMessage(concurrentMsg);
232 			context2.setThreads(threads);
233 		}
234 
235 		// Add context1 to the list (if it has any locations)
236 		if (!CollectionUtils.isEmpty(context1.getLocations())) {
237 			contexts.add(context1);
238 		}
239 
240 		// Add context2 to the list (if it has any locations)
241 		if (!CollectionUtils.isEmpty(context2.getLocations())) {
242 			contexts.add(context2);
243 		}
244 
245 		// Return the list
246 		return contexts;
247 	}
248 
249 	protected NotifyingListener getDbaListener() {
250 		LogSqlListener lsl = new LogSqlListener();
251 		lsl.setLevel(LoggerLevel.INFO);
252 		lsl.setFlatten(true);
253 		List<SqlListener> listeners = new ArrayList<SqlListener>();
254 		listeners.add(lsl);
255 		listeners.add(new SummaryListener(false));
256 		return new NotifyingListener(listeners);
257 	}
258 
259 	protected void validateExists(List<String> locations) {
260 		for (String location : locations) {
261 			if (!LocationUtils.exists(location)) {
262 				throw new IllegalArgumentException(location + " does not exist");
263 			}
264 		}
265 	}
266 
267 	protected String getInvalidOrderingMessage(String order) {
268 		StringBuilder sb = new StringBuilder();
269 		sb.append("Ordering [" + order + "] is invalid.  ");
270 		sb.append("Ordering must be provided as either [" + ExecutionMode.CONCURRENT + "," + ExecutionMode.SEQUENTIAL + "] or ");
271 		sb.append("[" + ExecutionMode.CONCURRENT + "," + ExecutionMode.SEQUENTIAL + "]");
272 		return sb.toString();
273 	}
274 
275 }