001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright (c) 2014 Edugility LLC. 004 * 005 * Permission is hereby granted, free of charge, to any person 006 * obtaining a copy of this software and associated documentation 007 * files (the "Software"), to deal in the Software without 008 * restriction, including without limitation the rights to use, copy, 009 * modify, merge, publish, distribute, sublicense and/or sell copies 010 * of the Software, and to permit persons to whom the Software is 011 * furnished to do so, subject to the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be 014 * included in all copies or substantial portions of the Software. 015 * 016 * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 017 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 018 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 020 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 021 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 022 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 023 * DEALINGS IN THE SOFTWARE. 024 * 025 * The original copy of this license is available at 026 * http://www.opensource.org/license/mit-license.html. 027 */ 028package com.edugility.maven.liquibase; 029 030import java.io.File; 031import java.io.IOException; 032 033import java.net.MalformedURLException; 034import java.net.URL; 035import java.net.URLClassLoader; 036 037import java.util.ArrayList; 038import java.util.Collection; 039import java.util.Collections; 040 041import com.edugility.maven.ArtifactsProcessingException; 042import com.edugility.maven.ArtifactsProcessor; 043 044import org.apache.maven.artifact.Artifact; 045 046import org.apache.maven.plugin.logging.Log; 047 048import org.apache.maven.model.Build; 049 050import org.apache.maven.project.MavenProject; 051 052/** 053 * An {@link ArtifactsProcessor} for use in conjunction with the <a 054 * href="http://ljnelson.github.io/artifact-maven-plugin/index.html"><code>artifact-maven-plugin</code></a> 055 * that creates a <a href="http://www.liquibase.org/">Liquibase</a> <a 056 * href="http://www.liquibase.org/documentation/databasechangelog.html">changelog</a> 057 * <a 058 * href="http://www.liquibase.org/documentation/include.html">aggregating</a> 059 * changelog fragments found in a {@link MavenProject}'s dependencies. 060 * 061 * @author <a href="http://about.me/lairdnelson" 062 * target="_parent">Laird Nelson</a> 063 * 064 * @see <a 065 * href="http://ljnelson.github.io/artifact-maven-plugin/apidocs/index.html" 066 * target="_parent">The documentation for the 067 * artifact-maven-plugin</a> 068 */ 069public class LiquibaseChangeLogArtifactsProcessor implements ArtifactsProcessor { 070 071 072 /* 073 * Instance fields. 074 */ 075 076 077 /** 078 * The names of Liquibase changelog fragments to be sought. 079 * 080 * <p>This field may be {@code null}.</p> 081 * 082 * @see #getChangeLogResourceNames() 083 * 084 * @see #setChangeLogResourceNames(Collection) 085 */ 086 private Collection<String> changeLogResourceNames; 087 088 /** 089 * The {@link AggregateChangeLogGenerator} to use to perform 090 * changelog generation. 091 * 092 * <p>This field may be {@code null}.</p> 093 * 094 * @see #getChangeLogGenerator() 095 * 096 * @see #setChangeLogGenerator(AggregateChangeLogGenerator) 097 */ 098 private AggregateChangeLogGenerator changeLogGenerator; 099 100 101 /* 102 * Constructors. 103 */ 104 105 106 /** 107 * Creates a new {@link LiquibaseChangeLogArtifactsProcessor}. 108 */ 109 public LiquibaseChangeLogArtifactsProcessor() { 110 super(); 111 this.setChangeLogGenerator(new AggregateChangeLogGenerator()); 112 this.setChangeLogResourceNames(Collections.singleton("META-INF/liquibase/changelog.xml")); 113 } 114 115 116 /* 117 * Properties. 118 */ 119 120 121 /** 122 * Returns the {@link AggregateChangeLogGenerator} to be used to 123 * generate changelogs. 124 * 125 * <p>This method may return {@code null}.</p> 126 * 127 * @return an {@link AggregateChangeLogGenerator}, or {@code null} 128 * 129 * @see #setChangeLogGenerator(AggregateChangeLogGenerator) 130 */ 131 public AggregateChangeLogGenerator getChangeLogGenerator() { 132 return this.changeLogGenerator; 133 } 134 135 /** 136 * Sets the {@link AggregateChangeLogGenerator} to be used to 137 * generate changelogs. 138 * 139 * @param changeLogGenerator the new generator to use; may be {@code 140 * null} in which case a new {@link AggregateChangeLogGenerator} 141 * will be used internally instead 142 * 143 * @see #getChangeLogGenerator() 144 */ 145 public void setChangeLogGenerator(final AggregateChangeLogGenerator changeLogGenerator) { 146 this.changeLogGenerator = changeLogGenerator; 147 } 148 149 /** 150 * Returns the relative names of resources representing Liquibase 151 * changelog fragments that this {@link 152 * LiquibaseChangeLogArtifactsProcessor} will look for. 153 * 154 * <p>This method may return {@code null}.</p> 155 * 156 * <p>Typically, this method returns a singleton {@link Collection} 157 * containing the text {@code META-INF/liquibase/changelog.xml}.</p> 158 * 159 * @return a {@link Collection} of relative resource names 160 * representing Liquibase changelog fragments, or {@code null} 161 * 162 * @see #setChangeLogResourceNames(Collection) 163 */ 164 public Collection<String> getChangeLogResourceNames() { 165 return this.changeLogResourceNames; 166 } 167 168 /** 169 * Sets the {@link Collection} of relative resource names 170 * representing Liquibase changelog fragments that this {@link 171 * LiquibaseChangeLogArtifactsProcessor} will look for. 172 * 173 * @param changeLogResourceNames the names; may be {@code null} 174 * 175 * @see #getChangeLogResourceNames() 176 */ 177 public void setChangeLogResourceNames(final Collection<String> changeLogResourceNames) { 178 this.changeLogResourceNames = changeLogResourceNames; 179 } 180 181 182 /* 183 * ArtifactsProcessor implementation. 184 */ 185 186 187 /** 188 * Harvests {@code jar:} {@link URL}s from the supplied resolved 189 * {@link Artifact}s and lists them in topological order as {@code 190 * <include>} elements inside a generated Liquibase changelog file. 191 * 192 * <p>This method never returns {@code null}.</p> 193 * 194 * @param project the {@link MavenProject} currently in effect; will 195 * not be {@code null} 196 * 197 * @param artifacts a {@link Collection} of {@link Artifact}s 198 * representing the full, transitive set of resolved dependencies of 199 * the supplied {@link MavenProject}; will not be {@code null} 200 * 201 * @param log a {@link Log} for logging to a Maven console; may be 202 * {@code null} 203 * 204 * @return the supplied artifacts 205 * 206 * @exception ArtifactsProcessingException if an error occurs 207 * 208 * @see ArtifactsProcessor 209 */ 210 @Override 211 public Collection<? extends Artifact> process(final MavenProject project, final Collection<? extends Artifact> artifacts, final Log log) throws ArtifactsProcessingException { 212 final Collection<? extends URL> changeLogUrls = this.gatherUrls(project, artifacts, log); 213 if (changeLogUrls != null && !changeLogUrls.isEmpty()) { 214 this.generateChangeLog(project, changeLogUrls, log); 215 } 216 return artifacts; 217 } 218 219 220 /* 221 * Private methods. 222 */ 223 224 225 private final Collection<? extends URL> gatherUrls(final MavenProject project, final Collection<? extends Artifact> artifacts, final Log log) throws ArtifactsProcessingException { 226 final Collection<? extends URL> artifactUrls = this.gatherArtifactUrls(project, artifacts, log); 227 final int artifactUrlsSize = artifactUrls == null ? 0 : artifactUrls.size(); 228 229 final Collection<? extends URL> projectUrls = this.gatherProjectUrls(project, log); 230 final int projectUrlsSize = projectUrls == null ? 0 : projectUrls.size(); 231 232 final Collection<URL> returnValue = new ArrayList<URL>(artifactUrlsSize + projectUrlsSize); 233 if (artifactUrlsSize > 0) { 234 returnValue.addAll(artifactUrls); 235 } 236 if (projectUrlsSize > 0) { 237 returnValue.addAll(projectUrls); 238 } 239 return returnValue; 240 } 241 242 private final Collection<? extends URL> gatherArtifactUrls(final MavenProject project, final Collection<? extends Artifact> artifacts, final Log log) throws ArtifactsProcessingException { 243 Collection<URL> returnValue = null; 244 if (artifacts != null && !artifacts.isEmpty()) { 245 final Collection<? extends String> names = this.getChangeLogResourceNames(); 246 if (names != null && !names.isEmpty()) { 247 for (final Artifact artifact : artifacts) { 248 if (artifact != null && artifact.isResolved() && (project == null || !artifact.equals(project.getArtifact()))) { 249 final File artifactFile = artifact.getFile(); 250 if (artifactFile != null && artifactFile.canRead()) { 251 for (final String name : names) { 252 if (name != null) { 253 URL url = null; 254 try { 255 url = artifactFile.toURI().toURL(); 256 } catch (final MalformedURLException wrapMe) { 257 throw new ArtifactsProcessingException(wrapMe); 258 } 259 final URLClassLoader loader = new URLClassLoader(new URL[] { url }, Thread.currentThread().getContextClassLoader()); 260 final URL jarUrlToChangeLog = loader.getResource(name); 261 if (jarUrlToChangeLog != null) { 262 if (returnValue == null) { 263 returnValue = new ArrayList<URL>(artifacts.size() * names.size()); 264 } 265 returnValue.add(jarUrlToChangeLog); 266 } 267 } 268 } 269 } 270 } 271 } 272 } 273 } 274 return returnValue; 275 } 276 277 private final Collection<? extends URL> gatherProjectUrls(final MavenProject project, final Log log) throws ArtifactsProcessingException { 278 Collection<URL> urls = null; 279 if (project != null) { 280 final Build build = project.getBuild(); 281 if (build != null) { 282 final Collection<? extends String> names = this.getChangeLogResourceNames(); 283 if (names != null && !names.isEmpty()) { 284 final String[] directoryNames = new String[] { build.getOutputDirectory(), build.getTestOutputDirectory() }; 285 for (final String directoryName : directoryNames) { 286 if (directoryName != null) { 287 final File directory = new File(directoryName); 288 if (directory.isDirectory()) { 289 for (final String resourceName : names) { 290 if (resourceName != null) { 291 final File changeLogFile = new File(directory, resourceName); 292 if (changeLogFile.isFile() && changeLogFile.canRead()) { 293 if (urls == null) { 294 urls = new ArrayList<URL>(2 * names.size()); 295 } 296 try { 297 urls.add(changeLogFile.toURI().toURL()); 298 } catch (final MalformedURLException wrapMe) { 299 throw new ArtifactsProcessingException(wrapMe); 300 } 301 } 302 } 303 } 304 } 305 } 306 } 307 } 308 } 309 } 310 return urls; 311 } 312 313 private final File generateChangeLog(final MavenProject project, final Collection<? extends URL> urls, final Log log) throws ArtifactsProcessingException { 314 File returnValue = null; 315 if (urls != null && !urls.isEmpty()) { 316 AggregateChangeLogGenerator generator = this.getChangeLogGenerator(); 317 if (generator == null) { 318 generator = new AggregateChangeLogGenerator(); 319 } 320 final File changeLogFile = generator.getAggregateChangeLogFile(); 321 if (changeLogFile != null) { 322 final File parent = changeLogFile.getParentFile(); 323 if (parent != null) { 324 parent.mkdirs(); 325 } 326 } 327 try { 328 returnValue = generator.generate(urls); 329 } catch (final IOException wrapMe) { 330 throw new ArtifactsProcessingException(wrapMe); 331 } 332 } 333 return returnValue; 334 } 335 336}