View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.ear.it;
20  
21  import javax.xml.parsers.DocumentBuilder;
22  import javax.xml.parsers.DocumentBuilderFactory;
23  import javax.xml.parsers.ParserConfigurationException;
24  
25  import java.io.File;
26  import java.io.FilenameFilter;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.List;
32  import java.util.jar.JarFile;
33  import java.util.jar.JarInputStream;
34  import java.util.jar.Manifest;
35  import java.util.zip.ZipEntry;
36  
37  import junit.framework.TestCase;
38  import org.apache.maven.plugins.ear.util.ResourceEntityResolver;
39  import org.apache.maven.shared.verifier.VerificationException;
40  import org.apache.maven.shared.verifier.Verifier;
41  import org.apache.maven.shared.verifier.util.ResourceExtractor;
42  import org.custommonkey.xmlunit.Diff;
43  import org.custommonkey.xmlunit.XMLAssert;
44  import org.junit.Assert;
45  import org.xml.sax.SAXException;
46  import org.xml.sax.helpers.DefaultHandler;
47  
48  /**
49   * Base class for ear test cases.
50   *
51   * @author <a href="snicoll@apache.org">Stephane Nicoll</a>
52   */
53  public abstract class AbstractEarPluginIT extends TestCase {
54  
55      private static final String FINAL_NAME_PREFIX = "maven-ear-plugin-test-";
56  
57      private static final String FINAL_NAME_SUFFIX = "-99.0";
58  
59      /**
60       * The base directory.
61       */
62      private File basedir;
63  
64      private File settingsFile = new File(getBasedir().getAbsolutePath(), "target/test-classes/settings.xml");
65  
66      /**
67       * Execute the EAR plugin for the specified project.
68       *
69       * @param projectName the name of the project
70       * @param expectNoError true/false
71       * @param cleanBeforeExecute call clean plugin before execution
72       * @return the base directory of the project
73       */
74      protected File executeMojo(final String projectName, boolean expectNoError, boolean cleanBeforeExecute)
75              throws VerificationException, IOException {
76          System.out.println("  Building: " + projectName);
77  
78          File testDir = getTestDir(projectName);
79          Verifier verifier = new Verifier(testDir.getAbsolutePath());
80          verifier.setAutoclean(cleanBeforeExecute);
81          // Let's add alternate settings.xml setting so that the latest dependencies are used
82          String localRepo = System.getProperty("localRepositoryPath");
83          verifier.setLocalRepo(localRepo);
84  
85          verifier.addCliArguments("-s", settingsFile.getAbsolutePath()); //
86          verifier.addCliArgument("-X");
87          verifier.addCliArgument("package");
88  
89          // On linux and MacOS X, an exception is thrown if a build failure occurs underneath
90          try {
91              verifier.execute();
92          } catch (VerificationException e) {
93              // @TODO needs to be handled nicely in the verifier
94              if (expectNoError || !e.getMessage().contains("Exit code was non-zero")) {
95                  throw e;
96              }
97          }
98  
99          // If no error is expected make sure that error logs are free
100         if (expectNoError) {
101             verifier.verifyErrorFreeLog();
102         }
103         return testDir;
104     }
105 
106     /**
107      * Execute the EAR plugin for the specified project.
108      *
109      * @param projectName the name of the project
110      * @return the base directory of the project
111      */
112     protected File executeMojo(final String projectName) throws VerificationException, IOException {
113         return executeMojo(projectName, true, true);
114     }
115 
116     /**
117      * Executes the specified projects and asserts the given artifacts. Asserts the deployment descriptors are valid.
118      * Asserts Class-Path entry of manifest of EAR modules.
119      *
120      * @param projectName the project to test
121      * @param earModuleName the name of 1st level EAR module in multi-module project or {@code null}
122      *                      if project is single-module
123      * @param expectedArtifacts the list of artifacts to be found in the EAR archive
124      * @param artifactsDirectory whether the artifact from {@code expectedArtifacts} list is an exploded or not
125      * @param artifactsToValidateManifest the list of EAR archive artifacts to validate Class-Path entry of artifact
126      *                                    manifest or {@code null} if there is no need to validate Class-Path entry
127      * @param artifactsToValidateManifestDirectory whether the artifact from {@code artifactsToValidateManifest} list is
128      *                                             an exploded or not, can be {@code null} if
129      *                                             {@code artifactsToValidateManifest} is {@code null}
130      * @param expectedClassPathElements the list of elements of Class-Path entry of manifest, rows should match
131      *                                  artifacts passed in {@code artifactsToValidateManifest} parameter;
132      *                                  can be {@code null} if {@code artifactsToValidateManifest} is {@code null}
133      * @param cleanBeforeExecute call clean plugin before execution
134      * @return the base directory of the project
135      */
136     protected File doTestProject(
137             final String projectName,
138             final String earModuleName,
139             final String[] expectedArtifacts,
140             boolean[] artifactsDirectory,
141             final String[] artifactsToValidateManifest,
142             boolean[] artifactsToValidateManifestDirectory,
143             final String[][] expectedClassPathElements,
144             final boolean cleanBeforeExecute)
145             throws VerificationException, IOException {
146         final File baseDir = executeMojo(projectName, true, cleanBeforeExecute);
147 
148         final File earModuleDir = getEarModuleDirectory(baseDir, earModuleName);
149         assertEarArchive(earModuleDir, projectName);
150         assertEarDirectory(earModuleDir, projectName);
151         assertArchiveContent(earModuleDir, projectName, expectedArtifacts, artifactsDirectory);
152         assertDeploymentDescriptors(earModuleDir, projectName);
153         assertClassPathElements(
154                 earModuleDir,
155                 projectName,
156                 artifactsToValidateManifest,
157                 artifactsToValidateManifestDirectory,
158                 expectedClassPathElements);
159 
160         return baseDir;
161     }
162 
163     /**
164      * Executes the specified projects and asserts the given artifacts. Assert the deployment descriptors are valid.
165      *
166      * @param projectName the project to test
167      * @param expectedArtifacts the list of artifacts to be found in the EAR archive
168      * @param artifactsDirectory whether the artifact from {@code expectedArtifacts} list is an exploded or not
169      * @return the base directory of the project
170      */
171     protected File doTestProject(
172             final String projectName, final String[] expectedArtifacts, final boolean[] artifactsDirectory)
173             throws VerificationException, IOException {
174         return doTestProject(projectName, null, expectedArtifacts, artifactsDirectory, null, null, null, true);
175     }
176 
177     /**
178      * Executes the specified projects and asserts the given artifacts as artifacts (non directory)
179      *
180      * @param projectName the project to test
181      * @param expectedArtifacts the list of artifacts to be found in the EAR archive
182      * @return the base directory of the project
183      */
184     protected File doTestProject(final String projectName, final String[] expectedArtifacts)
185             throws VerificationException, IOException {
186         return doTestProject(projectName, expectedArtifacts, new boolean[expectedArtifacts.length]);
187     }
188 
189     /**
190      * Executes the specified projects and asserts the given artifacts as artifacts (non directory)
191      *
192      * @param projectName the project to test
193      * @param expectedArtifacts the list of artifacts to be found in the EAR archive
194      * @param cleanBeforeExecute call clean plugin before execution
195      * @return the base directory of the project
196      */
197     protected File doTestProject(final String projectName, final String[] expectedArtifacts, boolean cleanBeforeExecute)
198             throws VerificationException, IOException {
199         return doTestProject(
200                 projectName,
201                 null,
202                 expectedArtifacts,
203                 new boolean[expectedArtifacts.length],
204                 null,
205                 null,
206                 null,
207                 cleanBeforeExecute);
208     }
209 
210     protected void assertEarArchive(final File baseDir, final String projectName) {
211         assertTrue(
212                 "EAR archive does not exist",
213                 getEarArchive(baseDir, projectName).exists());
214     }
215 
216     protected void assertEarDirectory(final File baseDir, final String projectName) {
217         assertTrue(
218                 "EAR archive directory does not exist",
219                 getEarDirectory(baseDir, projectName).exists());
220     }
221 
222     protected File getEarModuleDirectory(final File baseDir, final String earModuleName) {
223         return earModuleName == null ? baseDir : new File(baseDir, earModuleName);
224     }
225 
226     protected File getTargetDirectory(final File basedir) {
227         return new File(basedir, "target");
228     }
229 
230     protected File getEarArchive(final File baseDir, final String projectName) {
231         return new File(getTargetDirectory(baseDir), buildFinalName(projectName) + ".ear");
232     }
233 
234     protected File getEarDirectory(final File baseDir, final String projectName) {
235         return new File(getTargetDirectory(baseDir), buildFinalName(projectName));
236     }
237 
238     protected String buildFinalName(final String projectName) {
239         return FINAL_NAME_PREFIX + projectName + FINAL_NAME_SUFFIX;
240     }
241 
242     private void assertArchiveContent(
243             final File baseDir,
244             final String projectName,
245             final String[] artifactNames,
246             final boolean[] artifactsDirectory) {
247         // sanity check
248         assertEquals(
249                 "Wrong parameter, artifacts mismatch directory flags", artifactNames.length, artifactsDirectory.length);
250 
251         File dir = getEarDirectory(baseDir, projectName);
252 
253         // Let's build the expected directories sort list
254         final List<File> expectedDirectories = new ArrayList<>();
255         for (int i = 0; i < artifactsDirectory.length; i++) {
256             if (artifactsDirectory[i]) {
257                 expectedDirectories.add(new File(dir, artifactNames[i]));
258             }
259         }
260 
261         final List<File> actualFiles = buildArchiveContentFiles(dir, expectedDirectories);
262         assertEquals("Artifacts mismatch " + actualFiles, artifactNames.length, actualFiles.size());
263         for (int i = 0; i < artifactNames.length; i++) {
264             String artifactName = artifactNames[i];
265             final boolean isDirectory = artifactsDirectory[i];
266             File expectedFile = new File(dir, artifactName);
267 
268             assertEquals(
269                     "Artifact[" + artifactName + "] not in the right form (exploded/archive",
270                     isDirectory,
271                     expectedFile.isDirectory());
272             assertTrue("Artifact[" + artifactName + "] not found in ear archive", actualFiles.contains(expectedFile));
273         }
274     }
275 
276     /**
277      * Asserts that given EAR archive artifacts have expected entries and don't have unexpected entries.
278      *
279      * @param baseDir the directory of the tested project
280      * @param projectName the project to test
281      * @param earModuleName the name of 1st level EAR module in multi-module project or {@code null}
282      *                      if project is single-module
283      * @param artifacts the list of artifacts to be found in the EAR archive
284      * @param artifactsDirectory whether the artifact from {@code artifacts} list is an exploded or not
285      * @param includedEntries entries which should exist in artifacts, rows should match artifacts passed in
286      *                        {@code artifacts} parameter; can be {@code null} if there is no need to assert
287      *                        existence of entries in artifacts
288      * @param excludedEntries entries which should not exist in artifacts, rows should match artifacts passed in
289      *                        {@code artifacts} parameter; can be {@code null} if there is no need to assert
290      *                        absence of paths in artifacts
291      * @throws IOException exception in case of failure during reading of artifact archive.
292      */
293     protected void assertEarModulesContent(
294             final File baseDir,
295             final String projectName,
296             final String earModuleName,
297             final String[] artifacts,
298             final boolean[] artifactsDirectory,
299             final String[][] includedEntries,
300             final String[][] excludedEntries)
301             throws IOException {
302         assertTrue(
303                 "Wrong parameter, artifacts mismatch directory flags", artifacts.length <= artifactsDirectory.length);
304         if (includedEntries != null) {
305             assertTrue(
306                     "Rows of includedEntries parameter should match items of artifacts parameter",
307                     artifacts.length <= includedEntries.length);
308         }
309         if (excludedEntries != null) {
310             assertTrue(
311                     "Rows of excludedEntries parameter should match items of artifacts parameter",
312                     artifacts.length <= excludedEntries.length);
313         }
314 
315         final File earDirectory = getEarDirectory(getEarModuleDirectory(baseDir, earModuleName), projectName);
316         for (int i = 0; i != artifacts.length; ++i) {
317             final String artifactName = artifacts[i];
318             final File module = new File(earDirectory, artifactName);
319             assertTrue("Artifact [" + artifactName + "] should exist in EAR", module.exists());
320 
321             final boolean artifactDirectory = artifactsDirectory[i];
322             assertEquals(
323                     "Artifact [" + artifactName + "] should be a " + (artifactDirectory ? "directory" : "file"),
324                     artifactDirectory,
325                     module.isDirectory());
326 
327             if (includedEntries == null && excludedEntries == null) {
328                 continue;
329             }
330 
331             final boolean includedEntriesDefined =
332                     includedEntries != null && includedEntries[i] != null && includedEntries[i].length != 0;
333             final boolean excludedEntriesDefined =
334                     excludedEntries != null && excludedEntries[i] != null && excludedEntries[i].length != 0;
335             if (!includedEntriesDefined && !excludedEntriesDefined) {
336                 continue;
337             }
338 
339             if (artifactDirectory) {
340                 if (includedEntriesDefined) {
341                     for (String includedEntry : includedEntries[i]) {
342                         if (includedEntry == null || includedEntry.length() == 0) {
343                             continue;
344                         }
345                         final File inclusion = new File(artifactName, includedEntry);
346                         assertTrue(
347                                 "Entry [" + includedEntry + "] should exist in artifact [" + artifactName + "] of EAR",
348                                 inclusion.exists());
349                     }
350                 }
351                 if (excludedEntriesDefined) {
352                     for (String excludedEntry : excludedEntries[i]) {
353                         if (excludedEntry == null || excludedEntry.length() == 0) {
354                             continue;
355                         }
356                         final File exclusion = new File(artifactName, excludedEntry);
357                         assertFalse(
358                                 "Entry [" + excludedEntry + "] should not exist in artifact [" + artifactName
359                                         + "] of EAR",
360                                 exclusion.exists());
361                     }
362                 }
363             } else {
364                 try (JarFile moduleJar = new JarFile(module)) {
365                     if (includedEntriesDefined) {
366                         for (String includedEntry : includedEntries[i]) {
367                             if (includedEntry == null || includedEntry.length() == 0) {
368                                 continue;
369                             }
370                             final ZipEntry inclusion = moduleJar.getEntry(includedEntry);
371                             assertNotNull(
372                                     "Entry [" + includedEntry + "] should exist in artifact [" + artifactName
373                                             + "] of EAR",
374                                     inclusion);
375                         }
376                     }
377                     if (excludedEntriesDefined) {
378                         for (String excludedEntry : excludedEntries[i]) {
379                             if (excludedEntry == null || excludedEntry.length() == 0) {
380                                 continue;
381                             }
382                             final ZipEntry exclusion = moduleJar.getEntry(excludedEntry);
383                             assertNull(
384                                     "Entry [" + excludedEntry + "] should not exist in artifact [" + artifactName
385                                             + "] of EAR",
386                                     exclusion);
387                         }
388                     }
389                 }
390             }
391         }
392     }
393 
394     private static List<File> buildArchiveContentFiles(final File baseDir, final List<File> expectedDirectories) {
395         final List<File> result = new ArrayList<>();
396         addFiles(baseDir, result, expectedDirectories);
397 
398         return result;
399     }
400 
401     private static void addFiles(final File directory, final List<File> files, final List<File> expectedDirectories) {
402         File[] result = directory.listFiles(new FilenameFilter() {
403             public boolean accept(File dir, String name) {
404                 return !name.equals("META-INF");
405             }
406         });
407 
408         /*
409          * Kinda complex. If we found a file, we always add it to the list of files. If a directory is within the
410          * expectedDirectories short list we add it but we don't add it's content. Otherwise, we don't add the directory
411          * *BUT* we browse it's content
412          */
413         for (File file : result) {
414             if (file.isFile()) {
415                 files.add(file);
416             } else if (expectedDirectories.contains(file)) {
417                 files.add(file);
418             } else {
419                 addFiles(file, files, expectedDirectories);
420             }
421         }
422     }
423 
424     private File getBasedir() {
425         if (basedir != null) {
426             return basedir;
427         }
428 
429         final String basedirString = System.getProperty("basedir");
430         if (basedirString == null) {
431             basedir = new File("");
432         } else {
433             basedir = new File(basedirString);
434         }
435         return basedir;
436     }
437 
438     protected File getTestDir(String projectName) throws IOException {
439         return ResourceExtractor.simpleExtractResources(getClass(), "/projects/" + projectName);
440     }
441 
442     // Generated application.xml stuff
443 
444     /**
445      * Asserts that the deployment descriptors have been generated successfully.
446      *
447      * This test assumes that deployment descriptors are located in the {@code expected-META-INF} directory of the
448      * project. Note that the {@code MANIFEST.mf} file is ignored and is not tested.
449      *
450      * @param baseDir the directory of the tested project
451      * @param projectName the name of the project
452      * @throws IOException exception in case of an error.
453      */
454     protected void assertDeploymentDescriptors(final File baseDir, final String projectName) throws IOException {
455         final File earDirectory = getEarDirectory(baseDir, projectName);
456         final File[] actualDeploymentDescriptors = getDeploymentDescriptors(new File(earDirectory, "META-INF"));
457         final File[] expectedDeploymentDescriptors = getDeploymentDescriptors(new File(baseDir, "expected-META-INF"));
458 
459         if (expectedDeploymentDescriptors == null) {
460             assertNull("No deployment descriptor was expected", actualDeploymentDescriptors);
461         } else {
462             assertNotNull("Missing deployment descriptor", actualDeploymentDescriptors);
463 
464             // Make sure we have the same number of files
465             assertEquals(
466                     "Number of Deployment descriptor(s) mismatch",
467                     expectedDeploymentDescriptors.length,
468                     actualDeploymentDescriptors.length);
469 
470             // Sort the files so that we have the same behavior here
471             Arrays.sort(expectedDeploymentDescriptors);
472             Arrays.sort(actualDeploymentDescriptors);
473 
474             for (int i = 0; i < expectedDeploymentDescriptors.length; i++) {
475                 File expectedDeploymentDescriptor = expectedDeploymentDescriptors[i];
476                 File actualDeploymentDescriptor = actualDeploymentDescriptors[i];
477 
478                 assertEquals(
479                         "File name mismatch",
480                         expectedDeploymentDescriptor.getName(),
481                         actualDeploymentDescriptor.getName());
482 
483                 try {
484                     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
485                     dbf.setNamespaceAware(true);
486                     dbf.setValidating(true);
487                     DocumentBuilder docBuilder = dbf.newDocumentBuilder();
488                     docBuilder.setEntityResolver(new ResourceEntityResolver());
489                     docBuilder.setErrorHandler(new DefaultHandler());
490 
491                     final Diff myDiff = new Diff(
492                             docBuilder.parse(expectedDeploymentDescriptor),
493                             docBuilder.parse(actualDeploymentDescriptor));
494                     XMLAssert.assertXMLEqual(
495                             "Wrong deployment descriptor generated for[" + expectedDeploymentDescriptor.getName() + "]",
496                             myDiff,
497                             true);
498                 } catch (SAXException | ParserConfigurationException e) {
499                     e.printStackTrace();
500                     fail("Could not assert deployment descriptor " + e.getMessage());
501                 }
502             }
503         }
504     }
505 
506     private static File[] getDeploymentDescriptors(final File ddDirectory) {
507         return ddDirectory.listFiles(new FilenameFilter() {
508             public boolean accept(File dir, String name) {
509                 return !name.equalsIgnoreCase("manifest.mf");
510             }
511         });
512     }
513 
514     /**
515      * Asserts that given EAR archive artifacts have expected elements in artifact manifest Class-Path entry.
516      *
517      * @param baseDir the directory of the tested project
518      * @param projectName the name of the project
519      * @param artifacts the list of EAR archive artifacts to validate Class-Path entry of artifact manifest or
520      *                  {@code null} if there is no need to validate Class-Path entry
521      * @param artifactsDirectory whether the artifact from {@code artifacts} list is an exploded or not,
522      *                           can be {@code null} if {@code artifacts} is {@code null}
523      * @param expectedClassPathElements the list of expected elements of Class-Path entry of manifest, rows should match
524      *                                  artifacts passed in {@code artifacts} parameter; can be {@code null}
525      *                                  if {@code artifacts} is {@code null}
526      * @throws IOException exception in case of failure during reading of artifact manifest.
527      */
528     protected void assertClassPathElements(
529             final File baseDir,
530             String projectName,
531             String[] artifacts,
532             boolean[] artifactsDirectory,
533             String[][] expectedClassPathElements)
534             throws IOException {
535         if (artifacts == null) {
536             return;
537         }
538 
539         assertNotNull("artifactsDirectory should be provided if artifacts is provided", artifactsDirectory);
540         assertTrue(
541                 "Size of artifactsDirectory should match size of artifacts parameter",
542                 artifacts.length <= artifactsDirectory.length);
543         assertNotNull(
544                 "expectedClassPathElements should be provided if artifacts is provided", expectedClassPathElements);
545         assertTrue(
546                 "Rows of expectedClassPathElements parameter should match items of artifacts parameter",
547                 artifacts.length <= expectedClassPathElements.length);
548 
549         final File earFile = getEarArchive(baseDir, projectName);
550         for (int i = 0; i != artifacts.length; ++i) {
551             final String moduleArtifact = artifacts[i];
552             final String[] classPathElements = getClassPathElements(earFile, moduleArtifact, artifactsDirectory[i]);
553             if (expectedClassPathElements[i] == null) {
554                 assertNull(
555                         "Class-Path entry should not exist in module [" + moduleArtifact + "] manifest",
556                         classPathElements);
557             } else {
558                 Assert.assertArrayEquals(
559                         "Wrong elements of Class-Path entry of module [" + moduleArtifact + "] manifest",
560                         expectedClassPathElements[i],
561                         classPathElements);
562             }
563         }
564     }
565 
566     /**
567      * Retrieves elements of Class-Path entry of manifest of given EAR module.
568      *
569      * @param earFile the EAR file to investigate
570      * @param artifact the name of artifact in EAR archive representing EAR module
571      * @return elements of Class-Path entry of manifest of EAR module which is represented by
572      * {@code artifact} artifact in {@code earFile} file
573      */
574     protected String[] getClassPathElements(final File earFile, final String artifact, final boolean directory)
575             throws IOException {
576         final String classPath;
577         try (JarFile earJarFile = new JarFile(earFile)) {
578             final ZipEntry moduleEntry = earJarFile.getEntry(artifact);
579             assertNotNull("Artifact [" + artifact + "] should exist in EAR", moduleEntry);
580             if (directory) {
581                 final String manifestEntryName = artifact + "/META-INF/MANIFEST.MF";
582                 final ZipEntry manifestEntry = earJarFile.getEntry(manifestEntryName);
583                 assertNotNull(manifestEntryName + " manifest file should exist in EAR", manifestEntry);
584                 try (InputStream manifestInputStream = earJarFile.getInputStream(manifestEntry)) {
585                     final Manifest manifest = new Manifest(manifestInputStream);
586                     classPath = manifest.getMainAttributes().getValue("Class-Path");
587                 }
588             } else {
589                 try (InputStream moduleInputStream = earJarFile.getInputStream(moduleEntry);
590                         JarInputStream moduleJarInputStream = new JarInputStream(moduleInputStream)) {
591                     final Manifest manifest = moduleJarInputStream.getManifest();
592                     assertNotNull("Artifact [" + artifact + "] of EAR should have manifest", manifest);
593                     classPath = manifest.getMainAttributes().getValue("Class-Path");
594                 }
595             }
596         }
597         if (classPath == null) {
598             return null;
599         }
600         final String trimmedClassPath = classPath.trim();
601         if (trimmedClassPath.length() == 0) {
602             return new String[0];
603         }
604         return trimmedClassPath.split(" ");
605     }
606 }