View Javadoc

1   /**
2    * Pyx4me framework
3    * Copyright (C) 2006-2008 pyx4j.com.
4    * 
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing,
12   * software distributed under the License is distributed on an
13   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14   * KIND, either express or implied.  See the License for the
15   * specific language governing permissions and limitations
16   * under the License.
17   * 
18   * @author vlads
19   * @version $Id: PackageMojo.java 3347 2009-04-27 14:43:50Z vlads $
20   */
21  package com.pyx4me.maven.j2me;
22  
23  import java.io.File;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import org.apache.maven.archiver.MavenArchiveConfiguration;
30  import org.apache.maven.archiver.MavenArchiver;
31  import org.apache.maven.artifact.Artifact;
32  import org.apache.maven.model.Dependency;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugin.MojoFailureException;
35  import org.apache.maven.project.MavenProjectHelper;
36  import org.codehaus.plexus.archiver.jar.JarArchiver;
37  
38  /**
39   * The j2me:package task is an wrapper for Jar task that handles JAD files correctly. It
40   * also allows for obfuscation, preverification and signing of the generated file.
41   * 
42   * Preverification can be done without WTK using ProGuard.
43   * 
44   * Antenna library is used to execute WTK tasks.
45   * 
46   * @author vlads
47   * 
48   * @goal package
49   * @phase package
50   * @description Create JAR and JAD files for j2me application
51   */
52  
53  public class PackageMojo extends AbstractJadWtkMojo {
54  
55      /**
56       * The directory containing generated classes.
57       * 
58       * @parameter expression="${project.build.outputDirectory}"
59       * @required
60       * @readonly
61       */
62      private File classesDirectory;
63  
64      /**
65       * Specifies whether or not to attach the JAR and JAD artifacts to the project
66       * 
67       * @parameter expression="${j2me.attach}" default-value="true"
68       */
69      private boolean attach = true;
70  
71      /**
72       * Specifies whether or not to attach the JAR and JAD test artifacts to the project
73       * 
74       * @parameter default-value="false"
75       */
76      private boolean testAttach = false;
77  
78      /**
79       * Specifies whether or not execute ClassPreprocessor on created JAR.
80       * 
81       * @parameter expression="${j2me.preprocess}" default-value="false"
82       */
83      private boolean preprocess = false;
84  
85      /**
86       * Reads configuration options from the given jour xml config file.
87       * 
88       * @parameter
89       */
90      private String jourConfig;
91  
92      /**
93       * Reads configuration options from the given jour xml config file when processing
94       * "test" artifact.
95       * 
96       * @parameter
97       */
98      private String jourConfigTest;
99  
100     /**
101      * Specifies whether or not execute ProGuard on created JAR.
102      * 
103      * @parameter expression="${j2me.obfuscate}" default-value="true"
104      */
105     private boolean proguard = true;
106 
107     /**
108      * Specifies whether or not sign created MIDLet JAR. In particular, it will sign the
109      * jar and add the certificate to the jad.
110      * 
111      * @parameter expression="${j2me.sign}" default-value="false"
112      */
113     private boolean sign = false;
114 
115     /**
116      * Specifies not to obfuscate the input class files.
117      * 
118      * @parameter default-value="true"
119      */
120     private boolean obfuscate;
121 
122     /**
123      * Use ProGuard preverification instead of WTK. (ProGuard option -microedition)
124      * 
125      * @parameter default-value="false"
126      */
127     private boolean proguardPreverify = false;
128 
129     /**
130      * Apply ProGuard classpathentry Filters to input jar. e.g. <code>!**.gif,!**&#47;awt&#47;**,!**&#47;tests&#47;**'</code>
131      * 
132      * @parameter
133      */
134     protected String inFilter;
135 
136     /**
137      * Bundle project dependency to resulting jar. Specifies list of artifact inclusions
138      * and exclusions. By default all provided and system scope dependency are excluded
139      * from final jar all other included.
140      * 
141      * @parameter
142      */
143     protected Assembly assembly;
144 
145     /**
146      * ProGuard configuration options
147      * 
148      * @parameter
149      */
150     private ProguardOptions proguardOptions;
151 
152     /**
153      * Additional ProGuard configuration options when processing test package.
154      * 
155      * @parameter
156      */
157     private ProguardOptions proguardTestOptions;
158 
159     /**
160      * Recursively reads ProGuard configuration options from the given file filename
161      * 
162      * @parameter default-value="${basedir}/proguard.conf"
163      */
164     private File proguardInclude;
165 
166     /**
167      * Recursively reads ProGuard configuration options from the given file filename when
168      * processing test pakage.
169      * 
170      * @parameter default-value="${basedir}/proguard-test.conf"
171      */
172     private File proguardTestInclude;
173 
174     /**
175      * Copy custom properties from the JAD into the Manifest.
176      * 
177      * @parameter default-value="true"
178      */
179     private boolean copyJadAttributesToManifest = true;
180 
181     /**
182      * The keystore file used to sign created MIDLet JAR/JAD. See <a href="#sign">sign</a>
183      * 
184      * @parameter expression="${j2me.keystore}"
185      */
186     protected File keystore;
187 
188     /**
189      * The keystore password used to sign created MIDLet JAR/JAD. See <a
190      * href="#sign">sign</a>
191      * 
192      * @parameter expression="${j2me.keystorepass}"
193      */
194     protected String keystorepass;
195 
196     /**
197      * The certificate alias name used to sign created MIDLet JAR/JAD. See <a
198      * href="#sign">sign</a>
199      * 
200      * @parameter expression="${j2me.keyalias}"
201      */
202     protected String keyalias;
203 
204     /**
205      * The certificate (private key) password used to sign created MIDLet JAR/JAD. See <a
206      * href="#sign">sign</a>
207      * 
208      * @parameter expression="${j2me.keypass}"
209      */
210     protected String keypass;
211 
212     /**
213      * The maven archive configuration to use.
214      * 
215      * @parameter
216      */
217     protected MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
218 
219     /**
220      * The Jar archiver.
221      * 
222      * @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#jar}"
223      * @required
224      */
225     private JarArchiver jarArchiver;
226 
227     /**
228      * @component
229      */
230     private MavenProjectHelper projectHelper;
231 
232     public void execute() throws MojoExecutionException, MojoFailureException {
233 
234         if ("pom".equals(packaging)) {
235             getLog().info("NOT applicable for packaging: \'" + packaging + "\'.");
236             return;
237         }
238 
239         executePackage(classifier, false);
240         if (test) {
241             executePackage(testClassifier, true);
242         }
243     }
244 
245     public void executePackage(String packageClassifier, boolean isTest) throws MojoExecutionException, MojoFailureException {
246 
247         File jarFile = getJarFile(packageClassifier);
248         getLog().info("Building j2me " + packageClassifier + " package: " + jarFile);
249 
250         getLog().debug(((!this.useWtkLibs) ? "NOT " : "") + "Using WTK Libraries");
251 
252         if (getLog().isDebugEnabled()) {
253             Set artifacts = mavenProject.getArtifacts();
254             for (Iterator i = artifacts.iterator(); i.hasNext();) {
255                 Artifact artifact = (Artifact) i.next();
256                 getLog().debug("artifact:" + artifact.getArtifactId() + " " + artifact.getScope());
257             }
258             artifacts = mavenProject.getDependencyArtifacts();
259             for (Iterator i = artifacts.iterator(); i.hasNext();) {
260                 Artifact artifact = (Artifact) i.next();
261                 getLog().debug("dependency artifact:" + artifact.getArtifactId() + " " + artifact.getScope());
262             }
263 
264             List dependancy = mavenProject.getCompileArtifacts();
265             for (Iterator i = dependancy.iterator(); i.hasNext();) {
266                 Artifact artifact = (Artifact) i.next();
267                 getLog().debug("compile artifact:" + artifact.getArtifactId() + " " + artifact.getScope());
268             }
269             dependancy = mavenProject.getTestArtifacts();
270             for (Iterator i = dependancy.iterator(); i.hasNext();) {
271                 Artifact artifact = (Artifact) i.next();
272                 getLog().debug("test artifact:" + artifact.getArtifactId() + " " + artifact.getScope());
273             }
274             dependancy = mavenProject.getTestDependencies();
275             for (Iterator i = dependancy.iterator(); i.hasNext();) {
276                 Dependency dependency = (Dependency) i.next();
277                 getLog().debug("test dependancy:" + dependency.getArtifactId() + " " + dependency.getScope());
278             }
279         }
280 
281         performJarPackaging(packageClassifier, isTest);
282 
283         File preprocessed = null;
284 
285         if (preprocess) {
286             String config = isTest ? jourConfigTest : jourConfig;
287             if (config != null) {
288 
289                 // This will fix ProGuard: The output is up to date
290                 File baseFile = new File(jarFile.toString() + "_instrument_base.jar");
291                 if (baseFile.exists()) {
292                     if (!baseFile.delete()) {
293                         throw new MojoFailureException("Can't delete " + baseFile);
294                     }
295                 }
296                 if (!jarFile.renameTo(baseFile)) {
297                     throw new MojoFailureException("Can't rename " + jarFile);
298                 }
299 
300                 preprocessed = PreprocessMojo.executeClassPreprocessor(config, baseFile, "iclasses" + (isTest ? "-test" : ""), this);
301             } else {
302                 getLog().info("No Preprocessor defined for j2me " + packageClassifier + " package");
303             }
304         }
305 
306         boolean noPreverify = "false".equals(System.getProperty("j2me.preverify"));
307         if (proguard || proguardPreverify) {
308             if (preprocessed == null) {
309                 preprocessed = jarFile;
310             }
311             ProGuardMojo.executeProGuard(preprocessed, jarFile, proguardInclude, obfuscate, (noPreverify ? false : proguardPreverify), this, isTest,
312                     proguardTestInclude, inFilter, proguardOptions, proguardTestOptions);
313         } else if (preprocessed != null) {
314             throw new MojoFailureException("Unsupported configuration, should use proguard after preprocessing");
315             // TODO updateJarFile(jarFile, preprocessed);
316         }
317 
318         if ((!proguardPreverify) && (!noPreverify)) {
319             WtkPreverifyMojo.executeWtkPreverifyTask(jarFile, this);
320         }
321 
322         WtkJadMojo.executeCreateJad(this, packageClassifier, isTest);
323 
324         if (sign) {
325             WtkSignMojo.executeSignJar(this, packageClassifier, isTest, keystore, keystorepass, keyalias, keypass);
326         }
327 
328         if ((attach && (!isTest)) || (isTest && testAttach)) {
329             projectHelper.attachArtifact(mavenProject, "jad", packageClassifier, getJadFile(packageClassifier));
330         }
331 
332     }
333 
334     private void populateManifestEntries(boolean isTest) {
335         populateJadAttributes();
336         cleanupJadAttributes();
337         Map entries = archive.getManifestEntries();
338         // Copy required Jad Attributes
339         if (!entries.containsKey(JAD_ATR_PROFILE)) {
340             entries.put(JAD_ATR_PROFILE, jadAttributes.get(JAD_ATR_PROFILE));
341         }
342         if (!entries.containsKey(JAD_ATR_CONFIGURATION)) {
343             entries.put(JAD_ATR_CONFIGURATION, jadAttributes.get(JAD_ATR_CONFIGURATION));
344         }
345         if ((!entries.containsKey(JAD_ATR_MIDLET_ICON)) && (jadAttributes.containsKey(JAD_ATR_MIDLET_ICON))) {
346             entries.put(JAD_ATR_MIDLET_ICON, jadAttributes.get(JAD_ATR_MIDLET_ICON));
347         }
348 
349         if (!entries.containsKey(JAD_ATR_MIDLET_NAME)) {
350             entries.put(JAD_ATR_MIDLET_NAME, midletName);
351         }
352         if (!entries.containsKey(JAD_ATR_MIDLET_VENDOR)) {
353             entries.put(JAD_ATR_MIDLET_VENDOR, midletVendor);
354         }
355         if (!entries.containsKey(JAD_ATR_MIDLET_VERSION)) {
356             entries.put(JAD_ATR_MIDLET_VERSION, properMidletVersion());
357         } else {
358             entries.put(JAD_ATR_MIDLET_VERSION, properMidletVersion((String) entries.get(JAD_ATR_MIDLET_VERSION)));
359         }
360 
361         if (midlets != null) {
362             int cnt = 1;
363             for (int i = 0; i < midlets.length; i++) {
364                 MIDlet m = midlets[i];
365                 if (m.test && (!isTest)) {
366                     continue;
367                 }
368                 entries.put("MIDlet-" + cnt, m.name + ", " + m.icon + ", " + m.cls);
369                 cnt++;
370             }
371         }
372 
373         // Copy other Jad Attributes
374         if (copyJadAttributesToManifest) {
375             for (Iterator i = jadAttributes.entrySet().iterator(); i.hasNext();) {
376                 Map.Entry jadEntry = (Map.Entry) i.next();
377                 if (!entries.containsKey(jadEntry.getKey())) {
378                     entries.put(jadEntry.getKey(), jadEntry.getValue());
379                 }
380             }
381         }
382 
383         // "Archiver-Version: Plexus Archiver" is causing problem for Mororola
384         if (!entries.containsKey("Archiver-Version")) {
385             // TODO get Maven version
386             entries.put("Archiver-Version", "1.0");
387         }
388 
389         if (getLog().isDebugEnabled()) {
390             for (Iterator i = entries.entrySet().iterator(); i.hasNext();) {
391                 Map.Entry jarEntry = (Map.Entry) i.next();
392                 getLog().debug("ManifestEntry | " + jarEntry.getKey() + ": " + jarEntry.getValue());
393             }
394         }
395     }
396 
397     private void performJarPackaging(String packageClassifier, boolean isTest) throws MojoExecutionException, MojoFailureException {
398 
399         populateManifestEntries(isTest);
400 
401         MavenArchiver archiver = new MavenArchiver();
402 
403         try {
404             getLog().debug("jarPackaging " + (isTest ? "test" : "regular"));
405 
406             // Add classes to Package
407             if ((classesDirectory != null) && classesDirectory.exists()) {
408                 getLog().debug("merge classesDirectory: " + classesDirectory);
409                 jarArchiver.addDirectory(classesDirectory);
410             } else {
411                 getLog().info("classesDirectory: does not exists, not adding to final jars");
412             }
413 
414             if (isTest) {
415                 getLog().debug("merge TestOutputDirectory: " + mavenProject.getBuild().getTestOutputDirectory());
416                 File testClassesDirectory = new File(mavenProject.getBuild().getTestOutputDirectory());
417                 if (testClassesDirectory.exists()) {
418                     jarArchiver.addDirectory(testClassesDirectory);
419                 }
420             }
421             archiver.setArchiver(jarArchiver);
422 
423             archiver.setOutputFile(getJarFile(packageClassifier));
424 
425             archive.setAddMavenDescriptor(false);
426 
427             List dependancy;
428             if (!isTest) {
429                 dependancy = mavenProject.getCompileArtifacts();
430             } else {
431                 dependancy = mavenProject.getTestArtifacts();
432             }
433             // Verify assembly exclusions
434             if ((assembly != null) && (assembly.exclusions != null)) {
435                 for (Iterator iter = assembly.exclusions.iterator(); iter.hasNext();) {
436                     Exclusion excl = (Exclusion) iter.next();
437                     boolean exclusionFound = false;
438                     for (Iterator i = dependancy.iterator(); i.hasNext();) {
439                         Artifact artifact = (Artifact) i.next();
440                         if (excl.match(artifact)) {
441                             exclusionFound = true;
442                             break;
443                         }
444                     }
445                     if (!exclusionFound) {
446                         getLog().warn("assembly.exclusions artifact not found " + excl.toString());
447                     }
448                 }
449             }
450 
451             for (Iterator i = dependancy.iterator(); i.hasNext();) {
452                 Artifact artifact = (Artifact) i.next();
453                 if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) {
454                     if (!isInclusion(artifact)) {
455                         continue;
456                     }
457                 }
458                 if (Artifact.SCOPE_SYSTEM.equals(artifact.getScope())) {
459                     if (!isInclusion(artifact)) {
460                         continue;
461                     }
462                 }
463                 if ((!isTest) && (Artifact.SCOPE_TEST.equals(artifact.getScope()))) {
464                     continue;
465                 }
466                 if ("junit".equals(artifact.getArtifactId()) && "junit".equals(artifact.getGroupId())) {
467                     if (!isInclusion(artifact)) {
468                         continue;
469                     }
470                 }
471 
472                 if (isExclusion(artifact)) {
473                     continue;
474                 }
475 
476                 File file = getClasspathElement(artifact, mavenProject);
477                 if (file.isDirectory()) {
478                     getLog().info("merge project: " + artifact.getArtifactId() + " " + file);
479                     jarArchiver.addDirectory(file);
480                 } else {
481                     getLog().info("merge artifact: " + artifact.getArtifactId() + " scope:" + artifact.getScope());
482                     jarArchiver.addArchivedFileSet(file);
483                 }
484             }
485 
486             // create archive
487 
488             archiver.createArchive(mavenProject, archive);
489         } catch (Throwable e) {
490             getLog().error("Unable to create jar " + e.getMessage(), e);
491             throw new MojoExecutionException("Unable to create jar", e);
492         }
493 
494         if ((attach && (!isTest)) || (isTest && testAttach)) {
495             projectHelper.attachArtifact(mavenProject, "jar", packageClassifier, getJarFile(packageClassifier));
496         }
497     }
498 
499     private boolean isExclusion(Artifact artifact) {
500         if ((assembly == null) || (assembly.exclusions == null)) {
501             return false;
502         }
503         for (Iterator iter = assembly.exclusions.iterator(); iter.hasNext();) {
504             Exclusion excl = (Exclusion) iter.next();
505             if (excl.match(artifact)) {
506                 return true;
507             }
508         }
509         return false;
510     }
511 
512     private boolean isInclusion(Artifact artifact) {
513         if ((assembly == null) || (assembly.inclusions == null)) {
514             return false;
515         }
516         for (Iterator iter = assembly.inclusions.iterator(); iter.hasNext();) {
517             Inclusion incl = (Inclusion) iter.next();
518             if (incl.match(artifact)) {
519                 return true;
520             }
521         }
522         return false;
523     }
524 
525 }