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 }