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&apos; {@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    }