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