Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ContentPackageOsgiConfigPostProcessor: Support Combined JSON files #86

Merged
merged 2 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
<action type="update" dev="sseifert" issue="85">
ContentPackageOsgiConfigPostProcessor: Write OSGi configurations as .cfg.json files instead of .config files.
</action>
<action type="update" dev="sseifert" issue="86"><![CDATA[
ContentPackageOsgiConfigPostProcessor: Accept both <a href="https://devops.wcm.io/conga/plugins/sling//osgi-config-combined-json.html">Combined JSON files</a>
and Sling Provisioning File Format as input for generating OSGi configurations.
]]></action>
</release>

<release version="2.19.10" date="2023-12-18">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
Expand All @@ -39,25 +40,24 @@
import org.apache.sling.provisioning.model.Model;
import org.slf4j.Logger;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.wcm.devops.conga.generator.GeneratorException;
import io.wcm.devops.conga.generator.plugins.postprocessor.AbstractPostProcessor;
import io.wcm.devops.conga.generator.spi.context.FileContext;
import io.wcm.devops.conga.generator.spi.context.FileHeaderContext;
import io.wcm.devops.conga.generator.spi.context.PostProcessorContext;
import io.wcm.devops.conga.plugins.aem.util.ContentPackageUtil;
import io.wcm.devops.conga.plugins.sling.postprocessor.JsonOsgiConfigPostProcessor;
import io.wcm.devops.conga.plugins.sling.util.ConfigConsumer;
import io.wcm.devops.conga.plugins.sling.util.JsonOsgiConfigUtil;
import io.wcm.devops.conga.plugins.sling.util.OsgiConfigUtil;
import io.wcm.devops.conga.plugins.sling.util.ProvisioningUtil;
import io.wcm.tooling.commons.contentpackagebuilder.ContentPackage;
import io.wcm.tooling.commons.contentpackagebuilder.ContentPackageBuilder;

/**
* Transforms a Sling Provisioning file into OSGi configurations (ignoring all other provisioning contents)
* and then packages them up in an AEM content package to be deployed via CRX package manager.
* Transforms a Sling Provisioning file or Combined JSON file as used by CONGA Sling Plugin
* into OSGi configurations and then packages them up in an AEM content package to be deployed via CRX package manager.
*/
public class ContentPackageOsgiConfigPostProcessor extends AbstractPostProcessor {

Expand All @@ -73,7 +73,8 @@ public String getName() {

@Override
public boolean accepts(FileContext file, PostProcessorContext context) {
return ProvisioningUtil.isProvisioningFile(file);
return ProvisioningUtil.isProvisioningFile(file)
|| StringUtils.endsWith(file.getFile().getName(), JsonOsgiConfigPostProcessor.FILE_EXTENSION);
}

@Override
Expand All @@ -88,14 +89,20 @@ public List<FileContext> apply(FileContext fileContext, PostProcessorContext con
FileHeaderContext fileHeader = extractFileHeader(fileContext, context);

// generate OSGi configurations
Model model = ProvisioningUtil.getModel(fileContext);
Model model;
if (ProvisioningUtil.isProvisioningFile(fileContext)) {
model = ProvisioningUtil.getModel(fileContext);
}
else {
model = JsonOsgiConfigUtil.readToProvisioningModel(file);
}

// check if any osgi configuration is present
boolean hasAnyConfig = !ProvisioningUtil.visitOsgiConfigurations(model,
(ConfigConsumer<Boolean>)(path, properties) -> true).isEmpty();

// create AEM content package with configurations
File zipFile = new File(file.getParentFile(), FilenameUtils.getBaseName(file.getName()) + ".zip");
File zipFile = new File(file.getParentFile(), getBaseFileName(file.getName()) + ".zip");
logger.info("Generate {}", zipFile.getCanonicalPath());

String rootPath = ContentPackageUtil.getMandatoryProp(options, PROPERTY_PACKAGE_ROOT_PATH);
Expand All @@ -113,19 +120,19 @@ public List<FileContext> apply(FileContext fileContext, PostProcessorContext con
else {
// create folder for root path if package is empty otherwise
// (to make sure probably already existing config is overridden/cleaned)
contentPackage.addContent(rootPath, ImmutableMap.of("jcr:primaryType", "nt:folder"));
contentPackage.addContent(rootPath, Map.of("jcr:primaryType", "nt:folder"));
}
}

// delete provisioning file after transformation
file.delete();
Files.delete(file.toPath());

// set force to true by default for CONGA-generated packages (but allow override from role definition)
Map<String, Object> modelOptions = new HashMap<>();
modelOptions.put("force", true);
modelOptions.putAll(fileContext.getModelOptions());

return ImmutableList.of(new FileContext().file(zipFile).modelOptions(modelOptions));
return List.of(new FileContext().file(zipFile).modelOptions(modelOptions));
}
catch (IOException ex) {
throw new GeneratorException("Unable to post-process sling provisioning OSGi configurations.", ex);
Expand Down Expand Up @@ -168,12 +175,21 @@ public Void accept(String path, Dictionary<String, Object> properties) throws IO
}
finally {
// remove temporary file
tempFile.delete();
Files.delete(tempFile.toPath());
}
return null;
}
});
return !result.isEmpty();
}

private String getBaseFileName(String fileName) {
if (StringUtils.endsWith(fileName, JsonOsgiConfigPostProcessor.FILE_EXTENSION)) {
return StringUtils.substringBeforeLast(fileName, JsonOsgiConfigPostProcessor.FILE_EXTENSION);
}
else {
return FilenameUtils.getBaseName(fileName);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@
import org.w3c.dom.Document;
import org.zeroturnaround.zip.ZipUtil;

import com.google.common.collect.ImmutableMap;

import io.wcm.devops.conga.generator.spi.PostProcessorPlugin;
import io.wcm.devops.conga.generator.spi.context.FileContext;
import io.wcm.devops.conga.generator.spi.context.PluginContextOptions;
Expand All @@ -61,7 +59,7 @@ class ContentPackageOsgiConfigPostProcessorTest {

private PostProcessorPlugin underTest;

private static final Map<String, Object> PACKAGE_OPTIONS = ImmutableMap.<String, Object>of(
private static final Map<String, Object> PACKAGE_OPTIONS = Map.of(
PROPERTY_PACKAGE_GROUP, "myGroup",
PROPERTY_PACKAGE_NAME, "myName",
PROPERTY_PACKAGE_DESCRIPTION, "myDesc",
Expand All @@ -74,15 +72,33 @@ void setUp() {
}

@Test
void testPostProcess() throws Exception {
void testPostProcess_Provisioning() throws Exception {
// prepare provisioning file
File target = new File("target/" + ContentPackageOsgiConfigPostProcessor.NAME + "-test");
File target = new File("target/" + ContentPackageOsgiConfigPostProcessor.NAME + "-provisioning");
if (target.exists()) {
FileUtils.deleteDirectory(target);
}
File contentPackageFile = new File(target, "test.txt");
FileUtils.copyFile(new File(getClass().getResource("/provisioning/provisioning.txt").toURI()), contentPackageFile);

postProcess_assertResult(target, contentPackageFile, "myDesc\n---\nSample comment in provisioning.txt");
}

@Test
void testPostProcess_JSON() throws Exception {
// prepare provisioning file
File target = new File("target/" + ContentPackageOsgiConfigPostProcessor.NAME + "-json");
if (target.exists()) {
FileUtils.deleteDirectory(target);
}
File contentPackageFile = new File(target, "test.osgiconfig.json");
FileUtils.copyFile(new File(getClass().getResource("/osgi-config-json/sample.osgiconfig.json").toURI()), contentPackageFile);

postProcess_assertResult(target, contentPackageFile, "myDesc");
}

private void postProcess_assertResult(File target, File contentPackageFile,
String expectedPackageDescriptionProperty) throws Exception {
// post-process
FileContext fileContext = new FileContext()
.file(contentPackageFile)
Expand Down Expand Up @@ -124,7 +140,7 @@ void testPostProcess() throws Exception {
Document propsXml = getXmlFromZip(zipFile, "META-INF/vault/properties.xml");
assertXpathEvaluatesTo("myGroup", "/properties/entry[@key='group']", propsXml);
assertXpathEvaluatesTo("myName", "/properties/entry[@key='name']", propsXml);
assertXpathEvaluatesTo("myDesc\n---\nSample comment in provisioning.txt", "/properties/entry[@key='description']", propsXml);
assertXpathEvaluatesTo(expectedPackageDescriptionProperty, "/properties/entry[@key='description']", propsXml);
assertXpathEvaluatesTo("1.5", "/properties/entry[@key='version']", propsXml);
assertXpathEvaluatesTo("container", "/properties/entry[@key='packageType']", propsXml);

Expand All @@ -134,13 +150,30 @@ void testPostProcess() throws Exception {
@Test
void testPostProcess_EmptyProvisioning() throws Exception {
// prepare provisioning file
File target = new File("target/" + ContentPackageOsgiConfigPostProcessor.NAME + "-test-empty");
File target = new File("target/" + ContentPackageOsgiConfigPostProcessor.NAME + "-empty-provisioning");
if (target.exists()) {
FileUtils.deleteDirectory(target);
}
File contentPackageFile = new File(target, "test.txt");
FileUtils.copyFile(new File(getClass().getResource("/provisioning/provisioning_empty.txt").toURI()), contentPackageFile);

postProcess_Empty_assertResult(target, contentPackageFile);
}

@Test
void testPostProcess_EmptyJSON() throws Exception {
// prepare provisioning file
File target = new File("target/" + ContentPackageOsgiConfigPostProcessor.NAME + "-empty-json");
if (target.exists()) {
FileUtils.deleteDirectory(target);
}
File contentPackageFile = new File(target, "test.osgiconfig.json");
FileUtils.copyFile(new File(getClass().getResource("/osgi-config-json/sample_empty.osgiconfig.json").toURI()), contentPackageFile);

postProcess_Empty_assertResult(target, contentPackageFile);
}

private void postProcess_Empty_assertResult(File target, File contentPackageFile) throws Exception {
// post-process
FileContext fileContext = new FileContext()
.file(contentPackageFile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
Expand All @@ -46,9 +47,6 @@
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import io.wcm.devops.conga.generator.UrlFileManager;
import io.wcm.devops.conga.generator.spi.PostProcessorPlugin;
import io.wcm.devops.conga.generator.spi.context.FileContext;
Expand All @@ -70,26 +68,25 @@ void setUp() {

@Test
void testPostProcess() throws Exception {
Map<String, Object> options = new HashMap<String, Object>();
Map<String, Object> options = new HashMap<>();
options.put(PROPERTY_PACKAGE_GROUP, "myGroup");
options.put(PROPERTY_PACKAGE_NAME, "myName");
options.put(PROPERTY_PACKAGE_ROOT_PATH, "/content/test");
options.put(PROPERTY_PACKAGE_AC_HANDLING, "ignore");
options.put(PROPERTY_PACKAGE_PACKAGE_TYPE, "content");
options.put(PROPERTY_PACKAGE_THUMBNAIL_IMAGE, "classpath:/package/thumbnail.png");
options.put(PROPERTY_PACKAGE_FILTERS, ImmutableList.of(
ImmutableMap.<String, Object>of("filter", "/content/test/1"),
ImmutableMap.<String, Object>of("filter", "/content/test/2",
"rules", ImmutableList.of(ImmutableMap.<String, Object>of("rule", "include", "pattern", "pattern1"),
ImmutableMap.<String, Object>of("rule", "exclude", "pattern", "pattern2")))
));
options.put(PROPERTY_PACKAGE_PROPERTIES, ImmutableMap.<String,Object>of(
options.put(PROPERTY_PACKAGE_FILTERS, List.of(
Map.of("filter", "/content/test/1"),
Map.of("filter", "/content/test/2",
"rules", List.of(Map.of("rule", "include", "pattern", "pattern1"),
Map.of("rule", "exclude", "pattern", "pattern2")))));
options.put(PROPERTY_PACKAGE_PROPERTIES, Map.of(
"prop1", "value1",
"my.custom.prop2", 123));
options.put(PROPERTY_PACKAGE_FILES, ImmutableList.of(
ImmutableMap.<String, Object>of("url", "classpath:/package/thumbnail.png", "path", "/content/image.png"),
ImmutableMap.<String, Object>of("file", "README.txt", "dir", "readme", "path", "/content/README.txt", "delete", true),
ImmutableMap.<String, Object>of("fileMatch", "file_(.*).txt", "dir", "files", "path", "/content/files/$1.txt", "delete", true)));
options.put(PROPERTY_PACKAGE_FILES, List.of(
Map.of("url", "classpath:/package/thumbnail.png", "path", "/content/image.png"),
Map.of("file", "README.txt", "dir", "readme", "path", "/content/README.txt", "delete", true),
Map.of("fileMatch", "file_(.*).txt", "dir", "files", "path", "/content/files/$1.txt", "delete", true)));

// prepare JSON file
File target = new File("target/" + ContentPackagePostProcessor.NAME + "-test");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMap;

import io.wcm.devops.conga.generator.spi.PostProcessorPlugin;
import io.wcm.devops.conga.generator.spi.context.FileContext;
import io.wcm.devops.conga.generator.spi.context.PluginContextOptions;
Expand All @@ -56,7 +54,7 @@ void testContentPackage() throws Exception {
.file(new File("src/test/resources/package/example.zip"));

// post-process
applyPlugin(fileContext, ImmutableMap.of());
applyPlugin(fileContext, Map.of());

// validate
Map<String, Object> props = (Map<String, Object>)fileContext.getModelOptions().get(ContentPackagePropertiesPostProcessor.MODEL_OPTIONS_PROPERTY);
Expand All @@ -74,7 +72,7 @@ void testContentPackageOverridePackageType() throws Exception {
.file(new File("src/test/resources/package/example.zip"));

// post-process
applyPlugin(fileContext, ImmutableMap.of("contentPackage", ImmutableMap.of("packageType", "mytype")));
applyPlugin(fileContext, Map.of("contentPackage", Map.of("packageType", "mytype")));

// validate
Map<String, Object> props = (Map<String, Object>)fileContext.getModelOptions().get(ContentPackagePropertiesPostProcessor.MODEL_OPTIONS_PROPERTY);
Expand All @@ -88,7 +86,7 @@ void testNonContentPackage() throws Exception {
.file(new File("src/test/resources/package/no-content-package.zip"));

// post-process
applyPlugin(fileContext, ImmutableMap.of());
applyPlugin(fileContext, Map.of());

// validate
assertNull(fileContext.getModelOptions().get(ContentPackagePropertiesPostProcessor.MODEL_OPTIONS_PROPERTY));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"configurations": {
"my.pid": {
"stringProperty": "value1",
"stringArrayProperty": ["v1","v2","v3"],
"booleanProperty": true,
"longProperty": 999999999999
},
"my.factory-my.pid": {
"stringProperty": "value2"
}
},
"configurations:mode1": {
"my.factory-my.pid2": {
"stringProperty": "value3"
}
},
"configurations:mode2": {
"my.pid2": {
"stringProperty": "value4"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ files:
filters:
- filter: /apps/sample

# Define a AEM content package containing OSGi configurations from a JSON file
- file: config-sample.osgiconfig.json
dir: packages
template: config-sample.osgiconfig.json.hbs
# Transform OSGi configs from provisoning file to AEM content package
postProcessors:
- aem-contentpackage-osgiconfig
postProcessorOptions:
contentPackage:
name: config-sample-from-json
packageType: container
description: The description of the sample package.
version: "${version}"
rootPath: /apps/sample/config
filters:
- filter: /apps/sample

# Define a AEM content package with some JCR content (Sling Mapping Example)
- file: sling-mapping.json
dir: packages
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"configurations": {
"my.pid": {
"heapspaceMax": "{{jvm.heapspace.max}}",
"booleanProp": true,
"numberProp": 123,
"arrayProp": ["v1","v2","v3"],
"numberArrayProp": [1,2]
}
},
"configurations:mode1": {
"my.pid2": {
"stringProperty": "{{var1}}",
"stringProperty2": "{{var2}}"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@

my.pid
heapspaceMax="{{jvm.heapspace.max}}"
booleanProp=B"true"
numberProp=I"123"
arrayProp=["v1","v2","v3"]
numberArrayProp=I["1","2"]

[configurations runModes=mode1]

my.pid2
stringProperty="{{var1}}"
stringProperty2="{{var2}}"