Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractJarsignerMojo |
|
| 3.6470588235294117;3,647 | ||||
AbstractJarsignerMojo$1 |
|
| 3.6470588235294117;3,647 | ||||
AbstractJarsignerMojo$2 |
|
| 3.6470588235294117;3,647 | ||||
AbstractJarsignerMojo$3 |
|
| 3.6470588235294117;3,647 |
1 | package org.apache.maven.plugins.jarsigner; | |
2 | ||
3 | /* | |
4 | * Licensed to the Apache Software Foundation (ASF) under one | |
5 | * or more contributor license agreements. See the NOTICE file | |
6 | * distributed with this work for additional information | |
7 | * regarding copyright ownership. The ASF licenses this file | |
8 | * to you under the Apache License, Version 2.0 (the | |
9 | * "License"); you may not use this file except in compliance | |
10 | * with the License. You may obtain a copy of the License at | |
11 | * | |
12 | * http://www.apache.org/licenses/LICENSE-2.0 | |
13 | * | |
14 | * Unless required by applicable law or agreed to in writing, | |
15 | * software distributed under the License is distributed on an | |
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
17 | * KIND, either express or implied. See the License for the | |
18 | * specific language governing permissions and limitations | |
19 | * under the License. | |
20 | */ | |
21 | ||
22 | import java.io.File; | |
23 | import java.io.FileInputStream; | |
24 | import java.io.IOException; | |
25 | import java.io.InputStream; | |
26 | import java.text.MessageFormat; | |
27 | import java.util.Iterator; | |
28 | import java.util.List; | |
29 | import java.util.Properties; | |
30 | import java.util.ResourceBundle; | |
31 | import java.util.zip.ZipEntry; | |
32 | import java.util.zip.ZipInputStream; | |
33 | ||
34 | import org.apache.maven.artifact.Artifact; | |
35 | import org.apache.maven.plugin.AbstractMojo; | |
36 | import org.apache.maven.plugin.MojoExecutionException; | |
37 | import org.apache.maven.project.MavenProject; | |
38 | ||
39 | import org.codehaus.plexus.util.FileUtils; | |
40 | import org.codehaus.plexus.util.Os; | |
41 | import org.codehaus.plexus.util.StringUtils; | |
42 | import org.codehaus.plexus.util.cli.CommandLineException; | |
43 | import org.codehaus.plexus.util.cli.CommandLineUtils; | |
44 | import org.codehaus.plexus.util.cli.Commandline; | |
45 | import org.codehaus.plexus.util.cli.StreamConsumer; | |
46 | ||
47 | /** | |
48 | * Maven Jarsigner Plugin base class. | |
49 | * | |
50 | * @author <a href="cs@schulte.it">Christian Schulte</a> | |
51 | * @version $Id: AbstractJarsignerMojo.java 802605 2009-08-09 21:10:34Z bentmann $ | |
52 | */ | |
53 | 0 | public abstract class AbstractJarsignerMojo |
54 | extends AbstractMojo | |
55 | { | |
56 | ||
57 | /** | |
58 | * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>. | |
59 | * | |
60 | * @parameter expression="${jarsigner.verbose}" default-value="false" | |
61 | */ | |
62 | private boolean verbose; | |
63 | ||
64 | /** | |
65 | * The maximum memory available to the JAR signer, e.g. <code>256M</code>. See <a | |
66 | * href="http://java.sun.com/javase/6/docs/technotes/tools/windows/java.html#Xms">-Xmx</a> for more details. | |
67 | * | |
68 | * @parameter expression="${jarsigner.maxMemory}" | |
69 | */ | |
70 | private String maxMemory; | |
71 | ||
72 | /** | |
73 | * Archive to process. If set, neither the project artifact nor any attachments or archive sets are processed. | |
74 | * | |
75 | * @parameter expression="${jarsigner.archive}" | |
76 | */ | |
77 | private File archive; | |
78 | ||
79 | /** | |
80 | * The base directory to scan for JAR files using Ant-like inclusion/exclusion patterns. | |
81 | * | |
82 | * @parameter expression="${jarsigner.archiveDirectory}" | |
83 | * @since 1.1 | |
84 | */ | |
85 | private File archiveDirectory; | |
86 | ||
87 | /** | |
88 | * The Ant-like inclusion patterns used to select JAR files to process. The patterns must be relative to the | |
89 | * directory given by the parameter {@link #archiveDirectory}. By default, the pattern | |
90 | * <code>**/*.?ar</code> is used. | |
91 | * | |
92 | * @parameter | |
93 | * @since 1.1 | |
94 | */ | |
95 | 0 | private String[] includes = { "**/*.?ar" }; |
96 | ||
97 | /** | |
98 | * The Ant-like exclusion patterns used to exclude JAR files from processing. The patterns must be relative to the | |
99 | * directory given by the parameter {@link #archiveDirectory}. | |
100 | * | |
101 | * @parameter | |
102 | * @since 1.1 | |
103 | */ | |
104 | 0 | private String[] excludes = {}; |
105 | ||
106 | /** | |
107 | * List of additional arguments to append to the jarsigner command line. | |
108 | * | |
109 | * @parameter expression="${jarsigner.arguments}" | |
110 | */ | |
111 | private String[] arguments; | |
112 | ||
113 | /** | |
114 | * Set to {@code true} to disable the plugin. | |
115 | * | |
116 | * @parameter expression="${jarsigner.skip}" default-value="false" | |
117 | */ | |
118 | private boolean skip; | |
119 | ||
120 | /** | |
121 | * Controls processing of the main artifact produced by the project. | |
122 | * | |
123 | * @parameter expression="${jarsigner.processMainArtifact}" default-value="true" | |
124 | * @since 1.1 | |
125 | */ | |
126 | private boolean processMainArtifact; | |
127 | ||
128 | /** | |
129 | * Controls processing of project attachments. If enabled, attached artifacts that are no JARs will be automatically | |
130 | * excluded from processing. | |
131 | * | |
132 | * @parameter expression="${jarsigner.processAttachedArtifacts}" default-value="true" | |
133 | * @since 1.1 | |
134 | */ | |
135 | private boolean processAttachedArtifacts; | |
136 | ||
137 | /** | |
138 | * Controls processing of project attachments. | |
139 | * | |
140 | * @parameter expression="${jarsigner.attachments}" | |
141 | * @deprecated As of version 1.1 in favor of the new parameter <code>processAttachedArtifacts</code>. | |
142 | */ | |
143 | private Boolean attachments; | |
144 | ||
145 | /** | |
146 | * The Maven project. | |
147 | * | |
148 | * @parameter default-value="${project}" | |
149 | * @required | |
150 | * @readonly | |
151 | */ | |
152 | private MavenProject project; | |
153 | ||
154 | /** | |
155 | * The path to the jarsigner we are going to use. | |
156 | */ | |
157 | private String executable; | |
158 | ||
159 | public final void execute() | |
160 | throws MojoExecutionException | |
161 | { | |
162 | 0 | if ( !this.skip ) |
163 | { | |
164 | 0 | this.executable = getExecutable(); |
165 | ||
166 | 0 | int processed = 0; |
167 | ||
168 | 0 | if ( this.archive != null ) |
169 | { | |
170 | 0 | processArchive( this.archive ); |
171 | 0 | processed++; |
172 | } | |
173 | else | |
174 | { | |
175 | 0 | if ( processMainArtifact ) |
176 | { | |
177 | 0 | processed += processArtifact( this.project.getArtifact() ) ? 1 : 0; |
178 | } | |
179 | ||
180 | 0 | if ( processAttachedArtifacts && !Boolean.FALSE.equals( attachments ) ) |
181 | { | |
182 | 0 | for ( Iterator it = this.project.getAttachedArtifacts().iterator(); it.hasNext(); ) |
183 | { | |
184 | 0 | final Artifact artifact = (Artifact) it.next(); |
185 | ||
186 | 0 | processed += processArtifact( artifact ) ? 1 : 0; |
187 | } | |
188 | } | |
189 | else | |
190 | { | |
191 | 0 | if ( verbose ) |
192 | { | |
193 | 0 | getLog().info( getMessage( "ignoringAttachments" ) ); |
194 | } | |
195 | else | |
196 | { | |
197 | 0 | getLog().debug( getMessage( "ignoringAttachments" ) ); |
198 | } | |
199 | } | |
200 | ||
201 | 0 | if ( archiveDirectory != null ) |
202 | { | |
203 | 0 | String includeList = ( includes != null ) ? StringUtils.join( includes, "," ) : null; |
204 | 0 | String excludeList = ( excludes != null ) ? StringUtils.join( excludes, "," ) : null; |
205 | ||
206 | List jarFiles; | |
207 | try | |
208 | { | |
209 | 0 | jarFiles = FileUtils.getFiles( archiveDirectory, includeList, excludeList ); |
210 | } | |
211 | 0 | catch ( IOException e ) |
212 | { | |
213 | 0 | throw new MojoExecutionException( "Failed to scan archive directory for JARs: " |
214 | + e.getMessage(), e ); | |
215 | 0 | } |
216 | ||
217 | 0 | for ( Iterator it = jarFiles.iterator(); it.hasNext(); ) |
218 | { | |
219 | 0 | File jarFile = (File) it.next(); |
220 | ||
221 | 0 | processArchive( jarFile ); |
222 | 0 | processed++; |
223 | } | |
224 | } | |
225 | } | |
226 | ||
227 | 0 | getLog().info( getMessage( "processed", new Integer( processed ) ) ); |
228 | } | |
229 | else | |
230 | { | |
231 | 0 | getLog().info( getMessage( "disabled", null ) ); |
232 | } | |
233 | 0 | } |
234 | ||
235 | /** | |
236 | * Gets the {@code Commandline} to execute for a given Java archive taking a command line prepared for executing | |
237 | * jarsigner. | |
238 | * | |
239 | * @param archive The Java archive to get a {@code Commandline} to execute for. | |
240 | * @param commandLine A {@code Commandline} prepared for executing jarsigner without any arguments. | |
241 | * | |
242 | * @return A {@code Commandline} for executing jarsigner with {@code archive}. | |
243 | * | |
244 | * @throws NullPointerException if {@code archive} or {@code commandLine} is {@code null}. | |
245 | */ | |
246 | protected abstract Commandline getCommandline( final File archive, final Commandline commandLine ); | |
247 | ||
248 | /** | |
249 | * Gets a string representation of a {@code Commandline}. | |
250 | * <p>This method creates the string representation by calling {@code commandLine.toString()} by default.</p> | |
251 | * | |
252 | * @param commandLine The {@code Commandline} to get a string representation of. | |
253 | * | |
254 | * @return The string representation of {@code commandLine}. | |
255 | * | |
256 | * @throws NullPointerException if {@code commandLine} is {@code null}. | |
257 | */ | |
258 | protected String getCommandlineInfo( final Commandline commandLine ) | |
259 | { | |
260 | 0 | if ( commandLine == null ) |
261 | { | |
262 | 0 | throw new NullPointerException( "commandLine" ); |
263 | } | |
264 | ||
265 | 0 | return commandLine.toString(); |
266 | } | |
267 | ||
268 | /** | |
269 | * Checks Java language capability of an artifact. | |
270 | * | |
271 | * @param artifact The artifact to check. | |
272 | * | |
273 | * @return {@code true} if {@code artifact} is Java language capable; {@code false} if not. | |
274 | */ | |
275 | private boolean isJarFile( final Artifact artifact ) | |
276 | { | |
277 | 0 | return artifact != null && artifact.getFile() != null && isJarFile( artifact.getFile() ); |
278 | } | |
279 | ||
280 | /** | |
281 | * Checks whether the specified file is a JAR file. For our purposes, a JAR file is a (non-empty) ZIP stream with a | |
282 | * META-INF directory or some class files. | |
283 | * | |
284 | * @param file The file to check, must not be <code>null</code>. | |
285 | * @return <code>true</code> if the file looks like a JAR file, <code>false</code> otherwise. | |
286 | */ | |
287 | private boolean isJarFile( final File file ) | |
288 | { | |
289 | try | |
290 | { | |
291 | // NOTE: ZipFile.getEntry() might be shorter but is several factors slower on large files | |
292 | ||
293 | 0 | ZipInputStream zis = new ZipInputStream( new FileInputStream( file ) ); |
294 | try | |
295 | { | |
296 | 0 | for ( ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry() ) |
297 | { | |
298 | 0 | if ( ze.getName().startsWith( "META-INF/" ) || ze.getName().endsWith( ".class" ) ) |
299 | { | |
300 | 0 | return true; |
301 | } | |
302 | } | |
303 | } | |
304 | finally | |
305 | { | |
306 | 0 | zis.close(); |
307 | 0 | } |
308 | } | |
309 | 0 | catch ( Exception e ) |
310 | { | |
311 | // ignore, will fail below | |
312 | 0 | } |
313 | ||
314 | 0 | return false; |
315 | } | |
316 | ||
317 | /** | |
318 | * Processes a given artifact. | |
319 | * | |
320 | * @param artifact The artifact to process. | |
321 | * @return <code>true</code> if the artifact is a JAR and was processed, <code>false</code> otherwise. | |
322 | * | |
323 | * @throws NullPointerException if {@code artifact} is {@code null}. | |
324 | * @throws MojoExecutionException if processing {@code artifact} fails. | |
325 | */ | |
326 | private boolean processArtifact( final Artifact artifact ) | |
327 | throws MojoExecutionException | |
328 | { | |
329 | 0 | if ( artifact == null ) |
330 | { | |
331 | 0 | throw new NullPointerException( "artifact" ); |
332 | } | |
333 | ||
334 | 0 | boolean processed = false; |
335 | ||
336 | 0 | if ( isJarFile( artifact ) ) |
337 | { | |
338 | 0 | processArchive( artifact.getFile() ); |
339 | ||
340 | 0 | processed = true; |
341 | } | |
342 | else | |
343 | { | |
344 | 0 | if ( this.verbose ) |
345 | { | |
346 | 0 | getLog().info( getMessage( "unsupported", artifact ) ); |
347 | } | |
348 | 0 | else if ( getLog().isDebugEnabled() ) |
349 | { | |
350 | 0 | getLog().debug( getMessage( "unsupported", artifact ) ); |
351 | } | |
352 | } | |
353 | ||
354 | 0 | return processed; |
355 | } | |
356 | ||
357 | /** | |
358 | * Pre-processes a given archive. | |
359 | * | |
360 | * @param archive The archive to process, must not be <code>null</code>. | |
361 | * @throws MojoExecutionException If pre-processing failed. | |
362 | */ | |
363 | protected void preProcessArchive( final File archive ) | |
364 | throws MojoExecutionException | |
365 | { | |
366 | // default does nothing | |
367 | 0 | } |
368 | ||
369 | /** | |
370 | * Processes a given archive. | |
371 | * | |
372 | * @param archive The archive to process. | |
373 | * @throws NullPointerException if {@code archive} is {@code null}. | |
374 | * @throws MojoExecutionException if processing {@code archive} fails. | |
375 | */ | |
376 | private void processArchive( final File archive ) | |
377 | throws MojoExecutionException | |
378 | { | |
379 | 0 | if ( archive == null ) |
380 | { | |
381 | 0 | throw new NullPointerException( "archive" ); |
382 | } | |
383 | ||
384 | 0 | preProcessArchive( archive ); |
385 | ||
386 | 0 | if ( this.verbose ) |
387 | { | |
388 | 0 | getLog().info( getMessage( "processing", archive ) ); |
389 | } | |
390 | 0 | else if ( getLog().isDebugEnabled() ) |
391 | { | |
392 | 0 | getLog().debug( getMessage( "processing", archive ) ); |
393 | } | |
394 | ||
395 | 0 | Commandline commandLine = new Commandline(); |
396 | ||
397 | 0 | commandLine.setExecutable( this.executable ); |
398 | ||
399 | 0 | commandLine.setWorkingDirectory( this.project.getBasedir() ); |
400 | ||
401 | 0 | if ( this.verbose ) |
402 | { | |
403 | 0 | commandLine.createArg().setValue( "-verbose" ); |
404 | } | |
405 | ||
406 | 0 | if ( StringUtils.isNotEmpty( maxMemory ) ) |
407 | { | |
408 | 0 | commandLine.createArg().setValue( "-J-Xmx" + maxMemory ); |
409 | } | |
410 | ||
411 | 0 | if ( this.arguments != null ) |
412 | { | |
413 | 0 | commandLine.addArguments( this.arguments ); |
414 | } | |
415 | ||
416 | 0 | commandLine = getCommandline( archive, commandLine ); |
417 | ||
418 | try | |
419 | { | |
420 | 0 | if ( getLog().isDebugEnabled() ) |
421 | { | |
422 | 0 | getLog().debug( getMessage( "command", getCommandlineInfo( commandLine ) ) ); |
423 | } | |
424 | ||
425 | 0 | final int result = CommandLineUtils.executeCommandLine( commandLine, |
426 | new InputStream() | |
427 | { | |
428 | ||
429 | 0 | public int read() |
430 | { | |
431 | 0 | return -1; |
432 | } | |
433 | ||
434 | }, new StreamConsumer() | |
435 | { | |
436 | ||
437 | 0 | public void consumeLine( final String line ) |
438 | { | |
439 | 0 | if ( verbose ) |
440 | { | |
441 | 0 | getLog().info( line ); |
442 | } | |
443 | else | |
444 | { | |
445 | 0 | getLog().debug( line ); |
446 | } | |
447 | 0 | } |
448 | ||
449 | }, new StreamConsumer() | |
450 | { | |
451 | ||
452 | 0 | public void consumeLine( final String line ) |
453 | { | |
454 | 0 | getLog().warn( line ); |
455 | 0 | } |
456 | ||
457 | } ); | |
458 | ||
459 | 0 | if ( result != 0 ) |
460 | { | |
461 | 0 | throw new MojoExecutionException( getMessage( "failure", getCommandlineInfo( commandLine ), |
462 | new Integer( result ) ) ); | |
463 | } | |
464 | } | |
465 | 0 | catch ( CommandLineException e ) |
466 | { | |
467 | 0 | throw new MojoExecutionException( getMessage( "commandLineException", getCommandlineInfo( commandLine ) ), |
468 | e ); | |
469 | 0 | } |
470 | 0 | } |
471 | ||
472 | /** | |
473 | * Locates the executable for the jarsigner tool. | |
474 | * | |
475 | * @return The executable of the jarsigner tool, never <code>null<code>. | |
476 | */ | |
477 | private String getExecutable() | |
478 | { | |
479 | 0 | String command = "jarsigner" + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? ".exe" : "" ); |
480 | ||
481 | 0 | String executable = |
482 | findExecutable( command, System.getProperty( "java.home" ), new String[] { "../bin", "bin", "../sh" } ); | |
483 | ||
484 | 0 | if ( executable == null ) |
485 | { | |
486 | try | |
487 | { | |
488 | 0 | Properties env = CommandLineUtils.getSystemEnvVars(); |
489 | ||
490 | 0 | String[] variables = { "JDK_HOME", "JAVA_HOME" }; |
491 | ||
492 | 0 | for ( int i = 0; i < variables.length && executable == null; i++ ) |
493 | { | |
494 | 0 | executable = |
495 | findExecutable( command, env.getProperty( variables[i] ), new String[] { "bin", "sh" } ); | |
496 | } | |
497 | } | |
498 | 0 | catch ( IOException e ) |
499 | { | |
500 | 0 | if ( getLog().isDebugEnabled() ) |
501 | { | |
502 | 0 | getLog().warn( "Failed to retrieve environment variables, cannot search for " + command, e ); |
503 | } | |
504 | else | |
505 | { | |
506 | 0 | getLog().warn( "Failed to retrieve environment variables, cannot search for " + command ); |
507 | } | |
508 | 0 | } |
509 | } | |
510 | ||
511 | 0 | if ( executable == null ) |
512 | { | |
513 | 0 | executable = command; |
514 | } | |
515 | ||
516 | 0 | return executable; |
517 | } | |
518 | ||
519 | /** | |
520 | * Finds the specified command in any of the given sub directories of the specified JDK/JRE home directory. | |
521 | * | |
522 | * @param command The command to find, must not be <code>null</code>. | |
523 | * @param homeDir The home directory to search in, may be <code>null</code>. | |
524 | * @param subDirs The sub directories of the home directory to search in, must not be <code>null</code>. | |
525 | * @return The (absolute) path to the command if found, <code>null</code> otherwise. | |
526 | */ | |
527 | private String findExecutable( String command, String homeDir, String[] subDirs ) | |
528 | { | |
529 | 0 | if ( StringUtils.isNotEmpty( homeDir ) ) |
530 | { | |
531 | 0 | for ( int i = 0; i < subDirs.length; i++ ) |
532 | { | |
533 | 0 | File file = new File( new File( homeDir, subDirs[i] ), command ); |
534 | ||
535 | 0 | if ( file.isFile() ) |
536 | { | |
537 | 0 | return file.getAbsolutePath(); |
538 | } | |
539 | } | |
540 | } | |
541 | ||
542 | 0 | return null; |
543 | } | |
544 | ||
545 | /** | |
546 | * Gets a message for a given key from the resource bundle backing the implementation. | |
547 | * | |
548 | * @param key The key of the message to return. | |
549 | * @param args Arguments to format the message with or {@code null}. | |
550 | * | |
551 | * @return The message with key {@code key} from the resource bundle backing the implementation. | |
552 | * | |
553 | * @throws NullPointerException if {@code key} is {@code null}. | |
554 | * @throws java.util.MissingResourceException if there is no message available matching {@code key} or accessing | |
555 | * the resource bundle fails. | |
556 | */ | |
557 | private String getMessage( final String key, final Object[] args ) | |
558 | { | |
559 | 0 | if ( key == null ) |
560 | { | |
561 | 0 | throw new NullPointerException( "key" ); |
562 | } | |
563 | ||
564 | 0 | return new MessageFormat( ResourceBundle.getBundle( "jarsigner" ).getString( key ) ).format( args ); |
565 | } | |
566 | ||
567 | private String getMessage( final String key ) | |
568 | { | |
569 | 0 | return getMessage( key, null ); |
570 | } | |
571 | ||
572 | private String getMessage( final String key, final Object arg ) | |
573 | { | |
574 | 0 | return getMessage( key, new Object[] { arg } ); |
575 | } | |
576 | ||
577 | private String getMessage( final String key, final Object arg1, final Object arg2 ) | |
578 | { | |
579 | 0 | return getMessage( key, new Object[] { arg1, arg2 } ); |
580 | } | |
581 | ||
582 | } |