001 /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil -*- 002 * 003 * $Id$ 004 * 005 * Copyright (c) 2011 Edugility LLC. 006 * 007 * Permission is hereby granted, free of charge, to any person 008 * obtaining a copy of this software and associated documentation 009 * files (the "Software"), to deal in the Software without 010 * restriction, including without limitation the rights to use, copy, 011 * modify, merge, publish, distribute, sublicense and/or sell copies 012 * of the Software, and to permit persons to whom the Software is 013 * furnished to do so, subject to the following conditions: 014 * 015 * The above copyright notice and this permission notice shall be 016 * included in all copies or substantial portions of the Software. 017 * 018 * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 019 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 020 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 021 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 022 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 023 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 024 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 025 * DEALINGS IN THE SOFTWARE. 026 * 027 * The original copy of this license is available at 028 * http://www.opensource.org/license/mit-license.html. 029 */ 030 package com.edugility.jpa.maven.plugin; 031 032 import java.lang.reflect.Field; 033 034 import java.net.URISyntaxException; 035 import java.net.URL; 036 037 import java.io.File; 038 import java.io.IOException; 039 040 import java.util.Map; 041 import java.util.Set; 042 043 import org.apache.maven.artifact.DependencyResolutionRequiredException; 044 045 import org.apache.maven.plugin.AbstractMojo; 046 import org.apache.maven.plugin.MojoExecutionException; 047 import org.apache.maven.plugin.MojoFailureException; 048 049 import org.apache.maven.plugin.logging.Log; 050 051 import org.apache.maven.model.Build; // for javadoc only 052 053 import org.apache.maven.project.MavenProject; 054 055 import org.scannotation.archiveiterator.DirectoryIteratorFactory; 056 import org.scannotation.archiveiterator.FileIterator; 057 import org.scannotation.archiveiterator.FileProtocolIteratorFactory; 058 import org.scannotation.archiveiterator.Filter; 059 import org.scannotation.archiveiterator.IteratorFactory; 060 import org.scannotation.archiveiterator.JarIterator; 061 import org.scannotation.archiveiterator.StreamIterator; 062 063 /** 064 * An {@link AbstractMojo} that provides support for scanning a set of 065 * {@link URL}s and reporting back on the annotated classnames found 066 * there. 067 * 068 * @author <a href="mailto:ljnelson@gmail.com">Laird Nelson</a> 069 * 070 * @since 1.0-SNAPSHOT 071 */ 072 public abstract class AbstractJPAMojo extends AbstractMojo { 073 074 /** 075 * Static initializer; works around <a 076 * href="http://sourceforge.net/tracker/?func=detail&aid=3134533&group_id=214374&atid=1029423">Scannotation 077 * bug #3134533</a> by installing a patched {@link 078 * FileProtocolIteratorFactory} into the {@link IteratorFactory} 079 * class' {@link IteratorFactory#registry} field. 080 * 081 * @see <a 082 * href="http://sourceforge.net/tracker/?func=detail&aid=3134533&group_id=214374&atid=1029423">Scannotation 083 * bug #3134533</a> 084 */ 085 static { 086 Field field = null; 087 try { 088 field = IteratorFactory.class.getDeclaredField("registry"); 089 assert field != null; 090 field.setAccessible(true); 091 @SuppressWarnings("unchecked") 092 final Map<String, DirectoryIteratorFactory> registry = (Map<String, DirectoryIteratorFactory>)field.get(null); 093 assert registry != null; 094 assert registry.containsKey("file"); 095 final Object old = registry.put("file", new FileProtocolIteratorFactory() { 096 @Override 097 public StreamIterator create(final URL url, final Filter filter) throws IOException { 098 StreamIterator returnValue = null; 099 if (url != null) { 100 // See http://sourceforge.net/tracker/?func=detail&aid=3134533&group_id=214374&atid=1029423 101 File file; 102 try { 103 file = new File(url.toURI()); 104 } catch (final URISyntaxException e) { 105 file = new File(url.getPath()); 106 } 107 if (file.isDirectory()) { 108 returnValue = new FileIterator(file, filter); 109 } else { 110 returnValue = new JarIterator(url.openStream(), filter); 111 } 112 } 113 return returnValue; 114 } 115 }); 116 assert old != null; 117 } catch (final Exception ohWell) { 118 ohWell.printStackTrace(); 119 } 120 } 121 122 /** 123 * The {@link MavenProject} usually injected by the Maven runtime. 124 * Used for the return value of its {@link 125 * MavenProject#getTestClasspathElements() 126 * getTestClasspathElements()} method and its associated {@link 127 * Build}'s {@link Build#getTestOutputDirectory() 128 * getTestOutputDirectory()} method. This field may be {@code null} 129 * when this {@link AbstractJPAMojo} is not <a 130 * href="http://maven.apache.org/guides/mini/guide-configuring-plugins.html">configured 131 * by Maven</a>. 132 * 133 * @parameter default-value="${project}" property="project" 134 * 135 * @readonly 136 * 137 * @required 138 * 139 * @see <a 140 * href="http://maven.apache.org/guides/mini/guide-configuring-plugins.html">Guide 141 * to Configuring Plug-ins</a> 142 */ 143 private MavenProject project; 144 145 /** 146 * The {@link AnnotationDB} that will be {@linkplain 147 * #cloneAnnotationDB() cloned} for use by this {@link 148 * AbstractJPAMojo}. This field may be {@code null} at any point, 149 * and may be populated by either <a 150 * href="http://maven.apache.org/guides/mini/guide-configuring-plugins.html">Maven</a>, 151 * the {@link #setAnnotationDB(AnnotationDB)} method or the {@link 152 * #createAnnotationDB()} method. 153 * 154 * @parameter alias="db" property="annotationDB" 155 * 156 * @see #cloneAnnotationDB() 157 * 158 * @see #createAnnotationDB() 159 * 160 * @see #setAnnotationDB(AnnotationDB) 161 * 162 * @see <a 163 * href="http://maven.apache.org/guides/mini/guide-configuring-plugins.html">Guide 164 * to Configuring Plug-ins</a> 165 */ 166 private AnnotationDB db; 167 168 /** 169 * A {@link URLFilter} that will be used to construct the {@link 170 * Set} of {@link URL}s that will be scanned by this {@link 171 * AbstractJPAMojo}. This field may be {@code null} at any point 172 * and may be populated by either <a 173 * href="http://maven.apache.org/guides/mini/guide-configuring-plugins.html">Maven</a> 174 * or the {@link #setURLFilter(URLFilter)} method. 175 * 176 * @parameter property="URLFilter" 177 * 178 * @see #getURLFilter() 179 * 180 * @see #setURLFilter(URLFilter) 181 * 182 * @see <a 183 * href="http://maven.apache.org/guides/mini/guide-configuring-plugins.html">Guide 184 * to Configuring Plug-ins</a> 185 */ 186 private URLFilter urlFilter; 187 188 /** 189 * Constructs a new {@link AbstractJPAMojo}. No configuration 190 * automatic or otherwise will have taken place as a result of 191 * calling this constructor. 192 */ 193 protected AbstractJPAMojo() { 194 super(); 195 } 196 197 /** 198 * Creates a new {@link AnnotationDB} in the (common) case where a 199 * user has not supplied this {@link AbstractJPAMojo} with a 200 * pre-configured {@link AnnotationDB}. 201 * 202 * <p>This method never returns {@code null}. Subclasses overriding 203 * this method must ensure that their overridden implementation 204 * never returns {@code null}.</p> 205 * 206 * @return a new {@link AnnotationDB}; never {@code null} 207 * 208 * @see AnnotationDB 209 * 210 * @see org.scannotation.AnnotationDB 211 */ 212 protected AnnotationDB createAnnotationDB() { 213 return new AnnotationDB(); 214 } 215 216 /** 217 * Returns this {@link AbstractJPAMojo}'s associated {@link 218 * URLFilter}, or {@code null} if no such {@link URLFilter} exists. 219 * 220 * <p>This method may return {@code null}.</p> 221 * 222 * @return the {@link URLFilter} used by this {@link 223 * AbstractJPAMojo}, or {@code null} 224 * 225 * @see #setURLFilter(URLFilter) 226 * 227 * @see URLFilter 228 */ 229 public URLFilter getURLFilter() { 230 return this.urlFilter; 231 } 232 233 /** 234 * Sets this {@link AbstractJPAMojo}'s associated {@link URLFilter}. 235 * {@code null} is permitted as a parameter value. 236 * 237 * @param filter the {@link URLFilter} to set; may be {@code null} 238 * 239 * @see #getURLFilter() 240 * 241 * @see URLFilter 242 */ 243 public void setURLFilter(final URLFilter filter) { 244 this.urlFilter = filter; 245 } 246 247 /** 248 * Returns the {@link MavenProject} that Maven customarily injects 249 * into this mojo, or {@code null} if no such {@link MavenProject} 250 * has been set. 251 * 252 * <p>This method may return {@code null}.</p> 253 * 254 * @return the {@link MavenProject} associated with this mojo, or 255 * {@code null} 256 */ 257 public MavenProject getProject() { 258 return this.project; 259 } 260 261 /** 262 * Installs the {@link MavenProject} for use by this mojo during its 263 * run. 264 * 265 * @param project the {@link MavenProject} to use; may be {@code 266 * null} 267 */ 268 public void setProject(final MavenProject project) { 269 this.project = project; 270 } 271 272 /** 273 * Returns a {@linkplain AnnotationDB#clone() clone} of this {@link 274 * AbstractJPAMojo}'s {@linkplain #setAnnotationDB(AnnotationDB) 275 * associated <tt>AnnotationDB</tt>}. 276 * 277 * <p>A clone is returned because {@link 278 * org.scannotation.AnnotationDB} retains state after {@linkplain 279 * org.scannotation.AnnotationDB#scanArchives(URL[]) scanning}, and 280 * Maven plugins have no contractually defined lifecycle semantics. 281 * Consequently it is unknown how long-lived this {@link 282 * AbstractJPAMojo}'s {@link #db} reference might be.</p> 283 * 284 * <p>This method may return {@code null}.</p> 285 * 286 * @return a {@linkplain AnnotationDB#clone() clone} of this {@link 287 * AbstractJPAMojo}'s {@linkplain #setAnnotationDB(AnnotationDB) 288 * associated <tt>AnnotationDB</tt>}, or {@code null} if no such 289 * {@link AnnotationDB} could be cloned 290 * 291 * @see #setAnnotationDB(AnnotationDB) 292 * 293 * @see AnnotationDB 294 * 295 * @see AnnotationDB#clone() 296 * 297 * @see org.scannotation.AnnotationDB 298 * 299 * @see org.scannotation.AnnotationDB#annotationIndex 300 * 301 * @see org.scannotation.AnnotationDB#classIndex 302 */ 303 public final AnnotationDB cloneAnnotationDB() { 304 if (this.db == null) { 305 this.db = this.createAnnotationDB(); 306 } 307 if (this.db == null) { 308 return null; 309 } 310 return this.db.clone(); 311 } 312 313 /** 314 * Sets the {@link AnnotationDB} that will be used by this {@link 315 * AbstractJPAMojo}'s {@link #cloneAnnotationDB()} method. {@code 316 * null} is permitted as a parameter value. 317 * 318 * @param db the {@link AnnotationDB} to set; may be {@code null} 319 * 320 * @see #cloneAnnotationDB() 321 * 322 * @see AnnotationDB 323 */ 324 public void setAnnotationDB(final AnnotationDB db) { 325 this.db = db; 326 } 327 328 /** 329 * Scans the supplied {@link Set} of {@link URL}s and returns the 330 * {@link AnnotationDB} that contains the scanned annotation 331 * information. 332 * 333 * <p>This method may return {@code null} in exceptional 334 * circumstances.</p> 335 * 336 * @param urls the {@link Set} of {@link URL}s to scan; if {@code 337 * null}, then no scanning operation will take place 338 * 339 * @return the {@link AnnotationDB} that was used to perform the 340 * scan, or {@code null} if no {@link AnnotationDB} could be 341 * {@linkplain #cloneAnnotationDB() found} 342 * 343 * @exception IOException if an error occurs during scanning 344 * 345 * @see #cloneAnnotationDB() 346 * 347 * @see org.scannotation.AnnotationDB#scanArchives(URL[]) 348 */ 349 protected final AnnotationDB scan(final Set<URL> urls) throws IOException { 350 final AnnotationDB db = this.cloneAnnotationDB(); 351 final AnnotationDB result = this.scan(db, urls); 352 assert result == db; 353 return result; 354 } 355 356 /** 357 * Scans the supplied {@link Set} of {@link URL}s and as a 358 * convenience returns the supplied {@link AnnotationDB} that 359 * contains the scanned annotation information. 360 * 361 * <p>This method may return {@code null} if the supplied {@code db} 362 * is {@code null}.</p> 363 * 364 * @param db the {@link AnnotationDB} used to {@linkplain 365 * org.scannotation.AnnotationDB#scanArchives(URL[]) perform the 366 * scan}; if {@code null} then no scanning operation will take place 367 * 368 * @param urls the {@link Set} of {@link URL}s to scan; if {@code 369 * null}, then no scanning operation will take place 370 * 371 * @return the {@code db} parameter 372 * 373 * @exception IOException if an error occurs during scanning 374 * 375 * @see org.scannotation.AnnotationDB#scanArchives(URL[]) 376 */ 377 private final AnnotationDB scan(AnnotationDB db, final Set<URL> urls) throws IOException { 378 if (db != null && urls != null && !urls.isEmpty()) { 379 final Log log = this.getLog(); 380 if (log != null && log.isDebugEnabled()) { 381 log.debug("Scanning the following URLs: " + urls); 382 } 383 db.clear(); 384 db.scanArchives(urls.toArray(new URL[urls.size()])); 385 } 386 return db; 387 } 388 389 }