diff --git a/lib/types/application/ApplicationFormatter.js b/lib/types/application/ApplicationFormatter.js index 433462035..777c7db81 100644 --- a/lib/types/application/ApplicationFormatter.js +++ b/lib/types/application/ApplicationFormatter.js @@ -4,11 +4,12 @@ const fs = require("graceful-fs"); const {promisify} = require("util"); const readFile = promisify(fs.readFile); const AbstractFormatter = require("../AbstractFormatter"); +let readXML; // lazy definition of the readXML function (see readPOM) class ApplicationFormatter extends AbstractFormatter { /** * Validates the project and retrieves its manifest - * + * * @param {Object} project * @returns {Promise} when validated and manifest has been read */ @@ -19,34 +20,85 @@ class ApplicationFormatter extends AbstractFormatter { "/": project.resources.configuration.paths.webapp }; - return this.readManifest(project).then(function(manifest) { - if (!manifest["sap.app"] || !manifest["sap.app"].id) { + return this.readManifest(project).then((manifest) => { + // check for a proper sap.app/id in manifest.json to determine namespace + const appId = manifest["sap.app"] && manifest["sap.app"].id; + if (!appId) { log.warn(`No "sap.app" ID configuration found in manifest of project ${project.metadata.name}`); return; } - project.metadata.namespace = manifest["sap.app"].id.replace(/\./g, "/"); - }).catch((err) => { + return appId; + }, (err) => { log.verbose(`No manifest found for project ${project.metadata.name}.`); + }).then((appId) => { + // check app id for being a Maven placeholder and try to read the value from the pom.xml + const parts = appId && appId.match(/^\$\{(.*)\}$/); + if (parts) { + log.verbose(`"sap.app" ID configuration contains Maven placeholder "${parts[1]}". Resolving from pom.xml...`); + return this.readPOM(project).then((pom) => { + let mvnAppId; + if (pom.project && pom.project.properties && pom.project.properties[parts[1]]) { + mvnAppId = pom.project.properties[parts[1]]; + } else { + let obj = pom; + parts[1].split(".").forEach((part) => { + obj = obj && obj[part]; + }); + mvnAppId = obj; + } + if (!mvnAppId) { + log.warn(`"sap.app" ID configuration couldn't be resolved from Maven property "${parts[1]}" of pom.xml of project ${project.metadata.name}`); + return; + } + return mvnAppId; + }, (err) => { + log.verbose(`No or invalid pom.xml found for project ${project.metadata.name}.`); + }); + } + return appId; + }).then((appId) => { + if (appId) { + project.metadata.namespace = appId.replace(/\./g, "/"); + log.verbose(`"sap.app" ID configuration found and set as namespace ${project.metadata.namespace} for project ${project.metadata.name}.`); + } }); }); } /** * Reads the manifest - * + * * @param {Object} project * @returns {Promise} resolves with the json object */ readManifest(project) { - return readFile(path.join(project.path, project.resources.pathMappings["/"], "manifest.json")) + return readFile(path.join(project.path, project.resources.pathMappings["/"], "manifest.json"), "utf-8") .then((file) => { return JSON.parse(file); }); } + /** + * Reads the pom.xml file + * + * @param {Object} project + * @returns {Promise} resolves with the XML document from the pom.xml + */ + readPOM(project) { + if (!readXML) { + const xml2js = require("xml2js"); + const parser = new xml2js.Parser({ + explicitArray: false, + ignoreAttrs: true + }); + readXML = promisify(parser.parseString); + } + return readFile(path.join(project.path, "pom.xml"), "utf-8").then(readXML); + } + /** * Validates a project - * + * * @param {Object} project * @returns {Promise} resolves if successfully validated * @throws {Error} if validation fails diff --git a/test/expected/build/application.h/dest/manifest.json b/test/expected/build/application.h/dest/manifest.json index 0c7cbf74c..7de6072ce 100644 --- a/test/expected/build/application.h/dest/manifest.json +++ b/test/expected/build/application.h/dest/manifest.json @@ -2,7 +2,7 @@ "_version": "1.1.0", "sap.app": { "_version": "1.1.0", - "id": "application.g", + "id": "${project.artifactId}", "type": "application", "applicationVersion": { "version": "1.2.2" diff --git a/test/fixtures/application.h/pom.xml b/test/fixtures/application.h/pom.xml new file mode 100644 index 000000000..478ebc85c --- /dev/null +++ b/test/fixtures/application.h/pom.xml @@ -0,0 +1,41 @@ + + + + + + + 4.0.0 + + + + + com.sap.test + application.h + 1.0.0 + war + + + + + application.h + Simple SAPUI5 based application + + + + + + + application.h + + + + + diff --git a/test/fixtures/application.h/webapp-project.artifactId/manifest.json b/test/fixtures/application.h/webapp-project.artifactId/manifest.json new file mode 100644 index 000000000..7de6072ce --- /dev/null +++ b/test/fixtures/application.h/webapp-project.artifactId/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "${project.artifactId}", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/test/fixtures/application.h/webapp-properties.appId/manifest.json b/test/fixtures/application.h/webapp-properties.appId/manifest.json new file mode 100644 index 000000000..e1515df70 --- /dev/null +++ b/test/fixtures/application.h/webapp-properties.appId/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "${appId}", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/test/fixtures/application.h/webapp-properties.componentName/manifest.json b/test/fixtures/application.h/webapp-properties.componentName/manifest.json new file mode 100644 index 000000000..7d63e359c --- /dev/null +++ b/test/fixtures/application.h/webapp-properties.componentName/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "${componentName}", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/test/fixtures/application.h/webapp/manifest.json b/test/fixtures/application.h/webapp/manifest.json index 0c7cbf74c..7de6072ce 100644 --- a/test/fixtures/application.h/webapp/manifest.json +++ b/test/fixtures/application.h/webapp/manifest.json @@ -2,7 +2,7 @@ "_version": "1.1.0", "sap.app": { "_version": "1.1.0", - "id": "application.g", + "id": "${project.artifactId}", "type": "application", "applicationVersion": { "version": "1.2.2" diff --git a/test/lib/types/application/ApplicationFormatter.js b/test/lib/types/application/ApplicationFormatter.js index c9520b54d..f3ccbaa2c 100644 --- a/test/lib/types/application/ApplicationFormatter.js +++ b/test/lib/types/application/ApplicationFormatter.js @@ -155,3 +155,54 @@ test("format: set namespace to id", async (t) => { t.deepEqual(project.metadata.namespace, "my/id", "namespace was successfully set since readManifest provides the correct object structure"); }); + +const applicationHPath = path.join(__dirname, "..", "..", "..", "fixtures", "application.h"); +const applicationHTree = { + id: "application.h", + version: "1.0.0", + path: applicationHPath, + dependencies: [], + _level: 0, + specVersion: "0.1", + type: "application", + metadata: { + name: "application.h" + }, + resources: { + configuration: { + paths: { + webapp: "webapp" + } + } + } +}; + +test("namespace: detect namespace from pom.xml via ${project.artifactId}", async (t) => { + const myProject = clone(applicationHTree); + myProject.resources.configuration.paths.webapp = "webapp-project.artifactId"; + const applicationFormatter = new ApplicationFormatter(); + + await applicationFormatter.format(myProject); + t.deepEqual(myProject.metadata.namespace, "application/h", + "namespace was successfully set since readManifest provides the correct object structure"); +}); + +test("namespace: detect namespace from pom.xml via ${componentName} from properties", async (t) => { + const myProject = clone(applicationHTree); + myProject.resources.configuration.paths.webapp = "webapp-properties.componentName"; + const applicationFormatter = new ApplicationFormatter(); + + await applicationFormatter.format(myProject); + t.deepEqual(myProject.metadata.namespace, "application/h", + "namespace was successfully set since readManifest provides the correct object structure"); +}); + +test("namespace: detect namespace from pom.xml via ${appId} from properties", async (t) => { + const myProject = clone(applicationHTree); + myProject.resources.configuration.paths.webapp = "webapp-properties.appId"; + const applicationFormatter = new ApplicationFormatter(); + + await applicationFormatter.format(myProject); + t.falsy(myProject.metadata.namespace, + "namespace is falsy since readManifest resolves with an empty object"); +});