001: package org.andromda.maven.plugin.andromdapp;
002:
003: import java.io.BufferedReader;
004: import java.io.File;
005: import java.io.InputStream;
006: import java.io.InputStreamReader;
007:
008: import java.lang.reflect.Field;
009:
010: import java.net.MalformedURLException;
011: import java.net.URL;
012: import java.net.URLClassLoader;
013:
014: import java.sql.Connection;
015: import java.sql.Driver;
016: import java.sql.DriverManager;
017: import java.sql.SQLException;
018: import java.sql.Statement;
019:
020: import java.util.ArrayList;
021: import java.util.Arrays;
022: import java.util.Iterator;
023: import java.util.LinkedHashMap;
024: import java.util.LinkedHashSet;
025: import java.util.List;
026: import java.util.Map;
027: import java.util.Properties;
028: import java.util.Set;
029:
030: import org.andromda.core.common.AndroMDALogger;
031: import org.andromda.core.common.ClassUtils;
032: import org.andromda.core.common.ResourceUtils;
033: import org.andromda.maven.plugin.andromdapp.hibernate.HibernateCreateSchema;
034: import org.andromda.maven.plugin.andromdapp.hibernate.HibernateDropSchema;
035: import org.andromda.maven.plugin.andromdapp.hibernate.HibernateUpdateSchema;
036: import org.andromda.maven.plugin.andromdapp.hibernate.HibernateValidateSchema;
037: import org.apache.commons.lang.ObjectUtils;
038: import org.apache.maven.artifact.Artifact;
039: import org.apache.maven.artifact.DependencyResolutionRequiredException;
040: import org.apache.maven.artifact.factory.ArtifactFactory;
041: import org.apache.maven.artifact.repository.ArtifactRepository;
042: import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
043: import org.apache.maven.artifact.resolver.ArtifactResolutionException;
044: import org.apache.maven.artifact.resolver.ArtifactResolver;
045: import org.apache.maven.model.Dependency;
046: import org.apache.maven.plugin.AbstractMojo;
047: import org.apache.maven.plugin.MojoExecutionException;
048: import org.apache.maven.plugin.MojoFailureException;
049: import org.apache.maven.project.MavenProject;
050:
051: /**
052: * Provides the ability to drop database schemas.
053: *
054: * @goal schema
055: * @requiresDependencyResolution runtime
056: * @author Chad Brandon
057: */
058: public class SchemaMojo extends AbstractMojo {
059: /**
060: * The schema task to execute (create, drop, update, validate)
061: *
062: * @parameter expression="${tasks}"
063: */
064: private String tasks;
065:
066: /**
067: * The type of the create schema task to execute.
068: *
069: * @parameter expression="hibernate"
070: * @required
071: */
072: private String taskType;
073:
074: /**
075: * @parameter expression="${project}"
076: * @required
077: * @readonly
078: */
079: private MavenProject project;
080:
081: /**
082: * Any property files that should be loaded into the schema properties.
083: *
084: * @parameter
085: */
086: private String[] propertyFiles;
087:
088: /**
089: * The properties that can be passed to the schema task.
090: *
091: * @parameter
092: */
093: private Properties properties = new Properties();
094:
095: /**
096: * @parameter expression="${component.org.apache.maven.artifact.factory.ArtifactFactory}"
097: * @required
098: * @readonly
099: */
100: private ArtifactFactory factory;
101:
102: /**
103: * Whether or not scripts should be executed (if this is set to false, they will
104: * only be generated, but not executed).
105: *
106: * @parameter expression="${executeScripts}"
107: */
108: private boolean executeScripts = true;
109:
110: /**
111: * @parameter expression="${plugin.artifacts}"
112: * @required
113: */
114: private List pluginArtifacts;
115:
116: /**
117: * Artifact resolver, needed to download source jars for inclusion in
118: * classpath.
119: *
120: * @component role="org.apache.maven.artifact.resolver.ArtifactResolver"
121: * @required
122: * @readonly
123: */
124: private ArtifactResolver artifactResolver;
125:
126: /**
127: * @parameter expression="${localRepository}"
128: * @required
129: * @readonly
130: */
131: private ArtifactRepository localRepository;
132:
133: /**
134: * The name of the JDBC driver class.
135: *
136: * @parameter
137: * @required
138: */
139: private String jdbcDriver;
140:
141: /**
142: * The JDBC connection URL.
143: *
144: * @parameter
145: * @required
146: */
147: private String jdbcConnectionUrl;
148:
149: /**
150: * The JDBC username for the database.
151: *
152: * @parameter
153: * @required
154: */
155: private String jdbcUsername;
156:
157: /**
158: * The JDBC password for the database.
159: *
160: * @parameter
161: */
162: private String jdbcPassword = "";
163:
164: /**
165: * The jar containing the JDBC driver.
166: *
167: * @parameter
168: * @required
169: */
170: private String jdbcDriverJar;
171:
172: /**
173: * Defines the location(s) of any SQL scripts to be executed.
174: *
175: * @parameter
176: */
177: private List scripts;
178:
179: /**
180: * @see org.apache.maven.plugin.Mojo#execute()
181: */
182: public void execute() throws MojoExecutionException,
183: MojoFailureException {
184: Connection connection = null;
185: try {
186: AndroMDALogger.initialize();
187: this .initializeClassLoaderWithJdbcDriver();
188:
189: final List tasks = this .getTasks();
190: if (tasks != null && !tasks.isEmpty()) {
191: final Map tasksMap = (Map) SchemaMojo.tasksCache
192: .get(this .taskType);
193: if (tasksMap == null) {
194: throw new MojoExecutionException(
195: "'"
196: + taskType
197: + "' is not a valid task type, valid task types are: "
198: + tasksMap.keySet());
199: }
200:
201: this .properties.putAll(this .project.getProperties());
202: for (final Iterator iterator = this .getTasks()
203: .iterator(); iterator.hasNext();) {
204: final String task = ObjectUtils.toString(
205: iterator.next()).trim();
206: if (this .propertyFiles != null) {
207: final int numberOfPropertyFiles = propertyFiles.length;
208: for (int ctr2 = 0; ctr2 < numberOfPropertyFiles; ctr2++) {
209: final URL propertyFileUri = ResourceUtils
210: .toURL(propertyFiles[ctr2]);
211: if (propertyFileUri != null) {
212: final InputStream stream = propertyFileUri
213: .openStream();
214: this .properties.load(stream);
215: stream.close();
216: }
217: }
218: }
219:
220: // - load all the fields of this class into the properties
221: final Field[] fields = this .getClass()
222: .getDeclaredFields();
223: if (fields != null) {
224: final int numberOfFields = fields.length;
225: for (int ctr = 0; ctr < numberOfFields; ctr++) {
226: final Field field = fields[ctr];
227: final Object value = field.get(this );
228: if (value != null) {
229: this .properties.put(field.getName(),
230: value);
231: }
232: }
233: }
234:
235: final Set classpathElements = new LinkedHashSet(
236: this .project.getRuntimeClasspathElements());
237: classpathElements.addAll(this
238: .getProvidedClasspathElements());
239: this
240: .initializeClasspathFromClassPathElements(classpathElements);
241: final Class type = (Class) tasksMap.get(task);
242: if (type == null) {
243: throw new MojoExecutionException(
244: "'"
245: + task
246: + "' is not a valid task, valid types are: "
247: + tasksMap.keySet());
248: }
249:
250: final SchemaManagement schemaManagement = (SchemaManagement) ClassUtils
251: .newInstance(type);
252: connection = executeScripts ? this .getConnection()
253: : null;
254: this .executeSql(connection, schemaManagement
255: .execute(connection, this .properties));
256: }
257: }
258:
259: // - execute any additional scripts
260: this .executeScripts(connection);
261: } catch (final Throwable throwable) {
262: throw new MojoExecutionException(
263: "An error occured while attempting to create the schema",
264: throwable);
265: } finally {
266: if (connection != null) {
267: try {
268: connection.close();
269: } catch (SQLException e) {
270: // - ignore
271: }
272: }
273: }
274: }
275:
276: /**
277: * Retrieves the tasks as a List.
278: *
279: * @return the tasks as a List.
280: */
281: private List getTasks() {
282: return this .tasks != null ? Arrays
283: .asList(this .tasks.split(",")) : null;
284: }
285:
286: /**
287: * Executes any scripts found within the {@link #scriptLocations} and
288: * included using the {@link #scriptIncludes}
289: *
290: * @param connection the SQL connection used to execute the scripts.
291: * @throws MojoExecutionException
292: * @throws Exception
293: */
294: private void executeScripts(final Connection connection)
295: throws MojoExecutionException {
296: final List tasks = this .getTasks();
297: if (this .scripts != null && !this .scripts.isEmpty()) {
298: for (final Iterator iterator = scripts.iterator(); iterator
299: .hasNext();) {
300: final String location = (String) iterator.next();
301: try {
302: this .executeSql(connection, location);
303: } catch (final Exception exception) {
304: throw new MojoExecutionException(
305: "Execution failed on script: " + location,
306: exception);
307: }
308: }
309: } else if (tasks == null || tasks.isEmpty()) {
310: this .getLog().info("No scripts found to execute");
311: }
312: }
313:
314: /**
315: * Sets the current context class loader from the given runtime classpath
316: * elements.
317: *
318: * @throws DependencyResolutionRequiredException
319: * @throws MalformedURLException
320: */
321: protected void initializeClasspathFromClassPathElements(
322: final Set classpathFiles) throws MalformedURLException {
323: // - for some reason some of the plugind dependencies are being excluded from the classloader,
324: // so we explicity load them
325: if (this .pluginArtifacts != null) {
326: for (final Iterator iterator = this .pluginArtifacts
327: .iterator(); iterator.hasNext();) {
328: final Artifact artifact = (Artifact) iterator.next();
329: final File artifactFile = artifact.getFile();
330: if (artifactFile != null) {
331: classpathFiles.add(artifactFile.toString());
332: }
333: }
334: }
335:
336: final List files = new ArrayList(classpathFiles);
337: if (files != null && files.size() > 0) {
338: final URL[] classpathUrls = new URL[classpathFiles.size()];
339:
340: for (int ctr = 0; ctr < classpathFiles.size(); ++ctr) {
341: final File file = new File((String) files.get(ctr));
342: if (this .getLog().isDebugEnabled()) {
343: getLog()
344: .debug("adding to classpath '" + file + "'");
345: }
346: classpathUrls[ctr] = file.toURL();
347: }
348:
349: final URLClassLoader loader = new URLClassLoader(
350: classpathUrls, Thread.currentThread()
351: .getContextClassLoader());
352: Thread.currentThread().setContextClassLoader(loader);
353: }
354: }
355:
356: /**
357: * Initializes the context class loader with the given
358: * <code>jdbcDriverJar</code>
359: *
360: * @throws MalformedURLException
361: */
362: protected void initializeClassLoaderWithJdbcDriver()
363: throws MalformedURLException {
364: Thread.currentThread().setContextClassLoader(
365: new URLClassLoader(new URL[] { new File(
366: this .jdbcDriverJar).toURL() }, Thread
367: .currentThread().getContextClassLoader()));
368: }
369:
370: /**
371: * Adds any dependencies with a scope of 'provided' to the current project
372: * with a scope of runtime.
373: *
374: * @throws ArtifactNotFoundException
375: * @throws ArtifactResolutionException
376: */
377: protected List getProvidedClasspathElements()
378: throws ArtifactResolutionException,
379: ArtifactNotFoundException {
380: final List classpathElements = new ArrayList();
381: final List dependencies = this .project.getDependencies();
382: if (dependencies != null && !dependencies.isEmpty()) {
383: for (final Iterator iterator = dependencies.iterator(); iterator
384: .hasNext();) {
385: final Dependency dependency = (Dependency) iterator
386: .next();
387: if (Artifact.SCOPE_PROVIDED.equals(dependency
388: .getScope())) {
389: final String file = this
390: .getDependencyFile(dependency);
391: if (file != null) {
392: classpathElements.add(file);
393: }
394: }
395: }
396: }
397: return classpathElements;
398: }
399:
400: /**
401: * Adds a dependency to the current project's dependencies.
402: *
403: * @param dependency
404: * @throws ArtifactNotFoundException
405: * @throws ArtifactResolutionException
406: */
407: private String getDependencyFile(final Dependency dependency)
408: throws ArtifactResolutionException,
409: ArtifactNotFoundException {
410: String file = null;
411: if (dependency != null) {
412: final Artifact artifact = this .factory
413: .createArtifact(dependency.getGroupId(), dependency
414: .getArtifactId(), dependency.getVersion(),
415: null, dependency.getType());
416:
417: this .artifactResolver.resolve(artifact, project
418: .getRemoteArtifactRepositories(),
419: this .localRepository);
420: file = artifact.getFile() != null ? artifact.getFile()
421: .toString() : null;
422: }
423: return file;
424: }
425:
426: /**
427: * Retrieves a database connection, given the appropriate database
428: * information.
429: *
430: * @return the retrieved connection.
431: * @throws Exception
432: */
433: protected Connection getConnection() throws Exception {
434: Driver driver = (Driver) ClassUtils.loadClass(this .jdbcDriver)
435: .newInstance();
436: DriverManager.registerDriver(new JdbcDriverWrapper(driver));
437: return DriverManager.getConnection(this .jdbcConnectionUrl,
438: this .jdbcUsername, this .jdbcPassword);
439: }
440:
441: /**
442: * The statement end character.
443: */
444: private static final String STATEMENT_END = ";";
445:
446: /**
447: * Executes the SQL contained with the file located at the
448: * <code>sqlPath</code>.
449: *
450: * @param connection the connection used to execute the SQL.
451: * @param sqlPath the path to the SQL file.
452: * @throws Exception
453: */
454: public void executeSql(final Connection connection,
455: final String sqlPath) throws Exception {
456: if (sqlPath != null && sqlPath.length() > 0) {
457: final URL sqlUrl = ResourceUtils.toURL(sqlPath);
458: if (sqlUrl != null) {
459: this .successes = 0;
460: this .failures = 0;
461: Statement statement = null;
462: if (connection != null) {
463: statement = connection.createStatement();
464: }
465: final InputStream stream = sqlUrl.openStream();
466: final BufferedReader resourceInput = new BufferedReader(
467: new InputStreamReader(stream));
468: StringBuffer sql = new StringBuffer();
469: for (String line = resourceInput.readLine(); line != null; line = resourceInput
470: .readLine()) {
471: if (line.startsWith("//")) {
472: continue;
473: }
474: if (line.startsWith("--")) {
475: continue;
476: }
477: sql.append(line);
478: if (line.endsWith(STATEMENT_END)) {
479: if (statement != null) {
480: this .executeSql(statement, sql.toString()
481: .replaceAll(STATEMENT_END, ""));
482: }
483: sql = new StringBuffer();
484: }
485: sql.append("\n");
486: }
487: resourceInput.close();
488: if (statement != null) {
489: statement.close();
490: }
491: }
492: this .getLog().info(" Executed script: " + sqlPath);
493: final String count = String.valueOf(
494: (this .successes + this .failures)).toString();
495: this .getLog().info(
496: " " + count + " SQL statements executed");
497: this .getLog().info(" Failures: " + this .failures);
498: this .getLog().info(" Successes: " + this .successes);
499: }
500: }
501:
502: /**
503: * Stores the count of statements that were executed successfully.
504: */
505: private int successes;
506:
507: /**
508: * Stores the count of statements that failed.
509: */
510: private int failures;
511:
512: /**
513: * Executes the given <code>sql</code>, using the given
514: * <code>statement</code>.
515: *
516: * @param statement the statement to use to execute the SQL.
517: * @param sql the SQL to execute.
518: * @throws SQLException
519: */
520: private void executeSql(final Statement statement, final String sql) {
521: this .getLog().info(sql.trim());
522: try {
523: statement.execute(sql.toString());
524: this .successes++;
525: } catch (final SQLException exception) {
526: this .failures++;
527: this .getLog().warn(exception.toString());
528: }
529: }
530:
531: /**
532: * Stores the task types.
533: */
534: private static final Map tasksCache = new LinkedHashMap();
535:
536: static {
537: // - initialize the hibernate task types
538: final Map hibernateTasks = new LinkedHashMap();
539: tasksCache.put("hibernate", hibernateTasks);
540: hibernateTasks.put("create", HibernateCreateSchema.class);
541: hibernateTasks.put("drop", HibernateDropSchema.class);
542: hibernateTasks.put("update", HibernateUpdateSchema.class);
543: hibernateTasks.put("validate", HibernateValidateSchema.class);
544: }
545: }
|