2121
2222import static io .wcm .devops .conga .plugins .aem .maven .allpackage .RunModeUtil .RUNMODE_AUTHOR ;
2323import static io .wcm .devops .conga .plugins .aem .maven .allpackage .RunModeUtil .RUNMODE_PUBLISH ;
24+ import static org .apache .jackrabbit .vault .packaging .PackageProperties .NAME_DEPENDENCIES ;
2425
2526import java .io .File ;
27+ import java .io .FileOutputStream ;
2628import java .io .IOException ;
29+ import java .io .InputStream ;
30+ import java .util .Enumeration ;
2731import java .util .List ;
32+ import java .util .Properties ;
2833import java .util .stream .Collectors ;
34+ import java .util .zip .ZipEntry ;
35+ import java .util .zip .ZipFile ;
36+ import java .util .zip .ZipOutputStream ;
37+
38+ import org .apache .commons .io .FileUtils ;
39+ import org .apache .commons .io .IOUtils ;
40+ import org .apache .commons .lang3 .StringUtils ;
41+ import org .apache .jackrabbit .vault .packaging .Dependency ;
42+ import org .apache .jackrabbit .vault .packaging .DependencyUtil ;
43+ import org .apache .jackrabbit .vault .packaging .VersionRange ;
2944
3045import io .wcm .devops .conga .plugins .aem .maven .model .ContentPackageFile ;
3146import io .wcm .tooling .commons .contentpackagebuilder .ContentPackage ;
3752 */
3853public final class AllPackageBuilder {
3954
40- private AllPackageBuilder () {
41- // static methods only
42- }
55+ private final File targetFile ;
56+ private final String groupName ;
57+ private final String packageName ;
58+ private boolean autoDependencies ;
4359
4460 /**
45- * Build "all" content package.
4661 * @param targetFile Target file
47- * @param contentPackages Content packages (invalid will be filtered out)
4862 * @param groupName Group name
4963 * @param packageName Package name
64+ */
65+ public AllPackageBuilder (File targetFile , String groupName , String packageName ) {
66+ this .targetFile = targetFile ;
67+ this .groupName = groupName ;
68+ this .packageName = packageName ;
69+ }
70+
71+ /**
72+ * @param value Automatically generate dependencies between content packages based on file order in CONGA
73+ * configuration.
74+ * @return this
75+ */
76+ public AllPackageBuilder autoDependencies (boolean value ) {
77+ this .autoDependencies = value ;
78+ return this ;
79+ }
80+
81+ /**
82+ * Build "all" content package.
83+ * @param contentPackages Content packages (invalid will be filtered out)
5084 * @return true if "all" package was generated, false if not valid package was found.
85+ * @throws IOException I/O exception
5186 */
52- public static boolean build (File targetFile ,
53- List <ContentPackageFile > contentPackages ,
54- String groupName , String packageName ) throws IOException {
87+ public boolean build (List <ContentPackageFile > contentPackages ) throws IOException {
5588
5689 // collect AEM content packages for this node
5790 List <ContentPackageFile > validContentPackages = contentPackages .stream ()
@@ -72,12 +105,22 @@ public static boolean build(File targetFile,
72105 builder .filter (new PackageFilter (rootPath ));
73106
74107 // build content package
108+ ContentPackageFile previousPkg = null ;
75109 try (ContentPackage contentPackage = builder .build (targetFile )) {
76110 for (ContentPackageFile pkg : validContentPackages ) {
77111 String path = buildPackagePath (pkg , rootPath );
78- contentPackage .addFile (path , pkg .getFile ());
112+ if (autoDependencies && previousPkg != null ) {
113+ // wire previous package in package dependency
114+ addFileWithDependency (contentPackage , path , pkg , previousPkg );
115+ }
116+ else {
117+ // add package file directly
118+ contentPackage .addFile (path , pkg .getFile ());
119+ }
120+ previousPkg = pkg ;
79121 }
80122 }
123+
81124 return true ;
82125 }
83126
@@ -113,4 +156,84 @@ else if (RunModeUtil.isOnlyPublish(pkg)) {
113156 return rootPath + "/" + pkg .getPackageType () + "/install" + runModeSuffix + "/" + pkg .getFile ().getName ();
114157 }
115158
159+ /**
160+ * Rewrite content package ZIP file while adding to "all" package:
161+ * Add dependency to previous package in CONGA configuration file oder.
162+ * @param contentPackage Target content page
163+ * @param path Path in target content package
164+ * @param pkg Package to add
165+ * @param previousPkg Previous package to get dependency information from
166+ * @throws IOException I/O error
167+ */
168+ private static void addFileWithDependency (ContentPackage contentPackage , String path ,
169+ ContentPackageFile pkg , ContentPackageFile previousPkg ) throws IOException {
170+
171+ // create temp zip file to create rewritten copy of package
172+ File tempFile = File .createTempFile ("pkg" , ".zip" );
173+
174+ // open original content package
175+ try (ZipFile zipFileIn = new ZipFile (pkg .getFile ())) {
176+
177+ // iterate through entries and write them to the temp. zip file
178+ try (FileOutputStream fos = new FileOutputStream (tempFile );
179+ ZipOutputStream zipOut = new ZipOutputStream (fos )) {
180+ Enumeration <? extends ZipEntry > zipInEntries = zipFileIn .entries ();
181+ while (zipInEntries .hasMoreElements ()) {
182+ ZipEntry zipInEntry = zipInEntries .nextElement ();
183+ ZipEntry zipOutEntry = new ZipEntry (zipInEntry .getName ());
184+ if (!zipInEntry .isDirectory ()) {
185+ zipOut .putNextEntry (zipOutEntry );
186+ if (StringUtils .equals (zipInEntry .getName (), "META-INF/vault/properties.xml" )) {
187+ // if entry is properties.xml, update dependency information
188+ try (InputStream is = zipFileIn .getInputStream (zipInEntry )) {
189+ Properties props = new Properties ();
190+ props .loadFromXML (is );
191+ updateDependencies (props , previousPkg );
192+ props .storeToXML (zipOut , null );
193+ }
194+ }
195+ else {
196+ // otherwise transfer the binary data 1:1
197+ try (InputStream is = zipFileIn .getInputStream (zipInEntry )) {
198+ IOUtils .copy (is , zipOut );
199+ }
200+ }
201+ zipOut .closeEntry ();
202+ }
203+ }
204+ }
205+
206+ // add temp zip file to "all" content package
207+ contentPackage .addFile (path , tempFile );
208+ }
209+ finally {
210+ FileUtils .deleteQuietly (tempFile );
211+ }
212+ }
213+
214+ /**
215+ * Add dependency information to dependencies string in properties (if it does not exist already).
216+ * @param props Properties
217+ * @param dependencyFile Dependency package
218+ */
219+ private static void updateDependencies (Properties props , ContentPackageFile dependencyFile ) {
220+ String [] existingDepsStrings = StringUtils .split (props .getProperty (NAME_DEPENDENCIES ), "," );
221+ Dependency [] existingDeps = null ;
222+ if (existingDepsStrings != null && existingDepsStrings .length > 0 ) {
223+ existingDeps = Dependency .fromString (existingDepsStrings );
224+ }
225+
226+ Dependency newDependency = new Dependency (dependencyFile .getGroup (), dependencyFile .getName (),
227+ VersionRange .fromString (dependencyFile .getVersion ()));
228+ Dependency [] deps ;
229+ if (existingDeps != null ) {
230+ deps = DependencyUtil .add (existingDeps , newDependency );
231+ }
232+ else {
233+ deps = new Dependency [] { newDependency };
234+ }
235+
236+ props .put (NAME_DEPENDENCIES , Dependency .toString (deps ));
237+ }
238+
116239}
0 commit comments